LayUI—tree树形结构的使用

树形结构在实际开发中很长用到,比如部门管理,权限菜单等。因为用树形结构来展示会显的很清晰明了。最近写了一个个人博客小项目中用到了LayUI的树形结构,之后写了一个小案例整理一下。

官网地址:https://www.layui.com/v1/doc/modules/tree.html

先看一下显示的效果图。

LayUI—tree树形结构的使用_第1张图片

点击节点右面会显示对应部门的详情信息,可以修改。可以自定义按钮添加部门,也可以直接用自带的方法对部门进行新增,修改和删除。可以获取选中的节点,根据项目需求(有的需要选中保存)。

先需要引入LayUI的样式文件JS和CSS。

案例对应的实体类Dept。

@Entity
public class Dept {
    private Integer id;
    private String name;    //部门名称
    private String deptName;    //部门负责人
    private String phone;   //电话号
    private String number;  //编号
    private double idx;     //排序
    @JsonIgnore
    private Dept parent;
    @JsonIgnore
    private List children = new ArrayList<>();

    @Id
    @GeneratedValue
    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDeptName() {
        return deptName;
    }

    public void setDeptName(String deptName) {
        this.deptName = deptName;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

    public double getIdx() {
        return idx;
    }

    public void setIdx(double idx) {
        this.idx = idx;
    }

    @ManyToOne
    @CreatedBy
    public Dept getParent() {
        return parent;
    }

    public void setParent(Dept parent) {
        this.parent = parent;
    }

    @OneToMany(cascade=CascadeType.ALL,mappedBy="parent")
    @OrderBy(value="idx")
    public List getChildren() {
        return children;
    }

    public void setChildren(List children) {
        this.children = children;
    }

    public Dept(Integer id, String name, String deptName, String phone, String number, double idx, Dept parent, List children) {
        this.id = id;
        this.name = name;
        this.deptName = deptName;
        this.phone = phone;
        this.number = number;
        this.idx = idx;
        this.parent = parent;
        this.children = children;
    }

    public Dept(Integer id) {
        this.id = id;
    }

    public Dept() {
    }
}

显示LayUI树形菜单,只需要一个标签容器即可。

在案例中还有一些其他样式,比如右边的详情信息,新增按钮等。完整代码如下。



所有部门
部门详情
无数据

JS请求数据渲染页面代码,data为请求数据源,当时直接放入的请求链接,好像不行,所以之后才写了一个方法去请求数据源。

layui.use(['tree', 'util'], function() {
    var tree = layui.tree;
    var util = layui.util;
    tree.render({
        elem: '#dept_tree',
        data: getData(),
        id: 'treeId',
        showCheckbox: true,     //是否显示复选框
        onlyIconControl: true
    });
});

function getData(){
    var data = [];
    $.ajax({
        url: "dept/treeload",    //后台数据请求地址
        type: "post",
        async:false,
        success: function(resut){
            data = resut;
        }
    });
    return data;
}

