1
近期项目用到大量的树结构,比如目录树、文章标记动态生成树结构,实现的过程是基于vue的框架,结合vue数据驱动应用递归函数实现数据结构的增删改查

一切思绪的来源,结合vue官方提供的树形图实例,可以轻松实现自定义开源树结构,github上优秀的开源插件我都看过,都是基于树形结构实现的

vue树形视图


<item
  ref="treeNode"
  @handle="setSelectedNode"
  v-for='(item, index) in ztreeData'
  v-show='ztreeData&&elements'
  class="menuLi"
  :model="item">
</item>
 <script type="text/x-template" id="item-template">
      <li>
        <div style="positon:relative" @click="handle(model)" >
          <p class="col-md-6" style="position:absolute;left:0;top:0;bottom:0;" :id="'node'+model.resultId">
            <span class="paddingLeft" :style="{paddingLeft:(model.level==0? 12: model.level*32)+'px'}">
              <i class="fa fa-chevron-right pull-left" @click.stop="toggle(model)" :class="[!isFolder?'iconHidden':'', model.isExpand?'iconRotate':'']"></i>
              
              <i class="glyphicon glyphicon-edit pull-right" style="top:4px;"></i>
            </span>
          </p>
          <p class="col-md-6 text-center oldContent pull-right" 
            style="border-left:1px solid #d9d9d9;"
            :title="model.oldContent">
            {{model.oldContent}}
          </p>
        </div>
        <ul v-show="model.isExpand&&isFolder">
          <item
            class="item"
            v-for="(model, index) in model.children"
            :key="index"
            @handle="emitHandle"
            :model="model">
          </item>
        </ul>
      </li>
    </script>
以上是定义模板以及在页面中应用,首先遍历treedata把单项传入子组件也就是本例的模板,下面js文件

