在后台管理系统的权限分配模块中,树形菜单是最常见的交互形式。用户需要勾选角色对应的菜单或操作权限,保存后控制路由或按钮的可见性。如果权限数据量较大,一次性加载整棵树会带来不必要的网络与渲染开销,因此需要异步加载子节点。jQuery Fancytree 是一个基于 jQuery 的树插件,原生支持懒加载、复选框、拖拽和键盘导航,非常适合此类场景。下面结合实际案例说明如何配置与使用。

一、初始化 Fancytree 并开启复选框模式
引入 jQuery 与 Fancytree 的 CSS、JS 文件后,只需要在目标容器上调用 fancytree 方法即可生成树。开启复选框需要设置 checkbox: true,而 selectMode 则决定父子节点之间的选中联动关系。常用取值包括 1、2、3:1 表示父节点选中不影响子节点;2 表示子节点选中时自动影响父节点;3 表示双向联动,父节点勾选会同时勾选所有子节点,所有子节点勾选后父节点自动变为全选,部分子节点勾选时父节点进入半选状态。权限管理一般使用 selectMode: 3。
初始化时可以通过 source 配置根节点数据。每个节点使用 folder: true 表示该节点是文件夹节点,可以展开;lazy: true 表示该节点在首次展开时才异步加载子节点。下面给出基础初始化代码:
$(function() {
$("#tree").fancytree({
checkbox: true,
selectMode: 3,
source: [
{ title: "系统管理", key: "100", folder: true, lazy: true },
{ title: "用户管理", key: "200", folder: true, lazy: true }
],
lazyLoad: function(event, data) {
var node = data.node;
data.result = {
url: "/api/permissions/children",
data: { parentId: node.key },
cache: false
};
},
select: function(event, data) {
// 处理选中状态变化
}
});
});
这段代码创建了一棵只有两个根节点的权限树,根节点不会一次性加载其子节点,而是等到用户点击展开时再通过 lazyLoad 请求后端接口。
二、异步加载节点数据的实现细节
Fancytree 的 lazyLoad 回调会在节点首次展开时触发,参数中的 data.node 是当前展开的节点。回调内可以为 data.result 赋值一个 Promise、jQuery Ajax 对象或者直接返回数组。使用 jQuery 的 $.ajax 可以方便地处理加载状态,例如通过 node.setStatus("loading") 显示加载动画,通过 fail 回调设置错误状态。
以下示例通过 $.ajax 请求后端接口,并将返回的权限列表转换为 Fancytree 需要的节点格式。每个子节点根据 hasChildren 字段决定是否继续懒加载,这样多级权限可以逐层按需获取。
lazyLoad: function(event, data) {
var node = data.node;
node.setStatus("loading");
data.result = $.ajax({
url: "/api/permissions/children",
data: { parentId: node.key },
dataType: "json"
}).then(function(response) {
var children = response.data.map(function(item) {
return {
title: item.name,
key: item.id,
folder: item.hasChildren,
lazy: item.hasChildren,
selected: item.checked
};
});
return children;
}).fail(function() {
node.setStatus("error");
});
}
后端返回的数据结构可以是一个包含 name、id、hasChildren、checked 字段的数组。如果返回的是嵌套的树形结构,也可以直接交给 Fancytree 渲染,但懒加载场景下更推荐扁平结构,减少无用层级的数据传输,同时后端也更容易维护。
三、复选框联动与半选状态控制
在权限树中,半选状态非常关键。例如某个角色只拥有“用户管理”下的“查看”权限,而没有“编辑”和“删除”权限,那么“用户管理”节点应该显示为半选状态,表示子节点未全部勾选。Fancytree 使用 node.partsel 属性表示该节点处于部分选中状态,并且可以通过 CSS 类 fancytree-partsel 展示半选图标。
如果需要获取当前所有选中节点的 key,可以使用 visit 方法遍历整棵树。注意 node.selected 为 true 表示全选,node.partsel 为 true 表示半选。提交权限时通常只需要全选的叶子节点,但有时也需要提交半选父节点用于后台判断。
function getCheckedKeys() {
var tree = $("#tree").fancytree("getTree");
var selectedKeys = [];
var partialKeys = [];
tree.visit(function(node) {
if (node.selected) {
selectedKeys.push(node.key);
} else if (node.partsel) {
partialKeys.push(node.key);
}
});
return {
checked: selectedKeys,
partial: partialKeys
};
}
另外,select 事件会在节点选中状态改变后触发,适合做实时统计或联动其他界面元素。如果需要阻止某些节点被勾选,可以在 beforeSelect 回调中返回 false。例如禁用未发布的功能权限节点,或者根据当前角色动态控制可选范围。
四、权限回显与提交后端
编辑已有角色时,需要根据已保存的权限列表回显勾选状态。由于树节点是懒加载的,回显时不能直接对尚未加载的节点设置 selected,否则可能无效。常见做法是在节点数据构造时直接设置 selected 字段,或在节点加载完成后调用 setSelected。
以下方法展示了如何遍历整棵树并根据 key 数组勾选节点。如果节点尚未加载,可以先通过 load 方法加载该节点,但更推荐在初始化时使用后端返回的 selected 字段自动回显,这样可以减少额外的加载请求。
function setCheckedKeys(keys) {
var tree = $("#tree").fancytree("getTree");
tree.visit(function(node) {
if (keys.indexOf(node.key) !== -1) {
node.setSelected(true);
}
});
}
提交权限时,将 getCheckedKeys 返回的选中节点 key 数组通过 $.ajax 的 POST 请求发送给后端接口,后端再根据父级关系进行权限裁剪或合并。推荐后端存储完整的权限 key 列表,前端只负责交互,避免因半选状态导致权限丢失。同时后端需要校验权限 key 的合法性,防止越权提交。
五、常见优化与注意事项
第一,避免重复加载。Fancytree 默认在节点展开后不会再次触发 lazyLoad,但如果手动调用 load 方法或重新加载数据,需要设置 cache: true 或自行维护缓存。第二,节点较多时可以使用 filter 插件进行搜索过滤,但注意过滤状态下半选状态可能不准确,需要重新计算。第三,对不可用的权限节点可以设置 unselectable: true 或 hideCheckbox: true,防止误操作。
从权限管理角度看,树形菜单的层级不宜过深,一般控制在三级以内,否则用户操作成本较高。按钮级权限可以与菜单权限分开管理,或者使用叶子节点表示具体操作。结合后端路由守卫和前端指令,可以更完整地控制权限边界。Fancytree 的异步加载与三态复选框配合使用,能够在不牺牲性能的前提下满足大部分后台权限分配需求。
jQuery Fancytree异步加载权限管理修改时间:2026-08-30 15:22:08