LayUI树形表格treetable使用及说明

JavaScript/前端
329
0
0
2023-06-06
目录
  • LayUI是现在比较流行的一款前端框架
  • 实体类TbMenu代码
  • TbMenuForm接收类
  • 返回数据格式TreeTableModel类
  • 监听工具栏的新增修改删除和刷新按钮方法
  • 后台保存方法

LayUI是现在比较流行的一款前端框架

也有很多人基于LayUI开发了很多不错的组件,比如treetable树形表格。

因为treetable是第三方基于LayUI开发的,所以需要先用Layui引入一下文件。

layui.config({
    base : 'static/layui/'
}).extend({
    treetable : 'treetable-lay/treetable'
});

之后先看一下显示的效果。

之后页面只需要引入LayUI的CSS和JS就可以了。

页面给一个table标签,用于显示treetable中的数据样式。

<table class="layui-hide" id = "menu" lay-filter="menu"></table>

表格左上方的工具栏按钮组件代码。

<script type="text/html" id="toolbarDemo">
    <div class="layui-btn-group">
        <button class="layui-btn layui-btn-sm" lay-event="add"><i class="layui-icon">&#xe654;</i>新增</button>
        <button class="layui-btn layui-btn-sm" lay-event="updata"><i class="layui-icon">&#xe642;</i>修改</button>
        <button class="layui-btn layui-btn-sm" lay-event="delete"><i class="layui-icon">&#xe640;</i>删除</button>
        <button class="layui-btn layui-btn-sm" lay-event="refresh"><i class="layui-icon">&#xe666;</i>刷新</button>
    </div>
</script>

JS请求加载数据及设置表格样式。

yui.use(['treetable', 'table', 'layer'], function () {
    var table = layui.table;
    var layer = layui.layer;
    var treetable = layui.treetable;
    //渲染表格
    var renderTable = function(){
        layer.load(2);  //加载层
        treetable.render({
            height: 'full-160',
            id:'menu',
            treeColIndex: 1,	//树形图标显示在第几列
            treeSpid: '0',		//最上级的父级id
            treeIdName: 'id',	//id字段的名称
            treePidName: 'parentId',	//父级节点字段
            treeDefaultClose: false,	//是否默认折叠
            treeLinkage: false,		//父级展开时是否自动展开所有子级
            elem: '#menu',	//表格id
            url: 'menu/treedata',
            toolbar: '#toolbarDemo',
            page: false,
            cols: [ [
                {type:'radio'},
                {field: 'name', title: '菜单名称'},
                {field: 'url' , title: '地址'},
                {field: 'icon' , hide : true, title: '图标'},
                {field: 'idx', title: '排序'}
            ] ],
            //数据渲染完的回调
            done: function () {
                //关闭加载
                layer.closeAll('loading');
            }
        })
    };
    renderTable();
});

其中URL地址为请求数据地址。后台对应的方法。

@RequestMapping(value="/treedata")
@ResponseBody
public Object list(TbMenuForm form){
	Sort sort = bulidSort();    //排序
	Specification<TbMenu> spec = buildSpec(form);    //查询条件
	List<TbMenu> list = menuService.findAll(spec, sort);
	return new TreeTableModel(list);
}
 
public Sort bulidSort() {
	return Sort.by("idx");    //按idx字段排序
}
 
public Specification<TbMenu> buildSpec(TbMenuForm form){
	return null;
}

list方法中的TbMenuForm接收类中的字段和实体类字段差不多。其中TreeTableModel返回类为返回数据格式的工具类。

实体类TbMenu代码

import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.data.annotation.CreatedBy;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
 
@Entity
public class TbMenu {
    private Integer id;
    private String name;        //菜单名称
    private String url;         //路径
    private String icon;        //图标
    private double idx;         //排序
    @JsonIgnore
    private TbMenu parent;
    @JsonIgnore
    private List<TbMenu> 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 getUrl() {
        return url;
    }
 
    public void setUrl(String url) {
        this.url = url;
    }
 
    public String getIcon() {
        return icon;
    }
 
    public void setIcon(String icon) {
        this.icon = icon;
    }
 
    public double getIdx() {
        return idx;
    }
 
