前言
需求是:
- table 按照树形结构展示
- 每个节点有个开关操作 switch
- 父节点勾选后,所有的子节点也勾选
- 父节点取消勾选后,所有子节点也取消勾选
- 子节点勾选后,其祖先节点也必须勾选
- 子节点取消勾选后,需要判断最外层父节点是否取消勾选
- 如果此时最外层父节点的所有子节点都取消勾选,则该最外层节点取消勾选;
- 如果此时最外层父节点的任一子节点处于勾选,则不处理最外层节点的勾选状态
实现
整体流程
- permissionTree 是 Table 的数据源
- 每一项的 switch 的状态根据 record.checked 展示
- 打开关闭 switch 去修改 permissionTree 相关节点的 checked
table 树形结构展示
- 直接用 table 的 virtual api
- expandPermissionKeys 是 switch 打开后,需要展开的项 id 集合
- handleSetPermissionTreeSwitch 是 switch 切换时的回调
- handleSavePermission 是调接口保存 swtich 后的权限
tsx
<div className='w-[98%] ml-auto'>
<AntdTable
loading={runGetPermissionTree.loading || runSetPermission.loading}
columns={[
{
title: '权限名称',
dataIndex: 'menuName',
key: 'menuName',
},
{
title: '操作',
width: 300,
key: 'action',
render: (_, record) => {
return (
<div className='flex justify-start items-center'>
<Switch
checkedChildren='开启'
unCheckedChildren='关闭'
checked={record.checked}
onChange={(checked: boolean) => {
// 切换开关的回调
handleSetPermissionTreeSwitch(checked, record);
}}
/>
</div>
);
},
},
]}
dataSource={searchPermissionTree}
virtual
scroll={{
x: 1000,
y: 580,
}}
rowKey='id'
expandable={{
childrenColumnName: 'childList',
expandedRowKeys: expandPermissionKeys,
onExpandedRowsChange: handleExpandRowsChange,
}}
pagination={false}
/>
<div className='flex justify-end items-center mt-3'>
<span className='text-red-500'>注意:在编辑完权限后请点击此按钮进行保存</span>
<Button className='ml-3' type='primary' onClick={() => handleSavePermission()}>保存</Button>
</div>
</div>
界面显示:
handleSetPermissionTreeSwitch 处理逻辑
处理当前节点及其所有子节点
tsx
function findAndModifyChildListNode(tree: any[], targetId: any, checked: boolean) {
function traverse(node: any) {
const newNode = { ...node };
if ((newNode.id === targetId)) {
newNode.checked = checked;
// 当前节点如果存在子节点,则所有子节点的状态同步更改
if (
newNode.childList && Array.isArray(newNode.childList)
) {
newNode.childList = dfsUnsetChecked(newNode.childList, checked);
}
} else {
if (newNode.childList && Array.isArray(newNode.childList)) {
newNode.childList = newNode.childList.map((child: any) => traverse(child));
}
}
return newNode;
}
return tree.map((node) => traverse(node));
}
handleSetPermissionTreeSwitch(checked: boolean, record: any) {
const { id } = record;
// 处理当前节点及其子节点的 checked status
const childListTree = findAndModifyChildListNode(
// [...state.searchPermissionTree],
[...state.permissionTree],
id,
checked,
);
// 后续操作
},
处理当前节点的所有祖先节点
tsx
const findAndModifyParentListNode = (tree: any[], targetId: any, checked: boolean) => {
const newTree = [...tree];
function traverseAndModifyAncestors(node: any, ancestors: any[] = []) {
if (node.id !== targetId) {
ancestors.push(node);
if (node.childList && node.childList.length > 0) {
node.childList.forEach((child: any) => {
traverseAndModifyAncestors(child, [...ancestors]);
});
}
} else {
// 没有子节点时,回溯并修改祖先节点的状态
ancestors.forEach((ancestor) => {
// 如果是选中,则直接选
if (checked) {
ancestor.checked = true;
} else {
/**
* 如果是取消选中,需要判断当前节点是不是最外层的节点,如果是,判断它的下一级子阶段是不是都取消勾选了
* 如果都取消勾选了,则把当前这个最外层节点关掉,否则不处理
*/
if (!ancestor.parentCode) {
const isAllCancel = ancestor.childList.every((node: any) => !node.checked);
ancestor.checked = isAllCancel ? false : true;
}
}
});
}
}
newTree.map((root) => {
traverseAndModifyAncestors(root);
});
return newTree;
};
handleSetPermissionTreeSwitch(checked: boolean, record: any) {
const { id } = record;
// 处理当前节点及其子节点的 checked status
// ...
// 再处理处理当前节点祖先节点的 checked status,返回最终的 tree
const finalTree = findAndModifyParentListNode(
[...childListTree],
id,
checked,
);
},
根据处理后的结果,设置需要展开的 ids
tsx
function collectCheckedIds(tree: any[]) {
const result: any[] = [];
function traverse(node: any) {
if (node.checked) {
result.push(node.id);
}
if (node.childList && node.childList.length > 0) {
node.childList.forEach((child: any) => traverse(child));
}
}
tree.forEach((node) => traverse(node));
return result;
}
handleSetPermissionTreeSwitch(checked: boolean, record: any) {
const { id } = record;
// 处理当前节点及其子节点的 checked status
// ...
// 再处理处理当前节点祖先节点的 checked status,返回最终的 tree
// ...
// 根据 checked status 更改后,设置需要展开的项
const ids: string[] = collectCheckedIds(finalTree);
},
设置相关 state
tsx
handleSetPermissionTreeSwitch(checked: boolean, record: any) {
const { id } = record;
// 处理当前节点及其子节点的 checked status
// ...
// 再处理处理当前节点祖先节点的 checked status,返回最终的 tree
// ...
// 根据 checked status 更改后,设置需要展开的项
// ...
// 设置状态
setState((d) => {
d.permissionTree = finalTree;
d.searchPermissionTree = finalTree;
d.expandPermissionKeys = ids;
});
},