项目背景 : react + ant
ant 官网中目前只提供了 默认父子关联 或 checkStrictly(父子不关联)
注意 : 不能盲目选择父子关联 , 虽然选中父 , 子也联动确实是需要的效果 , 但有一个bug 如下图 (当选中部分子 , 所有子被选中)
解决方案 : 只能取消父子关联 , 自己去判断当前点击处是否为父 , 是父的话选中对应的所有子 , 当父为空子被选中时 , 父也应被选中
1. 将后台返回的平铺结构转成需要的树形父子结构
2. 判断不同情况下 父子选中的状态 , 目前我判断了5种 , 分别是 选中父对应子全部被选中 ; 取消父对应子全被取消 ; 父存在时单独取消子 ; 父被选中时添加子 ; 父为空时单独添加子
整体代码如下
//平铺数据转树形结构
function buildTree (data, parentId = 0) {
let tree = []; // 用于存放结果的数组
// 遍历数据,找到parentId匹配的项
for (let item of data) {
if (item.parentId === parentId) {
// 创建一个新对象,准备添加子节点
let node = { ...item, children: buildTree(data, item.permissionId) }; // 递归调用,处理子节点
tree.push(node); // 将当前节点添加到结果数组中
}
}
return tree;
}
const result = buildTree(res3, 0);
setTreeData(result);
// 选中二级父,二级子也被选中,这样会有bug,当取消子以后父也被取消了
const ontreeCheck = (keys, { checked, checkedNodes, node }) => {
console.log('keys', keys); // 包含checked和halfChecked俩个数组,checked包含了被选中节点的id
console.log('checked', checked); // true或false
console.log('node', node); // 目前选中的单个节点(对象格式)
console.log('checkedNodes', checkedNodes); // 被选中的所有节点(treeData的对象格式)
console.log('treeData', treeData); // 转换后的所有的树形结构数据
// 递归函数,判断一个数字是否在数组中
function numberInArray (number, array) {
return array.includes(number);
}
// 递归函数,根据父对象的 permissionId 找到所有子对象的 permissionId
function findChildrenPermissionIds (tree, parentId) {
let childrenPermissionIds = [];
// 遍历每个节点
for (let node of tree) {
if (node.permissionId === parentId) {
// 找到匹配的父节点,收集其所有子节点的 permissionId
if (node.children && node.children.length > 0) {
childrenPermissionIds = node.children.map((child) => child.permissionId);
}
break; // 找到匹配的父节点后可以直接跳出循环
} else if (node.children && node.children.length > 0) {
// 如果当前节点不是父节点,则递归地查找其子节点
childrenPermissionIds = findChildrenPermissionIds(node.children, parentId);
if (childrenPermissionIds.length > 0) {
break; // 如果找到了符合条件的子节点,也可以跳出循环
}
}
}
return childrenPermissionIds;
}
// 递归函数根据子来找到父
function findParentPermissionIds (permissionId, treeData) {
for (let i = 0; i < treeData.length; i++) {
const parent = treeData[i];
// 查找子节点
const child = parent.children.find((child) => child.permissionId === permissionId);
if (child) {
// 如果找到了匹配的子节点,则返回父节点的 permissionId
return parent.permissionId;
}
// 如果子节点中还有子节点,继续递归查找
if (parent.children && parent.children.length > 0) {
const result = findParentPermissionIds(permissionId, parent.children);
if (result !== null) {
return result;
}
}
}
// 如果没有找到,则返回 null
return null;
}
const parentID = treeData.map((item) => item.permissionId);
const selectedParents = treeData.filter((parent) => keys.checked.includes(parent.permissionId));
// 选中了父让子也被选中
if (checked && numberInArray(node.permissionId, parentID)) {
const aaa = findChildrenPermissionIds(treeData, node.permissionId);
const result = keys.checked.concat(aaa);
setCheckedKeys(result);
} else if (!checked && numberInArray(node.permissionId, parentID)) {
// 当取消了父以后, 让对应的子也取消
const aaa = findChildrenPermissionIds(treeData, node.permissionId);
const ccc = aaa.push(node.permissionId);
const result = checkedKeys.filter((item) => !aaa.includes(item));
setCheckedKeys(result);
} else if (!checked && !numberInArray(node.permissionId, parentID)) {
// 单独取消子,父存在的情况下
const result = checkedKeys.filter((item) => ![node.permissionId].includes(item));
setCheckedKeys(result);
} else if (checked && !numberInArray(node.permissionId, parentID)) {
// 父被选中情况下,单独添加子
if (selectedParents.length != 0) {
const result = checkedNodes.map((item) => item.permissionId);
const result1 = findParentPermissionIds(node.permissionId, treeData);
const ccc = [result, node.permissionId, result1];
// 使用concat和apply来将嵌套数组扁平化
const flattenedArray = [].concat.apply([], ccc);
// 使用Set来去重
const uniqueArray = [...new Set(flattenedArray)];
setCheckedKeys(uniqueArray);
} else {
// 父为空的情况下,单独添加子,让父也被选中
const result = findParentPermissionIds(node.permissionId, treeData);
const ccc = [result, node.permissionId];
setCheckedKeys(ccc);
}
}
};
<Tree
checkable
checkedKeys={checkedKeys}
defaultExpandAll={false} //让授权后的弹窗只展示根标签
treeData={treeData} // 这里是所有的树形数据
// showLine //删除这里,树形结构左侧的下拉线消失,图标从+-更改为默认的△
checkStrictly // 开启后,父子节点不关联(子空一个,父就空)
onCheck={ontreeCheck}
/>