    public void setIdx(double idx) {
        this.idx = idx;
    }
 
    @ManyToOne
    @CreatedBy
    public TbMenu getParent() {
        return parent;
    }
 
    public void setParent(TbMenu parent) {
        this.parent = parent;
    }
 
    @OneToMany(cascade=CascadeType.ALL,mappedBy="parent")
    @OrderBy(value="idx")
    public List<TbMenu> getChildren() {
        return children;
    }
 
    public void setChildren(List<TbMenu> children) {
        this.children = children;
    }
 
    public TbMenu(Integer id, String name, String url, String icon, double idx, TbMenu parent, List<TbMenu> children) {
        this.id = id;
        this.name = name;
        this.url = url;
        this.icon = icon;
        this.idx = idx;
        this.parent = parent;
        this.children = children;
    }
 
    public TbMenu(Integer id) {
        this.id = id;
    }
 
    public TbMenu() {
    }
 
    @Transient
    public Integer getParentId() {
        return parent==null? 0 : parent.getId();
    }
}

TbMenuForm接收类

 
public class TbMenuForm {
    private Integer id;
    private String name;        //菜单名称
    private String url;         //路径
    private String icon;        //图标
    private double idx;         //排序
    private Integer parentId;   //父节点id
 
    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 getUrl() {
        return url;
    }
 
    public void setUrl(String url) {
        this.url = url;
    }
 
    public String getIcon() {
        return icon;
    }
 
    public void setIcon(String icon) {
        this.icon = icon;
    }
 
    public double getIdx() {
        return idx;
    }
 
    public void setIdx(double idx) {
        this.idx = idx;
    }
 
    public Integer getParentId() {
        return parentId;
    }
 
    public void setParentId(Integer parentId) {
        this.parentId = parentId;
    }
}

返回数据格式TreeTableModel类

public class TreeTableModel {
	private Integer code=0;
	private String msg="ok";
	private Integer count;
	private List data;
 
	public Integer getCode() {
		return code;
	}
 
	public void setCode(Integer code) {
		this.code = code;
	}
 
	public String getMsg() {
		return msg;
	}
 
	public void setMsg(String msg) {
		this.msg = msg;
	}
 
	public Integer getCount() {
		return count;
	}
 
	public void setCount(Integer count) {
		this.count = count;
	}
 
	public List getData() {
		return data;
	}
 
	public void setData(List data) {
		this.data = data;
	}
 
	public TreeTableModel() {
		super();
		// TODO Auto-generated constructor stub
	}
	public TreeTableModel(Integer code, String msg, Integer count, List data) {
		super();
		this.code = code;
		this.msg = msg;
		this.count = count;
		this.data = data;
	}
	public TreeTableModel(List data) {
		super();
		this.count=data.size();
		this.data = data;
	}
}

返回的JSON数据格式,这里需要注意的是parentId为父节点,需要和前面的JS中设置的属性值一样,没有父级节点parentId需要为0,不能为null。

{
	"code": 0,
	"msg": "ok",
	"count": 6,
	"data": [
		{
			"id": 1,
			"name": "系统设置",
			"url": "",
			"icon": "",
			"idx": 1.0,
			"parentId": 0	//最上级节点,父节点为0
		}, {
			"id": 2,
			"name": "角色管理",
			"url": "",
			"icon": "",
			"idx": 1.0,
			"parentId": 1		//上级节点
		}, {
			"id": 6,
			"name": "数据表格",
			"url": "",
			"icon": "",
			"idx": 1.0,
			"parentId": 5
		}, {
			"id": 3,
			"name": "部门管理",
			"url": "",
			"icon": "",
			"idx": 2.0,
			"parentId": 1
		}, {
			"id": 5,
			"name": "表格案例",
			"url": "",
			"icon": "",
			"idx": 2.0,
			"parentId": 0
		}, {
			"id": 7,
			"name": "树形表格",
			"url": "",
			"icon": "",
			"idx": 2.0,
			"parentId": 5
		}
	]
}

数据加载完成后,页面中就可以显示出数据了,效果如下。

使用table.on('toolbar(menu)', function(obj){})监听表格上面的工具类按钮点击事件。