 tree 组件提供的有以下基础参数,可根据需要进行相应的设置。

参数选项 说明 类型 示例值
elem 指向容器选择器 String/Object -
data 数据源 Array -
id 设定实例唯一索引,用于基础方法传参使用。 String -
showCheckbox 是否显示复选框 Boolean false
edit 是否开启节点的操作图标。默认 false。
  • 若为 true,则默认显示“改删”图标
  • 若为 数组,则可自由配置操作图标的显示状态和顺序,目前支持的操作图标有:addupdatedel,如: 
    edit: ['add', 'update', 'del']
Boolean/Array ['update', 'del']
accordion 是否开启手风琴模式,默认 false Boolean false
onlyIconControl 是否仅允许节点左侧图标控制展开收缩。默认 false(即点击节点本身也可控制)。若为 true,则只能通过节点左侧图标来展开收缩 Boolean false
isJump 是否允许点击节点时弹出新窗口跳转。默认 false,若开启,需在节点数据中设定 link 参数(值为 url 格式) Boolean false
showLine 是否开启连接线。默认 true,若设为 false,则节点左侧出现三角图标。 Boolean true
text 自定义各类默认文本,目前支持以下设定:

text: {

     defaultNodeName: '未命名' //节点默认名称

     ,none: '无数据' //数据为空时的提示文本

}

Object -

因为tree指定了json数据的键名称,所以后台传递过来的数据对应的键名不一样时需要做一下处理,或者实体类中的属性名就和tree的JSON数据的键名称一样。

键名:

属性选项 说明 类型 示例值
title 节点标题 String 未命名
id 节点唯一索引,用于对指定节点进行各类操作 String/Number 任意唯一的字符或数字
children 子节点。支持设定选项同父节点 Array [{title: '子节点1', id: '111'}]
href 点击节点弹出新窗口对应的 url。需开启 isJump 参数 String 任意 URL
spread 节点是否初始展开,默认 false Boolean true
checked 节点是否初始为选中状态(如果开启复选框的话),默认 false Boolean true
disabled 节点是否为禁用状态。默认 false Boolean false

后台请求数据的方法。

@RequestMapping(value = "/treeload")
@ResponseBody
public Object treeload(){
    Sort sort = Sort.by("idx");
    List dpet = deptService.findByParentIsNull(sort);    //查找所有菜单
    List> result = new ArrayList<>();    //定义一个map处理json键名问题
    return fun(dpet, result);
}

private Object fun(List dpet, List> result) {
    for(Dept d : dpet){
        HashMap map = new HashMap<>();
        map.put("id", d.getId());
        map.put("title", d.getName());
        map.put("spread", true);      //设置是否展开
        List> result1 = new ArrayList<>();
        List children = d.getChildren();    //下级菜单
        //这里可以根据自己需求判断节点默认选中
        /*if(m.getParent() != null || m.getChildren().size() == 0){
            map.put("checked", true);    //设置为选中状态
        }*/
        map.put("children", fun(children, result1));
        result.add(map);
    }
    return result;
}

因为这里新建的实体类字段名和tree指定了json数据的键名称不一样,所以这里用了一个fun递归方法处理的。中间可以根据项目需求,根据条件判断是否需要选中该节点。

返回的JSON数据格式

LayUI—tree树形结构的使用_第2张图片

[
	{
		"children": [		//子节点
			{
				"children": [
					{
						"children": [],
						"id": 30,
						"title": "测试",
						"spread": true
					}, {
						"children": [],
						"id": 31,
						"title": "开发",
						"spread": true
					}, {
						"children": [
							{
								"children": [],
								"id": 36,
								"title": "测试节点",
								"spread": true
							}
						],
						"id": 32,
						"title": "测试",
						"spread": true
					}
				],
				"id": 2,
				"title": "技术部",
				"spread": true
			}, {
				"children": [],
				"id": 19,
				"title": "财务部",
				"spread": true
			}
		],
		"id": 1,	//节点id
		"title": "某某公司",	//节点名称
		"spread": true
	}, {
		"children": [],
		"id": 33,
		"title": "测试",
		"spread": true
	}
]

设置节点点击回调方法(在加载数据方法tree.render中添加以下代码)。

click: function (obj) {
    var id = obj.data.id;
    $("#dept_home").load("dept/show?id="+id);
}

把请求过来的详情页面load到右边的div中显示。后台请求方法

@RequestMapping(value = "/show")
public void show(DeptForm form, ModelMap map) throws InstantiationException, IllegalAccessException {
    Dept model = new Dept();
    Integer id = form.getId();
    Integer parentId = 0;
    if(id!=null) {
        model = deptService.findById(id);
        parentId = model.getParent()==null?0:model.getParent().getId();
    }
    map.put("parentId", parentId);
    map.put("model", model);
}

DeptForm类为一个接收类,其中字段和实体类中一样。根据请求传递过来的id,查询这条数据的详细信息,之后把查询的当前部门详情数据及父级节点id(用于下拉树TreeSelect)传递给详情页面。

show.html详情页面代码。



上级部门使用的是LayUI下拉树显示的,下拉树数据请求方法。关于下拉树的使用,可以访问LayUI下拉树TreeSelect的使用

@RequestMapping(value="/treeSelect")
@ResponseBody
public Object treeSelect(Integer id) {
    Sort sort = Sort.by("idx");
    Specification spec = buildSpec1();
    List list = deptService.findAll(spec,sort);
    return buildTree(list, id);
}

private Object buildTree(List list, Integer id) {
    List> result=new ArrayList<>();
    for (Dept dept : list) {
        if(dept.getId() != id) {
            HashMap node=new HashMap<>();
            node.put("id", dept.getId());
            node.put("name",dept.getName());
            node.put("open", false);
            node.put("checked", false);
            if(dept.getChildren().size() != 0) {
                node.put("children",buildTree(dept.getChildren(), id));
            }
            result.add(node);
        }
    }

    return result;
}

public Specification buildSpec1() {
    Specification specification = new Specification() {

        private static final long serialVersionUID = 1L;

        @Override
        public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) {
            HashSet rules=new HashSet<>();
            Predicate parent = cb.isNull(root.get("parent"));
            rules.add(parent);
            return cb.and(rules.toArray(new Predicate[rules.size()]));
        }

    };
    return specification;
}

