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

111
src/page/index/index.vue Normal file
View File

@@ -0,0 +1,111 @@
<template>
<el-watermark :content="watermark" style="height: 100%">
<div class="avue-contail" :class="{ 'avue--collapse': isCollapse }">
<div class="avue-layout" :class="{ 'avue-layout--horizontal': isHorizontal }">
<div class="avue-sidebar" v-show="validSidebar">
<!-- 左侧导航栏 -->
<logo />
<sidebar />
</div>
<div class="avue-main">
<!-- 顶部导航栏 -->
<top ref="top" />
<!-- 顶部标签卡 -->
<tags />
<search class="avue-view" v-show="isSearch"></search>
<!-- 主体视图层 -->
<div id="avue-view" v-show="!isSearch" v-if="isRefresh">
<router-view #="{ Component }">
<keep-alive :include="$store.getters.tagsKeep">
<component :is="tabView($route, Component)" />
</keep-alive>
</router-view>
</div>
</div>
</div>
<!-- <wechat></wechat> -->
</div>
</el-watermark>
</template>
<script>
import index from '@/mixins/index';
import wechat from './wechat.vue';
//import { validatenull } from 'utils/validate';
import { mapGetters } from 'vuex';
import tags from './tags.vue';
import search from './search.vue';
import logo from './logo.vue';
import top from './top/index.vue';
import sidebar from './sidebar/index.vue';
import website from '@/config/website';
import { validatenull } from '@/utils/validate';
import { getMainMenu } from '@/api/system/menu';
import { tabView } from '@/router/tab';
export default {
mixins: [index],
components: {
top,
logo,
tags,
search,
sidebar,
wechat,
},
name: 'index',
provide() {
return {
index: this,
};
},
computed: {
...mapGetters([
'isHorizontal',
'isRefresh',
'isLock',
'isCollapse',
'isSearch',
'menu',
'setting',
]),
validSidebar() {
return !(
(this.$route.meta || {}).menu === false || (this.$route.query || {}).menu === 'false'
);
},
watermark() {
return website.watermark.mode ? website.watermark.text : '';
},
},
props: [],
methods: {
tabView,
//打开菜单
openMenu(item = {}, skipMainMenu = false) {
const doOpen = menuItem => {
this.$store.dispatch('GetMenu', menuItem.id).then(data => {
if (data.length !== 0) {
this.$router.$avueRouter.formatRoutes(data, true);
if (!validatenull(menuItem.path)) {
this.$router.push({ path: menuItem.path });
}
}
});
};
if (!item.id && !skipMainMenu) {
getMainMenu()
.then(res => {
const mainMenu = res.data.data;
doOpen(mainMenu && mainMenu.id ? { id: mainMenu.id } : item);
})
.catch(() => {
doOpen(item);
});
} else {
doOpen(item);
}
},
},
};
</script>

View File

@@ -0,0 +1,7 @@
<template>
<router-view #="{ Component }">
<keep-alive :include="$store.getters.tagsKeep">
<component :is="Component" />
</keep-alive>
</router-view>
</template>

44
src/page/index/logo.vue Normal file
View File