监听工具栏的新增修改删除和刷新按钮方法

table.on('toolbar(menu)', function(obj){
    var checkStatus = table.checkStatus('menu');
    var data = checkStatus.data;
    if(obj.event === 'add'){
        var parentId = data.length==0? 0 : data[0].id;
        $.get('menu/edit', {parentId: parentId}, 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('menu/save',data.field,function(result){
                                if(result.success){
                                    layer.close(index);
                                    //刷新,重新渲染表格
                                    renderTable();
                                }
                                layer.msg(result.msg,{offset:'rb'});
                            });
                            return false;
                        });
                    });
                }
            })
        })
    }else if(obj.event === 'updata'){
        if(data.length != 1){
            layer.msg("请选择一行进行编辑",{offset:'rb'});
        }else{
            var id = data[0].id;
            $.get('menu/edit', {id: id}, 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('menu/save',data.field,function(result){
                                    if(result.success){
                                        layer.close(index);
                                        //刷新,重新渲染表格
                                        renderTable();
                                    }
                                    layer.msg(result.msg,{offset:'rb'});
                                });
                                return false;
                            });
                        });
                    }
                })
            })
        }
    }else if(obj.event === "delete"){
        if(data.length != 1){
            layer.msg("请选择一行进行删除",{offset:'rb'});
        }else{
            var id = data[0].id;
            layer.confirm('确定删除选定行的数据吗?', function(index){
                $.post('menu/delete',{id:id},function(result){
                    if(result.success){
                        layer.close(index);
                        renderTable();
                    }
                    layer.msg(result.msg,{offset:'rb'});
                });
            });
        }
    }else if(obj.event === "refresh"){
        renderTable();
    }
})

其中obj.event值为点击工具栏按钮的lay-event属性值。

新增和修改方法,先请求后台menu/edit获取到新增修改的页面,把页面用LayUI的layer弹框显示出来。这里新增和修改用的是一个方法和一个页面。修改时传递了一个id参数,用于查询修改的数据和区别新增还是修改。新增时如果选中了一行,会把当前行的id作为参数,传递到后台,相当于默认的父节点id。

跳转到新增和修改页面的edit后台方法。如果修改就把当前修改的数据传递到前台,新增时,如果有选中的节点,就把选中节点的id作为父节点id传递到前台。

@Override
public void edit(TbMenuForm form, ModelMap map) throws InstantiationException, IllegalAccessException {
    TbMenu model = new TbMenu();
    Integer id = form.getId();
    if(id != null) {
        model = menuService.findById(id);
    }
    map.put("model", model);        //修改的对象,如果新增model就为null
    map.put("parentId", form.getParentId());    //父节点id
}

edit页面代码,上级菜单是用LayUI的TreeSelect做的,对于TreeSelect的用法,大家可以访问LayUI下拉树TreeSelect的使用。

<style type="text/css">
    .myData .layui-form-item{
        margin: 20px 100px 10px 45px;
    }
    .myData .layui-form-label{
        width: 80px;
    }
</style>
<form class="layui-form myData" action="save" method="post" lay-filter="stuform">
    <input type="hidden" name="id" data-th-value="${model.id}" />
 
	<div class="layui-form-item">
        <label class="layui-form-label">上级菜单:</label>
        <div class="layui-input-block">
            <input type="text" name="parentId" id="tree" lay-filter="tree" class="layui-input" />
        </div>
    </div>
    <div class="layui-form-item">
        <label class="layui-form-label">菜单名称:</label>
        <div class="layui-input-block">
            <input type="text" name="name" lay-verify="required" th:value="${model.name}" class="layui-input" />
        </div>
    </div>
    <div class="layui-form-item" >
        <label class="layui-form-label">菜单地址:</label>
        <div class="layui-input-block">
            <input type="text" name="url" th:value="${model.url}" class="layui-input" />
        </div>
    </div>
    <div class="layui-form-item" >
        <label class="layui-form-label">图标:</label>
        <div class="layui-input-block">
            <input type="text" name="icon" th:value="${model.icon}" class="layui-input" />
        </div>
    </div>
    <div class="layui-form-item" >
        <label class="layui-form-label">排序:</label>
        <div class="layui-input-block">
            <input type="text" name="idx" th:value="${model.idx}" class="layui-input" />
        </div>
    </div>
