前言

今天记录一下Vue权限菜单(动态路由),在我们写后台的时候用的比较多,Vue的权限菜单分两种,一种是通过本地进行,根据账号的权限进行筛选出可用的权限,组合菜单并在页面上渲染显示,另一种是根据登录的账号,后端直接回可用的权限菜单,前端进行整合渲染。第二种在日常中使用比第一种的情况多些,本篇文章讲述记录的也是第二种方式。我这里使用的是 element-ui的Admin 模板。

一、新建请求的js

我这里是新建了一个请求的js,你们可以直接将这个方法写在你们现在的请求js里面。
src目录下新建一个文件夹api,新建是一个名request的js文件。

request.js代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//这个request 是模板自带的,封装了axios的request,在utils目录下
import request from '@/utils/request'

export default {
//...其他的请求
// 获取权限菜单列表
getRoleMenu(){
return request({
url: `项目请求url`,
method: 'get',
})
},
// ...其他的请求
//
}

二、新建一个获取菜单的js

src目录下新建一个文件夹menu,在menu下新建一个menu.js

menu.js代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import Layout from '@/layout'//引入admin的layout布局
import router from '../router'//引入router
import request from '../api/request.js'//自定义封装的请求
import store from '../store'//Vuex

export default {
// 获取路由菜单
getMenu() {
return new Promise((resolve, reject) => {
return request.getRoleMenu().then(res => {
/* 回来的数据格式参考
// [{
// type:1,//1表示有子路由,0则没有
// path: '路径',
// component: Layout,
// children: [{
// path: '',
// name: '名称',
// component: '页面路径',
// meta: {
// title: '页面标题',
// icon: '图标'
// },
// children: []//如果有子路由则需要写
// }]
// }]
*/
//声明一个空数组,用来装处理好的菜单信息
const result = []
// 获取到路由菜单,进行递归数据处理
this.parseRoute(res.data, result, 1)
// 添加菜单
this.addMenu(result)
//缓存用户菜单,我这里使用的是sessionStorage,用localStorage也可以
sessionStorage.setItem("route", JSON.stringify(res.data))
// 输出成功
resolve()
}).catch(err =>{
reject()
})
})
},
// 对路由菜单递归数据处理
parseRoute(fullList, resultList, step) {
return new Promise((resolve, reject) => {
let result = []
// 路由类型申明,0表示没有二级路由,1表示有一级路由
const typeArray = [0, 1]
// 循环进行数据处理
for (let i = 0; i < fullList.length; i++) {
const itemElement = fullList[i]
let routerObject = null
const childrenList = []

// 路由类型过滤,如果回来的路由类型不存在,则直接结束当前循环进行下一次循环
if (!typeArray.includes(itemElement.type)) {
continue
}
//对当前执行步骤进行判断,step 等于1,则属于主路由,反之则属于子路由
if (step === 1) {
routerObject = {
path: itemElement['routePath'],
component: Layout,
children: [{
path: itemElement['routePath'],
name: itemElement['name'],
component: resolve => require([`@/views${itemElement.componentsPath}`], resolve),
meta: {
title: itemElement['name'],
icon: itemElement['icon']
},
children: childrenList
}]
}
} else {
routerObject = {
path: itemElement['routePath'],
name: itemElement['name'],
component: resolve => require([`@/views${itemElement.componentsPath}`], resolve),
meta: {
title: itemElement['name'],
icon: itemElement['icon']
},
children: childrenList
}
}

const itemResult = []
//对主路由尽心判断,满足条件则进行递归
if (itemElement['nextMenuList'] != null && itemElement['nextMenuList'].length) {
this.parseRoute(itemElement['nextMenuList'], itemResult, step + 1)
}
//将组装好的子路由信息插入数组
for (let j = 0; j < itemResult.length; j++) {
childrenList.push(itemResult[j])
}
//判断子路由数组长度
if (childrenList.length === 0) {//不存在子路由
if (step === 1) {//如果是主路由则删除第一个子路由,如果不删除则会出现一个空的父菜单
delete routerObject.children[0].children
} else {//如果是子路由,则删除子路由下的子路由,删除的这个子路由是空的,必须需要删除,否则也会出现一个空的菜单
delete routerObject.children
}
} else {//存在子路由
if (step === 1) {如果是主路由,则添加meta和子路由列表
routerObject.meta = routerObject.children[0].meta
routerObject.children = childrenList
// console.log(routerObject)
}
}
//插入组装好的数据
resultList.push(routerObject)
//插入最终完整的数据列表
result.push(routerObject)
}
//输出已经组装好并且能用的数据
resolve(result)
})
},
// 添加菜单
addMenu(data) {
return new Promise((resolve, reject) => {
// 在处理完的菜单列表数据后面插入404页面,404必须存在菜单列表的最后一项,否则会对所有页面进行拦截,并跳转404页面
data.push({
path: '*',
redirect: '/404',
hidden: true
})
// 打印菜单列表
// console.log(data)
// 将可用的路由权限列表存入Vuex
store.dispatch('user/modifyMenu', data)
// 添加菜单
router.addRoutes(data)
// 将路由元注入路由对象,必须添加
router.options.routes.push(data)
//输出成功
resolve()
})
},
//
}

