vue封装可实现选中、新增、修改、删除功能的树组件

我们在开发项目的时候经常会遇到一些树形结构的数据结构,比如部门、班级、关系等等层级结构的数据,通常常规的展示方式一般是通过树的结构来展示出来,现在我们自己就来实现一个可以新增、修改、删除的树。

实现的过程很简单,树一般都会采用递归思路,然后将数据展示出来。

<div class="tree" id="Tree" ref="TreeNode">
    <ul>
      <li v-for="(v, n) in treeData" :key="n">
        <span @click="nodeClick(v, $event)" :class="currentId === v.id ? 'active' : ''">
          <strong v-if="!v.isExpend" @click.stop="expendNode(v)">+</strong>
          <strong v-if="v.isExpend" @click.stop="expendNode(v)">-</strong>
          {{v.name}}
          <em @click.stop="addNode(v)" class="del-btn add-btn" v-if="isEdit">新增</em>
          <em @click.stop="editNode(v)" class="del-btn edit-btn" v-if="isEdit">编辑</em>
          <em @click.stop="delNode(v)" class="del-btn del" v-if="isEdit">删除</em>
        </span>
        <Tree
          :treeData="v.children"
          :isEdit="isEdit"
          v-if="v.children"
          v-show="v.isExpend"
          @nodeClick="nodeClick"
          @expendNode="expendNode"
          @delNode="delNode"
          @addNode="addNode"
          @editNode="editNode"
        />
      </li>
    </ul>
  </div>

然后抛出对应的节点,通过递归的方式获取到对应的数据结构的节点,然后就可以对节点进行增加,修改,查询了。

// 递归操作根据id获取节点操作
    getNode (data, id, type) {
      let that = this;
      data.map((item, index) => {
        if (item.id == id) {
          that.currentNode = item; // 结果赋值
          // 删除操作
          if (type === 'del') {
            if (window.confirm('确定要删除节点?')) {
              data.splice(index, 1);
              alert('删除成功!');
            }
          }
          // 展开收缩
          if (type === 'expend') {
            item.isExpend = !item.isExpend;
          }
          // 编辑操作
          if (type === 'edit') {

          }
          // 新增操作
          if (type === 'add') {
            if (!item.children) {
              that.$set(item, 'children', []);
            }
            item.children.push({
              id: new Date().getTime(), // 用时间戳标识唯一id,具体情况可以根据自己定一个唯一标识
              name: '新增节点' + new Date().getTime(),
              isExpend: false
            });
            // 当没有子节点的时候新增一个节点后展开节点
            if (item.children.length > 0) {
              item.isExpend = true;
            }
          }
        } else {
          if (item.children) {
            this.getNode(item.children, id, type);
          }
        }
      });
    }

整个过程主要的思路就是递归遍历输出树,然后通过点击节点递归获取树中对应的节点。

整个代码如下:

Tree组件部分:

<template>
  <div class="tree" id="Tree" ref="TreeNode">
    <ul>
      <li v-for="(v, n) in treeData" :key="n">
        <span @click="nodeClick(v, $event)" :class="currentId === v.id ? 'active' : ''">
          <strong v-if="!v.isExpend" @click.stop="expendNode(v)">+</strong>
          <strong v-if="v.isExpend" @click.stop="expendNode(v)">-</strong>
          {{v.name}}
          <em @click.stop="addNode(v)" class="del-btn add-btn" v-if="isEdit">新增</em>
          <em @click.stop="editNode(v)" class="del-btn edit-btn" v-if="isEdit">编辑</em>
          <em @click.stop="delNode(v)" class="del-btn del" v-if="isEdit">删除</em>
        </span>
        <Tree
          :treeData="v.children"
          :isEdit="isEdit"
          v-if="v.children"
          v-show="v.isExpend"
          @nodeClick="nodeClick"
          @expendNode="expendNode"
          @delNode="delNode"
          @addNode="addNode"
          @editNode="editNode"
        />
      </li>
    </ul>
  </div>
</template>

<script>
import Tree from './tree.vue';
export default {
  name: 'Tree',
  data () {
    return {
      currentId: 1 // 高亮当前节点标识
    };
  },
  computed: {
  },
  props: ['treeData', 'isEdit'],
  components: { Tree },
  created () {
  },
  mounted () {
  },
  methods: {
    // 删除节点
    delNode (v) {
      this.$emit('delNode', v);
    },
    // 新增节点
    addNode (v) {
      this.$emit('addNode', v);
    },
    // 修改节点
    editNode (v) {
      this.$emit('editNode', v);
    },
    // dom方法设置当前节点高亮显示
    setCurrentNode (node) {
      document.querySelector('#Tree span.active').classList.remove('active');
      node.classList.add('active');
    },
    // 点击当前节点
    nodeClick (v, $event) {
      this.setCurrentNode($event.target);
      this.$emit('nodeClick', v, $event);
    },
    // 收缩展开节点
    expendNode (v) {
      this.$emit('expendNode', v);
    }
  }
};
</script>
<style lang="less" scoped>
  .tree {
    user-select: none;
    ul li {
      padding:15px 0 0 15px;
      span {
        &.active {
          color:red;
          font-weight: bolder;
        }
        &:hover {
          cursor: pointer;
        }
        display:block;
        font-size: 20px;
        padding: 15px 0;
        color: #fff;
        strong {
          margin-right: 20px;
          display:inline-block;
          width: 20px;
          height: 20px;
          background:#909090;
          color:#333;
          font-size: 20px;
          text-align:center;
          line-height:20px;
          border-radius:10px;
        }
        em {
          margin-left: 15px;
          font-style:normal;
          color:#1a63de;
        }
        .add-btn {
          color: green;
        }
        em.del {
          color: red;
        }
      }
    }
  }