</form>
 
<script th:inline="javascript">
	layui.use(["treeSelect", "form"], function () {
		var form = layui.form;
        form.render('select');
	    var treeSelect = layui.treeSelect;
	    treeSelect.render({
	        // 选择器
	        elem: '#tree',
	        // 数据
	        data: 'menu/treeSelect?id='+[[${model.id==null ? 0 : model.id}]],
	        // 异步加载方式:get/post,默认get
	        type: 'post',
	        // 占位符
	        placeholder: '上级菜单',
	        // 是否开启搜索功能:true/false,默认false
	        search: true,
	        // 一些可定制的样式
	        style: {
	            folder: {
	                enable: true
	            },
	            line: {
	                enable: true
	            }
	        },
	        // 加载完成后的回调函数
	        success: function (d) {
				// 选中节点,根据id筛选
	            treeSelect.checkNode('tree', [[${model.parent == null? parentId: model.parent.id}]]);
				// 刷新树结构
	           	treeSelect.refresh('tree');
	        }
	    });
	});
</script>

menu/treeSelect加载TreeSelect数据。对于TreeSelect的用法,大家可以访问LayUI下拉树TreeSelect的使用。

@RequestMapping(value="/treeSelect")
@ResponseBody
public Object treeSelect(Integer id) {
    Sort sort = Sort.by("idx");    //排序
    Specification<TbMenu> spec = buildSpec1();    //查询条件,可以自行添加,对应的buildSpec1方法
    List<TbMenu> list = menuService.findAll(spec,sort);
    return buildTree(list, id);
}
 
