一、RBAC 权限控制体系
要实现动态Menu,我们必要先来统逐一下认知,明白项目中的权限控制体系。
网上找了张图,我们可以大抵的看下
从图中,我们可以简单的如许明白RBAC 权限控制体系。
- 用户:我们登录后台管理体系的账号。举个例子:张三这个人,我们可以以为他是一个用户
- 脚色:用户的“头衔”。张三是一个贩卖司理,那么“贩卖司理”,我们可以以为他是一个脚色。
- 权限:每个脚色都有差别的权限。“贩卖司理”这个脚色,可以检察、删除、编辑客户资料,那么张三就可以检察、删除、编辑客户资料,这时间如果有个李四,李四是平凡的“贩卖”的脚色,而平凡的“贩卖”只能检察客户信息,不能删除、编辑客户信息,以是李四只能检察客户信息。
那么明白好了 RBAC 的概念之后,接下来我们就可以往复实现我们的辅助业务了,所谓辅助业务详细指的就是:
我们先直接做好的后台先看看效果,明白下RBAC在我们后台管理体系中的寄义。
我们从上面两张图中,可以看到,账号(test),是一个“测试-脚色”的脚色,
而测试脚色的只能看到下面的菜单(权限列表)
而如果我们用超管的账号登录进去,是能看到全部的菜单(权限列表)的
那么由此出现我们可以看出,整个权限体系实在分成了两部门:
- 页面权限:根据差别的 权限数据,展示差别的页面(就是展示差别的菜单Menu,由于一个菜单按钮,是对应一个详细的页面)
- 功能权限:根据差别的 权限数据,一个页面里展示差别的 功能按钮
二、下面我们说下代码实现的逻辑
- 页面权限实现的核心在于 路由表设置
- 路由表设置的核心在于 私有路由表 privateRoutes
- 私有路由表 privateRoutes 的核心在于 addRoute API
那么简单一句话总结,我们只必要:根据差别的权限数据,使用 addRoute API 天生差别的私有路由表 即可实现 页面权限 功能
而*实现功能权限的核心在于 根据数据潜伏功能按钮,那么潜伏的方式我们可以通过Vue的指令举行控制
三、页面权限代码实现
起首我们的路由表必要分成公有路由表和私有路由表
- 私有路由表:就是差别脚色拥有差别的路由表
- 共有路由表:就是每个脚色都有的路由表:比方登录界面、404界面、401界面
讲清了这些下面实现起来也是很简单的,只是一些细节大概要注意,那么直接看代码吧,代码里都有表明
- 创建每一个私有路由表
此中一个路由表的代码,其他都是雷同的,要注意的是每个路由表的path是要反面服务端返回的path雷同的,我们到时间是根据路由的path去筛选数据的,这里我用到的全部界面都是test-page页面,但不影响详细大逻辑,大家明白就行
const RightRouter = { path: '/manage', component: Layout, redirect: '/manage/manageList', alwaysShow: true, // will always show the root menu name: 'manage', meta: { title: '管理1', icon: 'el-icon-s-check' }, children: [ { path: '/manage/manageList', component: () => import('@/views/test-page/index.vue'), name: 'list1', meta: { title: '列表1' } }, { path: '/manage/manageList2', component: () => import('@/views/test-page/index.vue'), name: 'rightSetList', meta: { title: '列表2' } } ]}export default RightRouter
/** * 私有路由表 */export var privateRoutes = [ permissions, manageList,]/** * 公开路由表 */export var publicRoutes = [ { path: '/login', component: () => import('@/views/login/index') }, { path: '/', // 注意:带有路径“/”的记录中的组件“默认”是一个不返回 Promise 的函数 component: layout, redirect: '/home', children: [ { path: '/home', name: 'home', component: () => import('@/views/home/index'), meta: {title: '首页', affix: true},//affix=true,tagViews右侧没有关闭按钮 hidden: true,//不表现在侧边栏 }, { path: '/404', name: '404', component: () => import('@/views/error-page/404') }, { path: '/401', name: '401', component: () => import('@/views/error-page/401') } ] }]const router = createRouter({ history: createWebHashHistory(), // routes: [...publicRoutes, ...privateRoutes] routes: publicRoutes})export default router我们先看下接口返回的数据
从接口返回的数据中我们能可以看出,一级菜单和二级菜单都是有一个url字段的,我们就是要根据这个url字段和我蹊径由表的path字段去做对表,如果存在,就渲染这个路由,不存在就不去渲染这个路由,以是我们必要先将服务端返回的路由数据,转化成这个格式的数据
筛选路由的详细方法代码
/** * 根据服务端返回的路由数据,筛选过滤当地的路由数据 * @param routes asyncRoutes 当地写的数据 * @param roles 接口获取的数据 */export function filterPrivateRoutes(routes, roles) { const res = [] routes.forEach(route => { const tmp = { ...route } //查抄是否符合权限规则:根据自己公司界说的规则 if (hasPermission(roles, tmp)) { if (tmp.children) { tmp.children = filterPrivateRoutes(tmp.children, roles) } res.push(tmp) } }) return res}export default { namespaced: true, state: { // 路由表:初始拥有静态路由权限 routes: publicRoutes }, mutations: { /** * 增长路由 */ setRoutes(state, newRoutes) { // 永世在静态路由的底子上增长新路由 state.routes = [...publicRoutes, ...newRoutes] } }, actions: {}末了,在在 src/permission 中,获取路由数据之后调用这些代码,干系表明都写到代码里了
// 白名单const whiteList = ['/login']/** * 路由前置保卫 */router.beforeEach(async (to, from, next) => { .................... const {roles} = await store.dispatch('user/getPermissionData') // 处置处罚用户权限,筛选出必要添加的权限 const accessRoutes = await store.dispatch('permission/generateRoutes', roles) console.log("筛选出必要addRoute的路由",accessRoutes) // 使用 addRoute 循环添加 accessRoutes.forEach(item => { router.addRoute(item) }) // router.addRoutes(accessRoutes) // hack method to ensure that addRoutes is complete // set the replace: true, so the navigation will not leave a history record next({...to, replace: true}) ........................到这里动态菜单差不多就讲完了,但另有一个题目,就是如果我们更换和账户的登录,只有手动革新下页面,左边菜单才会改变,不会自动去改变。这是由于我们退出的时间,没有重置路由表。以是我们在退出的时间,重置下就行了
/** * 重置路由表 */export function resetRouter() { if (store.getters.hasRoles) { const menus = store.getters.roles //removeRoute是根据路由的name去删除路由的,以是我们要对路由的名字举行截取 // const menus = ['getRoleList','admintorList','adminAuth'] // console.log("menus==",menus) // console.log("router==",router.getRoutes()) menus.forEach(menu => { let url = menu.url let i = url.lastIndexOf('/') let name = url.substring(i+1,url.length) router.removeRoute(name) }) }}import router, { resetRouter } from '@/router'logout(context) { resetRouter() ... }四、功能权限代码实现
以是起首我们先去创建如许一个指令(vue3 自界说指令)
- 我们渴望终极可以通过如许格式的指令举行功能受控 v-permission="'/adminAuth/admintorList'"
- 以此创建对应的自界说指令 directives/permission
import store from '@/store'import {lowerCase} from '@/utils/index'function checkPermission(el, binding) { // 获取绑定的值,此处为权限 const value = lowerCase(binding.value); const auths = store.getters.buttons || []; if (!auths.includes(value)) { el.parentNode.removeChild(el); }}export default { // 在绑定元素的父组件被挂载后调用 mounted(el, binding) { checkPermission(el, binding) }, // 在包含组件的 VNode 及其子组件的 VNode 更新后调用 update(el, binding) { checkPermission(el, binding) }}3.在 directives/index 中绑定该指令
import permission from './permission'export default app => { app.directive('permission', permission)}4.在页面中,添加指令
<el-button type="primary" @click="searchEvent" v-permission="'/adminAuth/admintorList'">查询</el-button>五、总结
那么到这里我们整个权限受控就算是全部完成了。
整个这一大节中,核心就是 RBAC的权限受控体系 。围绕着 用户->脚色->权限 的体系是如今在包含权限控制的体系中使用率最广的一种方式。
那么怎么针对于权限控制的方案而言,除了文中提到的这种方案之外,实在另有很多其他的方案,大家可以在我们的话题讨论中踊跃发言,多多讨论。 |