This commit is contained in:
kk
2026-07-08 18:00:33 +08:00
commit e66b4a3680
312 changed files with 54718 additions and 0 deletions
+364
View File
@@ -0,0 +1,364 @@
<template>
<div class="avue-top">
<div class="top-bar__left">
<div
class="avue-breadcrumb"
:class="[{ 'avue-breadcrumb--active': isCollapse }]"
v-if="setting.collapse && !isHorizontal"
>
<i class="icon-navicon" @click="setCollapse"></i>
</div>
</div>
<div class="top-bar__title">
<top-menu ref="topMenu" v-if="setting.menu"></top-menu>
<top-search class="top-bar__item" v-if="setting.search"></top-search>
</div>
<div class="top-bar__right">
<div v-if="setting.color" class="top-bar__item">
<top-color></top-color>
</div>
<div v-if="setting.theme" class="top-bar__item">
<top-theme></top-theme>
</div>
<div v-if="setting.lock" class="top-bar__item">
<top-lock></top-lock>
</div>
<div class="top-bar__item">
<top-lang></top-lang>
</div>
<div class="top-bar__item" v-if="setting.fullscreen">
<top-full></top-full>
</div>
<div class="top-bar__item" v-if="setting.debug">
<top-logs></top-logs>
</div>
<div class="top-user">
<img class="top-bar__img" :src="userInfo.avatar" />
<el-dropdown :hide-on-click="false">
<span class="el-dropdown-link">
{{ userInfo.real_name }}
<el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="toDashboard">
<i class="icon-zuzhixiaxia" /> {{ $t('navbar.dashboard') }}
</el-dropdown-item>
<el-dropdown-item @click="toAllMenu">
<i class="icon-liebiao" /> {{ $t('navbar.allMenu') }}
</el-dropdown-item>
<el-dropdown-item @click="toInfo">
<i class="icon-yonghu" />{{ $t('navbar.userinfo') }}
</el-dropdown-item>
<el-dropdown-item divided>
<i class="icon-hezuohuobanmiyueguanli" />
<el-tooltip :content="roleName" placement="top" trigger="click">
<strong>
{{
func.contains(roleId, ',')
? $t('navbar.roleName')
: $t('navbar.currentRoleName')
}}: {{ func.truncateString(roleName) }}
</strong>
</el-tooltip>
</el-dropdown-item>
<el-dropdown-item
v-if="Object.keys(roleObj).length < 5"
v-for="(name, id) in roleObj"
:key="id"
@click="toRole(id, name)"
>
<i class="icon-changjingguanli" />
{{ $t('navbar.switchRoleTo') }}: {{ name }}
</el-dropdown-item>
<el-dropdown-item v-if="Object.keys(roleObj).length >= 5" @click="switchRole">
<i class="icon-changjingguanli" />
{{ $t('navbar.switchRole') }}
</el-dropdown-item>
<el-dropdown-item divided>
<i class="iconfont iconicon_group" />
<el-tooltip :content="deptName" placement="top" trigger="click">
<strong>
{{
func.contains(deptId, ',')
? $t('navbar.deptName')
: $t('navbar.currentDeptName')
}}: {{ func.truncateString(deptName) }}
</strong>
</el-tooltip>
</el-dropdown-item>
<el-dropdown-item
v-if="Object.keys(deptObj).length < 5"
v-for="(name, id) in deptObj"
:key="id"
@click="toDept(id, name)"
>
<i class="icon-guanlianshebei" />
{{ $t('navbar.switchDeptTo') }}: {{ name }}
</el-dropdown-item>
<el-dropdown-item v-if="Object.keys(deptObj).length >= 5" @click="switchDept">
<i class="icon-guanlianshebei" />
{{ $t('navbar.switchDept') }}
</el-dropdown-item>
<el-dropdown-item @click="logout" divided>
<i class="icon-tuichu" /> {{ $t('navbar.logOut') }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<top-setting></top-setting>
<el-dialog title="请选择身份信息后切换" append-to-body v-model="userBox" width="400px">
<avue-form :option="userOption" v-model="userForm" @submit="toAll" />
</el-dialog>
</div>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import topLock from './top-lock.vue';
import topMenu from './top-menu.vue';
import topSearch from './top-search.vue';
import topTheme from './top-theme.vue';
import topLogs from './top-logs.vue';
import topColor from './top-color.vue';
import topLang from './top-lang.vue';
import topFull from './top-full.vue';
import topSetting from '../setting.vue';
import { getUserInfo as getUerDetailInfo } from '@/api/system/user';
import { getUserInfo as getUerOauthInfo } from '@/api/user';
import { validatenull } from '@/utils/validate';
import func from '@/utils/func';
export default {
components: {
topLock,
topMenu,
topSearch,
topTheme,
topLogs,
topColor,
topLang,
topFull,
topSetting,
},
name: 'top',
inject: ['index'],
data() {
return {
userId: '',
roleId: '',
roleName: '',
roleObj: {},
deptId: '',
deptName: '',
deptObj: {},
userBox: false,
userForm: {
deptId: '',
roleId: '',
},
userOption: {
labelWidth: 70,
submitBtn: true,
emptyBtn: false,
submitText: '切 换',
column: [
{
label: '部门',
prop: 'deptId',
type: 'select',
props: {
label: 'deptName',
value: 'id',
},
dicUrl: '/blade-system/dept/select',
filterable: true,
span: 24,
display: false,
rules: [
{
required: true,
message: '请选择部门',
trigger: 'blur',
},
],
},
{
label: '角色',
prop: 'roleId',
type: 'select',
props: {
label: 'roleName',
value: 'id',
},
dicUrl: '/blade-system/role/select',
filterable: true,
span: 24,
display: false,
rules: [
{
required: true,
message: '请选择角色',
trigger: 'blur',
},
],
},
],
},
};
},
filters: {},
created() {
this.init();
},
computed: {
func() {
return func;
},
...mapGetters([
'setting',
'userInfo',
'tagWel',
'tagList',
'isCollapse',
'tag',
'logsLen',
'logsFlag',
'isHorizontal',
]),
},
methods: {
init() {
getUerDetailInfo().then(res => {
// 获取绑定信息
const roleId = res.data.data.roleId;
const roleName = res.data.data.roleName;
const roleIds = roleId.split(',');
const roleNames = roleName.split(',');
const deptId = res.data.data.deptId;
const deptName = res.data.data.deptName;
const deptIds = deptId.split(',');
const deptNames = deptName.split(',');
roleIds.forEach((id, index) => {
if (id === this.roleId) {
this.roleName = roleNames[index];
} else {
this.roleObj[id] = roleNames[index];
}
});
deptIds.forEach((id, index) => {
if (id === this.deptId) {
this.deptName = deptNames[index];
} else {
this.deptObj[id] = deptNames[index];
}
});
if (validatenull(this.roleName)) {
this.roleName = roleName;
}
if (validatenull(this.deptName)) {
this.deptName = deptName;
}
});
getUerOauthInfo().then(res => {
this.userId = res.data.userId;
this.roleId = res.data.roleId;
this.deptId = res.data.deptId;
});
},
toDashboard() {
this.$router.push(this.tagWel);
},
toAllMenu() {
this.index.openMenu({}, true);
this.$router.push(this.tagWel);
},
toInfo() {
this.$router.push({ path: '/info/index' });
},
toRole(roleId, name) {
const userInfo = { roleId, deptId: this.deptId };
this.switchTo(userInfo, name);
},
toDept(deptId, name) {
const userInfo = { roleId: this.roleId, deptId };
this.switchTo(userInfo, name);
},
toAll(form, done) {
let userInfo;
if (!validatenull(form.deptId)) {
userInfo = { roleId: this.roleId, deptId: form.deptId };
}
if (!validatenull(form.roleId)) {
userInfo = { roleId: form.roleId, deptId: this.deptId };
}
this.switchTo(userInfo);
done();
},
switchRole() {
const columnRole = this.findColumn(this.userOption.column, 'roleId');
const columnDept = this.findColumn(this.userOption.column, 'deptId');
columnRole.dicUrl = `/blade-system/role/select?userId=${this.userId}`;
columnRole.display = true;
columnDept.dicUrl = '';
columnDept.display = false;
this.userBox = true;
},
switchDept() {
const columnDept = this.findColumn(this.userOption.column, 'deptId');
const columnRole = this.findColumn(this.userOption.column, 'roleId');
columnDept.dicUrl = `/blade-system/dept/select?userId=${this.userId}`;
columnDept.display = true;
columnRole.dicUrl = '';
columnRole.display = false;
this.userBox = true;
},
switchTo(userInfo, name) {
const tipMessage = validatenull(name)
? this.$t('navbar.switchTip') + ' ? '
: this.$t('navbar.switchTip') + ` [${name}] ?`;
this.$confirm(tipMessage, this.$t('confirmTip'), {
confirmButtonText: this.$t('submitText'),
cancelButtonText: this.$t('cancelText'),
type: 'warning',
}).then(() => {
this.$store
.dispatch('RefreshToken', userInfo)
.then(() => {
this.$message.success(this.$t('navbar.switchSuccess'));
location.reload();
})
.catch(err => {
console.log(err);
});
});
},
setCollapse() {
this.$store.commit('SET_COLLAPSE');
},
logout() {
this.$confirm(this.$t('logoutTip'), this.$t('confirmTip'), {
confirmButtonText: this.$t('submitText'),
cancelButtonText: this.$t('cancelText'),
type: 'warning',
}).then(() => {
this.$store.dispatch('LogOut').then(() => {
if (this.website.oauth2.ssoMode) {
window.location.href =
this.website.oauth2.ssoBaseUrl +
this.website.oauth2.ssoLogoutUrl +
this.website.oauth2.redirectUri;
} else {
this.$router.push({ path: '/login' });
}
});
});
},
},
};
</script>
<style lang="scss" scoped></style>
+75
View File
@@ -0,0 +1,75 @@
<template>
<el-color-picker
size="small"
class="theme-picker"
popper-class="theme-picker-dropdown"
v-model="themeVal"
></el-color-picker>
</template>
<script>
import { mapGetters } from 'vuex'; // default color
export default {
name: 'topColor',
data() {
return {
themeVal: '',
};
},
created() {
this.themeVal = this.colorName || '#2C77F1';
},
watch: {
themeVal(val, oldVal) {
this.$store.commit('SET_COLOR_NAME', val);
this.updateTheme(val, oldVal);
},
},
computed: {
...mapGetters(['colorName']),
},
methods: {
hexToRgb(str) {
let hexs = '';
str = str.replace('#', '');
hexs = str.match(/../g);
for (let i = 0; i < 3; i++) hexs[i] = parseInt(hexs[i], 16);
return hexs;
},
// r 代表红色 | g 代表绿色 | b 代表蓝色
rgbToHex(r, g, b) {
let hexs = [r.toString(16), g.toString(16), b.toString(16)];
for (let i = 0; i < 3; i++) if (hexs[i].length == 1) hexs[i] = `0${hexs[i]}`;
return `#${hexs.join('')}`;
},
getDarkColor(color, level) {
let rgb = this.hexToRgb(color);
for (let i = 0; i < 3; i++) rgb[i] = Math.floor(rgb[i] * (1 - level));
return this.rgbToHex(rgb[0], rgb[1], rgb[2]);
},
// color 颜色值字符串 | level 加深的程度,限0-1之间
getLightColor(color, level) {
let rgb = this.hexToRgb(color);
for (let i = 0; i < 3; i++) rgb[i] = Math.floor((255 - rgb[i]) * level + rgb[i]);
return this.rgbToHex(rgb[0], rgb[1], rgb[2]);
},
updateTheme(e) {
if (!e) return;
// e就是选择了的颜色
const pre = '--el-color-primary';
const el = document.documentElement;
el.style.setProperty(pre, e);
// 这里是覆盖原有颜色的核心代码
for (let i = 1; i < 10; i += 1) {
document.documentElement.style.setProperty(
`${pre}-light-${i}`,
`${this.getLightColor(e, i / 10)}`
);
}
},
},
};
</script>
+24
View File
@@ -0,0 +1,24 @@
<template>
<i :class="isFullScren ? 'icon-tuichuquanping' : 'icon-quanping'" @click="handleScreen"></i>
</template>
<script>
import { mapGetters } from 'vuex';
import { fullscreenToggel, listenfullscreen } from 'utils/util';
export default {
computed: {
...mapGetters(['isFullScren']),
},
mounted() {
listenfullscreen(this.setScreen);
},
methods: {
setScreen() {
this.$store.commit('SET_FULLSCREN');
},
handleScreen() {
fullscreenToggel();
},
},
};
</script>
+42
View File
@@ -0,0 +1,42 @@
<template>
<el-dropdown trigger="click" @command="handleSetLanguage">
<i class="icon-zhongyingwen"></i>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item :disabled="language === 'zh-cn'" command="zh-cn">中文</el-dropdown-item>
<el-dropdown-item :disabled="language === 'en-us'" command="en-us"
>English</el-dropdown-item
>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
name: 'top-lang',
data() {
return {};
},
created() {},
mounted() {},
computed: {
...mapGetters(['language', 'tag']),
},
props: [],
methods: {
handleSetLanguage(lang) {
this.$i18n.locale = lang;
this.$store.commit('SET_LANGUAGE', lang);
let tag = this.tag;
let title = this.$router.$avueRouter.generateTitle(tag);
//根据当前的标签也获取label的值动态设置浏览器标题
this.$router.$avueRouter.setTitle(title);
},
},
};
</script>
<style lang="scss" scoped></style>
+67
View File
@@ -0,0 +1,67 @@
<template>
<span v-if="text" @click="handleLock">{{ text }}</span>
<i v-else class="icon-suoping" @click="handleLock"></i>
<el-dialog title="设置锁屏密码" v-model="box" width="30%" append-to-body>
<el-form :model="form" ref="form" label-width="80px">
<el-form-item
label="锁屏密码"
prop="passwd"
:rules="[{ required: true, message: '锁屏密码不能为空' }]"
>
<el-input v-model="form.passwd" placeholder="请输入锁屏密码">
<template #append>
<el-button @click="handleSetLock" icon="el-icon-lock"></el-button>
</template>
</el-input>
</el-form-item>
</el-form>
</el-dialog>
</template>
<script>
import { validatenull } from 'utils/validate';
import { mapGetters } from 'vuex';
export default {
name: 'top-lock',
data() {
return {
box: false,
form: {
passwd: '',
},
};
},
created() {},
mounted() {},
computed: {
...mapGetters(['lockPasswd']),
},
props: {
text: String,
},
methods: {
handleSetLock() {
this.$refs['form'].validate(valid => {
if (valid) {
this.$store.commit('SET_LOCK_PASSWD', this.form.passwd);
this.handleLock();
}
});
},
handleLock() {
if (validatenull(this.lockPasswd)) {
this.box = true;
return;
}
this.$store.commit('SET_LOCK');
setTimeout(() => {
this.$router.push({ path: '/lock' });
}, 100);
},
},
components: {},
};
</script>
<style lang="scss" scoped></style>
+86
View File
@@ -0,0 +1,86 @@
<template>
<span @click="logsFlag ? '' : handleOpen()">
<el-badge :value="logsFlag ? '' : logsLen" :max="99">
<i class="icon-rizhi1"></i>
</el-badge>
<el-dialog title="日志" v-model="box" width="60%" append-to-body>
<el-button type="primary" icon="el-icon-upload" @click="send">上传服务器</el-button>
<el-button type="danger" icon="el-icon-delete" @click="clear">清空本地日志</el-button>
<el-table :data="logsList">
<el-table-column prop="type" label="类型" width="50px"> </el-table-column>
<el-table-column prop="url" label="地址" show-overflow-tooltip width="180">
</el-table-column>
<el-table-column prop="message" show-overflow-tooltip label="内容"> </el-table-column>
<el-table-column prop="stack" show-overflow-tooltip label="错误堆栈"> </el-table-column>
<el-table-column prop="time" label="时间"> </el-table-column>
</el-table>
</el-dialog>
</span>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
name: 'top-logs',
data() {
return {
box: false,
};
},
created() {},
mounted() {},
computed: {
...mapGetters(['logsList', 'logsFlag', 'logsLen']),
},
props: [],
methods: {
handleOpen() {
this.box = true;
},
send() {
this.$confirm('确定上传本地日志到服务器?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
this.$store.dispatch('SendLogs').then(() => {
this.box = false;
this.$message({
type: 'success',
message: '发送成功!',
});
});
})
.catch(() => {});
},
clear() {
this.$confirm('确定清空本地日志记录?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
this.$store.commit('CLEAR_LOGS');
this.box = false;
this.$message({
type: 'success',
message: '清空成功!',
});
})
.catch(() => {});
},
},
};
</script>
<style lang="scss" scoped>
.code {
font-size: 12px;
display: block;
font-family: monospace;
white-space: pre;
margin: 1em 0px;
}
</style>
+58
View File
@@ -0,0 +1,58 @@
<template>
<el-menu class="top-menu" :default-active="activeIndex" mode="horizontal" text-color="#333">
<el-menu-item index="0" @click="openHome">
<template #title>
<i :class="itemHome.source" style="padding-right: 5px"></i>
<span>{{ itemHome.name }}</span>
</template>
</el-menu-item>
<template v-for="(item, index) in items" :key="index">
<el-menu-item :index="item.id + ''" @click="openMenu(item)">
<template #title>
<i :class="item.source" style="padding-right: 5px"></i>
<span>{{ item.name }}</span>
</template>
</el-menu-item>
</template>
</el-menu>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
name: 'top-menu',
data() {
return {
itemHome: {
name: '首页',
source: 'iconfont iconicon_work',
},
activeIndex: '0',
items: [],
};
},
inject: ['index'],
created() {
this.getMenu();
},
computed: {
...mapGetters(['tagCurrent', 'menu', 'tagWel']),
},
methods: {
openMenu(item) {
this.index.openMenu(item);
},
openHome() {
this.index.openMenu();
this.$router.push(this.tagWel);
},
getMenu() {
this.$store.dispatch('GetTopMenu').then(res => {
this.items = res.filter(item => item.isMain !== 1);
});
},
},
};
</script>
+120
View File
@@ -0,0 +1,120 @@
<template>
<el-autocomplete
class="top-search"
popper-class="my-autocomplete"
v-model="value"
:fetch-suggestions="querySearch"
:placeholder="$t('search')"
@select="handleSelect"
>
<template #="{ item }">
<i :class="[item[iconKey], 'icon']"></i>
<div class="name">{{ item[labelKey] }}</div>
<div class="addr">{{ item[pathKey] }}</div>
</template>
</el-autocomplete>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
data() {
return {
value: '',
menuList: [],
};
},
created() {
this.getMenuList();
},
watch: {
menu() {
this.getMenuList();
},
},
computed: {
labelKey() {
return this.website.menu.label;
},
pathKey() {
return this.website.menu.path;
},
iconKey() {
return this.website.menu.icon;
},
childrenKey() {
return this.website.menu.children;
},
...mapGetters(['menu']),
},
methods: {
getMenuList() {
const findMenu = list => {
for (let i = 0; i < list.length; i++) {
const ele = Object.assign({}, list[i]);
if (this.validatenull(ele[this.childrenKey])) {
this.menuList.push(ele);
} else {
findMenu(ele[this.childrenKey]);
}
}
};
this.menuList = [];
findMenu(this.menu);
},
querySearch(queryString, cb) {
var restaurants = this.menuList;
var results = queryString ? restaurants.filter(this.createFilter(queryString)) : restaurants;
// 调用 callback 返回建议列表的数据
cb(results);
},
createFilter(queryString) {
return restaurant => {
return restaurant[this.labelKey].toLowerCase().indexOf(queryString.toLowerCase()) === 0;
};
},
handleSelect(item) {
this.value = '';
this.$router.push({
path: item[this.pathKey],
query: item.query,
});
},
},
};
</script>
<style lang="scss">
.my-autocomplete {
li {
line-height: normal !important;
padding: 7px !important;
.icon {
margin-right: 5px;
display: inline-block;
vertical-align: middle;
}
.name {
display: inline-block;
text-overflow: ellipsis;
overflow: hidden;
vertical-align: middle;
}
.addr {
padding-top: 5px;
width: 100%;
font-size: 12px;
color: #b4b4b4;
}
.highlighted .addr {
color: #ddd;
}
}
}
</style>
+119
View File
@@ -0,0 +1,119 @@
<template>
<div>
<el-dialog title="选择" v-model="box" width="50%">
<el-radio-group v-model="text" class="list">
<el-row :span="24">
<el-col v-for="(item, index) in list" :key="index" :md="4" :xs="12" :sm="4">
<el-radio :label="item.value">{{ item.name }}</el-radio>
</el-col>
</el-row>
</el-radio-group>
</el-dialog>
<span>
<i class="icon-zhuti" @click="open"></i>
</span>
</div>
</template>
<script>
import { setTheme } from 'utils/util';
import { mapGetters } from 'vuex';
export default {
data() {
return {
box: false,
text: '',
list: [
{
name: '默认主题',
value: 'default',
},
{
name: '白色主题',
value: 'theme-white',
},
{
name: '黑色主题',
value: 'theme-dark',
},
{
name: 'go主题',
value: 'theme-go',
},
{
name: 'hey主题',
value: 'theme-hey',
},
{
name: '炫彩主题',
value: 'theme-star',
},
{
name: 'vip主题',
value: 'theme-vip',
},
{
name: '智能工厂主题',
value: 'theme-bule',
},
{
name: 'iview主题',
value: 'theme-iview',
},
{
name: 'cool主题',
value: 'theme-cool',
},
{
name: 'd2主题',
value: 'theme-d2',
},
{
name: 'lte主题',
value: 'theme-lte',
},
{
name: 'beautiful主题',
value: 'theme-beautiful',
},
{
name: 'Mac OS主题',
value: 'mac-os',
},
],
};
},
watch: {
text: function (val) {
this.$store.commit('SET_THEME_NAME', val);
setTheme(val);
if (this.$store.getters.isMacOs) {
this.$router.push(this.tagWel);
setTimeout(() => location.reload());
}
},
},
computed: {
...mapGetters(['themeName', 'tagWel']),
},
mounted() {
this.text = this.themeName;
if (!this.text) {
this.text = '';
}
},
methods: {
open() {
this.box = true;
},
},
};
</script>
<style lang="scss" scoped>
.list {
width: 100%;
}
</style>