zTree 异步加载回调数据

最近在做用户组权限分配功能,一开始因为公司有列表设计器,很快完成表单数据查询功能,然后是控制层,模型层。最后重新卡在了视图层 zTree 数据回调操作上。

zTree 的官网demo上表示:如果父节点数量很大,请注意利用延时进行控制,避免异步进程过多。因此需要用延迟加载的方式控制。

服务端操作

获取树的主干

一般树可以无限层,当树下还有子树时,要递归循环加载,为了性能。最多就加载两层即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
* TODO 获取树的主干
* @param request
* @return String
*/
public List<Map<String,Object>> getUserGroupTreeJsonAsync(HttpServletRequest request) {
String code = request.getParameter("code");
String applicationCode = request.getParameter("applicationCode");
List<Map<String,Object>> maps = new ArrayList<Map<String,Object>>();
//封装页面传过来的数据
UserGroup group = new UserGroup();
group.setApplicationCode(applicationCode);
group.setCode(code);
//如果code为空 从根节点加载 应用系统作为用户组的根
if(StringUtils.isBlank(code)){
for(App app:apps){
Map<String,Object> map = new HashMap<String,Object>();
code = app.getApplicationCode();
//获取应用系统下的用户组
group.setApplicationCode(app.getApplicationCode());
group.setCode(app.getApplicationCode());
group = userGroupApi.getUserGroup(group);
map.put("id", app.getApplicationCode()+"_user_group");
map.put("code", app.getApplicationCode());
map.put("parentId", "");
map.put("applicationCode", app.getApplicationCode());
map.put("name", app.getAppName());
map.put("type", "app");
map.put("open", true);
map.put("icon", Global.getUIPath() + "/libs/icons/png_16/home.png");
map.put("open", "true");
map.put("isParent", group.getChildGroups().size()!=0);
maps.add(map);
maps.addAll(getUserGroupTreeAsync(group));
}
}else{
//异步获取点击节点下的数据
maps.addAll(getUserGroupTreeAsync(group));
}
return maps;
}

获取主干被选中的节点

利用父节点找到其一级子节点,每个节点打包成 Map,所有 Map 打包为 List,控制器回传时再打包为JSON 即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* @Description: 获取选中的用户组树
* @param Permission
* @param request
* @return List<Map<String,Object>>
*/
public List<Map<String,Object>> getUserGroupSelectTree(Permission per, HttpServletRequest request){
List<Map<String,Object>> userGroupTreeObj = this.getUserGroupTreeJsonAsync(request);
List<Permission> perList = this.permissionApi.getPermissionByFunCode(per);
if(perList != null && perList.size() > 0){
for(Permission pers : perList){
for(Map<String,Object> map : userGroupTreeObj){
if(pers.getUserCode().equals(map.get("code").toString())){
map.put("checked", true);
}
}
}
}
return userGroupTreeObj;
}
1
model.addAttribute("treeData", JsonMapper.toJsonString(this.userGroupService.getUserGroupSelectTree(per, request)));

异步获取节点树

点击节点展开树的时候,传回该节点的对象,只加载下一层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* TODO 获取用户组节点下数据
* @param group
* @return List<Map<String,Object>>
*/
public List<Map<String,Object>> getUserGroupTreeAsync(UserGroup group){
List<Map<String,Object>> maps = new ArrayList<Map<String,Object>>();
for (UserGroup child : userGroupApi.getUserGroup(group).getChildGroups()) {
Map<String,Object> map = new HashMap<String,Object>();
map.put("id", child.getId()+"_user_group");
map.put("code", child.getCode());
map.put("parentId", child.getParent().getCode()+"_user_group");
map.put("name", child.getGroupName());
map.put("applicationCode", child.getApplicationCode());
map.put("isParent", child.getChildGroups().size()!=0);
maps.add(map);
}
return maps;
}

每次点击都用 Ajax 异步加载。

被选中的节点

由于用户组树本身还关联功能地址权限模块,而接口早被定好了,只能在前端操作修改复选框勾选状态。服务端则先获取所有已勾选的功能地址权限模块 code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* @Description: 获取选中的用户组codes
* @param UserCode
* @return String GroupCodes
*/
public String getCheckedGroup(Permission per){
String checkedCode = "";
List<Permission> perList = this.permissionApi.getPermissionByFunCode(per);
if(perList != null && perList.size() > 0){
for(Permission pers : perList){
if(checkedCode != ""){
checkedCode += ",";
}
checkedCode += pers.getUserCode();
}
}
return checkedCode;
}
1
model.addAttribute("checkCodes",this.userGroupService.getCheckedGroup(per));

