1987WEB视界-分享互联网热点话题和事件

您现在的位置是:首页 > WEB开发 > 正文

WEB开发

LayUI—tree树形结构的使用解析

1987web2024-03-25WEB开发43
这篇文章主要介绍了LayUI—tree树形结构的使用,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

这篇文章主要介绍了LayUI—tree树形结构的使用,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

树形结构在实际开发中很长用到,比如部门管理,权限菜单等。因为用树形结构来展示会显的很清晰明了。

最近写了一个个人博客小项目中用到了LayUI的树形结构,之后写了一个小案例整理一下。

先看一下显示的效果图

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

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

案例对应的实体类Dept

@EntitypublicclassDept{privateIntegerid;privateStringname;//部门名称privateStringdeptName;//部门负责人privateStringphone;//电话号privateStringnumber;//编号privatedoubleidx;//排序@JsonIgnoreprivateDeptparent;@JsonIgnoreprivateList<Dept>children=newArrayList<>();@Id@GeneratedValuepublicIntegergetId(){returnid;}publicvoidsetId(Integerid){this.id=id;}publicStringgetName(){returnname;}publicvoidsetName(Stringname){this.name=name;}publicStringgetDeptName(){returndeptName;}publicvoidsetDeptName(StringdeptName){this.deptName=deptName;}publicStringgetPhone(){returnphone;}publicvoidsetPhone(Stringphone){this.phone=phone;}publicStringgetNumber(){returnnumber;}publicvoidsetNumber(Stringnumber){this.number=number;}publicdoublegetIdx(){returnidx;}publicvoidsetIdx(doubleidx){this.idx=idx;}@ManyToOne@CreatedBypublicDeptgetParent(){returnparent;}publicvoidsetParent(Deptparent){this.parent=parent;}@OneToMany(cascade=CascadeType.ALL,mappedBy="parent")@OrderBy(value="idx")publicList<Dept>getChildren(){returnchildren;}publicvoidsetChildren(List<Dept>children){this.children=children;}publicDept(Integerid,Stringname,StringdeptName,Stringphone,Stringnumber,doubleidx,Deptparent,List<Dept>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;}publicDept(Integerid){this.id=id;}publicDept(){}}

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

id="dept_tree">

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

完整代码如下

type="text/css">#dept_main, #dept_particulars{width:48.5%;display:inline-block;vertical-align:top;padding:20px;background:white;box-sizing:border-box;}#dept_tree{margin-top:20px;}
id="dept_main"> class="layui-elem-field layui-field-title">所有部门 class="layui-btn layui-btn-sm layui-btn-radius layui-btn-normal"lay-demo="addDept">class="layui-icon">添加部门 class="layui-btn layui-btn-sm layui-btn-radius layui-btn-normal"lay-demo="gain">获取选中节点 id="dept_tree">
id="dept_particulars"> class="layui-elem-field layui-field-title">部门详情 id="dept_home"> class="layui-tree-emptyText">无数据

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

layui.use([tree,util],function(){vartree=layui.tree;varutil=layui.util;tree.render({elem:#dept_tree,data:getData(),id:treeId,showCheckbox:true,//是否显示复选框onlyIconControl:true});});functiongetData(){vardata=[];$.ajax({url:"dept/treeload",//后台数据请求地址type:"post",async:false,success:function(resut){data=resut;}});returndata;}

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

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

text: {

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

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

}

Object-

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

键名:

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

后台请求数据的方法。

@RequestMapping(value="/treeload")@ResponseBodypublicObjecttreeload(){Sortsort=Sort.by("idx");List<Dept>dpet=deptService.findByParentIsNull(sort);//查找所有菜单List<HashMap<String,Object>>result=newArrayList<>();//定义一个map处理json键名问题returnfun(dpet,result);}privateObjectfun(List<Dept>dpet,List<HashMap<String,Object>>result){for(Deptd:dpet){HashMap<String,Object>map=newHashMap<>();map.put("id",d.getId());map.put("title",d.getName());map.put("spread",true);//设置是否展开List<HashMap<String,Object>>result1=newArrayList<>();List<Dept>children=d.getChildren();//下级菜单//这里可以根据自己需求判断节点默认选中/*if(m.getParent() != null || m.getChildren().size() == 0){map.put("checked", true); //设置为选中状态}*/map.put("children",fun(children,result1));result.add(map);}returnresult;}

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

返回的JSON数据格式

[{"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){varid=obj.data.id;$("#dept_home").load("dept/show?id="+id);}

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

@RequestMapping(value="/show")publicvoidshow(DeptFormform,ModelMapmap)throwsInstantiationException,IllegalAccessException{Deptmodel=newDept();Integerid=form.getId();IntegerparentId=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详情页面代码。

charset="UTF-8"/> type="text/css">.myData.layui-form-item{margin:20px100px10px45px;}.myData.layui-form-label{width:85px;}.layui-input-block{margin-left:120px;} class="layui-form myData"action="save"method="post"lay-filter="stuform"> type="hidden"name="id"data-th-value="${model.id}"/> class="layui-form-item"> class="layui-form-label">上级部门: class="layui-input-block"> type="text"name="parentId"id="tree"lay-filter="tree"class="layui-input"/>
class="layui-form-item"> class="layui-form-label">部门名称: class="layui-input-block"> type="text"name="name"lay-verify="required"th:value="${model.name}"class="layui-input"/> class="layui-form-item"> class="layui-form-label">部门负责人: class="layui-input-block"> type="text"name="deptName"th:value="${model.deptName}"class="layui-input"/> class="layui-form-item"> class="layui-form-label">电话: class="layui-input-block"> type="text"name="phone"th:value="${model.phone}"class="layui-input"/> class="layui-form-item"> class="layui-form-label">编号: class="layui-input-block"> type="text"name="number"th:value="${model.number}"class="layui-input"/> class="layui-form-item"> class="layui-form-label">排序: class="layui-input-block"> type="text"name="idx"value="0"th:value="${model.idx}"class="layui-input"/> class="layui-form-item"> class="layui-form-label"> class="layui-input-block"> lay-submitclass="layui-btn layui-btn-radius layui-btn-normal"lay-filter="btnSub"> class="layui-icon">修改并保存 th:inline="javascript">layui.use(["treeSelect","form","tree"],function(){varform=layui.form;vartree=layui.tree;form.render(select);vartreeSelect=layui.treeSelect;treeSelect.render({// 选择器elem:#tree,// 数据data:dept/treeSelect?id=+[[${model.id==null?0:model.id}]],// 异步加载方式:get/post,默认gettype:post,// 占位符placeholder:上级菜单,// 是否开启搜索功能:true/false,默认falsesearch: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);}});form.on(submit(btnSub),function(data){$.post(dept/save,data.field,function(result){if(result.success){tree.reload(treeId,{data:getData()});}layer.msg(result.msg,{offset:rb});});returnfalse;});});

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

@RequestMapping(value="/treeSelect")@ResponseBodypublicObjecttreeSelect(Integerid){Sortsort=Sort.by("idx");Specification<Dept>spec=buildSpec1();List<Dept>list=deptService.findAll(spec,sort);returnbuildTree(list,id);}privateObjectbuildTree(List<Dept>list,Integerid){List<HashMap<String,Object>>result=newArrayList<>();for(Deptdept:list){if(dept.getId()!=id){HashMap<String,Object>node=newHashMap<>();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);}}returnresult;}publicSpecification<Dept>buildSpec1(){Specification<Dept>specification=newSpecification<Dept>(){privatestaticfinallongserialVersionUID=1L;@OverridepublicPredicatetoPredicate(Root<Dept>root,CriteriaQueryquery,CriteriaBuildercb){HashSet<Predicate>rules=newHashSet<>();Predicateparent=cb.isNull(root.get("parent"));rules.add(parent);returncb.and(rules.toArray(newPredicate[rules.size()]));}};returnspecification;}

显示的效果。

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

@OverridepublicObjectsave(DeptFormform){try{Deptmodel=newDept();Integerid=form.getId();if(id!=null){model=deptService.findById(id);}//父级菜单idIntegerparentId=form.getParentId();if(parentId==null){model.setParent(null);}else{model.setParent(newDept(parentId));}BeanUtils.copyProperties(form,model,"id","parent");deptService.save(model);returnnewAjaxResult("数据保存成功!");}catch(Exceptione){returnnewAjaxResult(false,"数据保存失败");}}

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

edit:[add,update,del],//操作节点的图标operate:function(obj){vartype=obj.type;//得到操作类型:add、edit、delvardata=obj.data;//得到当前节点的数据varelem=obj.elem;//得到当前节点元素varid=data.id;varname=data.title;if(type===add){//增加节点$.post("dept/save",{parentId:id,name:"未命名"},function(result){tree.reload(treeId,{data:getData()});})//返回 key 值return;}elseif(type===update){//修改节点$.post("dept/update",{id:id,name:name},function(){tree.reload(treeId,{data:getData()});})}elseif(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")@ResponseBodypublicObjectupdate(DeptFormform){try{Deptmodel=deptService.findById(form.getId());model.setName(form.getName());deptService.save(model);returnnewAjaxResult("数据保存成功!");}catch(Exceptione){returnnewAjaxResult(false,"数据保存失败");}}

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

@RequestMapping(value="/delete")@ResponseBodypublicObjectdelete(Integerid){try{deptService.deleteById(id);returnnewAjaxResult("数据删除成功");}catch(Exceptione){returnnewAjaxResult(false,"数据删除失败");}}

使用按钮操作树形菜单。

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

class="layui-btn layui-btn-sm layui-btn-radius layui-btn-normal"lay-demo="addDept">class="layui-icon">添加部门 class="layui-btn layui-btn-sm layui-btn-radius layui-btn-normal"lay-demo="gain">获取选中节点

绑定添加部门和获取选中节点按钮的点击事件的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(){varform=layui.form;layero.addClass(layui-form);varsubmitBtn=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});});returnfalse;});});}})})},gain:function(){varcheckData=tree.getChecked(treeId);varstr=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")publicvoidedit(){}

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

charset="UTF-8"/> type="text/css">.myData.layui-form-item{margin:20px100px10px45px;}.myData.layui-form-label{width:85px;}.layui-input-block{margin-left:120px;} class="layui-form myData"action="save"method="post"lay-filter="stuform"> class="layui-form-item"> class="layui-form-label">上级部门: class="layui-input-block"> type="text"name="parentId"id="tree2"lay-filter="tree2"class="layui-input"/>
class="layui-form-item"> class="layui-form-label">部门名称: class="layui-input-block"> type="text"name="name"lay-verify="required"class="layui-input"/> class="layui-form-item"> class="layui-form-label">部门负责人: class="layui-input-block"> type="text"name="deptName"class="layui-input"/> class="layui-form-item"> class="layui-form-label">电话: class="layui-input-block"> type="text"name="phone"class="layui-input"/> class="layui-form-item"> class="layui-form-label">编号: class="layui-input-block"> type="text"name="number"class="layui-input"/> class="layui-form-item"> class="layui-form-label">排序: class="layui-input-block"> type="text"name="idx"value="0"class="layui-input"/> th:inline="javascript">layui.use(["treeSelect","form"],function(){varform=layui.form;form.render(select);vartreeSelect=layui.treeSelect;treeSelect.render({// 选择器elem:#tree2,// 数据data:dept/treeSelect,// 异步加载方式:get/post,默认gettype:post,// 占位符placeholder:上级菜单,// 是否开启搜索功能:true/false,默认falsesearch:true,// 一些可定制的样式style:{folder:{enable:true},line:{enable:true}},// 加载完成后的回调函数success:function(d){}});});

页面效果。

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

com.alibabafastjson1.2.47

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

importjava.util.ArrayList;importjava.util.List;publicclassDeptTree{privateIntegerid;privateStringtitle;privatebooleanchecked;privatebooleanspread;privateList<DeptTree>children=newArrayList<>();publicIntegergetId(){returnid;}publicvoidsetId(Integerid){this.id=id;}publicStringgetTitle(){returntitle;}publicvoidsetTitle(Stringtitle){this.title=title;}publicList<DeptTree>getChildren(){returnchildren;}publicvoidsetChildren(List<DeptTree>children){this.children=children;}publicbooleanisChecked(){returnchecked;}publicvoidsetChecked(booleanchecked){this.checked=checked;}publicbooleanisSpread(){returnspread;}publicvoidsetSpread(booleanspread){this.spread=spread;}}

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

@RequestMapping(value="/checkedGain")@ResponseBodypublicvoidcheckedGain(Stringdata){List<DeptTree>array2=JSONArray.parseArray(data,DeptTree.class);treeData(array2);}//递归输出选中数据privatevoidtreeData(List<DeptTree>array2){for(DeptTreetree:array2){System.out.println(tree.getTitle()+"==="+tree.getId());if(tree.getChildren()!=null){treeData(tree.getChildren());}}}

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

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

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

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

type="text/css">#dept_main, #dept_particulars{width:48.5%;display:inline-block;vertical-align:top;padding:20px;background:white;box-sizing:border-box;}#dept_tree{margin-top:20px;} id="dept_main"> class="layui-elem-field layui-field-title">所有部门 class="layui-btn layui-btn-sm layui-btn-radius layui-btn-normal"lay-demo="addDept">class="layui-icon">添加部门 class="layui-btn layui-btn-sm layui-btn-radius layui-btn-normal"lay-demo="gain">获取选中节点 id="dept_tree">
id="dept_particulars"> class="layui-elem-field layui-field-title">部门详情 id="dept_home"> class="layui-tree-emptyText">无数据 type="text/javascript">layui.use([tree,util,layer],function(){vartree=layui.tree;varutil=layui.util;varlayer=layui.layer;tree.render({elem:#dept_tree,data:getData(),id:treeId,showCheckbox:true,//时候显示复选框onlyIconControl:true,edit:[add,update,del],//操作节点的图标click:function(obj){varid=obj.data.id;$("#dept_home").load("dept/show?id="+id);},operate:function(obj){vartype=obj.type;//得到操作类型:add、edit、delvardata=obj.data;//得到当前节点的数据varelem=obj.elem;//得到当前节点元素varid=data.id;varname=data.title;if(type===add){//增加节点$.post("dept/save",{parentId:id,name:"未命名"},function(result){tree.reload(treeId,{data:getData()});})//返回 key 值return;}elseif(type===update){//修改节点$.post("dept/update",{id:id,name:name},function(){tree.reload(treeId,{data:getData()});})}elseif(type===del){//删除节点$.post("dept/delete",{id:id},function(){tree.reload(treeId,{data:getData()});});};}});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(){varform=layui.form;layero.addClass(layui-form);varsubmitBtn=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});});returnfalse;});});}})})},gain:function(){varcheckData=tree.getChecked(treeId);varstr=JSON.stringify(checkData);$.post(dept/checkedGain,{data:str},function(){});layer.alert(JSON.stringify(checkData),{shade:0});}});});functiongetData(){vardata=[];$.ajax({url:"dept/treeload",//后台数据请求地址type:"post",async:false,success:function(resut){data=resut;}});returndata;}

以上为个人经验,希望能给大家一个参考,也希望大家多多支持本站。

声明:本站所有文章,如无特殊说明或标注,均为爬虫抓取以及网友投稿,版权归原作者所有。