显示的效果。

LayUI—tree树形结构的使用_第3张图片

上面修改并保存后台方法(因为修改和新增共用的一个方法,用id区分的)。

@Override
public Object save(DeptForm form) {
    try {
        Dept model = new Dept();
        Integer id = form.getId();
        if(id != null) {
            model = deptService.findById(id);
        }
        //父级菜单id
        Integer parentId = form.getParentId();
        if(parentId == null) {
            model.setParent(null);
        }else {
            model.setParent(new Dept(parentId));
        }
        BeanUtils.copyProperties(form, model,"id", "parent");
        deptService.save(model);
        return new AjaxResult("数据保存成功!");
    } catch (Exception e) {
        return new AjaxResult(false,"数据保存失败");
    }
}

设置节点操作(在加载数据方法tree.render中添加以下代码)。

edit: ['add', 'update', 'del'], //操作节点的图标
operate: function(obj){
    var type = obj.type; //得到操作类型:add、edit、del
    var data = obj.data; //得到当前节点的数据
    var elem = obj.elem; //得到当前节点元素

    var id = data.id;
    var name = data.title;
    if(type === 'add'){ //增加节点
        $.post("dept/save", {parentId: id, name: "未命名"}, function (result) {
            tree.reload('treeId', {data: getData()});
        })
        //返回 key 值
        return ;
    } else if(type === 'update'){ //修改节点
        $.post("dept/update", {id: id, name: name}, function () {
            tree.reload('treeId', {data: getData()});
        })
    } else if(type === 'del'){ //删除节点
        $.post("dept/delete", {id: id}, function () {
            tree.reload('treeId', {data: getData()});
        });
    };
}

其中operate为操作节点回调方法。

obj.type为操作类型,add为新增,update为修改,edl为删除。obj.data为操作节点后的数据。

新增节点后,向后台发送请求添加节点,save方法和上面修改方法一样,id为新建节点的父级节点id。

修改节点,同样,向后台发送修改请求,并传递对象的id,和修改后的数据作为参数。后台响应方法。

@RequestMapping(value = "/update")
@ResponseBody
public Object update(DeptForm form) {
    try {
        Dept model = deptService.findById(form.getId());
        model.setName(form.getName());
        deptService.save(model);
        return new AjaxResult("数据保存成功!");
    } catch (Exception e) {
        return new AjaxResult(false,"数据保存失败");
    }
}

删除节点同理,传递删除节点的id。删除请求方法。

@RequestMapping(value="/delete")
@ResponseBody
public Object delete(Integer id) {
	try {
		deptService.deleteById(id);
		return new AjaxResult("数据删除成功");
	} catch (Exception e) {
		return new AjaxResult(false,"数据删除失败");
	}
}

使用按钮操作树形菜单。

现在页面中定义两个按钮,给按钮添加lay-demo=""属性,并设置属性值,JS通过这个属性值,绑定点击事件。


