Vue iView-admin 动态路由菜单,自定义指令实现按钮权限控制

动态路由菜单/权限是作为后台管理项目来说,是最基本的需求了;无奈对 Vue 不熟,都是边摸索学习边开发;也搞了好一段时间,一直没成功;
网友 @The Crazy Youth 给予了指导实现了动态菜单,网上看了教程实现了按钮权限;

文章比较长,耐心点看;

最终实现效果

客户端从服务端拿到路由和权限数据后,刷新项目的路由和菜单列表,实现动态路由并进行按钮权限控制。

我用的项目是 iView-admin 作为基础进行扩展的;

实现方式

  1. 将所有路由数据存储在本地文件中,然后从服务端获取用户的权限信息,在路由跳转时,添加权限判断钩子,如果用户前往的页面不在权限列表内,则禁止跳转;
  2. 本地只存储基本路由,如错误处理页面、无权限控制页面等,而权限路由则从服务器获取,服务器根据用户的权限下发相应的路由数据,客户端利用这些数据进行路由的动态生成和添加。
  • 本文教程使用的是第二种方法;

实现代码

服务端提供的数据格式

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
[
{
"name": "sys",
"path": "/sys",
"component": "Main",
"parentId": 0,
"children": [
{
"name": "sys_user",
"path": "/sys_user",
"component": "/sys/user",
"meta": {
"hideInMenu": false,
"icon": "md-person",
"notCache": false,
"title": "用户管理",
"permission": [
"sys_user_add",
"sys_user_edit"
]
}
},
{
"name": "sys_role",
"path": "/sys_role",
"component": "/sys/role",
"meta": {
"hideInMenu": false,
"icon": "md-contacts",
"notCache": false,
"title": "角色管理",
"permission": [
"sys_role_add",
"sys_role_edit"
]
}
},
{
"name": "sys_menu",
"path": "/sys_menu",
"component": "/sys/menu",
"meta": {
"hideInMenu": false,
"icon": "md-list",
"notCache": false,
"title": "菜单管理",
"permission": [
"sys_menu_addMenu",
"sys_menu_addRootMenu",
"sys_menu_deleteMenu"
]
}
},
{
"name": "sys_dict",
"path": "/sys_dict",
"component": "/sys/dict",
"meta": {
"hideInMenu": false,
"icon": "md-archive",
"notCache": false,
"title": "字典管理",
"permission": []
}
},
{
"name": "sys_log",
"path": "/sys_log",
"component": "/sys/log",
"meta": {
"hideInMenu": false,
"icon": "md-bug",
"notCache": false,
"title": "日志管理",
"permission": []
}
}
],
"meta": {
"hideInMenu": false,
"icon": "logo-buffer",
"notCache": true,
"title": "系统管理"
}
}
]

前端实现代码

  • 定义初始化菜单:libs/router-util.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
import { getToken, hasChild, localSave, localRead } from '@/libs/util'
import Main from '@/components/main'
import axios from 'axios'
import config from '@/config'
import { forEach } from '@/libs/tools'
const baseUrl = process.env.NODE_ENV === 'development' ? config.baseUrl.dev : config.baseUrl.pro

// 初始化路由
export const initRouter = (vm) => {
if (!getToken()) {
return
}
let list = []
// 模拟异步请求,改为您实际的后端请求路径
axios.get(baseUrl + '/system/permission/userMenuList', {
headers: { 'Authorization': 'Bearer ' + getToken() }
}).then(res => {
var menuData = res.data.data
// 这是后端回传给前端的数据,如上面所说的
console.log('menuData:', menuData)
localSave('route', JSON.stringify(menuData))
// 格式化菜单
list = formatMenu(menuData)
// 刷新界面菜单
vm.$store.commit('updateMenuList', list)
})

return list
}

// 加载菜单,在创建路由时使用
export const loadMenu = () => {
let list = []
let data = localRead('route')
if (!data) {
return list
}
list = formatMenu(JSON.parse(data))
return list
}