</style>

外层引入:

<template>
  <div id="app">
    <div class="loading" v-if="this.$store.state.loadingFlag">
      <img src="static/images/loading.gif" />
    </div>
    <div id="mainBox">
      <Tree
        :treeData="treeData"
        :isEdit="isEdit"
        @nodeClick="nodeClick"
        @expendNode="expendNode"
        @delNode="delNode"
        @addNode="addNode"
        @editNode="editNode"
      />
      <router-view />
    </div>
  </div>
</template>

<script>
import Tree from '@/pages/tree/Tree.vue';
export default {
  name: 'App',
  data () {
    return {
      isEdit: true,
      currentNode: null, // 当前点击的节点
      treeData: [
        {
          id: 1,
          name: '一级标题 1',
          isExpend: true,
          children: [
            {
              id: 2,
              name: '二级标题 2-1',
              isExpend: true,
              children: [
                {
                  id: 5,
                  name: '三级标题 3-1',
                  isExpend: true,
                  children: [
                    {
                      id: 7,
                      name: '四级标题 4-1',
                      isExpend: false
                    },
                    {
                      id: 8,
                      name: '四级标题 4-2',
                      isExpend: false
                    }
                  ]
                },
                {
                  id: 6,
                  name: '三级标题 3-1',
                  isExpend: false
                }
              ]
            },
            {
              id: 3,
              name: '一级标题 2-1',
              isExpend: false
            },
            {
              id: 4,
              name: '一级标题 2-1',
              isExpend: false
            }
          ]
        }
      ]
    };
  },
  components: { Tree },
  methods: {
    // 递归操作根据id获取节点操作
    getNode (data, id, type) {
      let that = this;
      data.map((item, index) => {
        if (item.id == id) {
          that.currentNode = item; // 结果赋值
          // 删除操作
          if (type === 'del') {
            if (window.confirm('确定要删除节点?')) {
              data.splice(index, 1);
              alert('删除成功!');
            }
          }
          // 展开收缩
          if (type === 'expend') {
            item.isExpend = !item.isExpend;
          }
          // 编辑操作
          if (type === 'edit') {

          }
          // 新增操作
          if (type === 'add') {
            if (!item.children) {
              that.$set(item, 'children', []);
            }
            item.children.push({
              id: new Date().getTime(), // 用时间戳标识唯一id,具体情况可以根据自己定一个唯一标识
              name: '新增节点' + new Date().getTime(),
              isExpend: false
            });
            // 当没有子节点的时候新增一个节点后展开节点
            if (item.children.length > 0) {
              item.isExpend = true;
            }
          }
        } else {
          if (item.children) {
            this.getNode(item.children, id, type);
          }
        }
      });
    },
    expendNode (v) {
      this.getNode(this.treeData, v.id, 'expend');
    },
    nodeClick (v) {
      console.log(v);
    },
    delNode (v) {
      this.getNode(this.treeData, v.id, 'del');
    },
    addNode (v) {
      this.getNode(this.treeData, v.id, 'add');
      alert('新增成功!');
    },
    editNode (v) {
      console.log(v);
    }
  },
  created () {
  },
  watch: {
    $route (to) {
      console.log(to);
      if (to.path.includes('login')) {
        this.hidden = false;
      } else {
        this.hidden = true;
      }
    }
  }
};
</script>

<style lang="less">
@import "style/common/reset";
@import "style/common/base";
</style>
<style lang="less" scoped>
#app {
  position: absolute;
  height: 100%;
  width: 100%;
  -webkit-overflow-scrolling: touch;
  padding-bottom: 120px;
  overflow-y: auto;
  background: url("static/images/back.png");
  background-size: 100%;
  background-image: linear-gradient(#030303, #170403);
  .loading {
    position: fixed;
    z-index: 999999;
    width: 100%;
    height: 100%;
    text-align: center;
    background: rgba(0, 0, 0, 0.75);
    img {
      position: relative;
      top: 50%;
      transform: translateY(-50%);
      height: 150px;
    }
  }
}
</style>

结果如下:

编辑部分我没有具体去一一实现,原理都是一样的传参进行修改,还有一天就要上班了,最近冠状病毒太厉害了,回去的时候200多个确诊,现在已经达到35000多个了,搞得人心慌慌,真有点像生化危机的感觉,希望病毒尽快消失,生活尽快回复正常。

结束语: 努力,努力,再努力! ( ̄_ ̄ )

  • 5
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值