最近做了蛮多需求都是在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,//异步加载成功的fun
                    beforeAsync: 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提供的可以自定义添加dom
                    removeHoverDom:this.removehoverdom,//和addHoverDom成对出现,离开节点时需要移除自定义的dom
                    fontCss: 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 返回数据进行预处理的函数,就是异步加载返回的数据你可以处理一下再用
                }
            },

页面结构

                <div class="panel-bottom">
                    <div class="data-trees-style data-trees-style-hasvy">
                        <ul id="treeDemo" class="ztree" style="padding-top:13px"></ul>
                    </div>  
                </div>  
//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"); // 获取节点 dom
            let spanObj = $("#" + treeNode.tId + "_a").find('.node_name')
            if ($("#diyBtnGroup").length>0) return; 
            // 查看是否存在自定义的按钮组,因为 addHoverDom 会触发多次                          <p class="toolnames" id='diyBtn_${treeNode.id}_names'>${treeNode.name}</p>      <li id='diyBtn_space_${treeNode.id}'> </li>
            var editStr = `<div id='diyBtnGroup'>
    
                            <ol>       
                                <li class='mydiydiv hot' id='diyBtn_${treeNode.id}_hot' οnfοcus='this.blur();'><span class="tooltips hothover">热点</span></li>
                                <li class='mydiydiv del'  id='diyBtn_${treeNode.id}_delete' οnfοcus='this.blur();'><span class="tooltips deletehover">删除</span></li>
                                <li class='mydiydiv mod' id='diyBtn_${treeNode.id}_modify' οnfοcus='this.blur();'><span class="tooltips edithover">编辑</span></li>
                                <li class='mydiydiv offline'  id='diyBtn_${treeNode.id}_offline' οnfοcus='this.blur();'><span class="tooltips offhover">下线</span></li>
                                <li class='mydiydiv online' id='diyBtn_${treeNode.id}_online' οnfοcus='this.blur();'><span class="tooltips onhover">上线</span></li>
                                <li class='mydiydiv add'  id='diyBtn_${treeNode.id}_add' οnfοcus='this.blur();'><span class="tooltips addhover">新建</span></li>
                            </ol>
                        </div>`;
            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"); // 获取节点 dom
            bObj.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'})
            }
        },

最后,来看下动态效果吧

Logo

基于 Vue 的企业级 UI 组件库和中后台系统解决方案,为数万开发者服务。

更多推荐