// 格式化菜单
export const formatMenu = (list) => {
let res = []
forEach(list, item => {
let obj = {
path: item.path,
name: item.name
}
obj.meta = item.meta
// 惰性递归 ****
if (item.parentId === 0) {
obj.component = Main
} else {
// 惰性递归 ****
let data = item.component
// 这里需要改成自己定义的 .vue 夜间路径
obj.component = () => import('@/view' + data)
}
if (hasChild(item)) {
obj.children = formatMenu(item.children)
}
res.push(obj)
})
return res
}
  • Store 缓存实现:store/module/app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { loadMenu } from '@/libs/router-util'

state: {
// 定义菜单变量
menuList: [],
...
}

getters: {
// 通过路由列表得到菜单列表
menuList: (state, getters, rootState) => getMenuByRouter(loadMenu(), rootState.user.access),
...
}

mutations: {
// 接受前台数组,刷新菜单
updateMenuList (state, routes) {
router.addRoutes(routes)
state.menuList = routes
},
...
}
  • 创建路由加载菜单:router/index.js
1
2
3
4
5
6
7
8
// 引入加载菜单
import { loadMenu } from '@/libs/router-util'

// 创建路由时加载菜单
const router = new Router({
routes: [...routes, ...loadMenu()],
mode: 'history'
})
  • 调用:main.js
1
2
3
4
5
6
7
8
9
10
// 动态菜单渲染
import { initRouter } from '@/libs/router-util'

new Vue({
...
mounted() {
// 调用方法,动态生成路由
initRouter(this);
}
})
  • login,401,403等默认路由 router/router.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
export const mainRouter = [
{
path: '/login',
name: 'login',
meta: {
title: 'Login - 登录',
hideInMenu: true
},
component: () => import('@/view/login/login.vue')
},
{
path: '/',
name: '_home',
redirect: '/home',
component: Main,
meta: {
hideInMenu: true,
notCache: true
},
children: [
{
path: '/home',
name: 'home',
meta: {
hideInMenu: true,
title: '首页',
notCache: true,
icon: 'md-home'
},
component: () => import('@/view/single-page/home')
}
]
},
{
path: '/401',
name: 'error_401',
meta: {
hideInMenu: true
},
component: () => import('@/view/error-page/401.vue')
},
{
path: '/500',
name: 'error_500',
meta: {
hideInMenu: true
},
component: () => import('@/view/error-page/500.vue')
},
{
path: '*',
name: 'error_404',
meta: {
hideInMenu: true
},
component: () => import('@/view/error-page/404.vue')
}
]

export const appRouter = []

export default [
...mainRouter,
...appRouter
]
  • 至此,动态加载路由就完成了;

全局命令组件实现按钮控制

  • 这个稍微简单一点,数据也都在获取菜单的时候定义好了,我们只需要进行过滤一遍即可。

实现代码

  • 定义一个按钮权限组件 libs/btnPermission.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import Vue from 'vue'

// 权限指令
const has = Vue.directive('has', {
bind: function (el, binding, vnode) {
// 获取页面按钮权限
let permissionList = vnode.context.$route.meta.permission
// 权限检验
if (permissionList && permissionList.length && !permissionList.includes(binding.value)) {
el.parentNode.removeChild(el)
}
}
})

export default has
  • 注册该组件:main.js
1
2
3
4
// 权限按钮控制
import has from '@/libs/btnPermissions.js'

Vue.use(has)
  • vue 页面使用情况
1
2
 type="primary" icon="md-add" v-has="'sys_user_add'">新增用户</Button>

  • DONE,至此,所有的动态菜单以及按钮权限控制就完成了;

有问题的,欢迎留言进行交流;
参考教程:https://www.cnblogs.com/zzayne/p/8833516.html
鸣谢网友:@The Crazy Youth