iview节点树添加拖拽功能

懒加载-搜索节点-新增节点-删除节点
因为是手打,可能会有一些地方打错,会尽可能检查无误
拖拽相关有备注出来,相关功能基本参考element-ui节点树拖拽
<template>
  <div class="menuBox">
    <div id="treeBox">
      <p class="tilte">菜单列表</p>
      <div class="button-box-list search">
        <Input v-model="inputVal">
        <div>
          <Button  type="primary" @click="search(inputVal)">
             <span class="hzbankicon hzbankicon-btn-searc"></span>
             <span>搜索</span>
          </Button>
        </div>
      </div>
      <Tree :data="treeData" ref="menuTree" :load-data="loadData" :render="renderContent"></Tree>
    </div>
    <div id="formBox">
      <p>菜单详情</p>
      <Form ref="menuData" :model="menuData" label-position="right" inline :label-width="126" :rules="ruleValidate">
        <FormItem label="菜单编号" prop="menuCode">
          <Input v-model="menuData.menuCode">
        </FormItem>
        <FormItem label="父节点编号" prop="parentId">
          <Input v-model="menuData.parentId">
        </FormItem>
        <FormItem label="菜单名称" prop="menuName">
          <Input v-model="menuData.menuName">
        </FormItem>
        <FormItem label="菜单状态" prop="state">
          <HZSelect class="selectClass" data-key="menuState" :dataArr="menuState" 
            data-url="..." :selectValue.sync="menuData.state.code"></HZSelect>
        </FormItem>
        <FormItem label="菜单描述" prop="remark">
          <Input v-model="menuData.remark">
        </FormItem>
        <FormItem label="菜单属性" prop="menuAttr">
          <HZSelect class="selectClass" data-key="menuAttr" :dataArr="menuAttr" 
            data-url="..." :selectValue.sync="menuData.menuAttr.code"></HZSelect>
        </FormItem>
        <FormItem label="菜单URL" prop="menuUrl">
          <Input v-model="menuData.menuUrl">
        </FormItem>
      </Form>
      <p class="submitBox">
        <Button type="primary" class="mr-5" @click="submit(menuData)" id="submit"></Button>
      </p>
    </div>
  </div>
