持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第15天,点击查看活动详情
铁铁们可能对于RBAC权限设计很陌生,接下来给大家简单介绍一下吧
三个关键点: 员工, 角色, 权限
- 给员工分配角色
- 给角色分配权限
进入一家公司, 入职, 人事将你录入系统 => 分配你的角色 (前端开发工程师)
同角色有着相同的权限, 操作角色权限的同时, 所有该角色的用户对应权限, 就会同步更新
给员工分配角色
用户和角色是**1对多**的关系,即一个用户可以拥有多个角色,比如公司的董事长可以拥有总经理和系统管理员一样的角色
点击这个员工,获取到员工的角色,给员工分配角色
实现步骤
新建分配角色弹框
首先,新建分配角色窗体 employees/components/assign-role.vue
<template>
<el-dialog class="assign-role" title="分配角色" :visible="showRoleDialog">
<!-- 这里准备复选框 -->
...
<template #footer>
<el-button type="primary" size="small">确定</el-button>
<el-button size="small">取消</el-button>
</template>
</el-dialog>
</template>
<script>
export default {
props: {
showRoleDialog: {
type: Boolean,
default: false
},
// 用户的id 用来查询当前用户的角色信息
userId: {
type: String,
default: null
}
}
}
</script>
弹层的显示和关闭
点击角色按钮显示弹层
注册组件
import AssignRole from './components/assign-role'
components: {
AddEmployee,
AssignRole
},
<assign-role :show-role-dialog.sync="showRoleDialog" :user-id="userId" />
点击角色按钮, 记录id, 显示弹层
<el-button type="text" size="small" @click="editRole(row.id)">角色</el-button>
editRole(id) {
this.userId = id
this.showRoleDialog = true
}
弹层的关闭
<template>
<el-dialog class="assign-role" title="分配角色" :visible="showRoleDialog" @close="closeDialog">
<!-- el-checkbox-group选中的是 当前用户所拥有的角色 需要绑定 当前用户拥有的角色-->
<el-checkbox-group>
<!-- 选项 -->
</el-checkbox-group>
<template #footer>
<div style="text-align: right">
<el-button type="primary">确定</el-button>
<el-button @click="closeDialog">取消</el-button>
</div>
</template>
</el-dialog>
</template>
<script>
export default {
props: {
showRoleDialog: {
type: Boolean,
default: false
},
// 用户的id 用来查询当前用户的角色信息
userId: {
type: String,
default: null
}
},
methods: {
closeDialog() {
this.$emit('update:showRoleDialog', false)
}
}
}
</script>
获取角色列表
<el-checkbox-group v-model="roleIds">
<el-checkbox label="110">管理员</el-checkbox>
<el-checkbox label="113">开发者</el-checkbox>
<el-checkbox label="115">人事</el-checkbox>
</el-checkbox-group>
发送请求获取角色列表
import { reqGetRoleList } from '@/api/setting'
export default {
props: {
showRoleDialog: {
type: Boolean,
default: false
},
// 用户的id 用来查询当前用户的角色信息
userId: {
type: String,
default: null
}
},
data() {
return {
list: [],
roleIds: []
}
},
created() {
this.getRoleList()
},
methods: {
closeDialog() {
this.$emit('update:showRoleDialog', false)
},
async getRoleList() {
const { data } = await reqGetRoleList(1, 100)
this.list = data.rows
}
}
}
渲染数据
<el-checkbox-group v-model="roleIds">
<el-checkbox v-for="item in list" :key="item.id" :label="item.id">
{{ item.name }}
</el-checkbox>
</el-checkbox-group>
微调样式
<style lang="scss" scoped>
.assign-role {
::v-deep {
.el-checkbox {
font-size: 30px;
}
}
}
</style>
然后根据传递过来的id,获取用户的角色信息进行回显,重点一点点的就是在open这个方法中实现,弹出框显示发送ajax,然后给员工分配角色
权限点管理页面开发
目标: 完成权限点页面的开发和管理 => 为了后面做准备
- 便于分配权限, 只有有权限了才能分配
- 只有分配好了权限, 有对应的权限规则, 才能控制路由(模块访问权), 才能控制按钮的显示(操作权)
新建权限点管理页面
人已经有了角色,只要给角色分配权限, 人就有了权限
那么权限是什么
在企业服务中,权限一般分割为 页面访问权限,按钮操作权限,API访问权限
API权限多见于在后端进行拦截,所以我们需要做**
页面访问和按钮操作授权**
由此,我们可以根据业务需求, 设计权限管理页面
在就是前端的路由了
前端权限-页面访问权(路由)
目标: 在当前项目应用用户的页面访问权限
权限受控的思路 - addRoutes
到了最关键的环节,我们设置的权限如何应用?
在上面的几个小节中,我们已经把给用户分配了角色, 给角色分配了权限,
那么在用户登录获取资料的时候,会自动查出该用户拥有哪些权限,这个权限需要和我们的菜单还有路由有效结合起来
而动态路由表其实就是根据用户的实际权限来访问的,接下来我们操作一下
在权限管理页面中,我们设置了一个标识, 这个标识可以和我们的路由模块进行关联,
如果用户拥有这个标识,那么用户就可以 拥有这个路由模块,如果没有这个标识,就不能访问路由模块
addRoutes 的基本使用
拿到权限信息之后, 应该根据权限信息, 从动态路由模块中筛选出, 需要追加的路由,
// 追加到routes规则中, addRoutes
router.addRoutes(asyncRoutes)
next({
...to, // next({ ...to })的目的,是保证路由添加完了再进入页面 (可以理解为重进一次)
replace: true // 重进一次, 不保留重复历史
})
return
}
最后将数据放入vuex中
新建Vuex路由权限模块
可以在vuex中新增一个permission模块, 专门维护管理, 所有的路由 routes 数组 (响应式, 所有地方都能用)
this.$router.options.routes 最初配置的路由信息表 如果有addRoutes动态新增的情况, vuex管理, 拿vuex中routes
在任何的组件中, 都可以非常方便的将来拿到 vuex 中存的 routes, 而不是去找 router.options.routes (拿的是默认配置)
src/store/modules/permission.js
import { constantRoutes } from '@/router'
const state = {
// 路由表, 标记当前用户所拥有的所有路由
routes: constantRoutes // 默认静态路由表
}
const mutations = {
// otherRoutes登录成功后, 需要添加的新路由
setRoutes(state, otherRoutes) {
// 静态路由基础上, 累加其他权限路由
state.routes = [...constantRoutes, ...otherRoutes]
}
}
const actions = {}
export default {
namespaced: true,
state,
mutations,
actions
}
在Vuex管理模块中引入permisson模块
import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
import app from './modules/app'
import settings from './modules/settings'
import user from './modules/user'
import permission from './modules/permission'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
app,
settings,
user,
permission
},
getters
})
export default store
Vuex-action筛选权限路由
将来登录成功时, 个人信息中会有 roles 的 menus 信息, 要基于menus来过滤出我们需要给用户 add 的路由,
但是**menus**中的标识又该怎么和路由对应呢?
可以将路由模块**name**属性命名和权限标识一致,这样只要标识能对上,就说明用户拥有了该权限
这一步,在我们命名路由的时候已经操作过了
接下来, vuex的 permission 中提供一个action,进行关联
import { asyncRoutes, constantRoutes } from '@/router'
const actions = {
// 筛选路由权限
filterRoutes(context, menus) {
const otherRoutes = asyncRoutes.filter(item => {
// 如果路由模块的首页name, 在menus数组中包含, 就是这个模块开放
if (menus.includes(item.children[0].name)) {
return true
} else {
return false
}
})
context.commit('setRoutes', otherRoutes)
return otherRoutes
}
}
调用Action管理路由渲染菜单
在 permission 拦截的位置,调用关联action, 获取新增routes,并且addRoutes
...
if (!store.state.user.userInfo.userId) {
// 调用获取信息的action
const { roles } = await store.dispatch('user/getUserInfo')
// 调用action同步到vuex中
const otherRoutes = await store.dispatch('permission/filterRoutes', roles.menus)
// 动态新增路由
router.addRoutes(otherRoutes)
next({
...to, // next({ ...to })的目的,是保证路由添加完了再进入页面 (可以理解为重进一次)
replace: true // 重进一次, 不保留重复历史
})
return
}
...
在**src/store/getters.js**配置导出routes
const getters = {
...
routes: state => state.permission.routes // 导出当前的路由
}
export default getters
在左侧菜单 layout/components/Sidebar/index.vue 组件中, 引入routes, 使用vuex的routes动态渲染
computed: {
...mapGetters([ 'sidebar', 'routes' ])
}
处理刷新 404 的问题
页面刷新的时候,本来应该拥有权限的页面出现了404,这是因为404的匹配权限放在了静态路由中 (静态路由的404要删除)
我们需要将404放置到动态路由的最后,404不放到最后就会出现刷新就出现404
src/permission.js
const otherRoutes = await store.dispatch('permission/filterRoutes', menus)
router.addRoutes([
...otherRoutes,
{ path: '*', redirect: '/404', hidden: true }
])
退出时重置路由
退出时, 需要将路由权限重置 (恢复默认), 将来登录后, 再次追加
我们的**router/index.js**文件,发现一个重置路由方法
// 重置路由
export function resetRouter() {
const newRouter = createRouter()
router.matcher = newRouter.matcher // 重新设置路由的可匹配路径
}
这个方法就是将路由重新实例化,相当于换了一个新的路由,之前**加的路由**就不存在了,需要在登出的时候, 调用一下即可
store/modules/user.js
import { resetRouter } from '@/router'
// 退出的action操作
logout(context) {
// 1. 移除vuex个人信息
context.commit('removeUserInfo')
// 2. 移除token信息
context.commit('removeToken')
// 3. 重置路由
resetRouter()
// 4. 重置 vuex 中的路由信息
context.commit('permission/setRoutes', [], { root: true })
},