private Object buildTree(List<TbMenu> list, Integer id) {
    List<HashMap<String, Object>> result=new ArrayList<>();
    for (TbMenu dept : list) {
        if(dept.getId() != id) {
            HashMap<String, Object> 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<TbMenu> buildSpec1() {
    Specification<TbMenu> specification = new Specification<TbMenu>() {
 
        private static final long serialVersionUID = 1L;
 
        @Override
        public Predicate toPredicate(Root<TbMenu> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
            HashSet<Predicate> rules=new HashSet<>();
            Predicate parent = cb.isNull(root.get("parent"));    //添加父节点为空的条件,即查询最上级数据
            rules.add(parent);
            return cb.and(rules.toArray(new Predicate[rules.size()]));
        }
 
    };
    return specification;
}

页面显示效果。

后台保存方法

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

AjaxResult类是一个请求完成返回的一个工具类。

import java.util.HashMap;
import org.springframework.data.domain.Page;
 
public class AjaxResult {
	private Boolean success;
	private String msg;
	
	public Boolean getSuccess() {
		return success;
	}
 
	public void setSuccess(Boolean success) {
		this.success = success;
	}
 
	public String getMsg() {
		return msg;
	}
 
	public void setMsg(String msg) {
		this.msg = msg;
	}
	
	public AjaxResult(String msg) {
		super();
		this.success=true;
		this.msg = msg;
	}
	
	public AjaxResult(Boolean success, String msg) {
		super();
		this.success = success;
		this.msg = msg;
	}
	
	public AjaxResult(boolean success) {
		this.success=success;
	}
 
	@SuppressWarnings("rawtypes")
	public static HashMap<String, Object> bulidPageResult(Page page) {
		HashMap<String, Object> result=new HashMap<>();
		result.put("total", page.getTotalElements());
		result.put("rows", page.getContent());
		return result;
	}
}

新增和修改就完了,下面就是删除数据。删除需要先判断是否选中了一行。然后把选中行的id作为参数,传递到后台,根据id删除数据就可以了。

if(data.length != 1){
    layer.msg("请选择一行进行删除",{offset:'rb'});
}else{
    var id = data[0].id;
    layer.confirm('确定删除选定行的数据吗?', function(index){
        $.post('menu/delete',{id:id},function(result){
            if(result.success){
                layer.close(index);
                renderTable();
            }
            layer.msg(result.msg,{offset:'rb'});
        });
    });
}

最后一个就是刷新了,刷新只需要把表格刷新一下就可以了,调用一下表格刷新方法。

renderTable();

这里持久层框架用的Spring-Data-Jpa,但只要数据传递到后台了,怎么处理都差不多,请求的数据只要按照规定的JSON格式返回就可以了。后台方法代码上面基本的有,前台页面代码有些零散,下面是显示页面完整代码。

<script type="text/html" id="toolbarDemo">
    <div class="layui-btn-group">
        <button class="layui-btn layui-btn-sm" lay-event="add"><i class="layui-icon">&#xe654;</i>新增</button>
        <button class="layui-btn layui-btn-sm" lay-event="updata"><i class="layui-icon">&#xe642;</i>修改</button>
        <button class="layui-btn layui-btn-sm" lay-event="delete"><i class="layui-icon">&#xe640;</i>删除</button>
        <button class="layui-btn layui-btn-sm" lay-event="refresh"><i class="layui-icon">&#xe666;</i>刷新</button>
    </div>
</script>
 
<table class="layui-hide" id = "menu" lay-filter="menu"></table>
 
<script type="text/javascript">
    layui.use(['treetable', 'table', 'layer'], function () {
        var table = layui.table;
        var layer = layui.layer;
        var treetable = layui.treetable;
        //渲染表格
        var renderTable = function(){
            layer.load(2);  //加载层
            treetable.render({
            	height: 'full-160',
                id:'menu',
                treeColIndex: 1,	//树形图标显示在第几列
                treeSpid: '0',		//最上级的父级id
                treeIdName: 'id',	//id字段的名称
                treePidName: 'parentId',	//pid字段的名称,父级菜单id
                treeDefaultClose: false,	//是否默认折叠
                treeLinkage: false,		//父级展开时是否自动展开所有子级
                elem: '#menu',	//表格id
                url: 'menu/treedata',
                toolbar: '#toolbarDemo',
                page: false,
                cols: [ [
                    {type:'radio'},
                    {field: 'name', title: '菜单名称'},
                    {field: 'url' , title: '地址'},
                    {field: 'icon' , hide : true, title: '图标'},
                    {field: 'idx', title: '排序'}
                ] ],
                //数据渲染完的回调
                done: function () {
                    //关闭加载
                    layer.closeAll('loading');
                }
            })
        };
        renderTable();
 
        table.on('toolbar(menu)', function(obj){
            var checkStatus = table.checkStatus('menu');
            var data = checkStatus.data;
            if(obj.event === 'add'){
            	var parentId = data.length==0? 0 : data[0].id;
	            $.get('menu/edit', {parentId: parentId}, 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('menu/save',data.field,function(result){
	                                    if(result.success){
	                                        layer.close(index);
	                                        //刷新,重新渲染表格
	                                        renderTable();
	                                    }
	                                    layer.msg(result.msg,{offset:'rb'});
	                                });
	                                return false;
	                            });
	                        });
	                    }
	                })
	            })
            }else if(obj.event === 'updata'){
                if(data.length != 1){
                    layer.msg("请选择一行进行编辑",{offset:'rb'});
                }else{
                    var id = data[0].id;
                    $.get('menu/edit', {id: id}, 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('menu/save',data.field,function(result){
                                            if(result.success){
                                                layer.close(index);
                                                //刷新,重新渲染表格
                                                renderTable();
                                            }
                                            layer.msg(result.msg,{offset:'rb'});
                                        });
                                        return false;
                                    });
                                });
                            }
                        })
                    })
                }
            }else if(obj.event === "delete"){
                if(data.length != 1){
                    layer.msg("请选择一行进行删除",{offset:'rb'});
                }else{
                    var id = data[0].id;
                    layer.confirm('确定删除选定行的数据吗?', function(index){
                        $.post('menu/delete',{id:id},function(result){
                            if(result.success){
                                layer.close(index);
                                renderTable();
                            }
                            layer.msg(result.msg,{offset:'rb'});
                        });
                    });
                }
            }else if(obj.event === "refresh"){
                renderTable();
            }
        })
    })
</script>

推荐文章,LayUI树形结构tree的使用