先看后台的主体样式 左侧菜单和右侧红框(PageTabs)
第一步:与后端同学讨论菜单的返回格式。可以随便定义,下面是项目中定义的
// type: 5页面权限 10是按钮权限。 visible:是否显示 orderNum:标识
const menu = {
elements: [
{
id: 1529296954965106690,
parentId: 0,
resourceId: 0,
name: '导航维护系统',
path: '-',
component: 'management',
visible: true,
type: 1,
icon: 'el-icon-place',
orderNum: 2,
props: {},
remark: '',
resource: null,
children: [
{
id: 1529296954965106693,
parentId: 1529296954965106690,
resourceId: 0,
name: '产品线配置SDK',
path: '/setting/productLine',
component: 'InspectionManagement',
visible: true,
type: 5,
icon: 'el-icon-document-add',
orderNum: 1,
props: {},
remark: '',
resource: null,
children: null,
},
{
id: 1529296954965106695,
parentId: 1529296954965106690,
resourceId: 0,
name: '字典管理',
path: '/setting/dictionaries',
component: 'dictionaries',
visible: true,
type: 5,
icon: 'el-icon-document-add',
orderNum: 2,
props: {},
remark: '',
resource: null,
children: null,
},
{
id: 1529296954965106691,
parentId: 1529296954965106690,
resourceId: 0,
name: '请求记录SDK',
path: '/setting/requestRecord',
component: 'RequestRecord',
visible: true,
type: 5,
icon: 'el-icon-document-add',
orderNum: 3,
props: {},
remark: '',
resource: null,
children: null,
},
],
},
],
};
第二步就是在登录的时候添加能够显示的菜单项
// 获取按钮权限
getPermissionList(elements, permissionList) {
elements.forEach((element) => {
if (element.type === 10) {
permissionList.push(element.path);
}
if (element.children) {
this.getPermissionList(element.children, permissionList);
}
});
},
//设置权限, res 就是请求接口获取当前用户的权限数据
afterLogin(res) {
this.$store.dispatch('fetchData', {
method: 'GET',
router: '/account/menu/tree',
}).then((res) => {
console.warn(res, '返回数据');
const { elements } = res.data;
const naviItems = [];
// 按钮权限列表
const permissionList = [];
this.getPermissionList(elements, permissionList);
this.$store.commit('setBtnsPermissionList', permissionList);
elements.forEach((element) => {
const temp = {
key: String(element.orderNum),
title: element.name,
icon: element.icon,
hidden: !element.visible,
};
const tempEle = [];
if (element.children) {
element.children.forEach((item) => {
tempEle.push({
//设置key这一步有点繁琐可以跟后端说直接返回
key: `${element.orderNum}.${item.orderNum}`,
title: item.name,
path: item.path,
hidden: !item.visible,
});
});
}
if (!element.children) {
temp.path = element.path;
} else {
temp.children = tempEle;
}
naviItems.push(temp);
});
// 保存权限 vuex做了持久化 保存到了Storage
this.$store.commit('setNaviItems', naviItems);
this.$store.commit('setOpenPages', []); // 先清空打开页面列表
// 第一个不是hidden菜单的索引
let theFirstNoHideMenuIndex = null;
for (let index = 0; index < naviItems.length; index += 1) {
const item = naviItems[index];
if (item.hidden === false) {
theFirstNoHideMenuIndex = index;
break;
}
}
if (!naviItems[theFirstNoHideMenuIndex].children) {
const firstNaviItem = naviItems[theFirstNoHideMenuIndex].path.split('/');
const componentName = firstNaviItem[firstNaviItem.length - 1];
const { path, title, key } = naviItems[theFirstNoHideMenuIndex];
this.$store.dispatch('updateOpenPages', { //设置第一次进页面pgeTabs的默认值
path, title, key, componentName,
});
this.$store.commit('setOpenKeys', [key]); //设置第一次进页面pgeTabs的默认值
this.$store.commit('setSelectedKeys', [key]);//设置左侧菜单的选中项(高亮)
// 需要设置一下导航默认打开页面
if (!this.redirect) {
this.$router.push(path);
} else {
this.$router.push(this.redirect);
}
} else if (naviItems[theFirstNoHideMenuIndex].children) { // 有子菜单的菜单
const firstNaviItem = naviItems[theFirstNoHideMenuIndex].children[0].path.split('/');
const componentName = firstNaviItem[firstNaviItem.length - 1];
const { path, title, key } = naviItems[theFirstNoHideMenuIndex].children[0];
this.$store.dispatch('updateOpenPages', {
path, title, key, componentName,
});
this.$store.commit('setOpenKeys', [naviItems[theFirstNoHideMenuIndex].key]);
this.$store.commit('setSelectedKeys', [key]);
// 需要设置一下导航默认打开页面
if (!this.redirect) {
this.$router.push(path);
} else {
this.$router.push(this.redirect);
}
}
});
}
第三步看下store的数据 (actions.js,mutations.js,states.js,index.js) 下面按顺序展示
onst JSONbig = require('json-bigint')({ storeAsString: true });
export default {
//右侧点击close图标的时候 关闭页面 PageTabs组件
deletePageByPath({ commit, state }, params) {
const { openPages } = state;
const { path } = params;
const newOpenPages = [];
openPages.forEach((item) => {
if (item.path !== path) {
newOpenPages.push(item);
}
});
commit('setOpenPages', newOpenPages);
},
// 更新 pageTabs列表
updateOpenPages({ commit, state }, params) {
const { openPages } = state;
console.log(openPages);
const openPagesString = [];
openPages.forEach((item) => { openPagesString.push(JSON.stringify(item)); });
if (!openPagesString.includes(JSON.stringify(params))) {
openPages.push(params);
commit('setOpenPages', openPages);
}
},
updateNaviData({ commit }, data) { // 更新控制选中的菜单项
const { key } = data;
if (key) {
commit('setSelectedKeys', [key]);
}
},
//下面是本项目封装的axios请求(不用在意)
// eslint-disable-next-line
fetchData ({ commit }, {
method = 'GET', page = {}, router, condition = {},
}) {
const { _vm } = this;
const option = { ...condition };
let paramPage = '';
if (JSON.stringify(page) !== '{}') {
option.page = { current: page.current, size: (typeof page.size === 'undefined' ? global.config.pageSize : page.size) };
paramPage = `${JSON.stringify(condition) !== '{}' ? '&' : ''}pageNum=${page.pageNum}&pageSize=${page.pageSize}`;
}
if (typeof router === 'string') {
switch (method) {
default:
case 'GET':
// eslint-disable-next-line no-case-declarations
let params = '';
Object.keys(condition).forEach((item, key) => {
params += `${key === 0 ? '' : '&'}${item}=${condition[item]}`;
});
return new Promise((resolve, reject) => {
// _vm.$axios.get(`${global.config.domain}${router}${(params || paramPage) ? '?' : ''}${params}${paramPage}`).then((res) => { if (typeof res !== 'undefined') { resolve(res); } }).catch((e) => { reject(e); });
_vm.$axios.get(`${global.config.domain}${router}${(params || paramPage) ? '?' : ''}${params}${paramPage}`, {
transformResponse: [(data) => {
let newData = '';
if (typeof data === 'string') {
try {
newData = JSONbig.parse(data);
} catch (e) { /* Ignore */ }
}
// // console.log(newData);
return newData;
}],
}).then((res) => { if (typeof res !== 'undefined') { resolve(res); } }).catch((e) => { reject(e); });
});
case 'POST':
// 解决精度丢失的bug --- 默认把bigint 转换成字符串
return new Promise((resolve, reject) => {
_vm.$axios.post(`${global.config.domain}${router}`, option, {
transformResponse: [(data) => {
if (typeof data === 'string') {
try {
data = JSONbig.parse(data);
} catch (e) { /* Ignore */ }
}
return data;
}],
}).then((res) => { if (typeof res !== 'undefined') { resolve(res); } }).catch((e) => { reject(e); });
});
}
}
},
};
export default {
setToken(state, data) {
state.token = data;
},
setUserInfo(state, data) {
state.userInfo = data;
},
setNaviItems(state, data) {//保存左侧菜单项
state.naviItems = data;
},
setSelectedKeys(state, data) {//保存当前选中高亮的菜单
state.selectedKeys = data;
},
setOpenKeys(state, data) { //展开项有子菜的时候 在能够展开多个菜单项的时候
state.openKeys = data;
},
setOpenPages(state, data) {// 打开的页面列表 pageTabs组件使用
state.openPages = data;
},
setRolesRoutes(state, data) {
state.rolesRoutes = data;
},
setBtnsPermissionList(state, list) {//按钮权限
state.btnsPermissionList = list;
}
};
export default {
pageList: [],
token: '',
userInfo: {},
authStatus: false,
//
naviItems: [],
selectedKeys: [],
openKeys: [],
openPages: [],
//
rolesRoutes: [],
};
index.js
import Vue from 'vue';
import Vuex from 'vuex';
import state from '@/store/states';
// import crypto from '@/assets/js/crypto';
import getters from '@/store/getters';
import mutations from '@/store/mutations';
import actions from '@/store/actions';
import createPersistedState from 'vuex-persistedstate';
Vue.use(Vuex);
export default new Vuex.Store({
state,
mutations,
getters,
actions,
plugins: [createPersistedState({
key: window.atob('GKHealth'),
reducer: (allStates) => ({
token: allStates.token,
userInfo: allStates.userInfo,
naviItems: allStates.naviItems,
selectedKeys: allStates.selectedKeys,
openKeys: allStates.openKeys,
openPages: allStates.openPages,
btnsPermissionList: allStates.btnsPermissionList,
}),
storage: {
getItem: (key) => window.sessionStorage.getItem(key),
setItem: (key, value) => window.sessionStorage.setItem(key, value),
removeItem: (key) => window.sessionStorage.removeItem(key),
},
})],
});
Navi.vue 左侧菜单组件
<template>
<div class="menu-wrapper">
<el-menu
ref="navi"
:default-active="defaultActive"
class="el-menu-vertical"
background-color="#202637"
text-color="#fff"
active-text-color="#6882bd"
>
<div v-for="item in this.naviItems.filter((x) => !x.hidden)" :key="item.key + item.path">
<el-menu-item
v-if="!item.children"
:key="item.key + item.path"
:index="String(item.key)"
@click="onMenuSelect(item.path, item.title, item.key)"
>
<i :class="item.icon"></i>
<span slot="title">{{ item.title }}</span>
</el-menu-item>
<template v-else>
<el-submenu :index="String(item.key + item.path)">
<template slot="title">
<i :class="item.icon"></i>
<span>{{ item.title }}</span>
</template>
<el-menu-item
v-for="itemIn in item.children.filter((x) => !x.hidden)"
:key="itemIn.key"
:index="itemIn.key"
@click="onMenuSelect(itemIn.path, itemIn.title, itemIn.key)"
>{{ itemIn.title }}</el-menu-item
>
</el-submenu>
</template>
</div>
</el-menu>
</div>
</template>
<script>
import { mapState } from 'vuex';
export default {
data() {
return {
defaultActive: '',
defaultOpen: [],
list: [],
};
},
computed: {
//naviItems权限列表 selectedKeys当前选择的菜单 高亮
...mapState(['naviItems', 'selectedKeys']),
openPages: {
// openPages 打开的页面 右侧菜单要是使用
set(data) {
this.$store.commit('setOpenPages', data);
},
get() {
return this.$store.state.openPages;
},
},
},
watch: {
$route(newVal) {
const componentInfo = this.list.find((item) => item.path && item.path === newVal.path);
if (JSON.stringify(componentInfo) !== '{}' && !componentInfo.hidden) {
const { path, title, key } = componentInfo;
this.updateKeyTitle(path, title, key);
}
},
},
mounted() {
this.findComponent(this.naviItems);
this.defaultActive = this.selectedKeys.join('');
},
methods: {
onMenuSelect(path, label) {
if (this.$route.path !== path) {
this.$router.push(path);
}
},
updateKeyTitle(path, title, key) {
this.defaultActive = key;
// this.$store.commit('setSelectedKeys', [key]);
const componentName = path.split('/')[2];//获取在项目中 页面的名字
console.log('添加tab');
this.$store.dispatch('updateOpenPages', {//更新高亮选中菜单
path,
title,
key,
componentName,
});
},
findComponent(list) {
list.forEach((item) => {
this.list.push(item);
if (item.children) {
this.findComponent(item.children);
}
});
},
},
};
</script>
<style lang="stylus" scoped>
.menu-wrapper
width: 220px
.el-menu-vertical
height: calc(100vh - 50px)
</style>
PageTabs.vue 右侧组件
<template>
<div class="page">
<span
@click="handleChangePath(page)"
:class="{ ml10: index > 0, active: currentPath === page.path, cp: currentPath !== page.path }"
v-for="(page, index) in openPages"
:key="index"
>
{{ page.title }}
<i
v-if="openPages.length > 1"
@click.stop="handleClosePath(page, index)"
class="el-icon-error cp"
></i>
</span>
</div>
</template>
<script>
export default {
data() {
return {
currentPath: '',
};
},
computed: {
openPages: {
set(data) {
this.$store.commit('setOpenPages', data);
},
get() {
return this.$store.state.openPages;
},
},
},
mounted() {
this.currentPath = this.$route.path;
},
watch: {
$route(value) {
this.currentPath = value.path;
},
},
methods: {
handleClosePath(page) {
this.openPages.forEach((item, x) => {
if (item.path === page.path) {
//这里有重新设置了openPages 可以看下上面store actions.js
this.$store.dispatch('deletePageByPath', { path: page.path });
if (this.$route.path === item.path) { // 关闭当前页
const localPage = this.openPages;
const localPageTempFlag = typeof this.openPages[x - 1] !== 'undefined';
this.$router.push(localPageTempFlag ? localPage[x - 1].path : localPage[x].path);
const keyTemp = localPageTempFlag ? localPage[x - 1].key : localPage[x].key;
this.$store.dispatch('updateNaviData', { key: keyTemp });//更新左侧高亮导航
}
}
});
},
handleChangePath(page) {
if (this.$route.path !== page.path) {
this.$router.push(page.path);
this.$store.dispatch('updateNaviData', { key: page.key });
}
},
},
};
</script>
<style lang="stylus" scoped>
@import "../assets/css/common.stylus"
.page
height:40px;
background: #F0F6FF
width 100%
disflex(row, flex-start, flex-end)
padding 0 10px
box-sizing: border-box
span
display: inline-block
font-size: 13px
color #666
height: 28px
line-height: 28px
background: #f0f6ff;
border-radius: 4px 4px 0px 0px;
border: 1px solid #e6e8eb;
border-bottom: none
position relative
z-index 100
padding 0px 22px 0 10px
overflow: hidden
text-overflow: ellipsis;
white-space: nowrap
&.active
transform: translateY(1px)
background: #f9f9f9;
border-color: #d4dce9
i
color #999
position: absolute
right: 5px
top 7px
</style>
下面重要的一步设置路由 新建permission.js 在main.js里面引入
import router from '@/router';
import store from '@/store';
const whiteList = ['/', '/LoginRedirect'];
router.beforeEach(async (to, from, next) => {
const hasToken = store.state.token;//已经登录过的用户
if (hasToken) {
if (to.path === '/' || to.path === '/LoginRedirect') {
next();
} else {
const hasRolesRoutes = store.state.rolesRoutes && store.state.rolesRoutes.length > 0;
// 已经有了权限菜单 则继续页面跳转
if (hasRolesRoutes) {
next();
} else {//没有权限的设置权限。
try {
// 根据登录接口返回的树生成路由信息嵌入Layout.vue中
const childrenRoutes = [];
const { naviItems } = store.state;
//这一步很多余是根据文件名字添加路由的可以自己优化(这里在router.js 新增页面不新增数据 都是在这一步添加)。所有当给后端路由表的时候要注意)。
const formatRoutes = (items) => {
items.forEach((item) => {
if (item.path) {
const name = item.path.split('/')[2];
childrenRoutes.push({
path: item.path,
name,
component: (resolve) => require([`@/views${item.path}.vue`], resolve),
});
}
if (item.children) { formatRoutes(item.children); }
});
};
formatRoutes(naviItems);
const asyncRoutes = [{
path: '/',
name: 'Layout',
component: () => import('@/components/Layout.vue'),
children: childrenRoutes,
}, {
path: '*',
component: () => import('@/views/404.vue'),
}];
store.commit('setRolesRoutes', asyncRoutes);
router.addRoutes(asyncRoutes);//添加路由
next({ ...to, replace: true });
} catch (error) {
store.commit('setToken', '');
next('/');
}
}
}
} else if (whiteList.indexOf(to.path) !== -1) {
next();
} else {
store.commit('setToken', '');
if (to.path === '/') {
next('/');
} else {
next('/');
}
}
});
版权声明:本文为qq_38743783原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。