在使用 vue
做中后台管理系统项目时,我们经常会涉及到权限管理,即对菜单树进行权限控制,由于不同框架本身的一些特性,通常实现权限功能的方式也大不相同。
本文就 vue
项目来介绍一种菜单树的权限管理方式。
前言
对于 vue
项目来说,我们通常会将 菜单树
和 路由表
进行关联,也就是说,最终的菜单树
会根据 完整的路由表
生成。
这里 完整的路由表
可以分为:静态路由
和 动态路由
这两部分。
指的是,用户可正常访问的路由,诸如 /login
、/404
、/about
等。这些路由信息不会与菜单树关联。
指的是,有对应权限的用户才可以访问,这些路由信息会与菜单树进行关联。
那么,最终的菜单树
又可以如何确定呢?
我们可以借鉴像 vue-element-admin 这样的后台模板框架实现菜单树的思路,对 完整的路由表
中路由对象 hidden
属性为 false
的项作为一个菜单树节点。
1 2 3 4 5 6 7 8 9
| [ { name: 'system', path: '/system', hidden: false, component: () => import('@/views/system') } ]
|
资源管理
为了做到对页面菜单项的灵活控制,我们的项目中应该有一个叫做 资源管理
的页面。该页面主要的作用是可以很方便的 新增、修改、删除
一个权限菜单项。
而该资源菜单树的每一个菜单项可以使用一个唯一的 权限编码(code)
与动态路由对象进行关联。
也就是说,后台存储的是一个有 权限编码(code)
信息的菜单树形数据结构,如下所示:
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
| const authTree = [ { code: "system_manage", name: "系统管理", child: [ { code: "system_user_menu", name: "用户管理" }, { code: "system_role_menu", name: "角色管理" }, { code: "system_function_menu", name: "资源管理" } ] }, { code: "product_manage", name: "商品管理", child: [ { code: "product_add_menu", name: "添加商品" }, { code: "product_all_menu", name: "所有商品" }, { code: "product_attribute_menu", name: "属性管理" } ] } ];
|
如上数据结构所示,code
属性表示该菜单节点的权限编码,name
属性表示该菜单节点的名称,当然,你也可以加入一些其他的可控字段。
动态路由定义
我们已经维护好了一份资源菜单树,那么,接下来的工作就是对每个 权限编码
去定义对应的路由对象信息,这样,我们就可以在后面通过 遍历权限菜单树 去生成一份动态路由表出来。
前端定义的 动态路由 类似于下面这样:
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
| const dynamicRoutes = { system_manage: { name: "systemmanage", path: "/systemmanage", component: "this is a component load with import() method", meta: { title: "系统管理", icon: "iconfont icon-xitongguanli" } }, system_user_menu: { name: "usermanage", path: "/usermanage", component: "this is a component load with import() method", meta: { title: "用户管理", icon: "iconfont icon-xitongguanli" } }, system_role_menu: { name: "rolemanage", path: "/rolemanage", component: "this is a component load with import() method", meta: { title: "角色管理", icon: "iconfont icon-xitongguanli" } }, system_function_menu: { name: "sourcemanage", path: "/sourcemanage", component: "this is a component load with import() method", meta: { title: "资源管理", icon: "iconfont icon-xitongguanli" } }, product_manage: { name: "productmanage", path: "/productmanage", component: "this is a component load with import() method", meta: { title: "商品管理", icon: "iconfont icon-xitongguanli" } }, product_add_menu: { name: "addproduct", path: "/addproduct", component: "this is a component load with import() method", meta: { title: "添加商品", icon: "iconfont icon-xitongguanli" } }, product_all_menu: { name: "allproduct", path: "/allproduct", component: "this is a component load with import() method", meta: { title: "所有商品", icon: "iconfont icon-xitongguanli" } }, product_attribute_menu: { name: "attributemanage", path: "/attributemanage", component: "this is a component load with import() method", meta: { title: "属性管理", icon: "iconfont icon-xitongguanli" } } };
|
根据权限树生成路由表
权限树数据 和 动态路由定义已经处理好了,现在,我们就要根据这两份数据生成 vue-router
可用的路由表数据了。
可以使用如下函数进行生成:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function filterMenu(res, authTree, routes) { authTree.forEach(v => { const routeObj = routes[v.code], name = v.name, child = v.child;
if ( name ) routeObj.meta.title = name;
if (child && child.length > 0) routeObj.children = filterMenu([], child, routes); res.push(routeObj); });
return res; }
console.log(filterMenu([], authTree, dynamicRoutes));
|
插入到路由表中
最后,我们可以使用 addRoutes
方法将生成的路由表数据添加到路由中,这样,路由、权限和菜单
就进行了很好的关联,后续对菜单项的控制也会非常方便。
1 2 3 4 5 6 7 8 9
| import router from '@/router'
router.addRoutes( routes )
|
上面的 routes
就是通过比对权限后生成的路由表数据了。
用户角色管理
通常,我们的权限是绑定到角色的,不同的角色有不同的权限,所以,我们可以在 用户角色管理
模块去绑定对应的菜单权限。
页面如何设计就看你自己了。
动态路由与后台关联的另一种方式
在第一种方式中,当将菜单权限赋予某个角色后,那么,该角色对于的菜单树结构就已经成型了,只不过需要将菜单 权限code
替换为真正的路由对象
。再在适合的时机插入到路由表中。
这里还有另一种实现方式。我们将完整的动态路由同步到后台数据库,除了路由对象中的 component
字段变为 String
类型以外,该动态路由没有任务其他特殊之处。
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
| [ { name: 'system', path: '/system', component: 'Layout', meta: { title: '系统管理', icon: 'system-icon', roles: ['admin'] }, children: [ { name: 'rolesManagement', path: 'roles_management', component: 'rolesManagement', meta: { title: '角色管理', icon: 'roles-icon', roles: ['admin'] } } ] } ]
|
如上动态路由所示,我们可以看到两个比较特殊的地方,第一个是 component
字段,第二个是 meta
中的 roles
字段。他们有什么意义呢?
component字段
该字段表示路由对象所映射的组件。由于我们需要将整个动态路由表存储到后台数据库,所以,原先的 component
设置方式不再适用,这里我们该用字典关联的方式来解决。我们会有这样一个文件,存储着动态路由中的所有动态组件。
1 2 3 4 5 6
| export default { Layout: () => import('@/layout'), rolesManagement: () => import('@/views/system/rolesManagement'), }
|
roles字段
该字段表示路由对象所关联的角色有哪些。也就是说,只要某个路由对象中的 roles
信息中的角色是当前用户所拥有的,那么该路由对象就该被显示出来。当然,roles
字段是后台动态追加进去的,可在 用户角色管理 页面进行处理。
如何过滤出当前用户有权访问的路由表
获取当前用户信息
我们首先需要获取当前用户信息,即,当前用户拥有的角色信息。
1 2 3 4 5
| { name: '张三', id: 1, roles: ['role1', 'role2', 'role3'] }
|
如上所示,张三
同时拥有 role1
、role2
和 role3
这三个角色。
获取动态路由信息
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
| [ { name: 'system', path: '/system', component: 'Layout', meta: { title: '系统管理', icon: 'system-icon', roles: ['role1', 'admin'] }, children: [ { name: 'rolesManagement', path: 'roles_management', component: 'rolesManagement', meta: { title: '角色管理', icon: 'roles-icon', roles: ['role1', 'admin''] } }, { name: 'usersManagement', path: 'users_management', component: 'usersManagement', meta: { title: '用户管理', icon: 'users-icon', roles: ['role1', 'admin'] } }, { name: 'resourceManagement', path: 'resource_management', component: 'resourceManagement', meta: { title: '资源管理', icon: 'resource-icon', roles: ['admin'] } }, // ... ] } ]
|
如上所示,这一个经过后台处理的含有完整 roles
信息的动态路由表。每个路由对象都有对应的 roles
信息。表示该路由对象对哪些角色可见。
经过过滤处理后,实际生成的动态路由表示这样的。
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
| [ { name: 'system', path: '/system', component: 'Layout', meta: { title: '系统管理', icon: 'system-icon', roles: ['role1', 'admin'] }, children: [ { name: 'rolesManagement', path: 'roles_management', component: 'rolesManagement', meta: { title: '角色管理', icon: 'roles-icon', roles: ['role1', 'admin''] } }, { name: 'usersManagement', path: 'users_management', component: 'usersManagement', meta: { title: '用户管理', icon: 'users-icon', roles: ['role1', 'admin'] } }, // ... ] } ]
|
因为,当前用户没有 admin
角色权限,所以 资源管理 这个路由对象被剔除。
最后,我们依然会使用 addRoutes
方法将生成的 动态路由表 插入到路由表中,菜单树,也会根据动态路由渲染出来。