el-tree使用内建搜索的时候如何显示子节点?

Daotin 于 2024-03-26 发布 编辑

背景

今天接到测试一个优化,需要“搜索用例集的时候,展开树状结构的子集”(如下图):

目前 el-tree 使用默认的 filter 搜索的话,是按照名称匹配的,只会显示匹配到的节点,而不会显示未匹配到的节点。

比如下面这个树形结构:

aaa
 -bbb
  -ccc

当搜索aaa的时候,只会显示aaa,而不会显示bbbccc。现在需要显示出来。

分析

之前尝试递归调用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。

这种方案可行,但是需要节点里面有 idparentId,对节点的属性有点要求。而且还要递归的判断是否有 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 等字段。