实现思路:
1.通过利用动态路由、静态路由实现动态路由
2.通过router-beforeEach钩子监听是否有权限
3.利用Vue.directive监听页面挂在完成控制按钮权限
实现代码:
route.js
router文件下有两个文件,1个是用来存储静态路由(登录页)、动态路由,另一个文件则是用于监听路由钩子
/**
* @description 默认路由
*/
export const constantRouterMap = [
{
path: '/',
redirect: '/login',
hidden: true
},
{
path: '/login',
name: 'login',
hidden: true,
component: () => import('../views/login/index.vue')
}
]
/**
* @description 动态路由
*/
export const asyncRouterMap = [
{
path:'/home',
name:'home',
meta: {
title: '首页',
icon:'' // 用于显示左侧菜单icon
},
component () => import(../views/Home/index.vue) //动态导入组件
},
{
path:'/dosc',
name:'Dosc',
redirect:'/dosc/dosclist' //重定向
meta: {
title: '文件管理',
icon:'' // 用于显示左侧菜单icon
},
component: () => import(../views/layout/index.vue) //默认入口组件
children:[{
path:'/dosc/dosclist',
name:'doscList',
meta:{
name:'文件列表',
breadcurmbName:'文件列表'
},
component:() => import(../view/dosc/dosclist/index.vue),
children:[{
hidden: true,
path: '/dosc/detail/:id',
name: 'doscDetail',
meta: {
breadcrumbName: '文件详情'
},
component: () => import('../views/dosclist/detail.vue')
}]
},
{
path:'/dosc/storelist',
name:'storeList',
meta:{
name:'存储列表',
breadcurmbName:'存储列表'
},
component:() => import(../view/dosc/store/index.vue)}]
}
]
index.js
页面刷新时,vuex会被清空所以需要从localStorage中重新获取路由及按钮权限。需要判断token是否过期,过期需要重新登录。
import { createRouter, createWebHistory } from 'vue-router'
import { constantRouterMap } from './route'
import store from '../store'
import { getToken} from '../utils/cook'
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes:constantRouterMap
})
router.beforeEach((to,from) => {
try{
const menu = localStorage.getItem("menus") !== null ?
JSON.parse(window.decodeURIComponent(window.atob(localStorage.getItem("menus")))) : []
const buttons = localStorage.getItem("buttons") !== null ?
JSON.parse(window.decodeURIComponent(window.atob(localStorage.getItem("buttons")))) : []
if (to.path === '/login') {
if (!getToken()) {
localStorage.clear()
return true
}
return router.push('/default')
}
if (!getToken()) {
localStorage.clear()
return router.push('/login')
}
if (store.getters.addRouters.length === 0) {
if(menus.length > 0){
store.dispatch('GenerateRoutes', { menus }).then(() => {
// 动态添加可访问路由表
addAsyncRoute(store.getters.addRouters)
/**
* @description 更新按钮权限
*/
store.dispatch('UpdateHasButtons',buttons)
return router.push(to.path)
})
}else{
store.dispatch('GetInfo').then(res => { // 拉取用户信息
let {menus,buttons} = res
localStorage.setItem('menus',window.btoa(window.encodeURIComponent(JSON.stringify(menus)))) localStorage.setItem('buttons',window.btoa(window.encodeURIComponent(JSON.stringify(buttons))))
store.dispatch('UpdateHasButtons',buttons)
store.dispatch('GenerateRoutes', { menus }).then(() => { // 生成可访问的路由表
addAsyncRoute(store.getters.addRouters); // 动态添加可访问路由表
return router.push(to.path)
})
}).catch((err) => {
store.dispatch('ClearUserInfo')
return router.push('/login')
})
/**
* @description 更新按钮权限
*/
store.dispatch('getButtons')
}
}else{
return true
}
}
catch{
localStorage.clear()
router.push('/login')}
})
function addAsyncRoute(routers){
for(let i= 0 ;i < routers.length;i++){
const routeItem = Object.assign({},routers[i])
router.addRoute(routeItem)
}
}
利用vuex存储路由及按钮
store文件夹下有modules模块文件夹(permession.js、user.js)、getters获取store文件
permession.js
用来存储路由权限、按钮权限。
import { asyncRouterMap, constantRouterMap } from '@/router/route';
import store from '../../store'
//判断是否有权限访问该菜单
function hasPermission(menus, route,parent) {
if (route.name) {
let currMenu = getMenu(route.name, menus);
if (currMenu!=null) {
return true;
} else {
if (route.hidden !== undefined && route.hidden === true) {
return true;
} else {
return false;
}
}
} else {
return true
}
}
//根据路由名称获取菜单
function getMenu(name, menus) {
console.log('name',name);
let targetMenu = {}
for (let i = 0; i < menus.length; i++) {
let menu = menus[i];
if (menu.name === name) {
return menu;
}
/**
* @description 返回嵌套菜单权限
*/
if(menu.children && menu.children.length > 0){
targetMenu = getMenu(name,menu.children)
if(targetMenu !== undefined) return targetMenu
}
}
// return null;
}
/**
* @description 多层深度拷贝
*/
function deepCopy(params) {
// 如果是数组
if (Array.isArray(params)) {
var res = [];
for (var i = 0; i < params.length; i++) {
if (params[i] instanceof Object) {
// 将深层拷贝的结果的 添加到 res 中
res.push(deepCopy(params[i]));
} else {
res.push(params[i]);
}
}
return res;
}
// 如果是对象 进行 对象的拷贝
if (params.constructor === Object) {
var res = {}; // 1 声明空对象
for (var x in params) {
// 遍历被拷贝独享
// 如果你是数组或者对象;需要再次拷贝
if (params[x] instanceof Object && !params[x] instanceof Function) {
// 将深层拷贝的结添加到 res中
res[x] = deepCopy(params[x]);
} else {
// params[x] 为基本类型数据 直接添加大res中
res[x] = params[x];
}
}
return res
}
}
const app = {
state: () => ({
routers: constantRouterMap,
addRouters: [],
hasButtons:[]
}),
mutations: {
SET_ROUTERS: (state, routers) => {
state.addRouters = routers;
routers.push(
{
path: '/:pathMatch(.*)*',
name: 'error',
hidden: true,
component: () => import('../../views/error/404/index.vue')
}
)
if(routers[0].children && routers[0].children.length > 0){
const path =routers[0].children[0].path
routers[0].children[0].path = '/default'
routers[0].children[0].alias = path
}else{
const path =routers[0].path
routers[0].path = '/default'
routers[0].alias = path
}
state.routers = constantRouterMap.concat(routers);
},
UPDATE_ROUTERS:(state,routers) =>{
state.routers = routers
},
UPDATE_HASBUTTONS:(state,buttons) =>{
state.hasButtons = buttons
},
CLEAR_ROUTES:(state) =>{
state.routers = constantRouterMap
state.addRouters = []
}
},
actions: {
GenerateRoutes({ commit }, data) {
return new Promise(resolve => {
const { menus } = data;
const primaryAsync = deepCopy(asyncRouterMap)
const accessedRouters = primaryAsync.filter(v => {
if (hasPermission(menus, v)) {
if (v.children && v.children.length > 0) {
v.children = v.children.filter(child => {
if (hasPermission(menus, child,v)) {
if(child.children && child.children.length > 0){
child.children = child.children.filter(ren => {
if(hasPermission(menus,ren,child)){
return ren
}
return false
})
return child
}else{
return child
}
}
return false;
});
return v
} else {
return v
}
}
return false;
});
commit('SET_ROUTERS', accessedRouters);
resolve();
})
},
UpdateRoutes({commit},routers){
commit('UPDATE_ROUTERS',routers)
},
UpdateHasButtons({commit},buttons){
commit('UPDATE_HASBUTTONS',buttons)
},
ClearRoutes({commit}){
commit('CLEAR_ROUTES')
}
}
};
user.js
用来存储用户权限。
import ajax from '@/api/account'
const app = {
state: () => ({
userInfo: {
username: localStorage.getItem('username') || '',
name: localStorage.getItem('name') || '',
role: localStorage.getItem('role') || ''
}
}),
mutations: {
CLEAR_USERINFO: (state) => {
state.userInfo = {
username: '',
name: '',
role: ''
}
},
UPDATE_USERINFO: (state, userInfo) => {
state.userInfo = {
...state.userInfo,
...userInfo
}
}
},
actions: {
ClearUserInfo ({commit}) {
commit('CLEAR_USERINFO')
},
UpdateUserInfo ({commit}, userInfo) {
commit('UPDATE_USERINFO', userInfo)
},
// 获取用户信息
GetInfo({ commit}) {
return new Promise((resolve, reject) => {
ajax.getInfo().then(response => {
resolve(response)
}).catch(error => {
reject(error)
})
})
},
}
}
export default app
getters.js
用来获取store中的存储信息。
const getters = {
userInfo: (state) => state.user.userInfo,
addRouters: (state) => state.permission.addRouters,
routers: (state) => state.permission.routers,
hasButtons:(state) => state.permission.hasButtons
}
export default getters
按钮权限检验
挂载完成时通过用户名称去判断该用户是否拥有该按钮,来控制按钮的显示、隐藏。
import store from "../store"
export default (Vue) => {
/**自定义按钮权限指令 */
Vue.directive('has', {
mounted(el, binding) {
//获取按钮权限
if (!Vue.config.globalProperties.$_has(binding.value)) {
//移除不匹配的按钮
el.parentNode.removeChild(el)
}
},
})
//检查权限方法
Vue.config.globalProperties.$_has = function (value) {
let isExist = false
const btnPermsArr = store.getters.hasButtons
if (btnPermsArr.includes(value)) {
isExist = true
}
console.log('isExist',isExist);
return isExist
}
}
登录时根据接口返回动态判断用户的路由、按钮,并同时存入vuex、locastorage
login(value).then(res => {
if (res) {
clearStorage()
setToken(res.access)
this.$store.dispatch('UpdateUserInfo', {
username: res.username,
name: res.name,
role: res.role
})
this.$store.dispatch('GetInfo').then(res => {
let {menus,buttons} = res
localStorage.setItem('menus',window.btoa(window.encodeURIComponent(JSON.stringify(menus))))
localStorage.setItem('buttons',window.btoa(window.encodeURIComponent(JSON.stringify(buttons))))
this.$store.dispatch('UpdateHasButtons',buttons)
this.$store.dispatch('GenerateRoutes', { menus }).then(() => {
// 生成可访问的路由表
this.addAsyncRoute(this.$store.getters.addRouters)
this.$router.push('/default')
})
}).catch((err) => {
clearStorage()
return this.$router.push('/login')
})
}
}).catch(() => {
})