目录
前置
什么是RBAC权限设计思想
页面级权限实现流程
分析
1 设置路由router/index.js
2 获取权限数据 过滤得到动态路由表
3 动态路由表加载router, 渲染左侧菜单
4 bug 解决
你需要先完成 给用户分配角色, 给角色分配权限的基础前置, 并且你可以从后端/mock 获取到该用户的权限信息.
通俗的讲, 假如你是一位职业棋手(角色), 那你可以进入中国棋院(权限), 你的朋友虽然是你的朋友(角色), 但他不是职业棋手, 中国棋院不让外人进, 他不能进入(权限)
有朝一日, 你朋友疯狂内卷, 一天24小时加班, 得到了职业棋手认证(角色), 他现在可以进中国棋院了(权限).
总之, 如果我一个一个的给员工添加权限是不是太麻烦了? 往往公司都有一套规则, a角色拥有a类权限, b角色拥有b权限, 我想给新来的员工添加权限, 直接根据老板指示给他个角色就行
角色就是一堆权限的集合
首先路由守卫里是登录后, 有token, 进入else里面的提交vuex actions异步获取userInfo, vuex的返回值里里面有权限数据
首先我们知道, 页面权限跟路由router有关, 其中路由表是控制能不能跳转到对应页面组件的关键. 所以这8个动态路由我们需要动态设置, 根据menus设置
router/index.js
可以看到, 我们这里是写死了, 所以我们每个用户都可以访问每个页面, 这里直接删除 ... asyncRoutes
接下来,我们要建立后端返回的menus和动态路由表的联系, 想想我们最先获得menus的地方, 是permission路由守卫提交的actions请求userInfo,里面带有menus. 而dispatch返回的是个promise对象, 所以我们可以在路由守卫 await 这个值
扩展: vuex官网 => actions 里面return promise对象带需要的值 这里也可以直接return 需要的值. 因为vuex官网这样说的首先,你需要明白 store.dispatch 可以处理被触发的 action 的处理函数返回的 Promise,并且 store.dispatch 仍旧返回 Promise:
vuex里面返回值
路由守卫里 取值
打印可以在路由守卫里得到最早的menus
接下来就是在这里 我们要做一个事, 将写好的 的动态路由表拿过来, 按照menus 过滤得到新的路由表
我每个动态路由的对象是这样的, 可以看到既可以用path属性取2位之后的字符串进行filter, 也可以直接用name, 我用path判断
路由守卫里
const { menus } = await store.dispatch('user/getInfo')
const filterAsyncRoutes = asyncRoutes.filter(item => menus.includes(item.path.slice(1)))
得到了过滤权限后的动态路由表
路由守卫里面 得到新的动态路由表了, 接下来要做两件事
我这里采用vuex 存储动态路由和静态路由, 然后加载到router, 因为左侧菜单是其他组件, 等会按照路由表渲染可以跨组件使用, 具体方法就是在上面路由守卫得到需要的数据后, 直接提交mutations, vuex里面导入静态路由表, mutations里面拼接 静态和过滤后的动态路由表 生成最终的totalRoutes
在路由守卫获取上面的值后, 立即加载动态路由到router.addRoutes方法
const { menus } = await store.dispatch('user/getInfo')
const filterAsyncRoutes = asyncRoutes.filter(item => menus.includes(item.path.slice(1)))
router.addRoutes(filterAsyncRoutes)
store.commit('menu/loadRoute', filterAsyncRoutes)
vuex代码 路由守卫提交mutations 传过滤的动态路由表 导入静态路由表, 合成最终路由表用来渲染左侧菜单
分析左侧菜单逻辑
siderbar组件的index.vue
原本是通过this.$router.options.routes 也就是我们在router/index.js 配置的路由表, 而我们动态添加的路由表是不会自动更新到路由表的, this.$router.options.routes 是拿到初始化时配置的路由规则, 所以这也是为什么我要自己存到vuex 这里也是vue故意这么设计的, 作者曾在issue回答过: options is the object passed to the vuerouter constructor. It's not modified afterwards.
但是作者不建议用上面的方法, 最好是存到vuex里面做
接着左侧菜单设置 更改代码 从vuex获取我们动态设置后的路由表
左侧菜单渲染完成
bug1: 刷新404
在动态路由页面, 比如上面的department 组织架构里面,我们本来是正常可以访问的, 但是只要刷新, 就会发现404的问题
因为我们静态路由表的最后的规则是*通配符到404, 刷新时,动态路由需要重新挂载到路由实例,但是还没挂载就被通配符拦截到404页面
解决办法: 404从静态路由表中删除, 添加到过滤后的动态路由表最后面, 也就是push进去404这条规则
bug2: 刷新空白 /页面不加载不报错
嘿嘿 解决了bug1 是不是发现又多了一个bug 刷新页面不加载不报错,空白
这又是为啥呢, 我第一次碰到的时候也纳闷, 后来经过查阅资料, 大概原因是我们在守卫里面addRoutes, 还没执行完, 就执行了next() 所以我们得要加个next(to.path) 重新进入一次守卫, 这时动态路由已经添加进去了
bug3: 退出登录再登录,控制台出现重复添加routes的信息,而且没有权限的账户只要不刷新页面, 可以通过地址栏输入地址,访问原本没有权限的地址
因为我们退出时没有重置路由, 这里最严重的就是权限混乱, 因为前面的用户动态路由信息没有清空, 造成权限泄漏, 所以在退出登录时重置路由
一般有三个地方需要设置
// router/index.js 定义重置路由方法
export function resetRouter() {
const newRouter = createRouter()
router.matcher = newRouter.matcher // reset router
}
// vuex登出actions中 最后重置路由
// 以及一切你知道的问题处 比如token失效时一般我们放到request拦截器里面处理
import { resetRouter } from '@/router'
logout(context) {
context.commit('updateToken', '')
context.commit('setUserInfo', {})
resetRouter()
}
// 我的拦截器中设置当401 status时 提交actions 登出 所以我只用设置上面vuex那段
// 响应拦截器
async error => {
// console.log('响应拦截器error')
if (error.response.status === 401) {
await store.dispatch('user/logout')
router.push(`/login?redirect=${router.currentRoute.fullPath}`)
}
Message.error(error.response.data.message)
return Promise.reject(error)
}
bug4: 由于我们做了个退出登录或者token过期会自动往地址栏加原网页地址的path信息, 如果我们退出登录后, 登录没有原网页权限的用户, 会出现404 在bug2 处的next(to.path) 加个判断,如果不在menus里面 直接跳转主页
menus.includes(to.path.substring(1)) ? next(to.path) : next('/')
menus: 后端返回的权限数组 元素是页面标识
至此, 基本上页面权限管理基本做完了, 你学废了吗...