jQuery EasyUI 的树形菜单(tree)是后台管理系统中使用率非常高的组件,组织架构、权限节点、商品分类都离不开它。它的本质是在 HTML 的 <ul> 和 <li> 结构上做增强,再通过 JSON 数据动态生成节点,因此跟普通的下拉框或表格在数据驱动方式上有明显区别。理解它不是简单“套一个树形样式”,而是要把数据格式、初始化方式、事件机制和异步策略都调对,否则很容易出现树挂了但功能不对的问题。

一、jQuery EasyUI 树形菜单的核心机制是什么
EasyUI 的 tree 组件底层依赖 <ul>、<li> 的原生嵌套结构,但它并不要求开发者在 HTML 里手动写出所有节点。通常的做法是放一个空的 <ul id="tt"></ul>,然后通过 JS 调用 $('#tt').tree({data: ...}) 或 tree({url:'...'}) 把数据渲染进去。渲染完成后,EasyUI 会自动生成 <li> 节点,并给有子节点的项添加展开图标。从这个角度看,树形菜单是一个“数据驱动”组件,而不是静态标签堆叠。
数据格式化这一步很容易被忽视。每个节点对象里,id 是唯一标识,text 是显示文字,state 控制节点初始状态(open 或 closed),children 存放子节点数组。如果 state 写成 closed,节点加载后是折叠的,点击才展开。如果不写 state,EasyUI 默认按 open 处理,所以在节点很多时最好显式写 closed 来减少首屏渲染压力。另外还有一个 checked 字段,主要用于配合复选框使用。
<ul id="tt"></ul>
<script>
$('#tt').tree({
data: [
{
id: 1,
text: '系统管理',
state: 'closed',
children: [
{ id: 11, text: '用户管理' },
{ id: 12, text: '角色管理' }
]
},
{
id: 2,
text: '内容管理',
state: 'open',
children: [
{ id: 21, text: '文章列表' },
{ id: 22, text: '分类管理' }
]
}
]
});
</script>
二、树形菜单在业务里到底怎么用
权限管理是最典型的场景。把菜单树和角色授权结合起来,每个节点代表一个功能模块,勾选父节点时需要把子节点一并选中,保存时再递归收集 checked 状态的节点 id。EasyUI 提供了 getChecked 方法可以快速获取选中的节点集合,但在层级较多时需要自己写过滤逻辑,避免把半选状态的父节点也提交上去。
另一个高频场景是异步加载组织架构。很多公司的部门数量多,用户层级深,如果一次性返回整棵树,接口数据会很大,页面卡顿明显。这时可以使用 tree 组件的 url 和 loadFilter 属性,让组件在点击展开时才向服务端请求子节点。但要注意,EasyUI 的异步加载默认只加载一次,如果后端返回格式不是组件期望的数组,就需要用 loadFilter 把响应的 data 包一层,否则节点会显示空白。
$('#deptTree').tree({
url: '/api/dept/tree',
method: 'get',
loadFilter: function(data, parent) {
// 假设服务端返回 { code: 200, rows: [...] }
if (data.code === 200) {
return data.rows;
}
return [];
},
onBeforeLoad: function(node, param) {
// 父节点不为空时,按父节点 id 请求子节点
if (node) {
param.id = node.id;
}
}
});
三、几个高频误区,很多人栽在这里
误区一:勾选状态与数据不同步
用 tree 组件做权限回显时,很多开发者直接把节点对象的 checked 设置为 true,然后调用 loadData 重新加载,结果发现树能显示勾选,但父节点没有自动关联子节点,或者保存时 getChecked 拿不到父节点。原因在于 EasyUI 的 checkbox 级联依赖 cascadeCheck 属性,这个属性默认是 true,但如果你使用 loadData 或 reload 时没有保留原有 checked 状态,或者把 checked 放在 children 里但父节点没有设置,就可能导致状态不完整。正确做法是在加载完整树数据后,先收集需要勾选的 id,再通过 find 和 check 方法逐个勾选,最后统一提交。
// 假设已加载完整树,needCheckIds 是需要勾选的节点 id 数组
var nodeList = $('#tt').tree('find', function(node) {
return needCheckIds.indexOf(node.id) !== -1;
});
for (var i = 0; i < nodeList.length; i++) {
$('#tt').tree('check', nodeList[i].target);
}
误区二:异步加载节点重复请求
很多人给 tree 配了 url 后,每次节点展开都会触发请求,但某些情况下还会因为 onExpand 事件里又调用 reload 导致双重请求。例如在 onExpand 里写 $(this).tree('reload', node.target),这个操作会再次请求一次,跟组件默认的异步机制叠加,就出现了两次请求甚至死循环。规避办法是明确异步加载只需要 url 和 loadFilter,不要手动 reload,除非确实需要强制刷新某个节点的子数据。
误区三:数据格式不一致导致树空白
EasyUI 的 tree 对根节点和子节点的数据类型要求一致,如果你从老接口拿到的数据是 [{id:1, text:'a', items:[...]}] 而不是 children 字段,直接传给 tree 后子节点不会显示,因为组件只认 children。这类问题可以通过 loadFilter 把 items 映射成 children,或者在服务端返回前统一字段。很多项目在这里排查半天,最后发现只是字段名写错了。
四、让树形菜单性能更稳的几个细节
当节点数量达到几千甚至上万时,树形菜单的渲染时间会明显增加。EasyUI 的 tree 在 open 状态会一次性渲染该层级所有节点,如果初始数据结构里所有节点都是 open,浏览器要同时创建大量 DOM 元素,页面容易假死。建议首屏只展开第一级,其余节点用 state:'closed',让用户按需展开。这样既减少首屏渲染量,也能降低内存占用。
另一个细节是 getChildren 和 find 的频繁调用。很多场景需要在树形菜单里做搜索,但 EasyUI 没有内置搜索 API,通常做法是用 find 遍历所有节点然后展开匹配项的父链。当节点很多时,这种遍历会很慢。可以自己在数据加载后维护一个 id 到节点的索引 Map,搜索时直接从 Map 取值,再通过 getParent 逐级展开父节点,比反复 find 高效得多。代码思路是先构建索引,再在 onChange 事件里做过滤。
最后,EasyUI 树形菜单的样式与主题也有关系。如果用了默认主题,某些低版本浏览器上虚线连接线会错位。可以通过 tree 组件的 lines 属性或修改主题包里的 tree.css 来调整。升级 EasyUI 版本时也要注意 API 变化,比如 1.4.x 和 1.10.x 在 tree 的 checkbox 初始化和 getChecked 返回结构上有些差异,迁移前要先看官方迁移说明。
jQueryEasyUI树形菜单树形菜单数据格式树形菜单异步加载修改时间:2026-10-03 15:17:53