史上最全,在vue中使用zTree实现树型结构
最近做了蛮多需求都是在vue里面去操作dom,着实让人头大
需求如下:要求树形结构按照设计稿样式(ztree原本的样式ui接受不了)
鼠标浮动上去的时候,有功能按钮出现,浮在功能按钮上面,出现对应的功能提示
支持树形结构搜索,搜索后高亮显示,并出现默认选中样式
树结构很长,支持滚动(会有一个问题,浮动的功能提示信息受overflow的影响,那么第一行的提示信息会被覆盖)
好了,看下做好的效果图吧:
搜索高亮,支持模糊搜索,右侧其实还有一个面包屑,和这个是联动效果,主要是介绍ztree用法,就不说太多业务场景了


这个样式比ztree自带的样式好看多了,需要修改ztree的样式文件,下面就说说用法步骤吧
ztree没有依赖包可以下载,需要在官网上下载后在vue中引用,ztree依赖jquery,使用前先安装好jqery的依赖包,在需要使用的页面引入,ztree的样式文件在main.js中引入使用


首先就是样式问题,找到页面上原本ztree的样式文件,替换ui切的icon就行,基操,就不赘述了
在data()中做ztree的定义,以下内容写在setting {}对象中,vue的data(){return {}}中定义ztreeObj:null,//zTree对象,用于赋值ztree初始化之后的树对象
setting: {treeId: "id",data: {simpleData: {enable: true,idKey:'id',pIdKey:'pId',rootPId:'-'},key:{isParent: "parent",name:'name',title:'name'},},callback: {// 树的点击事件onClick: this.zTreeOnClick,onAsyncSuccess: this.zTreeOnAsyncSuccess,//异步加载成功的funbeforeAsync: this.zTreeBeforeAsync,//异步加载前的回调onExpand: this.expandNode//节点展开回调},edit: {//是否支持拖拽,enable我改成了false,代表此功能禁用,也可以直接删除,为了防止后续他们提这个需求,所以我还是写上了drag: {isMove: true,prev: true,next: true,inner: true},enable: false,showRemoveBtn: false,showRenameBtn: false},view:{addHoverDom:this.addhoverdom,//ztree提供的可以自定义添加domremoveHoverDom:this.removehoverdom,//和addHoverDom成对出现,离开节点时需要移除自定义的domfontCss: function (treeId, treeNode) {//设置所有节点的样式,我这里的代码的意思是,当前节点是否高亮(树节点搜索的时候会高亮命中的节点),高亮就设置节点高亮样式,否则就是普通样式return (!!treeNode.highlight) ? {'backgroundColor':'#F6F7F8','display':'inline-block','width':'95%','min-width':'225px','padding':'3px 0'} : {color: "#000000", "font-weight": "normal"};},showTitle:true //是否显示titie属性(就是鼠标放到节点上是否显示html元素的title属性)},async:{//节点很多的情况下设置懒加载enable:true,//是否开启异步加载模式contentType: "application/json",//Ajax 提交参数的数据类型dataType: "json",//Ajax 获取的数据类型url:'/aa/bbb/ccc/loadTree',//点击树的展开节点,会重新加载子节点,这里是请求的url地址type:'POST',//当前的请求类型// autoParam:['id=parentId'],//将节点的pId值作为参数传递// otherParam:{'userId':()=>{return this.userId;},'userName':()=>{return this.userName;},'tenantId':()=>{return this.tenantId;}}otherParam:{'userId':this.userId,'userName':this.userName,'tenantId':this.tenantId,'parentId':'-'},//每次异步请求携带的参数dataFilter:function(treeId, parentNode, resp){sessionStorage.setItem('tongbunodes',JSON.stringify(resp.jsSubjects.children));return resp.subjects;}//对 Ajax 返回数据进行预处理的函数,就是异步加载返回的数据你可以处理一下再用}},
页面结构
//ul为存放树结构的位置,同一个页面如果有多个树结构,id不能重复(id本身也不能重复),否则就会遇到一个问题是你加载第二个树的时候,会发现第二个树没有渲染出来,因为他把第一个替换掉了
异步加载前的回调:
zTreeBeforeAsync(treeId, treeNode){if(this.ztreeObj){//树节点对象是否存在,如果存在,说明已经加载了根节点,通过点击展开节点,开启加载下一层级节点信息,这个时候传递的上级节点id的参数需要发生变化if(treeNode){this.ztreeObj.setting.async.otherParam['parentId'] = treeNode.id;}else{
//说明加载的是第一级根节点,无上级节点信息,和后台约束好传递什么,这里我传‘-’this.ztreeObj.setting.async.otherParam['parentId'] = '-';}}return true;},
树加载,在mounted中初始化
mounted(){this.ztreeObj = $.fn.zTree.init($("#treeDemo"), this.setting);this.ClientRect = $('#panel-top')[0].getBoundingClientRect()
//getBoundingClientRect获得dom元素在页面上的位置,无关定位,卷曲高度等,目的是为了获得“专题目录”这个固定定位的元素在页面中的位置,而当树节点滚动加载的时候,也可以使用同样的方法获得当前树节点在页面的位置,两者之间的差值是不变的,就可以将当前位置的第一个(并非dom结构中的第一个位置)的功能提示信息放到下面},
在生命周期函数中初始化树节点之后,页面就有节点信息了,现在需要将鼠标hover到节点上去的时候出现灰色背景,并且出现六个功能小图标
addhoverdom(treeId, treeNode){console.log(111)//this.removehoverdom();let _this= this// treeId 对应的是当前 tree dom 元素的 id// treeNode 是当前节点的数据var aObj = $("#" + treeNode.tId + "_a"); // 获取节点 domlet spanObj = $("#" + treeNode.tId + "_a").find('.node_name')if ($("#diyBtnGroup").length>0) return; // 查看是否存在自定义的按钮组,因为 addHoverDom 会触发多次${treeNode.name}
var editStr = ` `;spanObj.append(editStr);if(treeNode.isHover){//这里是核心代码,遇到一个贼大的坑,搞了两天,hover上去之后,图标可以显示出来,但是如果是点击某一个节点,再将鼠标移入到别的dom结构,会发现小图标不会出现,isHover是节点自带属性,hover上去的时候,给a标签设置hover属性,然后给a绑定一个鼠标离开事件, 设置鼠标从节点移出时,删除由 addHoverDom 增加的按钮aObj.css({'backgroundColor':'#F6F7F8','display':'inline-block','width':'95%','min-width':'225px','padding':'3.5px 0'});aObj.on('mouseleave',function(){_this.removehoverdom(null,treeNode);});}//$("#diyBtnGroup").css({'backgroundColor':'red'});var bObj = $("#" + treeNode.tId + "_icon"); // 获取节点 dombObj.css({'vertical-align':'inherit'});//避免鼠标浮上去之后,发生抖动(巨大一个坑,也搞了两天)var btnHot = $('#diyBtn_'+treeNode.id + '_hot');var btnDelete = $('#diyBtn_'+treeNode.id + '_delete');var btnModify = $('#diyBtn_'+treeNode.id + '_modify');var btnAdd = $('#diyBtn_'+treeNode.id + '_add');var btnOffline = $('#diyBtn_'+treeNode.id + '_offline');var btnOnline = $('#diyBtn_'+treeNode.id + '_online');// 小图标点击事件if (btnDelete) btnDelete.bind("click", function (){_this.delete(treeNode)});if (btnAdd) btnAdd.bind("click", function (){_this.add(treeNode)});if (btnModify) btnModify.bind("click", function (){_this.modify(treeNode)});if (btnHot) btnHot.bind("click", function (){_this.hot(treeNode)});if (btnOffline) btnOffline.bind("click", function (){_this.offline(treeNode)});if (btnOnline) btnOnline.bind("click", function (){_this.online(treeNode)}); // 小图标hover事件if (btnDelete) btnDelete.bind("mouseover", function (e){_this.deleteiconhover(treeNode,e)});if (btnAdd) btnAdd.bind("mouseover", function (e){_this.addiconhover(treeNode,e)});if (btnModify) btnModify.bind("mouseover", function (e){_this.modifyiconhover(treeNode,e)});if (btnHot) btnHot.bind("mouseover", function (e){_this.hoticonhover(treeNode,e)});if (btnOffline) btnOffline.bind("mouseover", function (e){_this.offlineiconhover(treeNode,e)});if (btnOnline) btnOnline.bind("mouseover", function (e){_this.onlineiconhover(treeNode,e)}); // 鼠标离开事件if (btnDelete) btnDelete.bind("mouseleave", function (){_this.deleteiconleave(treeNode)});if (btnAdd) btnAdd.bind("mouseleave", function (){_this.addiconleave(treeNode)});if (btnModify) btnModify.bind("mouseleave", function (){_this.modifyiconleave(treeNode)});if (btnHot) btnHot.bind("mouseleave", function (){_this.hoticonleave(treeNode)});if (btnOffline) btnOffline.bind("mouseleave", function (){_this.offlineiconleave(treeNode)});if (btnOnline) btnOnline.bind("mouseleave", function (){_this.onlineiconleave(treeNode)});},
- 热点
- 删除
- 编辑
- 下线
- 上线
- 新建
鼠标移除节点事件,和addhoverDom成对出现
removehoverdom(treeId, treeNode){// 为了方便删除整个 button 组,上面我用 #diyBtnGroup 这个包了起来,这里直接删除外层即可,不用挨个找了。$("#diyBtnGroup").unbind().remove();var aObj = $("#" + treeNode.tId + "_a"); if(!treeNode.highlight){aObj.css({'backgroundColor':'#fff'});}},
以删除功能为例,其他的功能一样写就完事了
deleteiconhover(treeNode,event){let domname = $('.deletehover')domname.css({'display':'inline-block'})this.measurement(domname,event)this.isHovering =treeNode.name || treeNode.text;记录当前鼠标所在的节点的title,原因是鼠标移入节点,我希望是弹出title,移到小图标上的时候,由于它属于这个dom结构,也会出现title提示和功能提示框,会发生遮挡this.operNode = treeNode;$("#" + treeNode.tId + "_a").attr('title','');//鼠标移入小图标的时候移除title属性},deleteiconleave(){$('.deletehover').css({'display':'none'})$("#" + this.operNode.tId + "_a").attr('title',this.isHovering);//鼠标离开的时候加上title属性(为我聪明的头脑骄傲)},
//上面提到的,如果是靠近“专题目录最近的一个,将功能提示移到下方”measurement(domname,event){let coordinates = $('#diyBtnGroup')[0].getBoundingClientRect()if(coordinates.bottom - this.ClientRect.bottom < 35){domname.css({'display':'inline-block','top':'25px'})} else {domname.css({'display':'inline-block','top':'-35px'})}},
接下来就是用户搜索树节点的时候,我希望高亮命中的节点树,这部分提供参考,由于我们的后台比较懒惰,不愿意按照我的想法返回后台数据(我的想法:既然要求是懒加载返回数据,那么我希望第一次请求返回给我所有的根节点信息,当我点击某一个带+号的根节点,将该id传递异步加载返回该节点下所有的子节点)我们后台的处理方式:第一次请求,给我返回了两个数组,一个是所有的根节点,第二个,给我返回了已存在的所有节点,包括子节点,真是优秀。so我在data()中又定义了一个同步的setting
tongbusetting: {treeId: "id",data: {simpleData: {enable: true,idKey:'id',pIdKey:'parentId',rootPId:'-'},key:{isParent: "parent",name:'text',title:'text'},},callback: {// 树的点击事件onClick: this.zTreeOnClick,onExpand: this.expandNode},edit: {drag: {isMove: true,prev: true,next: true,inner: true},enable: false,showRemoveBtn: false,showRenameBtn: false},view:{addHoverDom:this.addhoverdom,removeHoverDom:this.removehoverdom,fontCss: function (treeId, treeNode) {return (!!treeNode.highlight) ? {'color':'red','background-color':'#F6F7F8','display':'inline-block','width':'95%','min-width':'225px','padding':'3px 0','font-weight':'bold'} : {color: "#000000", "font-weight": "normal"};},showTitle:true}},
// 树搜索searchtrees(){/**以下为同步搜索节点的方法***/if(this.searchProject == null || '' == this.searchProject){return this.$message.error('请输入搜索关键字');}// let _this = this;// this.getSubjectTrees(function(){// _this.updateNodes(false);// let firstNodes = _this.ztreeObj.getNodes();// let nodeArr = _this.ztreeObj.transformToArray(firstNodes);// _this.oldNodes = nodeArr.filter(item=>item.text.indexOf(_this.searchProject) > -1);// // _this.oldNodes = _this.ztreeObj.getNodesByParamFuzzy("name", _this.searchProject, null);// _this.updateNodes(true);// });if(this.ztreeObj){this.ztreeObj.destroy();}// 回调成功之后,初始化树结构this.ztreeObj = $.fn.zTree.init($("#treeDemo"), this.tongbusetting, JSON.parse(sessionStorage.getItem('tongbunodes')));this.updateNodes(false);let firstNodes = this.ztreeObj.getNodes();//ztree的回调,返回所有根节点简单数据结构let nodeArr = this.ztreeObj.transformToArray(firstNodes);//ztree回调,返回所有节点简单数据结构this.oldNodes = nodeArr.filter(item=>item.text.indexOf(this.searchProject) > -1);//判断是否有命中节点if(this.oldNodes.length == 0) return this.$message.error('没有该专题资源')this.updateNodes(true);},
将命中的节点高亮
updateNodes(flag){//遍历搜索高亮显示for (var i = 0, l = this.oldNodes.length; i < l; i++) {this.oldNodes[i].highlight = flag;this.ztreeObj.updateNode(this.oldNodes[i]);this.ztreeObj.expandNode(this.oldNodes[i].getParentNode(), flag, null, null, false);}},
插补一下,由于考虑到某些客户会暴力检测,故意输入很长的节点名称,导致页面难看,我在节点展开回调增加如下代码:设置每一个层级展开的宽度,超出省略号显示,并且在input输入的时候限制最大长度为20个字符,就可以了

// 树节点展开事件expandNode(event, treeId, treeNode){switch(treeNode.level){case 0:$(`.level${treeNode.level+1}>.node_name`).css({'width':'200px'})break;case 1:$(`.level${treeNode.level+1}>.node_name`).css({'width':'180px'})break;case 2:$(`.level${treeNode.level+1}>.node_name`).css({'width':'160px'})break;case 3:$(`.level${treeNode.level+1}>.node_name`).css({'width':'140px'})break;case 4:$(`.level${treeNode.level+1}>.node_name`).css({'width':'130px'})break;case 5:$(`.level${treeNode.level+1}>.node_name`).css({'width':'115px'})break;case 6:$(`.level${treeNode.level+1}>.node_name`).css({'width':'95px'})break;case 7:$(`.level${treeNode.level+1}>.node_name`).css({'width':'80px'})break;case 8:$(`.level${treeNode.level+1}>.node_name`).css({'width':'75px'})break;case 9:$(`.level${treeNode.level+1}>.node_name`).css({'width':'60px'})break; }if(treeNode.level>9){$(`.level${treeNode.level+1}>.node_name`).css({'width':'60px'})}},
最后,来看下动态效果吧
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
