前端权限流程(基于rbac实现思想)
1. 权限控制
1.1. 实现思想
基于rbac权限控制思想实现,给用户分配角色,给角色分配权限
给用户分配角色业务
注意:上方图片是个示例图,代表给用户分配职位(角色),页面中使用了Element-plus的el- checkbox组件和el-checkbox-group组件
静态结构(不完整)
<el-form-item label="用户姓名">
<el-input v-model="userParams.username" :disabled="true"></el-input>
</el-form-item>
<el-form-item label="职位列表">
<el-checkbox>
全选
</el-checkbox>
<!-- 显示职位的的复选框 -->
<el-checkbox-group>
<el-checkbox
v-for="(role, index) in 10"
:key="index"
:label="index"
>
{{ index }}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
获取&&存储数据
首先先获取全部的职位(角色)的数据与当前用户已有的职位(角色)的数据,分别进行保存
//收集顶部复选框全选数据
let checkAll = ref<boolean>(false)
//控制顶部全选复选框不确定的样式
let isIndeterminate = ref<boolean>(true)
//存储全部职位的数据
let allRole = ref<AllRole>([])
//当前用户已有的职位
let userRole = ref<AllRole>([])
//分配角色按钮的回调
const setRole = async (row: User) => {
//存储已有的用户信息
Object.assign(userParams, row)
//获取全部的职位的数据与当前用户已有的职位的数据
let result: AllRoleResponseData = await reqAllRole(userParams.id as number)
if (result.code == 200) {
//存储全部的职位
allRole.value = result.data.allRolesList
//存储当前用户已有的职位
userRole.value = result.data.assignRoles
//抽屉显示出来
drawer1.value = true
}
}
展示数据
<!-- 抽屉结构:用户某一个已有的账号进行职位分配 -->
<el-drawer v-model="drawer1">
<template #header>
<h4>分配角色(职位)</h4>
</template>
<template #default>
<el-form>
<el-form-item label="用户姓名">
<el-input v-model="userParams.username" :disabled="true"></el-input>
</el-form-item>
<el-form-item label="职位列表">
<el-checkbox
@change="handleCheckAllChange"
v-model="checkAll"
:indeterminate="isIndeterminate"
>
全选
</el-checkbox>
<!-- 显示职位的的复选框 -->
<el-checkbox-group
v-model="userRole"
@change="handleCheckedCitiesChange"
>
<el-checkbox
v-for="(role, index) in allRole"
:key="index"
:label="role"
>
{{ role.roleName }}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-form>
</template>
</el-drawer>
详细解释
全选部分:
@change:全选框点击时的回调
v-model:绑定的数据,根据这个值决定是否全选(是一个布尔值)
:indeterminate:不确定状态,既没有全选也没有全不选
复选框部分:
v-for="(role, index) in allRole"
:遍历allRole。
:label="role"
:收集的数据(勾上的数据)
v-model="userRole"
:绑定收集的数据,也就是收集的数据存储到userRole中。
@change:勾选变化时的回调(点击每个复选框都会执行的回调)
全选框勾选的回调:
实现原理:函数会将勾选与否注入到val中,如果是,就将全部数据(allRole)赋值给选中的数据(userRole),选中的数据通过v-model实现页面的同步变化
该函数会接收到一个布尔值,如果为真代表全选按钮被勾选,在回调中需要将全部角色(职位)数据赋值给在el-checkbox-group使用v-model绑定的变量中(该变量表示被选中的数据)
//顶部的全部复选框的change事件
const handleCheckAllChange = (val: boolean) => {
//val:true(全选)|false(没有全选)
userRole.value = val ? allRole.value : []
//不确定的样式(确定样式)
isIndeterminate.value = false
}
复选框
每勾选一个复选框就会执行该函数,并收集勾选的数据到数组中传递进该函数
在函数内部判断数组的长度是否等于全部角色数据,如果等于需要将上方的权限按钮勾上,因为已经使用v-model绑定了,所以直接给checkAll.value赋一个布尔值即可
//顶部全部的复选框的change事件
const handleCheckedCitiesChange = (value: string[]) => {
//顶部复选框的勾选数据
//代表:勾选上的项目个数与全部的职位个数相等,顶部的复选框勾选上
checkAll.value = value.length === allRole.value.length
//不确定的样式
isIndeterminate.value = value.length !== allRole.value.length
}
当点击确定按钮的回调
需要收集两个参数:第一个:当前用户的id标识,第二个:从收集好的userRole中使用map过滤出选中的角色的id标识(数组)
然后发起分配用户角色的请求,请求成功,重新获取更新完毕用户的信息。
//确定按钮的回调(分配职位)
const confirmClick = async () => {
//收集参数
let data: SetRoleData = {
userId: userParams.id as number,
roleIdList: userRole.value.map((item) => {
return item.id as number
}),
}
//分配用户的职位
let result: any = await reqSetUserRole(data)
if (result.code == 200) {
//提示信息
ElMessage({ type: 'success', message: '分配职务成功' })
//关闭抽屉
drawer1.value = false
//获取更新完毕用户的信息,更新完毕留在当前页
getHasUser(pageNo.value)
}
}
看到这里,相信你一定有收获!!!
给角色分配权限业务
静态结构
当点击上图中的分配权限按钮时,会弹出一个抽屉组件(el-drawer),利用树组件展示所有权限数据,并可以看到点击的角色已有的权限数据(勾选的)
注意:上方图片是个示例图,代表给角色分配权限,页面中使用了Element-plus的el-table和el-drawer,el-tree组件。
注意:因为这是在vue3和typescript项目中,所以进行了类型限制。
type这里MenuData与MenuList互相调用,适合这种树状的数据结构(type还可以定义多个复杂类型)
//菜单与按钮数据的ts类型
export interface MenuData {
id: number
createTime: string
updateTime: string
pid: number
name: string
code: string
toCode: string
type: number
status: null
level: number
children?: MenuList
select: boolean
}
export type MenuList = MenuData[]
分配权限按钮
获取&&存储数据
根据角色id发送请求获取全部角色权限数据(包含该角色已有的职位)
//准备一个数组:数组用于存储勾选的节点的ID(四级的)
let selectArr = ref<number[]>([])
//已有的职位的数据
const setPermisstion = async (row: RoleData) => {
//抽屉显示出来
drawer.value = true
//收集当前要分类权限的职位的数据
Object.assign(RoleParams, row)
//根据职位获取权限的数据
let result: MenuResponseData = await reqAllMenuList(RoleParams.id as number)
if (result.code == 200) {
menuArr.value = result.data
//下面这行代码下面会具体讲解,可以先往下看
selectArr.value = filterSelectArr(menuArr.value, [])
}
}
使用树组件展示全部权限数据(树组件详细使用说明)
我们重点关注el-tree组件,先介绍一些常用属性和方法
const defaultProps = {
//子树为节点对象的children
children: 'children',
//节点标签为节点对象的name属性
label: 'name',
}
常用属性:
1. data:展示的数据(数据源)
2. show-checkbox:节点是否可被选择(点击可以选中)
3. node-key:每个树节点用来作为唯一标识的属性,整棵树应该是唯一的(如果树中包含children子数据,该属性不能省略)
4. default-expand-all:默认展开所有节点
5. default-checked-keys:默认勾选的节点的 key 的数组(是一个数组,数组中存放的就是上面node-key存放的唯一标识)
6. default-expanded-keys:默认展开的节点的 key 的数组(是一个数组,数组中存放的就是上面node-key存放的唯一标识)
7. current-node-key:当前选中的节点(可以是number或string类型)
8. props:接收一个对象,对象中可以包含以下两个属性(还可以包含其他属性,这里只列举了以下两个)
label:指定节点标签为节点对象的某个属性值(就是代表了要在页面中展示的节点名称) ,children:指定子树为节点对象的某个属性值(就是代表去哪个字段下读取数据当作子节点的数据)(注意:label和children这两个属性名是不变的,属性值需要根据项目需要进行修改)
常用方法:
使用el-tree树组件提供的方法时,需要先在el-tree组件标签上利用ref打上标识(<el-tree ref="xxx"> </el-tree>),然后通过ref得到el-tree组件实例才能调用对应方法!
1. getCheckedKeys:如果在el-tree标签上设置了show-checkbox属性且被选中,通过树组件实例.getCheckedKeys进行调用,它将返回当前选中节点key的数组(该数组由所有被选中的节点的id属性组成【为什么是id属性呢? 因为:在el-tree标签上设置了node-key="id"属性。所以该方法会收集所有选中的节点对象的id属性】)
2. getHalfCheckedKeys:如果在el-tree标签上设置了show-checkbox属性且被选中,通过树组件实例.getHalfCheckedKeys进行调用,它将返回当前半选中的节点的id属性组成的数组
如遇这种情况该方法一般会和上面的getCheckedKeys配合使用
在树组件中展示已分配的权限
为什么要使用递归函数?
因为不能直接根据1级,2级,3级的select属性进行判断,要判断最内层的职位对象的select属性是真是假,因为层级较深要判断最内层的职位对象的select属性,所以使用了递归函数
具体实现思路:
目的:要先对返回的全部权限数据进行过滤,找到最内层的职位对象,根据其select属性判断!
封装一个递归函数,接收两个参数,一个全部权限数据,一个空数组(用于存放满足level为4级职位对象中的select属性为真的职位的id),在递归函数 中使用for Each遍历全部权限数据,判断item.select && item.level === 4如果满足说明到最内层了,将当前这个对象的id属性(item.id)push到空数组中,否则代表没有到最内层,继续判断item.children && item.children.length > 0 满足该条件递归调用 filterSelectArr(item.children,initArr),最后返回这个过滤好的数组,交给el-tree组件的default-checked-keys属性,那么就可以找出该角色已有的权限就可以在树组件选中了
//分配权限按钮的回调
//已有的职位的数据
const setPermisstion = async (row: RoleData) => {
//抽屉显示出来
drawer.value = true
//收集当前要分类权限的职位的数据
Object.assign(RoleParams, row)
//根据职位获取权限的数据
let result: MenuResponseData = await reqAllMenuList(RoleParams.id as number)
if (result.code == 200) {
menuArr.value = result.data
/*
最后返回这个过滤好的数组,交给el-tree组件的default-checked-keys属性,
那么就可以找出该角色已有的权限就可以在树组件选中了
*/
selectArr.value = filterSelectArr(menuArr.value, [])
}
}
// 递归函数
const filterSelectArr = (allData: any, initArr: any) => {
allData.forEach((item: any) => {
if (item.select && item.level == 4) {
initArr.push(item.id)
}
if (item.children && item.children.length > 0) {
//层层递进,直到最内层的职位对象的level属性===4
filterSelectArr(item.children, initArr)
}
})
//返回这个过滤好的数组
return initArr
}
收集用户分配的权限
我们这里收集主要用到了2个方法:getCheckedKeys、getHalfCheckedKeys。它们会返回已选择以及半选择用户的id数组(这两个方法上方都有详细解释)
//抽屉确定按钮的回调
const handler = async () => {
//职位(角色)的ID
const roleId = RoleParams.id as number
//选中节点的ID getCheckedKeys方法会得到show-checkbox为true选中的全部节点对象的id组成的数组
//为什么是能收集到id 因为el-tree配置了node-key="id"属性
let arr = tree.value.getCheckedKeys()
//半选的ID
let arr1 = tree.value.getHalfCheckedKeys()
let permissionId = arr.concat(arr1)
//下发权限
let result: any = await reqSetPermisstion(roleId, permissionId)
if (result.code == 200) {
//抽屉关闭
drawer.value = false
//提示信息
ElMessage({ type: 'success', message: '分配权限成功' })
//页面刷新
window.location.reload()
}
}
继续加油!
1.2. 实现深度
3级权限:模块权限、页面权限、按钮权限