init
This commit is contained in:
111
src/page/index/index.vue
Normal file
111
src/page/index/index.vue
Normal 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>
|
||||
7
src/page/index/layout.vue
Normal file
7
src/page/index/layout.vue
Normal 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
44
src/page/index/logo.vue
Normal 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
178
src/page/index/search.vue
Normal 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
173
src/page/index/setting.vue
Normal 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>
|
||||
63
src/page/index/sidebar/index.vue
Normal file
63
src/page/index/sidebar/index.vue
Normal 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>
|
||||
105
src/page/index/sidebar/sidebarItem.vue
Normal file
105
src/page/index/sidebar/sidebarItem.vue
Normal 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
192
src/page/index/tags.vue
Normal 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>
|
||||
364
src/page/index/top/index.vue
Normal file
364
src/page/index/top/index.vue
Normal 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
src/page/index/top/top-color.vue
Normal file
75
src/page/index/top/top-color.vue
Normal 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
src/page/index/top/top-full.vue
Normal file
24
src/page/index/top/top-full.vue
Normal 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
src/page/index/top/top-lang.vue
Normal file
42
src/page/index/top/top-lang.vue
Normal 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
src/page/index/top/top-lock.vue
Normal file
67
src/page/index/top/top-lock.vue
Normal 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
src/page/index/top/top-logs.vue
Normal file
86
src/page/index/top/top-logs.vue
Normal 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
src/page/index/top/top-menu.vue
Normal file
58
src/page/index/top/top-menu.vue
Normal 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
src/page/index/top/top-search.vue
Normal file
120
src/page/index/top/top-search.vue
Normal 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
src/page/index/top/top-theme.vue
Normal file
119
src/page/index/top/top-theme.vue
Normal 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
59
src/page/index/wechat.vue
Normal 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>
|
||||
Reference in New Issue
Block a user