@@ -0,0 +1,44 @@
<template>
<div class="avue-logo">
<transition name="fade">
<span v-if="getScreen(isCollapse)" class="avue-logo_subtitle" key="0">
<img class="logo-img" src="/img/logo.png" />
</span>
</transition>
<transition-group name="fade">
<template v-if="getScreen(!isCollapse)">
<span class="logo-title" key="1">{{ website.indexTitle }} </span>
</template>
</transition-group>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
name: 'logo',
data() {
return {};
},
created() {},
computed: {
...mapGetters(['isCollapse']),
},
methods: {},
};
</script>
<style scoped>
.logo-title {
font-size: 20px;
background-image: linear-gradient(120deg, #54b6d0 16%, #3f8bdb, #2c77f1);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-weight: 700;
padding-left: 30px;
}
.logo-img {
width: 40px;
margin-top: 5px;
}
</style>

178
src/page/index/search.vue Normal file
View File

@@ -0,0 +1,178 @@
<template>
<div class="avue-searchs" @click.self="handleEsc">
<div class="avue-searchs__title">菜单搜索</div>
<div class="avue-searchs__content">
<div class="avue-searchs__form">
<el-input :placeholder="$t('search')" v-model="value" @keydown.esc="handleEsc">
<template #append>
<el-button icon="el-icon-search"></el-button>
</template>
</el-input>
<p>
<el-tag>你可以使用快捷键esc 关闭</el-tag>
</p>
</div>
<div class="avue-searchs__list">
<el-scrollbar class="avue-searchs__scrollbar">
<div
class="avue-searchs__item"
v-for="(item, index) in menus"
:key="index"
@click="handleSelect(item)"
>
<i :class="[item[iconKey], 'avue-searchs__item-icon']"></i>
<span class="avue-searchs__item-title">{{ item[labelKey] }}</span>
<div class="avue-searchs__item-path">
{{ item[pathKey] }}
</div>
</div>
</el-scrollbar>
</div>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
data() {
return {
value: '',
menus: [],
menuList: [],
};
},
created() {
this.getMenuList();
},
watch: {
value() {
this.querySearch();
},
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: {
handleEsc() {
this.$store.commit('SET_IS_SEARCH', false);
},
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);
this.menus = this.menuList;
},
querySearch() {
var restaurants = this.menuList;
var queryString = this.value;
this.menus = queryString ? this.menuList.filter(this.createFilter(queryString)) : restaurants;
},
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" scoped>
.avue-searchs {
padding-top: 50px;
width: 100%;
height: 100%;
background-color: #fff;
z-index: 1024;
&__title {
margin-bottom: 60px;
text-align: center;
font-size: 42px;
font-weight: bold;
letter-spacing: 2px;
text-indent: 2px;
}
&__form {
margin: 0 auto 50px auto;
width: 50%;
text-align: center;
p {
margin-top: 20px;
}
}
&__scrollbar {
height: 400px;
}
&__list {
box-sizing: border-box;
padding: 20px 30px;
margin: 0 auto;
width: 70%;
border-radius: 4px;
border: 1px solid #ebeef5;
background-color: #fff;
overflow: hidden;
color: #303133;
transition: 0.3s;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
&__item {
padding: 5px 0;
border-bottom: 1px dashed #eee;
&-icon {
margin-right: 5px;
font-size: 18px;
}
&-title {
font-size: 20px;
font-weight: 500;
color: #333;
}
&-path {
line-height: 30px;
color: #666;
}
}
}
</style>

173
src/page/index/setting.vue Normal file
View File

@@ -0,0 +1,173 @@
<template>
<!-- <el-button
@click="show = true"
class="setting-icon"
type="primary"
icon="el-icon-setting"
circle
></el-button>-->
<el-drawer append-to-body :with-header="false" v-model="show" size="30%">
<div class="setting">
<h5>导航模式</h5>
<div class="setting-checkbox">
<el-tooltip class="item" effect="dark" content="侧边菜单布局" placement="top">
<div
@click="setting.sidebar = 'vertical'"
class="setting-checkbox-item setting-checkbox-item--side"
></div>
</el-tooltip>
<el-tooltip class="item" effect="dark" content="顶部菜单布局" placement="top">
<div
@click="setting.sidebar = 'horizontal'"
class="setting-checkbox-item setting-checkbox-item--top"
></div>
</el-tooltip>
</div>
<h5>页面布局</h5>
<div class="setting-checkbox">
<div class="setting-item" v-for="(item, index) in list1" :key="index">
{{ item.label }}:
<el-switch v-model="setting[item.value]"></el-switch>
</div>
</div>
<h5>功能调试</h5>
<div class="setting-checkbox">
<div class="setting-item" v-for="(item, index) in list2" :key="index">
{{ item.label }}:
<el-switch v-model="setting[item.value]"></el-switch>
</div>
</div>
</div>
</el-drawer>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
data() {
return {
show: false,
list1: [
{
label: '导航标签',
value: 'tag',
},
{
label: '菜单折叠',
value: 'collapse',
},
{
label: '菜单搜索',
value: 'search',
},
{
label: '屏幕全屏',
value: 'fullscreen',
},
{
label: '主题选择',
value: 'theme',
},
{
label: '顶部菜单',
value: 'menu',
},
],
list2: [
{
label: '日志调试',
value: 'debug',
},
{
label: '屏幕锁定',
value: 'lock',
},
],
};
},
computed: {
...mapGetters(['isHorizontal', 'setting']),
},
};
</script>
<style lang="scss">
.setting {
&-icon {
color: #666;
position: fixed;
bottom: 200px;
right: 20px;
z-index: 2048;
}
&-item {
display: flex;
justify-content: space-between;
font-size: 14px;
margin-bottom: 8px;
}
&-checkbox {
&--check {
position: absolute;
color: var(--el-color-primary);
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
&-item {
display: inline-block;
position: relative;
width: 44px;
height: 36px;
margin-right: 16px;
overflow: hidden;
background-color: #f0f2f5;
border-radius: 4px;
box-shadow: 0 1px 2.5px 0 rgba(0, 0, 0, 0.18);
cursor: pointer;
&:before {
position: absolute;
top: 0;
left: 0;
width: 33%;
height: 100%;
background-color: #fff;
content: '';
}
&:after {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 25%;
background-color: #fff;
content: '';
}
&--side {
&:before {
z-index: 1;
background-color: #001529;
content: '';
}
&:after {
background-color: #fff;
}
}
&--top {
&:after {
background-color: #001529;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,63 @@
<template>
<el-scrollbar class="avue-menu">
<div v-if="menu && menu.length == 0 && !isHorizontal" class="avue-sidebar--tip">
{{ $t('menuTip') }}
</div>
<el-menu
unique-opened
:default-active="activeMenu"
:mode="setting.sidebar"
:collapse="getScreen(isCollapse)"
>
<sidebar-item :menu="menu"></sidebar-item>
</el-menu>
</el-scrollbar>
</template>
<script>
import { mapGetters } from 'vuex';
import website from '@/config/website';
import sidebarItem from './sidebarItem.vue';
export default {
name: 'sidebar',
components: { sidebarItem },
inject: ['index'],
created() {
this.index.openMenu();
},
computed: {
...mapGetters(['isHorizontal', 'setting', 'menu', 'tag', 'isCollapse', 'menuId']),
// 带参菜单的索引集合,用于激活态的精确匹配
menuIndexes() {
const props = website.menu;
const indexes = new Set();
const collect = list => {
(list || []).forEach(item => {
const query = item[props.query];
if (query && Object.keys(query).length) {
indexes.add(this.$router.resolve({ path: item[props.path], query }).fullPath);
}
collect(item[props.children]);
});
};
collect(this.menu);
return indexes;
},
activeMenu() {
const route = this.$route;
const { meta, path } = route;
if (meta.activeMenu) {
return meta.activeMenu;
}
// 完整地址命中带参菜单索引时精确高亮,其余场景按路径高亮
const fullPath = route.fullPath;
if (fullPath !== path && this.menuIndexes.has(fullPath)) {
return fullPath;
}
return path;
},
},
};
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,105 @@
<template>
<template v-for="item in menu">
<el-menu-item
v-if="validatenull(item[childrenKey]) && validRoles(item)"
:index="getIndex(item)"
@click="open(item)"
:key="item[labelKey]"
>
<i :class="item[iconKey]"></i>
<template #title>
<span :alt="item[pathKey]">{{ getTitle(item) }}</span>
</template>
</el-menu-item>
<el-sub-menu
v-else-if="!validatenull(item[childrenKey]) && validRoles(item)"
:index="getPath(item)"
:key="item[labelKey]"
>
<template #title>
<i :class="item[iconKey]"></i>
<span>{{ getTitle(item) }}</span>
</template>
<template v-for="(child, cindex) in item[childrenKey]" :key="child[labelKey]">
<el-menu-item
:index="getIndex(child)"
@click="open(child)"
v-if="validatenull(child[childrenKey])"
>
<i :class="child[iconKey]"></i>
<template #title>
<span>{{ getTitle(child) }}</span>
</template>
</el-menu-item>
<sidebar-item v-else :menu="[child]" :key="cindex"></sidebar-item>
</template>
</el-sub-menu>
</template>
</template>
<script>
import { mapGetters } from 'vuex';
import { validatenull } from 'utils/validate';
import website from '@/config/website';
import { generateIframePath, generateIframeQuery, isIframeRoute } from '@/router/router';
export default {
name: 'sidebarItem',
data() {
return {
props: website.menu,
};
},
props: {
menu: Array,
},
computed: {
...mapGetters(['roles']),
labelKey() {
return this.props.label;
},
pathKey() {
return this.props.path;
},
queryKey() {
return this.props.query;
},
iconKey() {
return this.props.icon;
},
childrenKey() {
return this.props.children;
},
},
methods: {
validatenull,
getPath(item) {
return generateIframePath(item, this.pathKey);
},
getIndex(item) {
// 带参菜单以完整地址为索引,保证同路径多菜单的激活态互不干扰
const { path, query } = this.menuRoute(item);
if (!query || !Object.keys(query).length) return path;
return this.$router.resolve({ path, query }).fullPath;
},
menuRoute(item) {
const path = this.getPath(item);
const originalPath = item[this.pathKey];
const isIframe = isIframeRoute(path, originalPath);
const query = isIframe
? generateIframeQuery(item, this.pathKey, this.queryKey)
: item[this.queryKey];
return { path, query };
},
getTitle(item) {
return this.$router.$avueRouter.generateTitle(item, this.props);
},
validRoles(item) {
item.meta = item.meta || {};
return item.meta.roles ? item.meta.roles.includes(this.roles) : true;
},
open(item) {
this.$router.push(this.menuRoute(item));
},
},
};
</script>

192
src/page/index/tags.vue Normal file
View File

@@ -0,0 +1,192 @@
<template>
<div class="avue-tags" v-if="setting.tag" @click="contextmenuFlag = false">
<!-- tag盒子 -->
<div
v-if="contextmenuFlag"
class="avue-tags__contentmenu"
:style="{ left: contentmenuX + 'px', top: contentmenuY + 'px' }"
>
<div class="item" @click="closeOthersTags">{{ $t('tagsView.closeOthers') }}</div>
<div class="item" @click="closeAllTags">{{ $t('tagsView.closeAll') }}</div>
<div class="item" @click="clearCacheTags">{{ $t('tagsView.clearCache') }}</div>
</div>
<div class="avue-tags__box">
<el-tabs
v-model="active"
type="card"
@contextmenu="handleContextmenu"
:closable="tagLen !== 1"
@tab-click="openTag"
@edit="menuTag"
>
<el-tab-pane
v-for="(item, index) in tagList"
:key="index"
:label="generateTitle(item)"
:name="item.fullPath"
>
<template #label>
<span>
{{ generateTitle(item) }}
<i
class="el-icon-refresh"
:class="{ turn: refresh }"
@click="handleRefresh"
v-if="active === item.fullPath"
></i>
</span>
</template>
</el-tab-pane>
</el-tabs>
<el-dropdown class="avue-tags__menu">
<el-button type="primary">
{{ $t('tagsView.menu') }}
<i class="el-icon-arrow-down el-icon--right"></i>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="openSearch"
><i class="icon-fangda" /> {{ $t('tagsView.search') }}</el-dropdown-item
>
<el-dropdown-item @click="closeOthersTags"
><i class="icon-fangkuai1" /> {{ $t('tagsView.closeOthers') }}
</el-dropdown-item>
<el-dropdown-item @click="closeAllTags"
><i class="icon-cuowukongxin" /> {{ $t('tagsView.closeAll') }}</el-dropdown-item
>
<el-dropdown-item @click="clearCacheTags"
><i class="icon-dingwei" /> {{ $t('tagsView.clearCache') }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import { clearCache } from '@/api/user';
export default {
name: 'tags',
data() {
return {
refresh: false,
active: '',
contentmenuX: '',
contentmenuY: '',
contextmenuFlag: false,
};
},
watch: {
tag: {
handler(val) {
this.active = val.fullPath;
},
immediate: true,
},
contextmenuFlag() {
window.addEventListener('mousedown', this.watchContextmenu);
},
},
computed: {
...mapGetters(['tagWel', 'tagList', 'tag', 'setting']),
tagLen() {
return this.tagList.length || 0;
},
},
methods: {
openSearch() {
this.$store.commit('SET_IS_SEARCH', true);
},
handleRefresh() {
this.refresh = true;
this.$store.commit('SET_IS_REFRESH', false);
setTimeout(() => {
this.$store.commit('SET_IS_REFRESH', true);
}, 100);
setTimeout(() => {
this.refresh = false;
}, 500);
},
generateTitle(item) {
return this.$router.$avueRouter.generateTitle({
...item,
...{
label: item.name,
},
});
},
watchContextmenu(event) {
if (!this.$el.contains(event.target) || event.button !== 0) {
this.contextmenuFlag = false;
}
window.removeEventListener('mousedown', this.watchContextmenu);
},
handleContextmenu(event) {
let target = event.target;
let flag = false;
if (target.className.indexOf('el-tabs__item') > -1) flag = true;
else if (target.parentNode.className.indexOf('el-tabs__item') > -1) {
target = target.parentNode;
flag = true;
}
if (flag) {
event.preventDefault();
event.stopPropagation();
this.contentmenuX = event.clientX;
this.contentmenuY = event.clientY;
this.tagName = target.getAttribute('aria-controls').slice(5);
this.contextmenuFlag = true;
}
},
menuTag(value, action) {
if (action === 'remove') {
let { tag, key } = this.findTag(value);
this.$store.commit('DEL_TAG', tag);
if (tag.fullPath === this.tag.fullPath) {
tag = this.tagList[key - 1];
this.$router.push({
path: tag.path,
query: tag.query,
});
}
}
},
openTag(item) {
let value = item.props.name;
let { tag } = this.findTag(value);
this.$router.push({
path: tag.path,
query: tag.query,
});
},
findTag(fullPath) {
let tag = this.tagList.find(item => item.fullPath === fullPath);
let key = this.tagList.findIndex(item => item.fullPath === fullPath);
return { tag, key };
},
closeOthersTags() {
this.contextmenuFlag = false;
this.$store.commit('DEL_TAG_OTHER');
},
closeAllTags() {
this.contextmenuFlag = false;
this.$store.commit('DEL_ALL_TAG');
this.$router.push(this.tagWel);
},
clearCacheTags() {
this.$confirm('是否需要清除缓存?', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
clearCache().then(() => {
this.contextmenuFlag = false;
this.$message.success('清除完毕');
});
});
},
},
};
</script>

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>

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>

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>

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>

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>

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>

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>

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>

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>

59
src/page/index/wechat.vue Normal file
View File

@@ -0,0 +1,59 @@
<template>
<el-dialog
center
:show-close="false"
:close-on-press-escape="false"
:close-on-click-modal="false"
append-to-body
v-model="dialogVisible"
title="人机识别"
width="400px"
>
<center>
<span>
扫码下方二维码回复<b>验证码</b><br />
<span style="color: red">获得验证码 + 交流群(一起摸🐟)</span>
</span>
<br />
<br />
<img width="200" src="https://avuejs.com/images/icon/wechat.png" />
<br />
<br />
<el-input v-model="value" placeholder="请输入验证码"></el-input>
</center>
<template #footer>
<span class="dialog-footer">
<el-button type="primary" @click="submit"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script>
export default {
data() {
return {
value: '',
dialogVisible: false,
};
},
created() {
if (window.localStorage.getItem('avue_lock')) {
return;
}
this.dialogVisible = true;
},
methods: {
submit() {
if (this.value == '') {
this.$message.error('验证码不能为空');
return;
} else if (this.value != 'avue') {
this.$message.error('验证码不正确');
return;
}
this.dialogVisible = false;
window.localStorage.setItem('avue_lock', true);
},
},
};
</script>