消费端操作

初始化主干树

  1. $(function(){initComplete();}) 初始化树
  2. initComplete()setting 保存树的配置:复选框、选中复选框时执行的方法等等
  3. 隐藏根节点的方法:hideRootNode()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
var ckNodes = ${treeData};
$(function() {
initComplete();
});
function initComplete() {
var setting = {
check: {
enable: true,
chkStyle: "checkbox",
chkboxType: { "Y" : "", "N" : "" }
},
data : {
simpleData : {
enable : true,
idKey : "id",
pIdKey : "parentId",
rootPId : ""
}
},
callback : {
beforeCheck: canCheck,
onCheck: hideRootNode
}
};
//加载模块资源树
if(${selectType}){
$.fn.zTree.init($("#areaDeptTree"), setting,ckNodes);
}else{
$.fn.zTree.init($("#areaDeptTree"), setting);
}
hideRootNode();
}
//选中复选框
function canCheck(treeId, treeNode) {
return true;
}
//隐藏根结点
function hideRootNode(){
$(".ztree>li>span.chk").hide();
}

异步加载枝叶

在 setting 中添加异步加载路径:

1
2
3
4
5
6
async: {
enable: true,
dataType: 'JSON',
url: "${ctx}/asc/userGroup/getUserGroupTreeJsonAsync",
autoParam: ["code","applicationCode"]
}

在 callback 中添加加载完成后载入被勾选用户组的方法:

1
onAsyncSuccess: zTreeOnAsyncSuccess,

添加 zTreeOnAsyncSuccess 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var ids = "${checkCodes}";
//展开树
function zTreeOnAsyncSuccess(event, treeId,treeNode, msg) {
initChecked(treeNode);
}
function initChecked(treeNode){
var treeObj = getTreeObj();
var ida = ids.split(",");
var node;
for(var i=0;i<ida.length;i++){
node = treeObj.getNodeByParam("code",ida[i],treeNode);
if(node!=null){
treeObj.checkNode(node, true, false);
}
}
$(".ztree>li>span.chk").hide();
}
//获取zTree整棵树
function getTreeObj(){
var treeObj;
treeObj = $.fn.zTree.getZTreeObj("areaDeptTree");
return treeObj;
}

消费端完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
var ckNodes = ${treeData};
var ids = "${checkCodes}";

$(function() {
initComplete();
});

function initComplete() {
var setting = {
check: {
enable: true,
chkStyle: "checkbox",
chkboxType: { "Y" : "", "N" : "" }
},
data : {
simpleData : {
enable : true,
idKey : "id",
pIdKey : "parentId",
rootPId : ""
}
},
callback : {
beforeCheck: canCheck,
onAsyncSuccess: zTreeOnAsyncSuccess,
onCheck:hideRootNode
},async: {
enable: true,
dataType: 'JSON',
url: "${ctx}/asc/userGroup/getUserGroupTreeJsonAsync",
autoParam: ["code","applicationCode"]
}
};
//加载模块资源树
if(${selectType}){
$.fn.zTree.init($("#areaDeptTree"), setting,ckNodes);
}else{
$.fn.zTree.init($("#areaDeptTree"), setting);
}
$(".ztree>li>span.chk").hide();
}

function initChecked(treeNode){
//getAllFunCode();
var treeObj = getTreeObj();
var ida = ids.split(",");
var node;
for(var i=0;i<ida.length;i++){
node = treeObj.getNodeByParam("code",ida[i],treeNode);
if(node!=null){
treeObj.checkNode(node, true, false);
}
}
$(".ztree>li>span.chk").hide();
}
//展开树
function zTreeOnAsyncSuccess(event, treeId,treeNode, msg) {
initChecked(treeNode);
}

//选中复选框
function canCheck(treeId, treeNode) {
return true;
}

function hideRootNode(){
$(".ztree>li>span.chk").hide();
}

//获取zTree整棵树
function getTreeObj(){
var treeObj;
treeObj = $.fn.zTree.getZTreeObj("areaDeptTree");
return treeObj;
}
文章目录
  1. 1. 服务端操作
    1. 1.1. 获取树的主干
    2. 1.2. 获取主干被选中的节点
    3. 1.3. 异步获取节点树
    4. 1.4. 被选中的节点
  2. 2. 消费端操作
    1. 2.1. 初始化主干树
    2. 2.2. 异步加载枝叶
    3. 2.3. 消费端完整代码

20170315-ztree/

本页二维码