一、介绍:el-tree在element文档中有查询全树的代码,本文主要是在此基础上添加了增加、删除、修改的界面样式与功能。

二、具体来说:

1、鼠标移动到树上显示删除和修改:

      点击删除,当前节点删除;

      点击修改,树的选中节点变成input可以重新输入名称并且右边出现取消或确认的icon。

2、点击底部添加按钮,如果未选中树节点,则在最外层新增input进行名称输入,右边同样有取消或确认的icon。若选中树节点,则在选中节点下添加子节点,同样以input形式输入名称。

三、实现效果(这个操作速度慢得我怀疑自己开了0.5倍速😂😂):

element ui tree后台接口 vue elementui 树_数组

文章会先用截图对部分代码进行解释(element文档中有的代码,比如查询就不做解释了),随后文章底部会给出相应的demo代码。

四、实现代码+解释+坑:

1、dom代码:

element ui tree后台接口 vue elementui 树_模版_02

2、data里的代码:

element ui tree后台接口 vue elementui 树_模版_03

3、节点的数据格式

element ui tree后台接口 vue elementui 树_模版_04

4、methods里的代码:

element ui tree后台接口 vue elementui 树_模版_05

element ui tree后台接口 vue elementui 树_ico_06

element ui tree后台接口 vue elementui 树_element ui tree后台接口_07

element ui tree后台接口 vue elementui 树_ico_08

element ui tree后台接口 vue elementui 树_element ui tree后台接口_09

element ui tree后台接口 vue elementui 树_ico_10

element ui tree后台接口 vue elementui 树_ico_11

项目要求,最多两层组:

element ui tree后台接口 vue elementui 树_element ui tree后台接口_12

element ui tree后台接口 vue elementui 树_数组_13

到这里这个整个代码就介绍完了,这里还有个坑。

问题:当点击新增节点,再点击取消的icon,这时候观察fetchData方法中两处this.curNode的打印,会发现打印结果是不一样的。这个问题其实并不影响树功能的实现,因为在fetchData中获取数据以后,进行了this.curNode=undefined。只是出于好奇,为什么会出现这个现象?

element ui tree后台接口 vue elementui 树_数组_14

分析:观察cancelUpdate执行后是不是走了其他方法?没有,也没有触发watch。会不会是引用数据类型的地址引用问题?发现没有。最后发现,是因为点击取消的时候冒泡了!!触发了handleNodeClick方法。

解决:

element ui tree后台接口 vue elementui 树_模版_15

element ui tree后台接口 vue elementui 树_数组_16

 

五、demo代码(以下代码根据小伙伴需要已经改成不限深度的模板组插入,并把原来深度为2的模板组插入改成了注释):

<template>
  <div>
    <el-input placeholder="请搜索" v-model="filterText"></el-input>
    <el-tree
      :highlight-current="true"
      class="filter-tree"
      :data="templateTree"
      :props="defaultProps"
      default-expand-all
      :filter-node-method="filterNode"
      ref="tree"
       @node-click="handleNodeClick"
      :expand-on-click-node="false"
      :render-content="isUpdateGroup ? updateRenderContent :renderContent"
    ></el-tree>
    <el-button type="primary" @click="handleAddGroup">添加组</el-button>
  </div>