</template>
<script>
  export default{
    name:"***",
    data(){
      return{
        menuState:[],
        menuAttr:[],
        menuData:{          //表单数据
          menuCode:'',
          parentId:'',
          menuName:'',
          state:'',
          remark:'',
          menuAttr:'',
          menuUrl:'',
          id:'',
          menuLevel:'',
        },
        initData:[],            //保存初始化的数据
        ruleValidate:{},     //表单验证规则
        treeData:[{           //最初节点数据
          title:"菜单管理",
          loading:false,
          hasChild:true,
          expand:true,
          searched:false,
          id:0,
          children:[]
        }],
        dragState:{            //用于拖拽数据存储相关参数
          draggingNode:null,
          dropNode:null,
          allowDrop:true
        },
        currentData:null,      //拖拽的数据
        inputVal:null,      //新查询条件
        oldInputVal:null    //旧查询条件
      }
    },
    mounted(){
      const {menuData}=this.$refs;
      this.$validate.getSelect(this);        //获取下拉框的数据
      this.setChildren(this.treeData[0],'0');        //初始化打开一级菜单
    },
    methods:{
      loadData(item,callback){          //异步请求数据(懒加载)
        let that=this;
        that.$http.get('路径',参数).then(res=>{
          if(res.success){
            item.children=that.handleData(res.data);      //将子节点推入
            callback(item.children);              //iview自身方法
          }else{
            //提示错误信息;
          }
        })
      },
      handleData(data){        //操作数据
        let that=this;
        data.forEach(function(val){
          val.title=val.menuName;    //节点名称
          if(val.hasChild==1){  //如果有子节点,传入loading和children属性
            val.loading=false;
            val.children=[];
          }
          val.hasChild=val.hasChild==1?true:false;      //判定是否有子节点
          val.selected=false;            //用于在选中节点时调用
          val.searched=false;          //用于在搜索节点时判定是否相关节点
        })
        return data;
      },
      //新增节点,这里的逻辑不是很完美,因为是懒加载,我这里是把新增的节点推入原始数据中全部显示
      append(elm,node,data){          
        let that=this;
        let children=data.children||[];
        that.$http.post('******',参数).then(resNew=>{      //新节点基本信息
          if(resNew.success){
            that.$http.get('*******',参数).then(resAll=>{      //获取所有子节点
              children=that.handleData(resAll.data);
              that.$set(data,'children',children);          //
              let icon=elm.getElementsByClassName('hzbankicon')[0];
              if(icon.classList.contains('hzbankicon-content-p')){    //添加子节点后,如果原本无子节点的节点修改图标为文件夹
                icon.classList.remove('*******');
                icon.classList.add('*******');
              }
              if(!data.expand){     //设置自动展开父节点
                that.$set(data,'expand',true);
              }
              for(let key in that.menuData){    //展示新菜单信息
                that.menuData[key]=resNew.data[key];  
              }
              that.initData=JSON.parse(JSON.stringify(that.menuData));
              //提示新增成功
            })
          }
        })
      },
      remove(parentLi,root,node,data){//删除节点
        let that=this;
        const parentKey=root.find(el=>el===node).parent;    //获取父节点nodeKey
        const parent=root.find(el=>el.nodeKey===parentKey).node;
        const index=parent.children.indexOf(data);
        that.$MsgModal.confirm({
          content:"确定删除当前节点?",
          title:"提示",
          icon:"warning",
          ok:function(){
            that.$http.delete('********').then(res=>{
              if(res.success){
                parent.children.splice(index,1);
                let parentId=data.parentId;
                root.forEach(function(val,index){    
                  if(parentId=val.node.id){    //只有一个子节点时,删除后,修改图标
                    let childrenNum=val.node.children.length;
                    if(childrenNum==0){
                      let icon=parentLi.getElementsByClassName('hzankicon')[0];
                      icon.classList.remove('******');
                      icon.classList.add('******');
                      delete parent.loading;
                      dalete parent.children;
                    }
                  }
                });
                //提示删除成功
              }
            })
          }
        })
      },
      submit(name){
        let that=this;
        let data=that.$validate.toJsonData(that[name]);//操作数据取消空值,自定义的方法
        if(!that.$validate.compare(data,that.initData)){    //校验是否修改数据
         // 如果未进行修改提示未修改,不能提交·
          return;
        }
        that.$refs[name].validate(valid=>{      //提交表单验证规则是否通过
          if(valid){
            that.$MsgModal.confirm({
              content:"确定保存修改吗?",title:"提示",icon:"warning",
              ok:function(){
                that.$http.put('********',参数).then(res=>{
                  document.getElementsByClassName('ivu-tree-title-selected')[0].innerHTML=data.menuName;
                  let selectedNode=that.$refs.menuTree.getSelectedNodes()[0];        //获取节点数据并进行更新
                  for(let key in selectedNode){
                    selectedNode[key]=data[key];
                  }
                  if(res.success){
                    //提示修改成功
                  }else{
                    //提示失败并展示原因
                  }
                })
              }
            })  
          }
        })
      },
      iconType(hasChild){      //图标对应class
        let iconType='hzbankicon';
        if(hasChild){
          iconType+=" hzankicon-content-f"
        }else{
          iconType+=" hzankicon-content-f";
        }
        return iconType;
      },
      renderContent(h,{root,node,data}){      //自定义节点内容
        let that=this;
        return h('span',{
          class:'node-box none',
          attrs:{draggable:"true",       //可拖拽},
          style:{userSelect:'none'      //禁止选中},
          on:{
            mouseover:(e)=>{//鼠标移入的效果},
            mouseleave:(e)=>{//鼠标移出的效果},
 //拖拽相关——————————————————————————————————————————————————start
            dragstart:(e)=>{
              this.dragState.draggingNode=node;
            },
            dragover:(e)=>{
              let dropNode;
              //便利节点,找到与鼠标上相匹配的节点获取信息
              root.find(el=>{
                if(el.node.title===e.target.outerText){
                  dropNode=el;
                }
              });
              const draggingNode=that.dragState.draggingNode;
              if(!draggingNode || !dropNode)return;      
              let dropPrev=true;
              let dropInner=true;
              let dropNext=true;
              if(typeof(that.allowDrop)==='function'){
                dropPrev=that.allowDrop(draggingNode.node,dropNode,'prev');
                dropInner=that.allowDrop(draggingNode.node,dropNode,'inner');
                dropNext=that.allowDrop(draggingNode.node,dropNode,'next');
              }
              e.dataTransfer.dropEffect=dropInner?'move':'none';
              if(dropPrev||dropInner||dropNext){
                that.dragState.dropNode=dropNode;
              }
              //如果拖拽节点的父节点=指向节点,不能移入
              if(dropNode.node.id===draggingNode.node.parentId){
                dropInner=false;
              }  
              //如果已经是最后一个元素,则不能在同一父节点下往后移动
              if(dropNode.parent===draggingNode.parent 
                  && root[dropNode.nodeKey+1] 
                  && root[dropNode.nodeKey+1].node.id===draggingNode.node.id){
                dropNext=false;
              }
              //如果是第一个节点,则不能在同一父节点下往前移动
              if(dropNode.parent===draggingNode.parent 
                  && draggingNode.nodeKey-1>=0
                  && root[dropNode.nodeKey-1].node.id===draggingNode.node.id){
                dropPrev=false;
              }
              if(draggingNode.node.id===dropNode.node.id || draggingNode.node.id===dropNode.node.parentId){
                dropPrev=false;
                dropInner=false;
                dropNext=false;
              }
              const targetPosition=e.currentTarget.getBoundingClientRect();
              const treePosition=document.getElementsByClassName('ivu-tree')[0].getBoundingClientRect();
              let dropType;
              const prevPercent=dropPrev?(dropInner?0.25:(dropNext?0.45:1)):-1;
              const nextPercent=dropNext?(dropInner?0.75:(dropPrev?0.55:0)):1;
              const distance=e.clientY-targetPosition.top;
              if(distance<targetPosition.height*prevPercent){
                dropType='before';
              }else if(distance>targetPosition.height*nextPercent){
                dropType='after';
              }else if(dropInner){
                dropType='inner';
                e.currentTarget.classList.add('inner');
              }else{
                dropType='none';
              }
              that.dragState.dropType=dropType;
            },
            dragleave:(e)=>{
              e.currentTarget.classList.remove('inner');
            },
            dragend:(e)=>{
              const {draggingNode,dropType,dropNode}=that.dragState;
              e.preventDefault();
              e.dataTransfer.dropEffect='move';
              if(draggingNode && dropNode){
                let draggingNodeCopy={};
                if(dropType !=='none'{
                  that.treeData=that.dragAboutDel(that.treeData,draggingNode.nodeKey);
                }
                draggingNodeCopy=that.currentData;
                if(dropType==='before'){
                  that.treeData=that.dragAboutAdd(that.treeData,dropNode.nodeKey,draggingNodeCopy,'before');
                }else  if(dropType==='after'){
                  that.treeData=that.dragAboutAdd(that.treeData,dropNode.nodeKey,draggingNodeCopy,'after');
                }else  if(dropType==='inner'){
                  that.treeData=that.dragAboutAdd(that.treeData,dropNode.nodeKey,draggingNodeCopy,'inner');
                }
                that.dragState.draggingNode=null;
                that.dragState.dropNode=null;
                that.dragState.allowDrop=true;
              }
            }
          }
        },[
          h('span',{
            class:'title-box',
            on:{
              click:()=>{            //点击选中节点
                if(node.nodeKey>0){
                  this.$refs.menuTree.handleSelect(node.node.nodeKey);
                  for(let key in this.menuData){
                    this.menuData[key]=node.node[key];
                  }
                  this.initData=JSON.parse(JSON.stringify(this.menuData));
                }
              }
            }
          },[
            h('span',{
              class:this.iconType(node.node.hasChild),
              style:{marginRight:'3px'},
            }),
            h('span',{
              attrs:{'data-id':data.id},
              class:[
                'ivu-tree-title',{
                  ['*******']:node.node.searched,        //是否查询节点
                  ['*******']:node.node.selected          //是否选中节点
                }
              ],
            },data.title)
          ]),
          h('span',{          //操作按钮
            class:'btn-box isHide'
          },[
            h('span',{        //新增节点
              class:'***** ***** menu-add',
              on:{
                click:(e)=>{
                  let elmLi=e.currenetTarget.parentNode.parentNode.parentNode;
                  this.append(elmLi,node,data);
                }
              }
            }),
            h('span',{        //删除节点
              class:'*** *** menu-remove',
              on:{
                click:(e)=>{
                  let parentLi=e.currenetTarget.parentNode.parentNode.parentNode.parentNode.parentNode;
                  this.remove(parentLi,root,node,data)
                }
              }
            })
          ])
        ]);
      },
      dragAboutDel(data,key){        //先删除拖拽节点
        for(let i=0;i<data.length;i++){
           if(data[i].nodeKey===key){
            this.currentData=data[i];
            data.splice(i,1);
            break;
          }else if(data[i].hasChild==true && data[i].children && data[i].children.length>0){
            //当没有子节点的时候,删除loading,children;优化菜单显示
            data[i].children=this.dragAboutDel(data[i].children,key);
            if(data[i].children.length==0){
              data[i].hasChild=false;
              delete data[i].children;
              delete data[i].loading;
            }
          }
        }
        return data;
      },
      dragAboutAdd(data,key,targetData,type){    //根据拖拽种类操作节点数据
        for(let i=0;i<data.length;i++){
           if(data[i].nodeKey===key){
              if(type=='before'){
                data.splice(i,0,targetData);
                break;
              }else if(type=='after'){
                data.splice(i+1,0,targetData);
                break;   
              }else if(type=='inner'){
                if(!data[i].hasChild){
                  data[i].hasChild=true;
                  this.$set(data[i],'children',[targetData]);
                  this.$set(data[i],'expand',true);
                  data[i].loading=false;
                }else{
                  data[i].children.push(targetData);
                }
                break;
              }
           }else if(data[i].hasChild==true && data[i].children){
             data[i].children=this.dragAboutAdd(data[i].children,key,targetData,type)
           }
        }
        return data;
      },
      allowDrop(draggingNode,dropNode,type){
        return true;
      },
 //拖拽相关——————————————————————————————————————————————————end
      setChildren(current,id){
        let that=this;
        that.$http.get('********',参数).then(res=>{
          if(res.success){
            current.children=that.handleData(res.data);
          }else{
            //提示获取数据失败
          }
        });
      },
      search(val){      //搜索节点
        let that=this;
        if(!val){
          //提示输入搜索关键词
        }else if(that.oldInputVal==null&&val || that.oldInputVal!=val){
          that.$http.get('********').then(res=>{
            if(res.success){
              if(res.data.length>0){
                let data=res.data,treeData=that.treeData[0].children;
                that.removeSearched(treeData);
                that.mapSearchData(data,treeData,val);
              }else{
                //提示未找到相关节点
              }
            }else{
              //提示查询失败
            }
          })
        }else{
        }
        that.oldInputVal=val;
      },
      removeSearched(treeData){      //给所有节点移除查询样式
        let that=this;
        for(let i=0,len=treeData.length;i<len;i++){
          treeData[i].searched=false;
          if(treeData[i].children){
            that.removeSearched(treeData[i].children);
          }
        }
      },
      mapSearchData(data,treeData,val){      //便利节点找到搜索词条相关
        let that=this;
        for(let i=0,len1=data.length;i<len1;i++){
          for(let j=0,len2=treeData.length;j<len2;j++){
            if(data[i].id==treeData[j].id){
              if(data[i].menuName.indexOf(val)>-1){
                treeData[j].searched=true;
                if(data[i].children && data[i].children.length>0){
                  if(treeData[j].children.length<1){
                    that.$http.get('********',参数).then(res=>{
                      if(res.success){
                        treeData[j].children=that.handleData(res.data);
                        that.$set(treeData[j],'expand',true);
                        that.mapSearchData(data[i].children,treeData[j].children,val);
                      }else{
                        //提示失败
                      }
                    })
                  }else{
                    that.mapSearchData(data[i].children,treeData[j].children,val);
                  }
                }
              }else{
                if(data[i].children && treeData[j].children.length<1){
                  that.$http.get('********',参数).then(res=>{
                    if(res.success){
                      treeData[j].children=that.handleData(res.data);
                      that.$set(treeData[j],'expand',true);
                      that.mapSearchData(data[i].children,treeData[j].children,val);
                    }else{
                      //提示失败
                    }
                  })
                }else{
                  that.mapSearchData(data[i].children,treeData[j].children,val);
                }
              }
            }
          }
        }    
      }
    }
  }
</script>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350