layui tree 组件的搜索需手动实现递归过滤并保留匹配节点及其祖先路径,配合 spread: true 展开父级、避免 display: none 影响可访问性,且 reload 时必须传全新数据引用。
layui tree 组件本身不提供开箱即用的搜索过滤能力,showsearch: true 只是插入一个输入框,不会自动绑定逻辑;真正起作用的必须是手动过滤数据 + tree.reload() 重绘,否则搜了也白搜。
为什么 tree.render() 或 tree.reload() 传 keyword 没反应
因为 layui tree 没有 filter() 方法,也没有内置关键词匹配机制。你调用 tree.reload({ data: [...] }) 时,它只认 data 字段——不传新数据,或传了但没筛过,搜索就等于没做。
-
showSearch: true仅生成 input DOM,不绑定事件也不处理逻辑 - 直接写
tree.filter('xxx')会报错:tree.filter is not a function - 用
$().find('.layui-tree-txt').text().includes(...)做 DOM 过滤,会导致展开状态丢失、父子关系断裂、键盘导航失效
如何正确实现递归过滤并保持层级结构
核心不是“删掉不匹配节点”,而是“保留匹配节点及其所有祖先”,否则路径断开,用户看不到“系统设置 > 权限管理 > 用户列表”这种完整路径。
- 用 DFS 遍历原始
data,每个节点加临时字段如match: true标记自身或后代是否命中 - 匹配判断别硬写
node.name.includes(keyword),先确认你用的是name还是title字段(常见坑:配置了textKey: 'label',但代码里还查node.name) - 子节点过滤后为空数组,父节点不能直接丢弃——只要
match === true就得留,哪怕children: [] - 返回的新数据必须是全新引用,避免
tree.reload()因 Vue 或 layui 内部 shallow compare 失效
示例关键逻辑:
function filterTreeData(data, keyword) {
if (!keyword) return data;
const reg = new RegExp(keyword, 'i');
function walk(nodes) {
return nodes.map(node => {
const matchedSelf = reg.test(node.title || node.name);
const children = node.children ? walk(node.children) : [];
const hasMatchedChild = children.some(c => c.match);
if (matchedSelf || hasMatchedChild) {
return {
...node,
match: true,
children: children.length ? children : undefined
};
}
return null;
}).filter(Boolean);
}
return walk(data);
}
搜索后怎么让父节点自动展开、且 reload 不塌陷
tree.reload() 是整棵树重绘,原始展开状态不会继承。你不显式标记,reload 后所有节点默认收起——用户搜“日志管理”,结果只看到一个孤零零的叶子节点,根本找不到上下文。
- 在
filterTreeData递归过程中,对每个match === true的节点,沿parent链路(或靠递归回溯)给所有祖先加spread: true - 过滤后的新数据中,把所有带
spread: true的节点及其祖先的spread字段都设为true - 渲染时必须传
id(如id: 'searchableTree'),并在数据里为每个节点提供唯一id字段,否则spread不生效 - 别依赖
showLine: true来“假装展开”,它只是画线,不控制折叠状态
高亮文本和可访问性容易被忽略的细节
用 innerHTML.replace() 直接插 <span class="highlight"></span> 很危险:多次搜索会嵌套标签、破坏 DOM 结构、导致 textContent 错乱;更严重的是,用 display: none 隐藏节点会让键盘用户 tab 键跳过整个子树。
- 高亮前先缓存原始文本,替换时只操作副本,避免污染源数据
- 遍历所有
td或所有文本节点,别只高亮第一列(比如权限编码、描述字段也可能含关键词) - 隐藏不匹配节点时,用
aria-hidden="true"+opacity: 0; height: 0; overflow: hidden;,而不是display: none - 同步更新
aria-expanded和aria-setsize/aria-posinset,否则屏幕阅读器和键盘导航会出错 - 搜索完成立刻
firstMatchElem.focus(),别等用户自己 tab —— 被aria-hidden的节点仍占逻辑位置,tab 顺序不可靠
防抖、空值清空、分页重置、cache 状态恢复——这些不是锦上添花,而是上线前必填的坑。尤其 treeTable 场景下,漏掉 page: { curr: 1 } 就可能显示空白页。











