基于vue的树形结构

8 篇文章 0 订阅

基于vue的树形结构封装

主要功能:添加同级、添加子级、拖拽、双击名称进行修改
依赖:vue、element-ui、sass
git地址:https://github.com/rongyanping/vue-tree

封装 MyTree.vue

<template>
  <div class="my_tree">
    <div
      class="brother"
      v-for="(data, idx) of treeData"
      :key="idx">
      <div class="node"
           @drop="drop($event,treeData,idx)"
           @dragover="allowDrop($event,treeData,idx)">
        <span @click="data.expand=!data.expand">
          <i class="el-icon-caret-bottom" v-if="data.expand && data.type==='group'"></i>
          <i class="el-icon-caret-right" v-if="!data.expand && data.type==='group'"></i>
        </span>
        <div :draggable="!data.editable"
             :class="{'cname_child':true,'cname_child_edit':data.editable}"
             :contentEditable="data.editable"
             @dragstart="drag($event,treeData,idx)"
             @dblclick="dbclickHandle($event,treeData,idx)"
             @click="data.expand=!data.expand">
            {{data.name}}
        </div>
        <button class="operation_btn" v-if=" data.type==='group'" @click="addBrother($event,data)">添加同级</button>
        <button class="operation_btn" v-if=" data.type==='group'" @click="addChild($event,data)">添加子级</button>
        <button class="operation_btn" v-if=" data.type==='group'"  @click="deleteNode($event, data)">删除</button>
      </div>
      <div
        class="children"
        v-if="data.children && data.children.length" v-show="data.expand">
        <my-tree
          :treeData="data.children"
          @addBrother="addBrother"
          @addChild="addChild"
          @deleteNode="deleteNode"
          @dragBegin="drag"
          @dragStop="drop"
          @dbclickChangeName="dbclickChangeName">
        </my-tree>
      </div>
    </div>
  </div>
</template>
<script>
  export default {
    name: 'MyTree',
    props: {
      treeData: {
        type: Array,
        default: () => [{
          id: 1,
          name: '分组1',
          expand: true,
          type:'group',
          children: [{
            id: 2,
            expand: true,
            name: '相机1',
            type:'camera',
          }]
        },
          {
            id: 3,
            expand: true,
            name: '分组2',
            type:'group',
            children: [
              {
                id: 5,
                expand: true,
                name: '分组2-1',
                type:'group',
                children: [{
                  id: 6,
                  expand: true,
                  name: '相机2-1',
                  type:'camera',
                }]
              }]
          }]
      },

    },
    methods: {
      // 双击
      dbclickHandle(event,data,index){
        const _this = this;
        this.$set(data[index],'editable',true);
        let target = event.target;
        setTimeout(function () {
          target.focus();
          let selection = getSelection();
          // 判断选定对象范围是编辑框还是文本节点
          if(selection.anchorNode.nodeName === '#text'){
            // 如果是文本节点则先获取光标对象
            let range = selection.getRangeAt(0);
            // 获取光标对象的范围界定对象,一般就是textNode对象
            let textNode = range.startContainer;
            // 光标移动到到原来的位置加上新内容的长度 rangeStartOffset +
            range.setStart(textNode, target.innerHTML.length);
            // 光标开始和光标结束重叠
            range.collapse(true);
            // 清除选定对象的所有光标对象
            selection.removeAllRanges();
            // 插入新的光标对象
            selection.addRange(range);
          }
          target.onblur = function () {
            _this.$emit('dbclickChangeName',data,index,target.innerText);
            // console.log('失焦---------',target.innerText)
          }
        },10);
      },
      // 传给父级新名称
      dbclickChangeName(data,index,name){
        this.$emit('dbclickChangeName',data,index,name);
        // console.log('====',name)
      },
      // 开始拖拽
      drag(event,data,index){
        this.$emit('dragBegin',event,data,index);
      },
      // 拖拽结束
      drop(event,data,index){
        event.preventDefault();
        this.$emit('dragStop',event,data,index);
        // console.log('drop>>>>>',event,data,index);
      },
      // 在何处放置被拖动的数据
      allowDrop(event,data,index){
        event.preventDefault();
      },
      // 添加子级
      addChild (event, data) {
        this.$emit('addChild', event, data)
      },
      // 添加同级
      addBrother (event, data) {
        this.$emit('addBrother', event, data);
      },
      // 删除
      deleteNode (event, data) {
        this.$emit('deleteNode', event, data)
      },
    }
  }
</script>
<style  scoped lang="scss">
  .my_tree{
    .brother{
      display: flex;
      flex-direction: column;
      padding-top: 20px;
      .node{
        display: flex;
        align-items: center;
        .cname_child{
          border-style: none;
          outline: none;
          width: 100px;
        }
        .cname_child_edit{
          border: 1px solid gray;
        }
        .operation_btn{
          margin-left: 20px
        }
      }
      .children {
        position: relative;
        margin-left: 20px;
      }
    }
  }

  input[disabled],input:disabled,input.disabled{
    color: #333;
    -webkit-text-fill-color:#333;
    opacity: 1;
    background-color:white;
  }
</style>

调用MyTree.vue

