目录
前置
你需要先完成 给用户分配角色, 给角色分配权限的基础前置, 并且你可以从后端/mock 获取到该用户的权限信息.
什么是RBAC权限设计思想
通俗的讲, 假如你是一位职业棋手(角色), 那你可以进入中国棋院(权限), 你的朋友虽然是你的朋友(角色), 但他不是职业棋手, 中国棋院不让外人进, 他不能进入(权限)
有朝一日, 你朋友疯狂内卷, 一天24小时加班, 得到了职业棋手认证(角色), 他现在可以进中国棋院了(权限).
总之, 如果我一个一个的给员工添加权限是不是太麻烦了? 往往公司都有一套规则, a角色拥有a类权限, b角色拥有b权限, 我想给新来的员工添加权限, 直接根据老板指示给他个角色就行
角色就是一堆权限的集合
页面级权限实现流程
分析
首先路由守卫里是登录后, 有token, 进入else里面的提交vuex actions异步获取userInfo, vuex的返回值里里面有权限数据
1 设置路由router/index.js
首先我们知道, 页面权限跟路由router有关, 其中路由表是控制能不能跳转到对应页面组件的关键. 所以这8个动态路由我们需要动态设置, 根据menus设置
router/index.js
可以看到, 我们这里是写死了, 所以我们每个用户都可以访问每个页面, 这里直接删除 ... asyncRoutes
2 获取权限数据 过滤得到动态路由表
接下来,我们要建立后端返回的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)))
得到了过滤权限后的动态路由表
3 动态路由表加载router, 渲染左侧菜单
路由守卫里面 得到新的动态路由表了, 接下来要做两件事
- 加载到router
- 拼接动态路由和静态路由 , 渲染左侧菜单
我这里采用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获取我们动态设置后的路由表
左侧菜单渲染完成
4 bug 解决
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: 后端返回的权限数组 元素是页面标识
至此, 基本上页面权限管理基本做完了, 你学废了吗...