实现效果分析
该权限树组件实现了以下的关键功能
- 从后端获取角色权限并初始化树的选择状态
- 父子节点联动选择
- 父节点全选子节点也全选,父节点不选子节点也全不选,
- 子节点选中一个父节点就要选中,子节点全都不选父节点可以单独选中
- 获取全选和半选节点的权限码集合
核心代码解析
1.Tree组件模版
html
<el-tree
ref="preTreeRef"
:data="permissionTree"
show-checkbox
node-key="code"
highlight-current
:expand-on-click-node="false"
:props="defaultPermissionProps"
:check-strictly="checkStrictly"
@check="handleCheckChange"
>
</el-tree>
关键属性解析
node-key="code"
:使用权限码作为节点的唯一标识:check-strictly="checkStrictly"
:禁用Element UI自带的父子关联,改为手动控制@check="handleCheckChange"
:节点选择状态变化时触发自定义事件
2.初始化角色权限
js
async getQueryRolePermission(id) {
const roleId = id || this.addForm.roleId;
const res = await queryRolePermissionCodes({ roleId });
if (+res.code !== 200) {
this.$message.error(res.msg || '请求失败');
return;
}
const { permissionCodes } = res.data || [];
const permissionCodesList = permissionCodes?.split(',') || [];
this.$nextTick(() => {
this.$refs.preTreeRef.setCheckedKeys(permissionCodesList, false);
this.permissionCodes = permissionCodesList;
});
}
关键属性解析
- 使用
$nextTick
确保DOM更新完成后再操作树组件 setCheckedKeys
第二个参数设为false
避免触发check
事件- 直接将权限码数组设置为选中状态
3.节点选择事件处理
js
handleCheckChange(node, checkedStatus) {
if (this.isHandling) return;
this.isHandling = true;
const checked = checkedStatus.checkedKeys.includes(node.code);
if (checked) {
// 选中节点时处理子节点和父节点
if (node.children?.length) {
this.checkAllChildren(node, true);
}
this.checkAllParents(node);
} else {
// 取消节点时处理子节点
if (node.children?.length) {
this.checkAllChildren(node, false);
}
}
this.$nextTick(() => (this.isHandling = false));
// 收集权限码
const childCode = this.$refs?.preTreeRef?.getCheckedKeys() || [];
const parentCode = this.$refs?.preTreeRef?.getHalfCheckedKeys() || [];
this.permissionCodes = childCode.concat(parentCode);
}
逻辑解析
- 使用
isHandling
标志位防止递归操作导致的死循环 - 当节点被选中时:
- 递归选中所有子节点
- 递归向上选中所有父节点
- 当节点被取消选中时:
- 递归取消所有子节点
- 收集全选和半选节点的权限码
4.递归操作子节点
js
checkAllChildren(node, checked) {
node.children.forEach(child => {
this.$refs.preTreeRef.setChecked(child.code, checked);
if (child.children) this.checkAllChildren(child, checked);
});
}
逻辑解析
- 递归操作子节点,使用
setChecked
方法设置每个节点的选中状态
5.递归操作父节点
js
checkAllParents(node) {
let parent = this.getParentNode(node);
while (parent) {
this.$refs.preTreeRef.setChecked(parent.code, true);
parent = this.getParentNode(parent);
}
}
逻辑解析
- 从当前节点开始向上遍历父节点链
- 将路径上的所有父节点设置为选中状态
- 使用
while
循环代替递归,避免栈溢出风险
6. 父节点查找算法,返回目标节点的直接父节点
js
getParentNode(node) {
const findParent = (data, targetCode, parent) => {
for (const item of data) {
if (item.code === targetCode) return parent;
if (item.children) {
const found = findParent(item.children, targetCode, item);
if (found) return found;
}
}
return null;
}
return findParent(this.permissionTree, node.code, null);
}