三、修改Vuex

找到在store目录下的modules/user.js打开这个js文件,
引入刚刚创建的menu.js和请求的request.js

1
2
import request from "../../api/request.js"
import menu from "../../menu/menu.js"

state里添加一个menu,也就是getDefaultState()

1
2
3
4
5
6
7
8
const getDefaultState = () => {
return {
token: getToken(),
name: '',
avatar: '',
menu: []//添加一个数组用来装菜单列表
}
}

并在mutations里添加一个SET_MENU的方法用来修改state里的菜单列表

1
2
3
SET_MENU: (state,menu) =>{
state.menu = menu
}

接下来就是修改actions里的login方法和添加一个方法用来修改mutations

1
2
3
4
5
6
7
8
9
10
//1、修改login方法
在login 方法的成功回调中添加一个这个方法,用来获取权限菜单
menu.getMenu()
//2、添加一个方法用来修改 mutations,这个方法是跟login方法同级的
modifyMenu({ commit },menu){
return new Promise(resolve => {
commit('SET_MENU',menu)
resolve()
})
}

修改login方法 如图:
在这里插入图片描述
再在logout那个方法里清除缓存,像下面这段代码这样

1
2
3
4
5
6
7
8
9
10
11
logout({ commit, state }) {
return new Promise((resolve, reject) => {
// 清除缓存的权限菜单
sessionStorage.removeItem("route")
//
removeToken() // must remove token first
// resetRouter()
location.reload()
commit('RESET_STATE')
})
}

现在已经能够拿到菜单信息并能够在侧边栏菜单显示了,控制台还有个报错,只是说一刷新页面就会没有,只能够通过地址栏跳转,下一步我们先来做路由/菜单的持久化。

四、修改 router,持久化处理

找到router目录,并打开index.js。引入获取权限菜单的menu.jsVuex

1
2
import store from '../store'
import menu from '../menu/menu.js'

constantRoutes 中只留下登录404首页这三个页面的router路径其他的全部不需要。类似于下面图中那样。
在这里插入图片描述
createRouter() 后面添加路由监听,以实现菜单的持久化。一定要放在const router = createRouter() 后面。
就像下图那样
在这里插入图片描述
监听路由的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
	//路由判断
router.beforeEach((to, from, next) => {
// 判断是否有token
if (store.state.user.token) {
// 判断是否是跳转到登录页面
if (to.path === '/login') next()
else {//判断菜单列表
if (!store.state.user.menu.length) { // 判断当前用户是否已拉取完权限菜单信息
// 如果本地不存在权限菜单,则获取权限菜单,生成菜单列表
if(!sessionStorage.getItem("route")){
//获取路由菜单
menu.getMenu().then(response =>{
//保险起见,组装一次数据
menu.parseRoute(JSON.parse(sessionStorage.getItem("route")), [], 1).then(res =>{
//添加路由并进行跳转
menu.addMenu(res).then(e =>{
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
})
})
}).catch(err =>{//失败则直接跳转登录页面
next({path: 'login'})
})
}else{//从缓存中读取用户权限列表,并添加菜单到侧边栏和路由元
menu.parseRoute(JSON.parse(sessionStorage.getItem("route")), [], 1).then(res =>{
menu.addMenu(res).then(e =>{
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
})
})
}
}
else next({path: 'login'}) //当有权限菜单存在时,说明所有可访问路由已生成 如访问没权限的全面会自动进入登录页面
}
} else next({path: 'login'}); // 否则全部重定向到登录页
})

这样我们的菜单/路由持久化就🆗了,怎么刷新都不会丢失了,如果没有这个缓存了,还会自动的去获取并加载上去,然后完成页面的跳转。

五、处理问题/修改源码

打开我们的项目,找到并打开在src > layout > components > Sidebar 的index.vue。打开过后往下滑,在第34行左右的位置,把routes中的return那行改一下,侧边栏的菜单就可以正常显示了。如下代码:

1
2
3
4
5
6
7
8
9
10
// 菜单
routes() {
//修改的代码
let menuRouter = this.$store.state.user.menu,
routeMenu = [...this.$router.options.routes,...menuRouter]

return routeMenu
//源码中的代码
// return this.$router.options.routes
},

经过上面的步骤我们的权限菜单(动态路由)就已经完成了,接下来我们需要去修改一下 element-ui的菜单源码,因为跟我们写的对不上,控制台还有个报错。

打开我们的项目,找到并打开在src > layout > components > Sidebar 的SidebarItem.vue。打开过后往下滑,在第41行左右的位置,把item中的type那行注释掉,就可以解决控制台报错的那个小问题了。如下图:
在这里插入图片描述
我们的 权限菜单(动态路由)就已经搞定了。以上就是操作 Vue 权限菜单(动态路由)的一个详细过程,有问题的小伙伴可以私信我,也可以评论留言,看到会回复