需求背景
实现一个菜单权限控制,功能分解:
- 后端返回
完整菜单列表
、该角色的菜单列表
。 - 前端递归
该角色菜单列表
,将所有菜单节点平铺,获得一个平铺的菜单节点id列表
。 - 使用Antd Vue Tree 渲染
完整菜单列表(treeData)
,选中的则为平铺的菜单节点id列表(checkedKeys)
问题
- Antd Vue Tree这个组件,使用关联状态
(checkStrictly = true)
,这个组件的机制是父节点选中了,其所有后代节点都被选中,而我们的平铺的菜单节点id列表
是包含父节点信息的,所以直接使用这个平铺列表会显示异常 - 使用非关联状态
(checkStrictly = true)
,手动点击父节点时,不能快捷选中父节点的所有后代节点,不太友好 - 非完全选中所有子节点时,组件回调
check(checkedKeys, e)
事件并不会将父节点也加入到checkedKeys
中,而后端是需要这个父节点的
解决方案
- 当不完全选中子菜单时,需要获取当前父菜单id并一并传给后端
- 重新获取菜单并渲染时,如果当前节点有子节点,则不将当前节点放入
checkedKeys
中
示例
- 菜单完整结构
[
{
"id":1,
"title":"数据平台",
"children":[...]
},
{
"id":2,
"title":"系统管理",
"children":[
{
"id":3,
"title":"用户管理"
},
{
"id":4,
"title":"文件服务",
"children":[
{
"id":5,
"title":"访问密钥"
},
{
"id":6,
"title":"桶列表"
}
]
}
]
}
]
- 该角色的菜单列表
[
{
"id":2,
"title":"系统管理",
"children":[
{
"id":3,
"title":"用户管理"
},
{
"id":4,
"title":"文件服务",
"children":[
{
"id":5,
"title":"访问密钥"
}
]
}
]
}
]
伪代码实现
后端数据渲染成Tree(正向)
- 使用递归,将
该角色的菜单列表
平铺
// 完整树
const tree = [
{
"id":1,
"title":"数据平台",
"children":[...]
},
{
"id":2,
"title":"系统管理",
"children":[
{
"id":3,
"title":"用户管理"
},
{
"id":4,
"title":"文件服务",
"children":[
{
"id":5,
"title":"访问密钥"
},
{
"id":6,
"title":"桶列表"
}
]
}
]
}
]
// 用户权限树
const roleTree = [
{
"id":2,
"title":"系统管理",
"children":[
{
"id":3,
"title":"用户管理"
},
{
"id":4,
"title":"文件服务",
"children":[
{
"id":5,
"title":"访问密钥"
}
]
}
]
}
]
function flatTree(tree) {
if (!tree || !tree.length) return [];
const flatArr = [];
for (const item of tree) {
const children = flatTree(item.children);
// 如果当前节点无子节点,才加入平铺列表中,解决问题1
if (!children.length) flatArr.push(item.id);
flatArr.push(...children);
}
return flatArr;
}
const checkedKeys = flatTree(roleTree) // 获得平铺的节点 [3, 5]
<Tree :treeData="tree" :checkedKeys="checkedKeys" @check="checkAction"/>
保存处理(反向)
Antd Vue Tree组件的check这个事件没有描述清楚
但是Antd Tree组件是描述清楚了的
正如问题3描述的,组件本身如果当前父节点如果不是其后代节点全部选中,check(checkedKeys,e)
中的checkedKeys
是不会包含父节点本身的,而e.halfCheckedKeys 半选中状态节点列表
就是正好就是这些我们需要的节点,所以只需将checkedKeys
和e.halfCheckedKeys
合并起来再传给后端就好了
let allCheckedKeys = [];
function checkAction(checkedKeys, e) {
// 这个就是我们需要提供给后端的选中id列表
allCheckedKeys = [...checkedKeys, ...e.halfCheckedKeys]
}