权限设置
1. 权限点
-
权限: 在一个系统内是否具有做某个操作的权利
-
权限分成两个级别:
- 菜单权限: 是否有权限访问某个菜单
- 按钮权限: 是否有权限操作页面上的某个按钮功能
2. 业务逻辑
-
对于权限数据来说, 有两个级别的设置
-
能不能访问某个页面
-
在页面上,能不能操作某个按钮
3. RBAC权限设计思想(Role-Based Access control)
-
目标: 不同账号登录系统后看到不同的页面, 能执行不同的功能
-
权限模式
-
三个关键点:
- 用户: 使用系统的人
- 权限点: 这个系统中有多少功能(例如: 有3个页面, 每个页面上有不同的操作)
- 角色: 不同的权限点的集合
-
关系:
- 给用户分配角色
- 给角色分配权限点
- 用户和角色是1对多的关系, 一个用户可以拥有多个角色
-
实际业务中:
- 先给员工分配一个具体的角色
- 给角色分配具体的权限点
4.权限点业务
- 整体分析
-
示例
-
用户在点击登录后, 进行路由跳转之前, 在路由前置守卫中获取用户信息, 将动态路由改成动态添加的方式(
router.addRoutes()
),菜单项使用的渲染数据项是this.$router.options.routes
, 它只能获取路由表中静态配置的部分 , 而 addRoutes()添加的部分获取不到 -
因为
this.$router.options.routes
的数据不是响应式的 , 所以即使有了数据也不会反应到视图上 -
如果调用
router.addRoutes()
, 想要数据反映到视图上, 需要将路由信息存在vuex中
-
在vuex中定义菜单数据
在src/store/modules下补充menu.js :
// 导入静态路由
import { constantRoutes } from '@/router'
export default {
namespaced: true,
state: {
// 先以静态路由作为菜单数据的初始值
menuList: [...constantRoutes]
},
mutations: {
setMenuList(state, asyncRoutes) {
// 将动态路由和静态路由组合起来
state.menuList = [...constantRoutes, ...asyncRoutes]
}
}
}
在src/store/index.js中注册这个模块
- 提交setMenuList
修改src/permission.js
if (!store.getters.userId) {
await store.dispatch('user/getUserInfo')
// 把动态路由数据交给菜单
store.commit('menu/setMenuList', asyncRoutes)
// 把动态路由添加到应用的路由系统里
router.addRoutes(asyncRoutes)
}
- 菜单生成部分该写使用vuex中的数据
注: 该项目使用vue-elment-admin进行二次开发(生成动态侧边栏具体参考vue-elment-admin源码)
routes() {
// 拿到的是一个完整的包含了静态路由和动态路由的数据结构
// return this.$router.options.routes
return this.$store.state.routeMenu.menuList
}
-
权限数据做过滤处理
-
通过后台返回的权限数据, 过滤出要显示的菜单, 过滤使用路由的name作为标识
-
从action中获取返回值
action本质上是一个promise 它的return 结果可以通过const res = await action名来接收
-
修改 store/modules/user.js
// 用来获取用户信息的action async getUserInfo(context) { // 1. ajax获取基本信息,包含用户id const rs = await getUserInfoApi() console.log('用来获取用户信息的,', rs) // 2. 根据用户id(rs.data.userId)再发请求,获取详情(包含头像) const info = await getUserDetailById(rs.data.userId) console.log('获取详情', info.data) // 把上边获取的两份合并在一起,保存到vuex中 context.commit('setUserInfo', { ...info.data, ...rs.data }) return rs.data.roles.menus },
-
-
在promise做过滤
if (!store.getters.userId) {
// 有token,要去的不是login,就直接放行
// 进一步获取用户信息
// 发ajax---派发action来做
const menus = await store.dispatch('user/getUserInfo')
console.log('当前用户能访问的页面', menus)
console.log('当前系统功能中提供的所有的动态路由页面是', asyncRoutes)
// 根据本用户实际的权限menus去 asyncRoutes 中做过滤,选出本用户能访问的页面
const filterRoutes = asyncRoutes.filter(route => {
const routeName = route.children[0].name
return menus.includes(routeName)
})
// 一定要在进入主页之前去获取用户信息
// addRoutes用来动态添加路由配置
// 只有在这里设置了补充了路由配置,才可能去访问页面
// 它们不会出现左侧
router.addRoutes(filterRoutes)
// 把它们保存在vuex中,在src\layout\components\Sidebar\index.vue
// 生成左侧菜单时,也应该去vuex中拿
store.commit('menu/setMenuList', filterRoutes)
// 解决刷新出现的白屏bug
next({
...to, // next({ ...to })的目的,是保证路由添加完了再进入页面 (可以理解为重进一次)
replace: true // 重进一次, 不保留重复历史
})
}
- 解决404问题
原因:现在我们的路由设置中的404页处在中间位置而不是所有路由的末尾了
解决办法:把404页改到路由配置的最末尾就可以了
1. 从route/index.js中的静态路由中删除path:’*'这一项
2. 在permission.js中补充在最后 - 代码示例:
// 把404加到最后一条
filterRoutes.push( // 404 page must be placed at the end !!!
{ path: '*', redirect: '/404', hidden: true })
-
退出时重置路由
router/index.js中// 重置路由 export function resetRouter() { const newRouter = createRouter() router.matcher = newRouter.matcher // 重新设置路由的可匹配路径 }
这个方法就是将路由重新实例化,相当于换了一个新的路由,之前加的路由就不存在了,需要在登出的时候, 调用一下即可
store/modules/user.jsimport { resetRouter } from '@/router' // 退出的action操作 logout(context) { // 1. 移除vuex个人信息 context.commit('removeUserInfo') // 2. 移除token信息 context.commit('removeToken') // 3. 重置路由 resetRouter() // 4. 重置 vuex 中的路由信息 只保留每个用户都一样的静态路由数据 // 在moudules中的一个module中去调用另一个modules中的mutation要加{root:true} //context.commit('menus/setMenuList', [], { root: true }) }
-
权限应用 - 控制操作按钮
-
定义全局检测方法
在main.js中Vue.prototype.$checkPoint = function(pointKey) { if (store.state.user.userInfo.roles.points) { // 进行权限点判断 return store.state.user.userInfo.roles.points.includes(pointKey) } // 没有权限点POINTS信息, 说明用户没有身份, 没有任何权限 return false }
// 在模板中通过if控制按钮显示
<template>
<div class="dashboard-container">
<div class="app-container">
<el-card>
<el-button v-if="$checkPoint('CKGZ')">查看工资</el-button>
</el-card>
</div>
</div>
</template>
注: $checkPoint中的参数以系统中权限点的标识符为准