</template>
<script>
export default {
  name:'tree',
  data() {
    return {
      templateTree: [
        //树的数据
        {
          id: "1",
          text: "模板组1",
          nodeId: "11",
          depth: 1,
          typeName: "模板组",
          childrenNum: 2,
          nodes: [
            {
              id: "12",
              text: "模板组2",
              nodeId: "122",
              depth: 2,
              typeName: "模板组",
              childrenNum: 0,
              nodes: [
                {
                  id: "21",
                  text: "模板组3",
                  nodeId: "144",
                  depth: 3,
                  typeName: "模板",
                  childrenNum: 0,
                  nodes:[]
                }
              ]
            },
            {
              id: "13",
              text: "模板1",
              nodeId: "133",
              depth: 2,
              typeName: "模板",
              childrenNum: 0
            }
          ]
        }
      ],
      defaultProps: {
        children: "nodes",
        label: "text"
      },
      isact: "", //当前hover的节点
      isactTitle: "", //记录修改节点名称
      curNode: undefined, //当前选中节点
      isUpdateGroup: false, //是否在修改模板组
      filterText: "",
      indexRecord:[],//记录节点轨迹
      isBreak:false,//是否结束循环
    };
  },
  watch: {
    filterText(val) {
      this.$refs.tree.filter(val);
    }
  },
  methods: {
    filterNode(value, data) {
      if (!value) return true;
      return data.text.indexOf(value) !== -1;
    },

    renderContent(h, { node, data, store }) {
      return (
        <span
          style="flex: 1; display: flex; align-items: center; justify-content: space-between; padding-right: 8px;"
          on-mouseenter={() => this.mouseenteract(data)}
          on-mouseleave={() => this.mouseleaveact(data)}
        >
          <span>
            <span>{node.label}</span>
          </span>
          {this.isact == data ? (
            <span>
              {this.isact.typeName == "模板组" ? (
                <el-button
                  class="m-r-10"
                  type="text"
                  icon="el-icon-edit"
                  on-click={() => this.handleUpdateGroup(node, data)}
                ></el-button>
              ) : (
                <span></span>
              )}
              <el-button
                type="text"
                icon="el-icon-delete"
                on-click={(e) => this.handleDelete(node, data, e)}
              ></el-button>
            </span>
          ) : (
            <span></span>
          )}
        </span>
      );
    },
    updateRenderContent(h, { node, data, store }) {
      return (
        <span style="flex: 1; display: flex; align-items: center; justify-content: space-between; padding-right: 8px;">
          <span>
            {this.isact == data ? (
              <input
                type="text"
                onChange={this.handleChangeTitle.bind(this)}
                value={node.label}
              />
            ) : (
              <span>{node.label}</span>
            )}
          </span>
          {this.isact == data ? (
            <span>
              {this.isact.typeName == "模板组" ? (
                <span>
                  <el-button
                    class="m-r-10"
                    type="text"
                    icon="el-icon-check"
                    on-click={() => this.updateGroup(node, data)}
                  ></el-button>
                  <el-button
                    class="m-r-10"
                    type="text"
                    icon="el-icon-close"
                    on-click={(e) => this.cancelUpdate(node, data, e)}
                  ></el-button>
                </span>
              ) : (
                <span></span>
              )}
            </span>
          ) : (
            <span></span>
          )}
        </span>
      );
    },
    //获取鼠标进入节点的数据
    mouseenteract(da) {
      this.isact = da;
    },
    mouseleaveact(da) {
      this.isact = "";
    },
    handleNodeClick(pdata) {
       this.curNode = pdata;
      document
        .getElementsByClassName("el-tree-node__content")[0]
        .setAttribute("class", "el-tree-node__content");
    },
    handleDelete(node, data, e) {
      e.stopPropagation();
      //存在则添加到子级
        const parent = node.parent;
        const children = parent.data.nodes || parent.data;
        //若parent.data是对象,操作的是子级;如果是数组,操作的是最外层
      if(Array.isArray(parent.data)){
        const parentIndex = parent.data.findIndex(d => d.id === data.id);
        parent.data.splice(parentIndex, 1);
      }else{
        const childIndex = children.findIndex(d => d.id === data.id) ;
        children.splice(childIndex, 1);
      }
      this.curNode = undefined;
    },
    //新增组
    handleAddGroup() {
      /**
       *  如果模版深度最多两层,取消该部分注释
       * //最多只有两层组 不可能添加在dept2的组上 curNode存在 并且  深度超过1
         if (this.curNode != undefined && this.curNode.depth != 1) { 
         this.$message.warning("不能添加超过两层");  
         return; 
       }
       */
      
       //如果isUpdateGroup 已经是true了 说明重复点击了
      if(this.isUpdateGroup){
        return;
      }
     
      let id = ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
        (
          c ^
          (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))
        ).toString(16)
      );
      let newChild = {
        parentId: "", //如果有这个id 是插入第二层 否则是第一层 可有可无
        text: "", //必须有 this.templateContent.tempName
        nodes: [],
        id: id,
        typeName: "模板组",
        temporaryData: "1" //用来区分临时数据
      };
      /* 如果模版深度最多两层,以下条件改成该部分注释
        this.curNode && this.curNode.depth == 1 
      */
      this.indexRecord=[]
      if (this.curNode) {
        if(!this.curNode.nodes){
          this.$message.warning('模板不可添加')
          return
        }
        newChild.parentId = this.curNode.id;
         /* 如果模版深度最多两层,以下条件改成该部分注释
          const index = this.templateTree.findIndex(
            item => item.id == this.curNode.id
          );
          this.templateTree[index].nodes.push(newChild);
        */
         //找到tree中的index轨迹
         this.getTemplateTreeNode(this.curNode.id,this.templateTree, 0)
         //按照index轨迹插入节点
         this.insertNode(newChild, this.templateTree, this.indexRecord, this.indexRecord.length )
         this.isBreak = false
      } else if (this.curNode == undefined) {
        //没有选中的时候 添加到最外层
        newChild.depth = 1
        this.templateTree.push(newChild);
      }
      //调用出updateRender的input
      this.isact = newChild;
      this.isUpdateGroup = true;
    },
    //递归遍历获得选中node
    getTemplateTreeNode(target, list, dept){
      //空数组直接返回
      if(list.length == 0) return;
      let dataLen = list.length;
      for(let i = 0; i < dataLen; i++){
        //如果不匹配
        if(target != list[i].id){
          //存在nodes 遍历nodes里的节点
          if(list[i].nodes){
            this.indexRecord[dept] = i 
            let recordDept = dept+1
            this.getTemplateTreeNode(target,list[i].nodes, recordDept)
          }else{
            //不存在nodes 继续遍历
            continue;
          }
        }else{
          //匹配,则修改下标数组
          this.indexRecord[dept] = i 
          this.isBreak = true
          break
        }
        //删除不匹配的轨迹 如果已经break了说明已经找到正确的节点,就不用再删了
        if(!this.isBreak){
          this.indexRecord.pop()
        }
      }
    },
    //插入节点
    insertNode(insertChild, tree, indexArr, len){
      let index = indexArr.length - len
      if(len == 0) {
        tree.push(insertChild)
      } else{
        this.insertNode(insertChild, tree[indexArr[index]].nodes, indexArr, len-1)
      }
    },
    //修改组
    handleUpdateGroup() {
      this.isUpdateGroup = true;
    },
    //修改组名时获取title
    handleChangeTitle(e) {
      let value = e.target.value;
      this.isactTitle = value;
    },
    updateGroup(node, data) {
      //先handleChangeTitle获取title 再调用
      setTimeout(() => {
        if (this.isactTitle.trim() == "") {
          this.$message.warning("名称不能为空");
          return;
        }
        //修改数据组
        this.isUpdateGroup = false;
        const parent = node.parent;
        const children = parent.data.nodes || parent.data;
        const index = children.findIndex(d => d.id === data.id);
        let temp = data;
        temp.text = this.isactTitle;
        children.splice(index, 1, temp);
      }, 500);
    },
    cancelUpdate(node, data, e) {
      this.$message.info("已取消");
      this.isUpdateGroup = false;
      //如果是插入操作 需要移除数据
      if (this.isact.temporaryData) {
        this.handleDelete(node, data, e);
      }
    }
  }
};
</script>
<style lang="scss" scoped>
.el-tree-node__content {
  .el-button {
    display: none;
  }
}
.el-tree-node__content:hover {
  .el-button {
    display: inline;
  }
}
.el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content {
  background-color: #eaebed;
  color: #4796ec;
  font-weight: bold;
}
.el-tree {
  height: 350px;
  overflow-y: auto !important;
  .el-tree-node__content span {
    text-overflow: ellipsis;
    white-space: nowrap;
    overflow: hidden;
  }
}
</style>