<template>
  <div class="tree">
    <my-tree
      @addBrother="addBrother"
      @addChild="addChild"
      @deleteNode="deleteNode"
      :treeData="datas"
      @dragBegin="drag"
      @dragStop="drop"
      @dbclickChangeName="dbclickChangeName">
    </my-tree>
    <div v-show="iptVisible" style="margin-top: 20px">
      <span>分组名:</span>
      <input type="text" v-model="groupName">
      <button @click="submitGroup">提交</button>
    </div>
    <div v-show="iptVisible2" style="margin-top: 20px">
      <span>子级名:</span>
      <input type="text" v-model="groupName2">
      类型:分组/相机
      <input type="text" v-model="addType">
      <button @click="submitChild">提交</button>
    </div>
  </div>
</template>
<script>
  import MyTree from './MyTree'
  export default {
    name: 'tree3',
    components: {
      'my-tree': MyTree
    },
    data () {
      return {
        id: 100,
        datas:[{
          id: 1,
          name: '分组1',
          expand: true,
          type:'group',
          editable:false,
          children: [{
            id: 2,
            expand: true,
            name: '相机1',
            type:'camera',
            editable:false,
          }]
        },
          {
            id: 3,
            expand: true,
            name: '分组2',
            type:'group',
            editable:false,
            children: [
              {
                id: 5,
                expand: true,
                name: '分组2-1',
                type:'group',
                editable:false,
                children: [{
                  id: 6,
                  expand: true,
                  name: '相机2-1',
                  type:'camera',
                  editable:false,
                },{
                  id: 7,
                  expand: true,
                  name: '相机2-2',
                  type:'camera',
                  editable:false,
                }]
              }]
          }],
        dragData:null,
        dragEvent:null,
        dragIndex:null,

        cid:10,
        groupName:'',
        iptVisible:false,
        brotherData:null,
        brotherEvent:null,

        addChildData:null,
        addChildEvent:null,
        iptVisible2:false,
        groupName2:'',
        addType:'group'
      }
    },
    methods: {
      /* 操作数据全部在本组件内部 */

      // 接收新名称
      dbclickChangeName(data,index,changeName){
        // 发送请求:修改名称
        this.$set(data[index],'name',changeName);
        this.$set(data[index],'editable',false);
        console.log('dbclick=====',changeName);
      },

      /* 拖拽 */
      drag(event,data,index){
        this.dragData = data;
        this.dragEvent = event;
        this.dragIndex = index;
        // console.log('dragStart=====',this.dragData,index);
      },
      drop(event,data,index){
        /* 拖拽只能是拖到某个分组内 成为该分组的子级 */
        /* 松开鼠标---调取修改接口,成功后添加新数据,删除之前位置*/
        const _this = this;
        data[index].children.push(this.dragData[this.dragIndex]);
        // 删除原有数据
        setTimeout(function () {
          console.log('dragOver=====',_this.dragEvent,_this.dragData);
          _this.deleteNode(_this.dragEvent,_this.dragData[_this.dragIndex])
        },10);
      },

      // 添加同级
      addBrother (event,data) {
        this.iptVisible = true;
        this.id++;
        this.brotherData = data;
        this.brotherEvent = event;
        console.log('addBrother>>>>',this.brotherData);
      },
      submitGroup(){
        let parentData = this.getParentData(this.brotherEvent.target);
          // console.log('parentData========',this.brotherEvent.target);
            if (parentData) {
              let index = parentData.indexOf(this.brotherData);
              if (index !== -1) {
                parentData.splice(index + 1, 0, {
                  id: this.id,
                  name: this.groupName,
                  expand: false,
                  type:'group',
                  children: []
                })
              }
        }

      },

      // 添加子级
      addChild (event, data) {
        this.iptVisible2 = true;
        this.cid++;
        this.addChildData = data;
        console.log(this.addChildData)
        // // data 父级数据
        // console.log('addchild===tree3==',data);
        // if (!data.children) {
        //   this.$set(data, 'children', [])
        // }
        //   data.children.push(this.newNode('camera'))
        //   console.log('data====',data)
      },
      submitChild(){
        if(!this.addChildData.children){
          this.$set(this.addChildData, 'children', [])
        }
        if(this.addType==='group'){
          this.addChildData.children.push({
            id: this.cid,
            children:[],
            expand: false,
            name: this.groupName2,
            type:'group',
          })
        }
        else {
          this.addChildData.children.push({
            id: this.cid,
            expand: true,
            name: this.groupName2+this.cid,
            type:'camera',
          })
        }
      },

      // 删除组
      deleteNode (event, data) {
        console.log('del====',event,data)
        let parentData = this.getParentData(event.target);
        if (parentData) {
          let index = parentData.indexOf(data);
          if (index !== -1) {
            parentData.splice(index, 1)
          }
        }
      },

      newNode (type) {
        let id = this.id++;
        return {
          id,
          name: type==='group'?'新分组' + id:'新相机'+id,
          expand: false,
          children: [],
          type:type
        }
      },
      getParentData (node) {
        while (node && node.tagName !== 'BODY') {
          // console.log('node=====',node,node.__vue__)
          if (node.__vue__ && node.__vue__.$options._componentTag === 'my-tree') {
            return node.__vue__.treeData
          }
          node = node.parentNode
        }
        return null
      }
    }
  }
</script>
<style lang="scss" scoped>
  .tree{
    padding: 50px 20px;
    text-align: left;
  }
  .tree-title{
    border-bottom: 1px solid gray;
    padding-bottom: 10px;
    margin-bottom: 10px;
  }
</style>

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值