绑定添加部门和获取选中节点按钮的点击事件的JS代码。

util.event('lay-demo', {
    addDept: function(othis){
        $.get('dept/edit', function(data) {
            layer.open({
                type: 1,
                title: '新增',
                area: ['530px'],
                content: data,
                btn: ['提交', '退出'],
                yes: function () {
                },
                success: function (layero, index) {
                    layui.use('form', function () {
                        var form = layui.form;
                        layero.addClass('layui-form');
                        var submitBtn = layero.find('.layui-layer-btn0');
                        submitBtn.attr('lay-filter', 'formVerify').attr('lay-submit', '');
                        layero.keydown(function (e) {
                            if (e.keyCode == 13) {
                                submitBtn.click();
                            }
                        });

                        form.on('submit(formVerify)', function (data) {
                            $.post('dept/save', data.field, function (result) {
                                if (result.success) {
                                    layer.close(index);
                                    tree.reload('treeId', {data: getData()});
                                }
                                layer.msg(result.msg, {offset: 'rb'});
                            });
                            return false;
                        });
                    });
                }
            })
        })
    },
    gain: function () {
        var checkData = tree.getChecked('treeId');
        var str = JSON.stringify(checkData);
        $.post('dept/checkedGain', {data: str}, function () {
        });
        layer.alert(JSON.stringify(checkData), {shade:0});
    }
});

添加部门按钮点击事件,先发送请求到后台,跳转到eidt新增页面,edit.html新增页面代码,和上面的show.html显示部门详情页面差不多。上级部门同样使用的LayUI下拉树显示的,下拉树数据请求方法,和上面的详情页面下拉树请求方法一致。LayUI下拉树TreeSelect的使用。新增后的保存方法也和上面的保存方法一致。

后台请求方法代码,跳转到edit页面。

@RequestMapping(value = "/edit")
public void edit(){
    
}

edit.html页面完整代码如下。



页面效果。

LayUI—tree树形结构的使用_第4张图片

获取选中节点按钮点击事件。如果项目需要保存数据时,就需要获取到选中节点的数据了。这里可以获取到选中节点的数据,之后当参数传递到后台。传递到后台是一个JSON数据的字符串,需要转换一下,这里给推荐大家两个很好用的JSON转换工具net.sf.json.JSONObject和Alibaba Fastjson。这里用的是Alibaba Fastjson,需要引入以下依赖。


	com.alibaba
	fastjson
	1.2.47

这里用于输出,新建了一个和tree的json数据的键名称一样的工具类DeptTree,代码如下。

import java.util.ArrayList;
import java.util.List;

public class DeptTree {
    private Integer id;
    private String title;
    private boolean checked;
    private boolean spread;
    private List children = new ArrayList<>();

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public List getChildren() {
        return children;
    }

    public void setChildren(List children) {
        this.children = children;
    }

    public boolean isChecked() {
        return checked;
    }

    public void setChecked(boolean checked) {
        this.checked = checked;
    }

    public boolean isSpread() {
        return spread;
    }

    public void setSpread(boolean spread) {
        this.spread = spread;
    }
}

后台接收到传递过来的JSON数据字符串,转换后输出方法。

@RequestMapping(value = "/checkedGain")
@ResponseBody
public void checkedGain(String data){
    List array2 = JSONArray.parseArray(data, DeptTree.class);
    treeData(array2);
}

//递归输出选中数据
private void treeData(List array2) {
    for (DeptTree tree: array2){
        System.out.println(tree.getTitle()+"==="+tree.getId());
        if(tree.getChildren() != null){
            treeData(tree.getChildren());
        }
    }
}

选中节点,点击获取选中节点数据。

LayUI—tree树形结构的使用_第5张图片

后台对应方法接收到数据,转换后输出结果。

LayUI—tree树形结构的使用_第6张图片

数据拿到了,之后保存方法就简单了。

后台方法代码基本都在上面了,页面全部代码。



所有部门
部门详情
无数据

如果对你有帮助,点赞关注一下呗^_^,留下你的足迹。

你可能感兴趣的:(前端,LayUI)