// 定义子组件
Vue.component('item', {
  template: '#item-template',
  props: {
    model: Object,
  },
  data: function () {
    return {
      openr:false,
    }
  },
  computed: {
    isFolder: function () {
      return this.model.children &&
      this.model.children.length;
    }
  },
  methods: {
    //获取选中节点数据
    toggle(model){
      if (this.isFolder) {
        vm.$set(model, 'isExpand', !model.isExpand);
      }
    },
    //获取选中节点数据以及设置选中状态
    handle (item) {
      this.emitHandle(item)
      console.log(item)
      var nodeId;
      if (event.path[0].id) {
        nodeId = event.path[0].id;
      }
      else if(event.path[1].id){
        nodeId = event.path[1].id;
      }
      else{
        nodeId = event.path[2].id;
      }
      setMouseMenu(nodeId,'.treeMenu');
    },
    emitHandle (item) {
      this.$emit('handle', item)
    }
  }
})
// 定义父组件
var vm = new Vue({
    el: '#bookMarker',
    data: {
      templateName:"",//内容模板
      associations:'',//症状关系
      elements:'', //模板下elements数组
      allIsExpand: true,
      dialogTit:'', //弹框的title
      getRangeText:'',//标引选中的文本
      resultName:'',//弹框input值
      elementId:"",
      elementType:'',
      orderNum:'',
      postArray:[],
      off:false,
      radio:'',
      scrollTop:0,//codemirror滚动条高度
      height:'',//codemirror内容高度
      oldContent:'',//子组件自定义title属性数据
      associationsElements:'',
      ariaHidden:true,//语义弹框显隐控制
      ztreeData:[],//ztree的数据列表               
    },
    created(){
      this.getMarkerList();
      this.getTemplateInfo();

    },
    mounted(){
      this.initView();
      this.setCodeMenu();
      setActiveClass();
      $('body').click(()=>{
        this.oldContent='';
      })
      setTreeFoundation(this.$refs.treebox, this.$refs.treeFoundation);
    },
    watch:{
      'ztreeData':{
        handler: function(val, oldval) {
          this.$nextTick(() => {
            this.$refs.treeMenu.style.display='none'
          })
        },
        deep: true
      }
    },
    methods:{
      // 获取选中节点
      setSelectedNode(model){
        this.off = true;
        var g=function(child){
          child.forEach(function (item, index) {
            if (item.Selected==true) {
              vm.$set(item, 'Selected', false)
            }
            var subChild = item.children;
            if(subChild!=null && subChild.length>0){
              g(subChild);
            }
          });
        } 
        this.ztreeData.forEach(function (item, index) {
          if (item.Selected==true) {
              vm.$set(item, 'Selected', false)
          }
          var child =item.children;
          g(child);
        });
        vm.$set(model, 'Selected', true)
        this.onCodemirrorLight(model);
      },
      getSelectedNode () {
        var roots = [];
        var model;
        this.ztreeData.forEach(function(item,index){
          if(item.parentId ==0){
            roots.push(item);
          }
        });
        var g=function(child){
          child.forEach(function (item, index) {
            if (item.Selected==true) {
              model=item;
              return;
            }
            var subChild = item.children;
            if(subChild!=null && subChild.length>0){
              g(subChild);
            }
          });
        } 
        roots.forEach(function (item, index) {
          if (item.Selected==true) {
              model=item;
              return;
          }
          var child =item.children;
          g(child);
        });
        return model;
      },
      //获取模态框title
      getDialogTitle: function(item){
         this.dialogTit='';
         this.dialogChilTit='';
         if (this.getRangeText) {
            this.dialogTit = item.elementName||item.associationName;
            this.orderNum=item.orderNum;
            this.elementId=item.elementId;
            this.elementType=item.elementType;
            event.target.dataset.toggle='modal';
         }
        else{
          event.target.dataset.toggle='';
        }
      },
      //全部折叠
      allClose(){
        this.updateAllIsExpand(false);
        this.allIsExpand = true;
      },
      //全部展开
      allOpen(){
        this.updateAllIsExpand(true);
        this.allIsExpand = false;
      },
      //删除节点
      removeNode(){
        var item = this.getSelectedNode()
        var index=0;
        if (item.parentId==0) {
          for(var i in this.ztreeData){
            if(this.ztreeData[i]['id']==item.id){
              index=i;
              break;
            }
          }
          this.ztreeData.splice(index,1);
        }
        else{
          var parentItem = this.getNodeItem(item.parentId).children;
          parentItem.splice(parentItem.indexOf(item), 1);
        }
        //重置选中状态
        $('div').removeClass('activeClass');
      },
      //codemirror鼠标右键菜单
      setCodeMenu(){
        var doc = document.getElementById('box_fr');
        var forRight = $('.codeMenu')
        var _this = this;
        doc.oncontextmenu=function(event){//关键点
          var event=event||window.event;
          if (_this.getRangeText) {
            forRight.get(0).style.display="block";
            forRight.get(0).style.left=event.pageX+"px";
            forRight.get(0).style.top=event.pageY+"px";
            return false;
          }
        };
        doc.onclick=function(e){
          forRight.get(0).style.display= "none";
          e.preventDefault(); 
        };
      },
      //设置语义关联
      setAssociation(item){
        this.resultName = item.elementName;
        this.Submit();
      },
      //全部展开收起公共方法
      updateAllIsExpand(boolean){
        var g=function(child,expand){
          child.forEach(function (item, index) {
            var childisExpand;
            childisExpand = vm.$set(item, 'isExpand', expand);
            var subChild = item.children;
            if(subChild!=null && subChild.length>0){
              g(subChild,childisExpand);
            }
          });
        }
        if (this.off==true) {
          var item = this.getSelectedNode()
          if (item) {
            var expand;
            expand = vm.$set(item, 'isExpand', boolean);
            var child =item.children;
            g(child,expand);
          }
        } 
        else{
          this.ztreeData.forEach((ite)=>{
            var expand;
            expand = vm.$set(ite, 'isExpand', boolean);
            var child =ite.children;
            g(child,expand);
          })
        }
      },
      //获取结构模板信息
      getTemplateInfo: function(){
        this.$ajax({
            method: 'post',
            url: '/marker/api/getTemplateInfo',
            data: {
              taskId:19
            }
        }).then((res)=>{
          if (res.data.code === 1000) {
            this.elements = res.data.data.elements;
            this.associations = res.data.data.associations;
            this.templateName = res.data.data.templateName;
            res.data.data.associations.forEach((item)=>{
              this.associationsElements = item.elements
            })
            console.log(res.data.data,'getTemplete');
          }
         },(err)=>{
            console.log(err);
        })
      },
      //获取根节点id
      getRoot(){
        this.off=false;
      },
      //获取树结构列表
      getMarkerList: function(){
         this.$ajax({
            method: 'post',
            url: '/marker/api/getMarkerList',
            data: {
              taskId:19
            }
         }).then((res)=>{
            if (res.data.code === 1000) {
              if (res.data.data==null) {return};
              this.ztreeData = res.data.data;
              var roots = [];
              this.ztreeData.forEach(function(item,index){
                if(item.parentId ==0){
                  roots.push(item);
                }
              });
              var g=function(child,level,isExpand,Selected){
                var childLevel=level+1;
                var childisExpand = isExpand;
                var childSelected = Selected;
                child.forEach(function (item, index) {
                  item.level=childLevel;
                  vm.$set(item, 'isExpand', childisExpand)
                  vm.$set(item, 'Selected', childSelected)
                  var subChild = item.children;
                  if(subChild!=null && subChild.length>0){
                    g(subChild,childLevel,childisExpand,childSelected);
                  }
                });
              } 
              roots.forEach(function (item, index) {
                item.level= 0 ;
                vm.$set(item, 'isExpand', false)
                vm.$set(item, 'Selected', false)
                var child =item.children;
                g(child, item.level, item.isExpand, item.Selected);
              });
              console.log(res.data,'getMarkerList');
            }
         },(err)=>{
            console.log(err);
         })
      },
      //通过resultId获取item
      getNodeItem:function(resultId){
        var resultItem = {};
        var g=function(child,resultId){
          child.forEach(function (item) {
            if (item.resultId==resultId) {
              resultItem = item;
              return;
            }
            var subChild = item.children;
            if(subChild!=null && subChild.length>0){
              g(subChild,resultId);
            }
          });
        } 
        this.ztreeData.forEach((item)=>{
          var child = item.children;
          if (item.resultId==resultId) {
            resultItem = item;
            return;
          }
          g(child, resultId);
        })
        return resultItem;
      },
      //codemirror滚动到顶部
      goDocUp(){
        this.CodeMirrorEditor.scrollTo(0,0)
      },
      //codemirror滚动到底部
      goDocDown(){
        var initH = 5000
        this.CodeMirrorEditor.scrollTo(0,this.height+ initH+ this.scrollTop)
      },
      //codemirror显示高亮
      onCodemirrorLight:function(item){
        if (!item) {return};
        var startPos = item.startPos, endPos = item.endPos, strat, end;
        //将返回_index转为Pos对象
        strat = this.posFromIndex(startPos-1);
        end = this.posFromIndex(endPos-1);
        //文本高亮
        this.CodeMirrorEditor.setSelection(strat,end);
        // this.CodeMirrorEditor.scrollTo(0,item.scrollTop)
        // this.CodeMirrorEditor.setValue(model.oldContent)
      },
      //提交用户操作
      Submit:function(){
        var parentId,level,Selected,isExpand;
        if (this.off==true) {
          var item = this.getSelectedNode();
          if (item) {
            parentId = item.resultId;
            level = item.level+1;
            Selected = item.Selected;
            isExpand = item.isExpand
          }
        }
        else{
          parentId = 0;
          level = 0;
          Selected = false;
          isExpand = false;
        }
        if (!this.resultName) { alert("请填写标题");return event.target.dataset.dismiss=''};
        var _data = {
          taskId:19,
          parentId:parentId, 
          startPos:this.postArray[0],
          endPos:this.postArray[1],
          resultName:this.resultName,
          elementId:this.elementId,
          elementType:this.elementType,
          orderNum:this.orderNum,
          oldContent:this.getRangeText,
          level:level,
          children:[],
          Selected:Selected,
          isExpand:isExpand,
        };
        this.$ajax({
          method: 'post',
          url: '/marker/api/save',
          data: _data
        }).then((res)=>{
          if (res.data.code === 1000) {
            $('#myModal').modal('hide')
            //结合接口返回设置_data数据
            this.resultName = '';
            this.allIsExpand = true;
            this.$refs.codeMenu.style.display='none';
            _data.resultId = res.data.data;
            if(parentId == 0){
              this.ztreeData.push(_data);
              // console.log(JSON.parse(JSON.stringify(this.ztreeData)),'追加以后ztreeData')
            }
            else{
              item.children.push(_data);
              vm.$set(item, 'isExpand', true);
            }
            console.log(JSON.parse(JSON.stringify(this.ztreeData)),'追加以后ztreeData');
          }
        },(err)=>{
          console.log(err);
        })
      },
      //用户取消操作
      Cancel:function(){
        this.resultName='';
        this.getRangeText='';
      },
      //初始化页面结构以及数据
      initView: function(){
          //获取右侧文章
          this.$ajax({
            method: 'post',
            url: '/marker/api/getFullText',
            data: {
              taskId:19
            }
          }).then((res)=>{
            if (res.data.code === 1000) {
              this.CodeMirrorEditor.setValue(res.data.data);
            }
          },(err)=>{
            console.log(err);
          })
          //初始化codemirror
          let myTextarea = document.getElementById('editor');
          this.CodeMirrorEditor = CodeMirror.fromTextArea(myTextarea, {
            lineWrapping :true,
            styleActiveLine: true,
            foldGutter: true,
            styleSelectedText: true,
            mode: 'text/javascript',
            matchBrackets: true,
            cursorScrollMargin:120,//光标上下预留额外空间
            showCursorWhenSelecting: true,
            theme: "default",
          });
          // 光标或选中(内容)事件
          this.cursorActivity();
          // 记录内容改变事件
          this.CodeMirrorEditor.on("change",(instance,changeObj)=>{
            console.log("change",instance,changeObj)
            this.height = instance.doc.height;
          });
          //记录滚动事件
          this.CodeMirrorEditor.on("scroll",(cm)=>{
            this.scrollTop = cm.doc.scrollTop  
          });
      },
      //设置resultName
      setResultName(event){
        this.resultName = getSelectText(event);
      },
      //根据Pos转为数字下标
      indexFromPos: function(Pos) {
        if(Pos.line < 0 || Pos.ch < 0) {
          return false;
        }
        var _index= Pos.ch+1;
        this.CodeMirrorEditor.eachLine(0, Pos.line, function(item){
          _index+= item.text.length+1;
        })
        return _index;
      },
      //根据数字下标转为Pos
      posFromIndex: function(_index) {
        var line = 0, ch;
        this.CodeMirrorEditor.eachLine(0, this.CodeMirrorEditor.lineCount(), function(item) {
          var textNum = item.text.length + 1;
          if(textNum > _index) {
            ch = _index;
            return true;
          }
          else{
            _index -= textNum; 
            ++line;
          }
        });
        return({
          line: line,
          ch: ch
        })
      },
      // 光标或选中(内容)事件
      cursorActivity(){
        this.CodeMirrorEditor.on("cursorActivity",(cm)=>{
          var startObj = {}, endObj = {}, objArray = [], newObjArray = [], activeArray = [];
          //拖动鼠标开始位置结束位置,支持正反选
          endObj.line = cm.getCursor('head').line;
          endObj.ch = cm.getCursor('head').ch;
          startObj.line = cm.getCursor('anchor').line;
          startObj.ch = cm.getCursor('anchor').ch;
          objArray.push(startObj,endObj);
          // 保存开始结束位置数字下标
          newObjArray.push(this.indexFromPos(startObj),this.indexFromPos(endObj));
          newObjArray.sort((x,y)=>{
            return x-y;
          })
          this.postArray = newObjArray;
          //根据Pos获取选中文本
          sortPosArray(objArray);
          this.getRangeText = cm.getRange(objArray[0],objArray[1]);
        });
      }
    }
});
/***
  ***dom.js*** 
***/
//设置选中active状态
function setActiveClass(){
  $(document).on('click','.menuLi>div', function(){
    $('.treeFoundation').removeClass('activeClass')
    $('div').removeClass('activeClass');
    $(this).addClass('activeClass');
  })
  $(document).on('click','.item>div', function(){
    $('div').removeClass('activeClass');
    $('.treeFoundation').removeClass('activeClass')
    $(this).addClass('activeClass');
  })
  $('.treeFoundation').click(function(){
    $(this).addClass('activeClass')
    $('.sidebar-menu div').removeClass('activeClass');
  })
};
//鼠标右键菜单
function setMouseMenu(target, cursorClass){
  if (!target||target==null||!cursorClass) {return}
  var doc = document.getElementById(target);
  var forRight = $(cursorClass)
  doc.oncontextmenu=function(event){//关键点
    var event=event||window.event;
    forRight.get(0).style.display="block";
    forRight.get(0).style.left=event.pageX+"px";
    forRight.get(0).style.top=event.pageY+"px";
    return false;
  };
  doc.onclick=function(e){
    forRight.get(0).style.display="none";
    e.preventDefault(); 
  };
};
//根据codemirror对Pos下标排序
function sortPosArray(PosArray){
  PosArray.sort((c1, c2) => {
    if (c1.ch == c2.ch) {
      return c1.line - c2.line
    }
    else if (c1.line>c2.line) {
      return c1.line - c2.line
    }
    else if(c1.line<c2.line){
      return c1.line - c2.line
    }
    else{
      return c1.ch - c2.ch
    }
  })
};
//初始化textarea
function getSelectText(event){
  var nullvalue = -1,
      selectStart,//选中开始位置
      selectEnd,//选中结束位置
      position,//焦点位置
      selectText,//选中内容
      rootId = event.target.id,
      oTxt = document.getElementById(rootId);
  if(oTxt.setSelectionRange){//非IE浏览器
    selectStart= oTxt.selectionStart;
    selectEnd = oTxt.selectionEnd;
    if(selectStart == selectEnd){
      position = oTxt.selectionStart;
      selectStart = nullvalue;
      selectEnd = nullvalue;
    }
    else{
      position =  nullvalue;
    }
    selectText = oTxt.value.substring(selectStart,selectEnd);
  }
  else{//IE
    var range = document.selection.createRange();
    selectText=range.text;
    range.moveStart("character",-oTxt.value.length);
    position = range.text.length;
    selectStart = position - (selectText.length);
    selectEnd = selectStart + (selectText.length);
    if(selectStart != selectEnd){
      position = nullvalue;
    }else{
      selectStart = nullvalue;
      selectEnd = nullvalue;
    }
  }
  return selectText
};
function setTreeFoundation(parent,child){
  parent.addEventListener('scroll', () => {
    if (parent.scrollTop>=90) {
      child.style.position='fixed';
      child.style.width=47.5+'%';
      child.style.zIndex=2;
      child.style.boxShadow='0px 1px 1px #d9d9d9';
    }
    else{
      child.style.position='static';
      child.style.width='auto';
      child.style.boxShadow='none';
    }
  }, false)
};

var roots = [];
              this.ztreeData.forEach(function(item,index){
                if(item.parentId ==0){
                  roots.push(item);
                }
              });
              var g=function(child,level,isExpand,Selected){
                var childLevel=level+1;
                var childisExpand = isExpand;
                var childSelected = Selected;
                child.forEach(function (item, index) {
                  item.level=childLevel;
                  vm.$set(item, 'isExpand', childisExpand)
                  vm.$set(item, 'Selected', childSelected)
                  var subChild = item.children;
                  if(subChild!=null && subChild.length>0){
                    g(subChild,childLevel,childisExpand,childSelected);
                  }
                });
              } 
              roots.forEach(function (item, index) {
                item.level= 0 ;
                vm.$set(item, 'isExpand', false)
                vm.$set(item, 'Selected', false)
                var child =item.children;
                g(child, item.level, item.isExpand, item.Selected);
              });
以上是递归函数的应用,很实用可以解决很多后端的问题

另外说一嘴,解决递归组件事件传递的方法有很多,比如事件车,本实例之初也用到了事件车,因为代码设计的要求换了另一种实现的方式,心细的朋友多看几遍就会发现


Lipa
175 声望13 粉丝