Commit 0b042bcf authored by 水落(YangLei)'s avatar 水落(YangLei)

feat: 菜单权限处理

parent 445c4639
...@@ -7,7 +7,6 @@ ...@@ -7,7 +7,6 @@
<script> <script>
import { enquireScreen } from './utils/commonUtil'; import { enquireScreen } from './utils/commonUtil';
import { mapState, mapMutations } from 'vuex'; import { mapState, mapMutations } from 'vuex';
import { getI18nKey } from '@/utils/routerUtil';
import { changeThemeColor } from '@/utils/themeUtil'; import { changeThemeColor } from '@/utils/themeUtil';
export default { export default {
...@@ -22,7 +21,7 @@ export default { ...@@ -22,7 +21,7 @@ export default {
this.setLanguage(this.lang); this.setLanguage(this.lang);
enquireScreen(isMobile => this.setDevice(isMobile)); enquireScreen(isMobile => this.setDevice(isMobile));
}, },
mounted() {},
watch: { watch: {
lang(val) { lang(val) {
this.setLanguage(val); this.setLanguage(val);
...@@ -31,15 +30,15 @@ export default { ...@@ -31,15 +30,15 @@ export default {
$route() { $route() {
this.setHtmlTitle(); this.setHtmlTitle();
}, },
'theme.mode': function(val) { 'theme.mode'(val) {
let closeMessage = this.$message.loading(`您选择了主题模式 ${val}, 正在切换...`); let closeMessage = this.$message.loading(`您选择了主题模式 ${val}, 正在切换...`);
changeThemeColor(this.theme.color, val).then(closeMessage); changeThemeColor(this.theme.color, val).then(closeMessage);
}, },
'theme.color': function(val) { 'theme.color'(val) {
let closeMessage = this.$message.loading(`您选择了主题色 ${val}, 正在切换...`); let closeMessage = this.$message.loading(`您选择了主题色 ${val}, 正在切换...`);
changeThemeColor(val, this.theme.mode).then(closeMessage); changeThemeColor(val, this.theme.mode).then(closeMessage);
}, },
layout: function() { layout() {
window.dispatchEvent(new Event('resize')); window.dispatchEvent(new Event('resize'));
}, },
}, },
......
...@@ -8,12 +8,12 @@ ...@@ -8,12 +8,12 @@
:trigger="null" :trigger="null"
> >
<div :class="['logo', theme]"> <div :class="['logo', theme]">
<router-link to="/dashboard/workplace"> <router-link to="/">
<img src="@/assets/img/logo.png" /> <img src="@/assets/img/logo.png" />
<h1>{{ systemName }}</h1> <h1>{{ systemName }}</h1>
</router-link> </router-link>
</div> </div>
<i-menu :theme="theme" :collapsed="collapsed" :options="menuData" @select="onSelect" class="menu" /> <i-menu :theme="theme" :collapsed="collapsed" :options="menuData" class="menu" />
</a-layout-sider> </a-layout-sider>
</template> </template>
...@@ -51,11 +51,6 @@ export default { ...@@ -51,11 +51,6 @@ export default {
}, },
...mapState('settingModule', ['isMobile', 'systemName']), ...mapState('settingModule', ['isMobile', 'systemName']),
}, },
methods: {
onSelect(obj) {
this.$emit('menuSelect', obj);
},
},
}; };
</script> </script>
......
...@@ -34,6 +34,7 @@ ...@@ -34,6 +34,7 @@
import Menu from 'ant-design-vue/es/menu'; import Menu from 'ant-design-vue/es/menu';
import Icon from 'ant-design-vue/es/icon'; import Icon from 'ant-design-vue/es/icon';
import { getUserInfo } from '@/utils'; import { getUserInfo } from '@/utils';
import { mapState } from 'vuex';
const { Item, SubMenu } = Menu; const { Item, SubMenu } = Menu;
...@@ -67,24 +68,21 @@ export default { ...@@ -67,24 +68,21 @@ export default {
sOpenKeys: [], sOpenKeys: [],
cachedOpenKeys: [], cachedOpenKeys: [],
menuData: [], menuData: [],
allMenuList: [],
}; };
}, },
computed: { computed: {
...mapState('settingModule', ['layout']),
menuTheme() { menuTheme() {
return this.theme == 'light' ? this.theme : 'dark'; return this.theme == 'light' ? this.theme : 'dark';
}, },
}, },
created() { created() {
const { menuList } = getUserInfo(); const { menuList } = getUserInfo();
// 增加查询速度
this.menuData = menuList.filter(i => i.menuType === 'MENU'); this.menuData = menuList.filter(i => i.menuType === 'MENU');
this.allMenuList = menuList;
this.updateMenu(); this.updateMenu();
// // 自定义国际化配置
// if (this.i18n && this.i18n.messages) {
// const messages = this.i18n.messages;
// Object.keys(messages).forEach(key => {
// this.$i18n.mergeLocaleMessage(key, messages[key]);
// });
// }
}, },
watch: { watch: {
i18n(val) { i18n(val) {
...@@ -103,9 +101,6 @@ export default { ...@@ -103,9 +101,6 @@ export default {
this.sOpenKeys = this.cachedOpenKeys; this.sOpenKeys = this.cachedOpenKeys;
} }
}, },
$route: function() {
this.updateMenu();
},
sOpenKeys(val) { sOpenKeys(val) {
this.$emit('openChange', val); this.$emit('openChange', val);
this.$emit('update:openKeys', val); this.$emit('update:openKeys', val);
...@@ -123,17 +118,37 @@ export default { ...@@ -123,17 +118,37 @@ export default {
} }
return !icon || icon == 'none' ? null : h(Icon, { props: { type: icon } }); return !icon || icon == 'none' ? null : h(Icon, { props: { type: icon } });
}, },
renderMenuItem(h, menu) { renderMenuItem(h, menu) {
const tag = 'router-link'; const tag = 'router-link';
const config = { const config = {
props: { to: menu.path }, props: { to: menu.menuUrl },
attrs: { style: 'overflow:hidden;white-space:normal;text-overflow:clip;' }, attrs: { style: 'overflow:hidden;white-space:normal;text-overflow:clip;' },
}; };
// 菜单
if (menu.menuType === 'MENU') {
return h(Item, { key: menu.menuId }, [
h(tag, config, [this.renderIcon(h, menu.menuIcon, menu.menuId), menu.menuName]),
]);
}
return h(Item, { key: menu.menuId }, [ return h(Item, { key: menu.menuId }, [
h(tag, config, [this.renderIcon(h, menu.menuIcon, menu.menuId), menu.menuName]), h(
'a',
{
attrs: { style: 'overflow:hidden;white-space:normal;text-overflow:clip;' },
on: {
click(e) {
e.preventDefault();
},
},
},
[this.renderIcon(h, menu.menuIcon, menu.menuId), menu.menuName],
),
]); ]);
}, },
renderSubMenu(h, menu) { renderSubMenu(h, menu) {
const subItem = [ const subItem = [
h( h(
...@@ -146,52 +161,63 @@ export default { ...@@ -146,52 +161,63 @@ export default {
), ),
]; ];
const itemArr = []; const itemArr = [];
menu.children.forEach(item => { menu.children.forEach(item => {
itemArr.push(this.renderItem(h, item)); itemArr.push(this.renderItem(h, item));
}); });
return h(SubMenu, { key: menu.menuId }, subItem.concat(itemArr)); return h(SubMenu, { key: menu.menuId }, subItem.concat(itemArr));
}, },
renderItem(h, menu) { renderItem(h, menu) {
return menu.children && menu.children.length return menu.children && menu.children.length
? this.renderSubMenu(h, menu) ? this.renderSubMenu(h, menu)
: this.renderMenuItem(h, menu); : this.renderMenuItem(h, menu);
}, },
renderMenu(h, menuTree) { renderMenu(h, menuTree) {
let menuArr = []; const menuArr = [];
menuTree.forEach((menu, i) => {
menuTree.forEach(menu => {
menuArr.push(this.renderItem(h, menu)); menuArr.push(this.renderItem(h, menu));
}); });
return menuArr; return menuArr;
}, },
updateMenu() { updateMenu() {
this.selectedKeys = this.getSelectedKey(this.$route); this.selectedKeys = this.getSelectedKey();
// 混合模式 不管
if (this.layout !== 'mix') this.sOpenKeys = this.getOpenKeysByPath(this.$route.path);
},
// let openKeys = matchedRoutes.map(item => item.path); getSelectedKey() {
const { path } = this.$route;
let routeToMenu = this.menuData.find(m => m.menuUrl === path);
// openKeys = openKeys.slice(0, openKeys.length - 1); if (this.layout !== 'mix') return [routeToMenu.menuId];
// if (!fastEqual(openKeys, this.sOpenKeys)) { // 说明这是头部菜单
// this.collapsed || this.mode === 'horizontal' if (this.mode === 'horizontal') {
// ? (this.cachedOpenKeys = openKeys) let parentMenuId = routeToMenu.parentMenuId;
// : (this.sOpenKeys = openKeys); while (parentMenuId !== 0) {
// } routeToMenu = this.allMenuList.find(m => m.menuId === parentMenuId);
this.sOpenKeys = this.getOpenKeysByPath(this.$route.path); parentMenuId = routeToMenu.parentMenuId;
}, }
getSelectedKey(route) { return [routeToMenu.menuId];
const { path } = route; }
return [this.menuData.find(m => m.menuUrl === path).menuId];
return [];
}, },
getOpenKeysByPath(path) { getOpenKeysByPath(path) {
const { menuList } = getUserInfo();
const { parentMenuId } = this.menuData.find(m => m.menuUrl === path); const { parentMenuId } = this.menuData.find(m => m.menuUrl === path);
if (parentMenuId === 0) return []; if (parentMenuId === 0) return [];
const parentMenus = [parentMenuId]; const parentMenus = [parentMenuId];
const res = [parentMenuId]; const res = [parentMenuId];
while (parentMenus.length) { while (parentMenus.length) {
const menuId = parentMenus.pop(); const menuId = parentMenus.pop();
const parentMenu = menuList.find(m => m.menuId === menuId); const parentMenu = this.allMenuList.find(m => m.menuId === menuId);
if (parentMenu.parentMenuId !== 0) { if (parentMenu.parentMenuId !== 0) {
parentMenus.push(parentMenu.parentMenuId); parentMenus.push(parentMenu.parentMenuId);
res.push(parentMenu.parentMenuId); res.push(parentMenu.parentMenuId);
...@@ -215,7 +241,7 @@ export default { ...@@ -215,7 +241,7 @@ export default {
this.sOpenKeys = val; this.sOpenKeys = val;
}, },
click: obj => { click: obj => {
obj.selectedKeys = [obj.key]; this.selectedKeys = [obj.key];
this.$emit('select', obj); this.$emit('select', obj);
}, },
}, },
......
<template> <template>
<div :class="['page-header', layout, pageWidth]"> <a-breadcrumb style="margin: 10px 0">
<a-breadcrumb> <a-breadcrumb-item :key="index" v-for="(item, index) in breadcrumb">
<a-breadcrumb-item :key="index" v-for="(item, index) in breadcrumb"> <span>{{ item }}</span>
<span>{{ item }}</span> </a-breadcrumb-item>
</a-breadcrumb-item> </a-breadcrumb>
</a-breadcrumb>
</div>
</template> </template>
<script> <script>
import { mapState } from 'vuex';
export default { export default {
name: 'PageHeader', name: 'PageHeader',
props: { props: {
title: {
type: [String, Boolean],
required: false,
},
breadcrumb: { breadcrumb: {
type: Array, type: Array,
required: false, required: false,
}, },
logo: {
type: String,
required: false,
},
avatar: {
type: String,
required: false,
},
},
computed: {
...mapState('settingModule', ['layout', 'showPageTitle', 'pageWidth']),
}, },
}; };
</script> </script>
<style lang="less" scoped>
.page-header {
padding: 16px 24px;
&.head.fixed {
margin: auto;
max-width: 1400px;
}
}
</style>
...@@ -8,7 +8,7 @@ import VueI18n from 'vue-i18n'; ...@@ -8,7 +8,7 @@ import VueI18n from 'vue-i18n';
import { accountModule, settingModule } from './pages/frame/store'; import { accountModule, settingModule } from './pages/frame/store';
import App from './App.vue'; import App from './App.vue';
import Plugins from './plugins'; import Plugins from './plugins';
import { loadRoutes, loadGuards, setAppOptions } from './utils/routerUtil'; import { loadGuards, setAppOptions } from './utils/routerUtil';
import guards from './router/guards'; import guards from './router/guards';
import { loadResponseInterceptor } from './utils/requestUtil'; import { loadResponseInterceptor } from './utils/requestUtil';
import langUtils from '@/utils/langUtils'; import langUtils from '@/utils/langUtils';
...@@ -56,8 +56,6 @@ Vue.use(Plugins); ...@@ -56,8 +56,6 @@ Vue.use(Plugins);
//设置应用配置 //设置应用配置
setAppOptions({ router, store, i18n }); setAppOptions({ router, store, i18n });
// 加载路由
loadRoutes();
// 加载路由守卫 // 加载路由守卫
loadGuards(guards, { router, store, i18n, message: Vue.prototype.$message }); loadGuards(guards, { router, store, i18n, message: Vue.prototype.$message });
......
<template> <template>
<a-layout-header :class="[headerTheme, 'admin-header']"> <a-layout-header :class="[headerTheme, 'admin-header']">
<div :class="['admin-header-wide', layout, pageWidth]"> <div :class="['admin-header-wide', layout, pageWidth]">
<router-link v-if="isMobile || layout === 'head'" to="/" :class="['logo', isMobile ? null : 'pc', headerTheme]"> <router-link
v-if="isMobile || layout === 'head'"
to="/"
:class="['logo', isMobile ? null : 'pc', headerTheme]"
>
<img width="32" src="@/assets/img/logo.png" /> <img width="32" src="@/assets/img/logo.png" />
<h1 v-if="!isMobile">{{systemName}}</h1> <h1 v-if="!isMobile">{{ systemName }}</h1>
</router-link> </router-link>
<a-icon v-if="showToggleCollapse" class="trigger" :type="collapsed ? 'menu-unfold' : 'menu-fold'" @click="toggleCollapse" /> <a-icon
<div v-if="layout !== 'side' && !isMobile" class="admin-header-menu" :style="`width: ${menuWidth};`"> v-if="showToggleCollapse"
<i-menu class="head-menu" :theme="headerTheme" mode="horizontal" :options="menuData" @select="onSelect" /> class="trigger"
:type="collapsed ? 'menu-unfold' : 'menu-fold'"
@click="toggleCollapse"
/>
<div
v-if="layout !== 'side' && !isMobile"
class="admin-header-menu"
:style="`width: ${menuWidth};`"
>
<i-menu
class="head-menu"
:theme="headerTheme"
mode="horizontal"
:options="menuData"
@select="onSelect"
/>
</div> </div>
<div :class="['admin-header-right', headerTheme]"> <div :class="['admin-header-right', headerTheme]">
<a-tooltip class="header-item" title="帮助文档" placement="bottom">
<a href="https://iczer.gitee.io/vue-antd-admin-docs/" target="_blank">
<a-icon type="question-circle-o" />
</a>
</a-tooltip>
<layout-top-header-notice class="header-item" /> <layout-top-header-notice class="header-item" />
<layout-top-header-avatar class="header-item" /> <layout-top-header-avatar class="header-item" />
<layout-top-header-lang class="header-item" /> <layout-top-header-lang class="header-item" />
</div> </div>
</div> </div>
</a-layout-header> </a-layout-header>
...@@ -37,8 +50,7 @@ export default { ...@@ -37,8 +50,7 @@ export default {
components: { IMenu, LayoutTopHeaderAvatar, LayoutTopHeaderNotice, LayoutTopHeaderLang }, components: { IMenu, LayoutTopHeaderAvatar, LayoutTopHeaderNotice, LayoutTopHeaderLang },
props: ['collapsed', 'menuData'], props: ['collapsed', 'menuData'],
data() { data() {
return { return {};
};
}, },
computed: { computed: {
...mapState('settingModule', ['theme', 'isMobile', 'layout', 'systemName', 'pageWidth']), ...mapState('settingModule', ['theme', 'isMobile', 'layout', 'systemName', 'pageWidth']),
...@@ -54,14 +66,13 @@ export default { ...@@ -54,14 +66,13 @@ export default {
const extraWidth = searchActive ? '600px' : '400px'; const extraWidth = searchActive ? '600px' : '400px';
return `calc(${headWidth} - ${extraWidth})`; return `calc(${headWidth} - ${extraWidth})`;
}, },
showToggleCollapse(){ showToggleCollapse() {
if (this.layout !== 'head' && this.isMobile===false){ if (this.layout !== 'head' && this.isMobile === false) {
return true; return true;
} } else {
else{
return false; return false;
} }
} },
}, },
methods: { methods: {
toggleCollapse() { toggleCollapse() {
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
</template> </template>
<script> <script>
import { setUserInfoByRequest } from '@/utils';
import langUtils from '@/utils/langUtils'; import langUtils from '@/utils/langUtils';
export default { export default {
...@@ -27,8 +28,9 @@ export default { ...@@ -27,8 +28,9 @@ export default {
}, },
}, },
methods: { methods: {
change({ key }) { async change({ key }) {
langUtils.set(key); langUtils.set(key);
await setUserInfoByRequest();
window.location.reload(); window.location.reload();
}, },
}, },
......
<template> <template>
<div class="side-setting"> <div :class="$style['side-setting']">
<setting-item> <setting-item>
<a-button @click="saveSetting" type="primary" icon="save">{{ $t('save') }}</a-button> <a-button @click="saveSetting" type="primary" icon="save">{{ $t('save') }}</a-button>
<a-button @click="resetSetting" type="dashed" icon="redo" style="float: right"> <a-button @click="resetSetting" type="dashed" icon="redo" style="float: right">
...@@ -306,7 +306,7 @@ export default { ...@@ -306,7 +306,7 @@ export default {
}; };
</script> </script>
<style lang="less" scoped> <style lang="less" module>
.side-setting { .side-setting {
min-height: 100%; min-height: 100%;
background-color: @base-bg-color; background-color: @base-bg-color;
...@@ -315,9 +315,7 @@ export default { ...@@ -315,9 +315,7 @@ export default {
line-height: 1.5; line-height: 1.5;
word-wrap: break-word; word-wrap: break-word;
position: relative; position: relative;
.flex {
display: flex;
}
.select-item { .select-item {
width: 80px; width: 80px;
} }
......
<template> <template>
<div class="setting-item"> <div :class="$style['setting-item']">
<h3 v-if="title" class="title">{{title}}</h3> <h3 v-if="title" :class="$style.title">{{ title }}</h3>
<slot></slot> <slot />
</div> </div>
</template> </template>
...@@ -12,14 +12,14 @@ export default { ...@@ -12,14 +12,14 @@ export default {
}; };
</script> </script>
<style lang="less" scoped> <style lang="less" module>
.setting-item { .setting-item {
margin-bottom: 24px; margin-bottom: 24px;
.title { }
font-size: 14px; .title {
color: @title-color; font-size: 14px;
line-height: 22px; color: @title-color;
margin-bottom: 12px; line-height: 22px;
} margin-bottom: 12px;
} }
</style> </style>
<template> <template>
<div :class="['tabs-head', layout, pageWidth]"> <a-tabs type="editable-card" :active-key="active" :hide-add="true">
<a-tabs <a-tooltip placement="left" :title="lockTitle" slot="tabBarExtraContent">
type="editable-card" <a-icon
:class="[ theme="filled"
'tabs-container', @click="onLockClick"
layout, class="header-lock"
pageWidth, :type="fixedTabs ? 'lock' : 'unlock'"
{ affixed: affixed, 'fixed-header': fixedHeader, collapsed: adminLayout.collapsed }, />
]" </a-tooltip>
:active-key="active" <a-tab-pane v-for="page in pageList" :key="page.fullPath">
:hide-add="true" <div slot="tab" class="tab" @contextmenu="e => onContextmenu(page.fullPath, e)">
>
<a-tooltip placement="left" :title="lockTitle" slot="tabBarExtraContent">
<a-icon <a-icon
theme="filled" @click="onRefresh(page)"
@click="onLockClick" :class="['icon-sync', { hide: page.fullPath !== active && !page.loading }]"
class="header-lock" :type="page.loading ? 'loading' : 'sync'"
:type="fixedTabs ? 'lock' : 'unlock'"
/> />
</a-tooltip> <div class="title" @click="onTabClick(page.fullPath)">{{ pageName(page) }}</div>
<a-tab-pane v-for="page in pageList" :key="page.fullPath"> <a-icon
<div slot="tab" class="tab" @contextmenu="e => onContextmenu(page.fullPath, e)"> v-if="!page.unclose"
<a-icon @click="onClose(page.fullPath)"
@click="onRefresh(page)" class="icon-close"
:class="['icon-sync', { hide: page.fullPath !== active && !page.loading }]" type="close"
:type="page.loading ? 'loading' : 'sync'" />
/> </div>
<div class="title" @click="onTabClick(page.fullPath)">{{ pageName(page) }}</div> </a-tab-pane>
<a-icon </a-tabs>
v-if="!page.unclose"
@click="onClose(page.fullPath)"
class="icon-close"
type="close"
/>
</div>
</a-tab-pane>
</a-tabs>
<div v-if="affixed" class="virtual-tabs"></div>
</div>
</template> </template>
<script> <script>
import { mapState, mapMutations } from 'vuex'; import { mapState, mapMutations } from 'vuex';
import { getI18nKey } from '@/utils/routerUtil';
import layoutTabHeaderI18n from './i18n'; import layoutTabHeaderI18n from './i18n';
import { getUserInfo } from '@/utils';
export default { export default {
name: 'LayoutTabsHeader', name: 'LayoutTabsHeader',
...@@ -56,18 +43,24 @@ export default { ...@@ -56,18 +43,24 @@ export default {
data() { data() {
return { return {
affixed: false, affixed: false,
menuList: [],
}; };
}, },
inject: ['adminLayout'], inject: ['adminLayout'],
created() { created() {
this.affixed = this.fixedTabs; this.affixed = this.fixedTabs;
const { menuList } = getUserInfo();
this.menuList = menuList.filter(i => i.menuType === 'MENU');
}, },
computed: { computed: {
...mapState('settingModule', ['layout', 'pageWidth', 'fixedHeader', 'fixedTabs', 'customTitles']), ...mapState('settingModule', ['layout', 'pageWidth', 'fixedHeader', 'fixedTabs', 'customTitles']),
lockTitle() { lockTitle() {
return this.$t(this.fixedTabs ? 'unlock' : 'lock'); return this.$t(this.fixedTabs ? 'unlock' : 'lock');
}, },
}, },
methods: { methods: {
...mapMutations('settingModule', ['setFixedTabs']), ...mapMutations('settingModule', ['setFixedTabs']),
onLockClick() { onLockClick() {
...@@ -96,8 +89,7 @@ export default { ...@@ -96,8 +89,7 @@ export default {
}, },
pageName(page) { pageName(page) {
const pagePath = page.fullPath.split('?')[0]; const pagePath = page.fullPath.split('?')[0];
const custom = this.customTitles.find(item => item.path === pagePath); return this.menuList.find(m => m.menuUrl === pagePath)?.menuName;
return (custom && custom.title) || page.title || this.$t(getI18nKey(page.keyPath));
}, },
}, },
}; };
...@@ -113,6 +105,8 @@ export default { ...@@ -113,6 +105,8 @@ export default {
.title { .title {
display: inline-block; display: inline-block;
height: 100%; height: 100%;
user-select: none;
-webkit-user-select: none;
} }
.icon-close { .icon-close {
font-size: 12px; font-size: 12px;
...@@ -136,54 +130,4 @@ export default { ...@@ -136,54 +130,4 @@ export default {
} }
} }
} }
.tabs-head {
margin: 0 auto;
&.head.fixed {
width: 1400px;
}
}
.tabs-container {
margin: -16px auto 8px;
transition: top, left 0.2s;
.header-lock {
font-size: 18px;
cursor: pointer;
color: @primary-3;
&:hover {
color: @primary-color;
}
}
&.affixed {
margin: 0 auto;
top: 0px;
padding: 8px 24px 0;
position: fixed;
height: 48px;
z-index: 1;
background-color: @layout-body-background;
&.side,
&.mix {
right: 0;
left: 256px;
&.collapsed {
left: 80px;
}
}
&.head {
width: inherit;
padding: 8px 0 0;
&.fluid {
left: 0;
right: 0;
padding: 8px 24px 0;
}
}
&.fixed-header {
top: 64px;
}
}
}
.virtual-tabs {
height: 48px;
}
</style> </style>
...@@ -39,6 +39,7 @@ ...@@ -39,6 +39,7 @@
:menuData="headMenuData" :menuData="headMenuData"
:collapsed="collapsed" :collapsed="collapsed"
@toggleCollapse="toggleCollapse" @toggleCollapse="toggleCollapse"
@menuSelect="menuSelect"
/> />
<a-layout-header <a-layout-header
:class="[ :class="[
...@@ -46,18 +47,16 @@ ...@@ -46,18 +47,16 @@
{ 'fixed-tabs': fixedTabs, 'fixed-header': fixedHeader, 'multi-page': multiPage }, { 'fixed-tabs': fixedTabs, 'fixed-header': fixedHeader, 'multi-page': multiPage },
]" ]"
v-show="fixedHeader" v-show="fixedHeader"
></a-layout-header> />
<a-layout-content class="admin-layout-content" :style="`min-height: ${minHeight}px;`"> <a-layout-content class="admin-layout-content">
<div style="position: relative"> <slot />
<slot></slot>
</div>
</a-layout-content> </a-layout-content>
</a-layout> </a-layout>
</a-layout> </a-layout>
</template> </template>
<script> <script>
import LayoutTopHeader from '../components/header/LayoutTopHeader'; import LayoutTopHeader from '../components/header/LayoutTopHeader.vue';
import Drawer from '@/components/tool/Drawer'; import Drawer from '@/components/tool/Drawer';
import SideMenu from '@/components/menu/SideMenu.vue'; import SideMenu from '@/components/menu/SideMenu.vue';
import Setting from '../components/setting/Setting'; import Setting from '../components/setting/Setting';
...@@ -76,6 +75,9 @@ export default { ...@@ -76,6 +75,9 @@ export default {
showSetting: false, showSetting: false,
drawerOpen: false, drawerOpen: false,
menuData: [], menuData: [],
firstMenu: [],
subMenu: [],
menuList: [],
}; };
}, },
provide() { provide() {
...@@ -84,17 +86,14 @@ export default { ...@@ -84,17 +86,14 @@ export default {
}; };
}, },
watch: { watch: {
$route(val) {
this.setActivated(val);
},
layout() {
this.setActivated(this.$route);
},
isMobile(val) { isMobile(val) {
if (!val) { if (!val) {
this.drawerOpen = false; this.drawerOpen = false;
} }
}, },
layout(layout) {
this.updateSildeMenu(layout);
},
}, },
computed: { computed: {
...mapState('settingModule', [ ...mapState('settingModule', [
...@@ -110,8 +109,6 @@ export default { ...@@ -110,8 +109,6 @@ export default {
'multiPage', 'multiPage',
]), ]),
...mapGetters('settingModule', ['firstMenu', 'subMenu']),
hideSettingExtend() { hideSettingExtend() {
if (this.hideSetting === false && process.env.NODE_ENV === 'development') { if (this.hideSetting === false && process.env.NODE_ENV === 'development') {
return false; return false;
...@@ -131,41 +128,63 @@ export default { ...@@ -131,41 +128,63 @@ export default {
}, },
headMenuData() { headMenuData() {
const { layout, menuData, firstMenu } = this; const { layout, menuData, firstMenu } = this;
console.log('first', firstMenu);
return layout === 'mix' ? firstMenu : menuData; return layout === 'mix' ? firstMenu : menuData;
}, },
sideMenuData() { sideMenuData() {
const { layout, menuData, subMenu } = this; const { layout, menuData, subMenu } = this;
console.log('sub', subMenu);
return layout === 'mix' ? subMenu : menuData; return layout === 'mix' ? subMenu : menuData;
}, },
}, },
methods: { methods: {
...mapMutations('settingModule', ['correctPageMinHeight', 'setActivatedFirst']), ...mapMutations('settingModule', ['correctPageMinHeight']),
toggleCollapse() { toggleCollapse() {
this.collapsed = !this.collapsed; this.collapsed = !this.collapsed;
}, },
onMenuSelect() { onMenuSelect() {
this.toggleCollapse(); this.toggleCollapse();
}, },
setActivated(route) { updateSildeMenu(layout) {
if (this.layout === 'mix') { if (layout === 'mix') {
let matched = route.matched; const { path } = this.$route;
matched = matched.slice(0, matched.length - 1); let currentMenu = this.menuList.find(i => i.menuUrl === path);
const { firstMenu } = this; let parentMenuId = currentMenu.parentMenuId;
for (let menu of firstMenu) {
if (matched.findIndex(item => item.path === menu.fullPath) !== -1) { while (parentMenuId !== 0) {
this.setActivatedFirst(menu.fullPath); currentMenu = this.menuList.find(i => i.menuId === parentMenuId);
break; parentMenuId = currentMenu.parentMenuId;
}
} }
this.subMenu = currentMenu.children || [];
} }
}, },
menuSelect(obj) {
console.log('切换', obj);
// 拿到 菜单id
const menuId = obj.key;
const currentMenu = this.menuList.find(i => i.menuId === menuId);
if (currentMenu.menuType === 'MENU') {
this.subMenu = [];
return;
}
this.subMenu = currentMenu.children || [];
},
}, },
created() { created() {
this.correctPageMinHeight(this.minHeight - 24); this.correctPageMinHeight(this.minHeight - 24);
this.setActivated(this.$route); const { menuList } = getUserInfo();
const userInfo = getUserInfo(); console.log(menuList);
const menuData = convertListToTree(userInfo?.menuList || [], false, true); const menuData = convertListToTree(menuList || [], false, true);
this.menuData = menuData; this.menuData = menuData;
this.menuList = menuList;
this.firstMenu = JSON.parse(JSON.stringify(menuData)).map(i => {
delete i.children;
return i;
});
}, },
beforeDestroy() { beforeDestroy() {
this.correctPageMinHeight(-this.minHeight + 24); this.correctPageMinHeight(-this.minHeight + 24);
...@@ -195,6 +214,9 @@ export default { ...@@ -195,6 +214,9 @@ export default {
} }
} }
.admin-layout-main { .admin-layout-main {
display: flow-root;
position: initial;
.admin-header { .admin-header {
top: 0; top: 0;
right: 0; right: 0;
......
<template> <template>
<div class="common-layout"> <div class="common-layout">
<div class="content"> <div class="content">
<slot></slot> <slot />
</div> </div>
</div> </div>
</template> </template>
<script>
import { mapState } from 'vuex';
export default {
name: 'CommonLayout',
computed: {
...mapState('settingModule', ['footerLinks', 'copyright']),
},
};
</script>
<style scoped lang="less"> <style scoped lang="less">
.common-layout { .common-layout {
display: flex; display: flex;
......
...@@ -4,12 +4,11 @@ ...@@ -4,12 +4,11 @@
ref="pageHeader" ref="pageHeader"
:style="`margin-top: ${multiPage ? 0 : -24}px`" :style="`margin-top: ${multiPage ? 0 : -24}px`"
:breadcrumb="breadcrumb" :breadcrumb="breadcrumb"
:title="pageTitle"
:logo="logo" :logo="logo"
:avatar="avatar" :avatar="avatar"
> >
<slot name="action" slot="action"></slot> <slot name="action" slot="action" />
<slot slot="content" name="headerContent"></slot> <slot slot="content" name="headerContent" />
<div slot="content" v-if="!this.$slots.headerContent && desc"> <div slot="content" v-if="!this.$slots.headerContent && desc">
<p>{{ desc }}</p> <p>{{ desc }}</p>
<div v-if="this.linkList" class="link"> <div v-if="this.linkList" class="link">
...@@ -20,16 +19,15 @@ ...@@ -20,16 +19,15 @@
</div> </div>
<slot v-if="this.$slots.extra" slot="extra" name="extra"></slot> <slot v-if="this.$slots.extra" slot="extra" name="extra"></slot>
</page-header> </page-header>
<div ref="page" :class="['page-content', layout, pageWidth]"> <slot />
<slot></slot>
</div>
</div> </div>
</template> </template>
<script> <script>
import PageHeader from '@/components/page/PageHeader'; import PageHeader from '@/components/page/PageHeader.vue';
import { mapState, mapMutations } from 'vuex'; import { mapState, mapMutations } from 'vuex';
import { getI18nKey } from '@/utils/routerUtil'; import { getI18nKey } from '@/utils/routerUtil';
import { getUserInfo } from '@/utils';
export default { export default {
name: 'PageLayout', name: 'PageLayout',
...@@ -61,33 +59,17 @@ export default { ...@@ -61,33 +59,17 @@ export default {
this.updatePageHeight(); this.updatePageHeight();
}, },
created() { created() {
const { menuList } = getUserInfo();
this.menuList = menuList;
this.page = this.$route.meta.page; this.page = this.$route.meta.page;
}, },
beforeDestroy() { beforeDestroy() {
this.updatePageHeight(0); this.updatePageHeight(0);
}, },
computed: { computed: {
...mapState('settingModule', ['layout', 'multiPage', 'pageMinHeight', 'pageWidth', 'customTitles']), ...mapState('settingModule', ['layout', 'multiPage', 'pageMinHeight', 'pageWidth']),
pageTitle() {
let pageTitle = this.page && this.page.title;
return this.customTitle || (pageTitle && this.$t(pageTitle)) || this.title || this.routeName;
},
routeName() {
const route = this.$route;
return this.$t(getI18nKey(route.matched[route.matched.length - 1].path));
},
breadcrumb() { breadcrumb() {
let page = this.page; return this.getRouteBreadcrumb();
let breadcrumb = page && page.breadcrumb;
if (breadcrumb) {
let i18nBreadcrumb = [];
breadcrumb.forEach(item => {
i18nBreadcrumb.push(this.$t(item));
});
return i18nBreadcrumb;
} else {
return this.getRouteBreadcrumb();
}
}, },
marginCorrect() { marginCorrect() {
return this.multiPage ? 24 : 0; return this.multiPage ? 24 : 0;
...@@ -96,18 +78,14 @@ export default { ...@@ -96,18 +78,14 @@ export default {
methods: { methods: {
...mapMutations('settingModule', ['correctPageMinHeight']), ...mapMutations('settingModule', ['correctPageMinHeight']),
getRouteBreadcrumb() { getRouteBreadcrumb() {
let routes = this.$route.matched;
const path = this.$route.path; const path = this.$route.path;
let breadcrumb = []; const currentMenu = this.menuList.find(m => m.menuUrl === path);
routes let parentMenuId = currentMenu.parentMenuId;
.filter(item => path.includes(item.path)) const breadcrumb = [currentMenu.menuName];
.forEach(route => { while (parentMenuId !== 0) {
const path = route.path.length === 0 ? '/home' : route.path; const parentMenu = this.menuList.find(m => m.menuId === parentMenuId);
breadcrumb.push(this.$t(getI18nKey(path))); breadcrumb.unshift(parentMenu.menuName);
}); parentMenuId = parentMenu.parentMenuId;
let pageTitle = this.page && this.page.title;
if (this.customTitle || pageTitle) {
breadcrumb[breadcrumb.length - 1] = this.customTitle || pageTitle;
} }
return breadcrumb; return breadcrumb;
}, },
...@@ -135,12 +113,4 @@ export default { ...@@ -135,12 +113,4 @@ export default {
} }
} }
} }
.page-content {
position: relative;
&.head.fixed {
margin: 0 auto;
max-width: 1400px;
}
}
</style> </style>
...@@ -42,14 +42,6 @@ export default { ...@@ -42,14 +42,6 @@ export default {
return menuItem; return menuItem;
}); });
}, },
subMenu(state) {
const { menuData, activatedFirst } = state;
if (menuData.length > 0 && !menuData[0].fullPath) {
formatFullPath(menuData);
}
const current = menuData.find(menu => menu.fullPath === activatedFirst);
return (current && current.children) || [];
},
}, },
mutations: { mutations: {
setDevice(state, isMobile) { setDevice(state, isMobile) {
...@@ -59,6 +51,7 @@ export default { ...@@ -59,6 +51,7 @@ export default {
state.theme = theme; state.theme = theme;
}, },
setLayout(state, layout) { setLayout(state, layout) {
console.log(layout);
state.layout = layout; state.layout = layout;
}, },
setMultiPage(state, multiPage) { setMultiPage(state, multiPage) {
......
...@@ -10,15 +10,10 @@ ...@@ -10,15 +10,10 @@
@refresh="refresh" @refresh="refresh"
@contextmenu="onContextmenu" @contextmenu="onContextmenu"
/> />
<div <a-keep-alive :exclude-keys="excludeKeys" v-if="multiPage && cachePage" v-model="clearCaches">
:class="['tabs-view-content', layout, pageWidth]" <router-view v-if="!refreshing" ref="tabContent" :key="$route.fullPath" />
:style="`margin-top: ${multiPage ? -20 : 0}px`" </a-keep-alive>
> <router-view ref="tabContent" v-else-if="!refreshing" />
<a-keep-alive :exclude-keys="excludeKeys" v-if="multiPage && cachePage" v-model="clearCaches">
<router-view v-if="!refreshing" ref="tabContent" :key="$route.fullPath" />
</a-keep-alive>
<router-view ref="tabContent" v-else-if="!refreshing" />
</div>
</admin-layout> </admin-layout>
</template> </template>
...@@ -28,7 +23,7 @@ import Contextmenu from '@/components/menu/Contextmenu.vue'; ...@@ -28,7 +23,7 @@ import Contextmenu from '@/components/menu/Contextmenu.vue';
import { mapState, mapMutations } from 'vuex'; import { mapState, mapMutations } from 'vuex';
import { getI18nKey } from '@/utils/routerUtil'; import { getI18nKey } from '@/utils/routerUtil';
import AKeepAlive from '@/components/cache/AKeepAlive'; import AKeepAlive from '@/components/cache/AKeepAlive';
import LayoutTabsHeader from '../../components/tab/LayoutTabsHeader'; import LayoutTabsHeader from '../../components/tab/LayoutTabsHeader.vue';
import templateI18n from './i18n'; import templateI18n from './i18n';
export default { export default {
......
...@@ -24,7 +24,6 @@ const hasAuthorityRoutes = [ ...@@ -24,7 +24,6 @@ const hasAuthorityRoutes = [
path: '/', path: '/',
component: TabsTemplateView, component: TabsTemplateView,
redirect: '/home', redirect: '/home',
name: '首页',
children: [ children: [
{ {
path: 'home', path: 'home',
...@@ -59,12 +58,6 @@ const hasAuthorityRoutes = [ ...@@ -59,12 +58,6 @@ const hasAuthorityRoutes = [
{ {
path: 'system', path: 'system',
name: '系统管理', name: '系统管理',
meta: {
icon: 'setting',
page: {
cacheAble: true,
},
},
component: PageTemplateView, component: PageTemplateView,
children: [ children: [
{ {
......
...@@ -57,43 +57,11 @@ const authorityGuard = (to, from, next, options) => { ...@@ -57,43 +57,11 @@ const authorityGuard = (to, from, next, options) => {
if (!hasAuthority(to)) { if (!hasAuthority(to)) {
message.warning(`对不起,您无权访问页面: ${to.fullPath},请联系管理员`); message.warning(`对不起,您无权访问页面: ${to.fullPath},请联系管理员`);
next({ path: '/403' }); next({ path: '/403' });
// NProgress.done()
} else { } else {
next(); next();
} }
}; };
/**
* 混合导航模式下一级菜单跳转重定向
* @param to
* @param from
* @param next
* @param options
* @returns {*}
*/
const redirectGuard = (to, from, next, options) => {
const { store } = options;
const getFirstChild = routes => {
const route = routes[0];
if (!route.children || route.children.length === 0) {
return route;
}
return getFirstChild(route.children);
};
if (store.state.settingModule.layout === 'mix') {
const firstMenu = store.getters['settingModule/firstMenu'];
if (firstMenu.find(item => item.fullPath === to.fullPath)) {
store.commit('settingModule/setActivatedFirst', to.fullPath);
const subMenu = store.getters['settingModule/subMenu'];
if (subMenu.length > 0) {
const redirect = getFirstChild(subMenu);
return next({ path: redirect.fullPath });
}
}
}
next();
};
/** /**
* 进度条结束 * 进度条结束
* @param to * @param to
...@@ -106,6 +74,6 @@ const progressDone = () => { ...@@ -106,6 +74,6 @@ const progressDone = () => {
}; };
export default { export default {
beforeEach: [progressStart, loginGuard, authorityGuard, redirectGuard], beforeEach: [progressStart, loginGuard, authorityGuard],
afterEach: [progressDone], afterEach: [progressDone],
}; };
import syncConfig from './config'; import syncConfig from './config';
import { formatRoutes } from '../utils/routerUtil';
// 不需要登录拦截的路由配置 // 不需要登录拦截的路由配置
const loginIgnore = { const loginIgnore = {
...@@ -22,7 +21,6 @@ const loginIgnore = { ...@@ -22,7 +21,6 @@ const loginIgnore = {
*/ */
function initRouter() { function initRouter() {
const options = syncConfig; const options = syncConfig;
formatRoutes(options.routes);
return options; return options;
} }
......
...@@ -70,29 +70,6 @@ function parseRoutes(routesConfig, routerMap) { ...@@ -70,29 +70,6 @@ function parseRoutes(routesConfig, routerMap) {
return routes; return routes;
} }
/**
* 加载路由
* @param routesConfig {RouteConfig[]} 路由配置
*/
function loadRoutes(routesConfig) {
// 应用配置
const { router, store, i18n } = appOptions;
routesConfig = store.getters['accountModule/routesConfig'];
// 提取路由国际化数据
mergeI18nFromRoutes(i18n, router.options.routes);
// 初始化Admin后台菜单数据
const rootRoute = router.options.routes.find(item => item.path === '/');
const menuRoutes = rootRoute && rootRoute.children;
if (menuRoutes) {
store.commit('settingModule/setMenuData', menuRoutes);
}
}
/** /**
* 深度合并路由 * 深度合并路由
* @param target {Route[]} * @param target {Route[]}
...@@ -131,57 +108,6 @@ function deepMergeRoutes(target, source) { ...@@ -131,57 +108,6 @@ function deepMergeRoutes(target, source) {
return parseRoutesMap(merge); return parseRoutesMap(merge);
} }
/**
* 格式化路由
* @param routes 路由配置
*/
function formatRoutes(routes) {
routes.forEach(route => {
const { path } = route;
if (!path.startsWith('/') && path !== '*') {
route.path = '/' + path;
}
});
formatAuthority(routes);
}
/**
* 格式化路由的权限配置
* @param routes 路由
* @param pAuthorities 父级路由权限配置集合
*/
function formatAuthority(routes, pAuthorities = []) {
routes.forEach(route => {
const meta = route.meta;
const defaultAuthority = pAuthorities[pAuthorities.length - 1] || { permission: '*' };
if (meta) {
let authority = {};
if (!meta.authority) {
authority = defaultAuthority;
} else if (typeof meta.authority === 'string') {
authority.permission = meta.authority;
} else if (typeof meta.authority === 'object') {
authority = meta.authority;
const { role } = authority;
if (typeof role === 'string') {
authority.role = [role];
}
if (!authority.permission && !authority.role) {
authority = defaultAuthority;
}
}
meta.authority = authority;
} else {
const authority = defaultAuthority;
route.meta = { authority };
}
route.meta.pAuthorities = pAuthorities;
if (route.children) {
formatAuthority(route.children, [...pAuthorities, route.meta.authority]);
}
});
}
/** /**
* 从路由 path 解析 i18n key * 从路由 path 解析 i18n key
* @param path * @param path
...@@ -213,13 +139,4 @@ function loadGuards(guards, options) { ...@@ -213,13 +139,4 @@ function loadGuards(guards, options) {
}); });
} }
export { export { parseRoutes, getI18nKey, loadGuards, deepMergeRoutes, setAppOptions };
parseRoutes,
loadRoutes,
formatAuthority,
getI18nKey,
loadGuards,
deepMergeRoutes,
formatRoutes,
setAppOptions,
};
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment