背景
今天接到测试一个优化,需要“搜索用例集的时候,展开树状结构的子集”(如下图):
目前 el-tree 使用默认的 filter 搜索的话,是按照名称匹配的,只会显示匹配到的节点,而不会显示未匹配到的节点。
比如下面这个树形结构:
aaa
-bbb
-ccc
当搜索aaa
的时候,只会显示aaa
,而不会显示bbb
和ccc
。现在需要显示出来。
分析
之前尝试递归调用filterNode
,但是不对。
因为实际上,filter-node-method
应该只基于当前节点的信息来返回一个布尔值,表示该节点是否匹配过滤条件。对子节点的处理应该是独立的,且不应影响当前节点的结果。
filterNode(value, data) {
if (!value) return true;
const flag = data.label.indexOf(value) !== -1;
if (flag) {
console.log(value, data);
if (data.children && data.children.length > 0) {
for (let i = 0; i < data.children.length; i++) {
const item = data.children[i];
this.filterNode(item.label, data.children[i]);
}
}
}
return flag;
},
然后我又有个想法:在filterNode
方法中,当某个节点filter
匹配上了的时候,我把id
保存到一个数组中,然后在filterNode
的开头判断该节点的parentId
是否在这个数组中,如果存在直接返回 true。
这种方案可行,但是需要节点里面有 id
和 parentId
,对节点的属性有点要求。而且还要递归的判断是否有 children 去设置子节点的 filterNode 的返回值为 true。
但是,如果节点没有 parentId 的话,要么去让后端加上,要么前端自己加。
由于还要找到孙子节点,所以构建一个祖先映射的键值对 ancestorMap,它的键是每个节点的 id,而值是一个数组,包含了该节点所有祖先节点的 id。这个映射允许你快速查找任何节点的祖先,以便在执行搜索或其他操作时判断节点之间的关系。
比如:假设你有如下的树形结构:
- Root (ID: 1)
- Child A (ID: 2)
- Grandchild A1 (ID: 4)
- Grandchild A2 (ID: 5)
- Child B (ID: 3)
- Grandchild B1 (ID: 6)
对于这个结构,ancestorMap 的示例内容将如下所示:
{
"2": ["1"], // Child A 的祖先是 Root
"3": ["1"], // Child B 的祖先是 Root
"4": ["2", "1"], // Grandchild A1 的祖先是 Child A 和 Root
"5": ["2", "1"], // Grandchild A2 的祖先是 Child A 和 Root
"6": ["3", "1"] // Grandchild B1 的祖先是 Child B 和 Root
}
这样的话,的代码为:
filterNode(value, data) {
if (!value) {
this.matchedNodeIds = []; // matchedNodeIds用于存储匹配节点的ID
return true;
}
let match = data.label.indexOf(value) !== -1;
if (match) {
this.matchedNodeIds.push(data.id); // 如果当前节点匹配,记录其ID
return true;
} else {
// 检查当前节点的任何祖先是否已经匹配
const ancestors = this.ancestorMap[data.id] || [];
return ancestors.some(ancestorId => this.matchedNodeIds.includes(ancestorId));
}
}
整体下来还是有点麻烦的。还有更简单的方法吗?
上面的逻辑是,如果匹配到节点,则往下查找子节点,其实还有一种方式是,如果未匹配到节点,则往上查找父节点是否匹配,如果父节点匹配,则该节点设置为 true。
解决方案
直接看代码:
// 筛选节点
filterNode(value, data, node) {
if (!value) return true;
const flag = data.label.indexOf(value) !== -1;
// 如果匹配上节点,直接返回true
if (flag) {
return true;
}
// 如果未匹配上,则执行checkShowChildNode方法
return this.checkShowChildNode(value, data, node);
},
// 检查是否显示子节点
checkShowChildNode(value, data, node) {
// node字段可以直接拿到tree的level(自带的)
const level = node.level;
// 如果是根节点,就不用校验了
if (level === 1) {
return false;
}
// 通过node.parent可以拿到父节点node对象
let parentData = node.parent;
let index = 0;
// 循环往上遍历父节点,父父节点...如果有匹配的,则该子节点设为true
while (index < level - 1) {
if (parentData.label.indexOf(value) !== -1) {
return true;
}
parentData = parentData.parent;
index++;
}
// 父节点,父父节点...都没有匹配到,则返回false
return false;
},
这思维方式可以学习下,正常的逻辑都是匹配到节点后,设置孙子节点未 true,这个思维是未匹配到,则查找祖先节点是否为 true,而且逻辑更加简单,也不需要额外添加 parentId,level 等字段。