使用vue实现的树形结构组件BasicTree

tree组件的经验总结

    最近项目中有了树形结构的功能需求,要求能够将分类信息使用树形结构展示、新增节点、删除节点、选择节点等功能,项目中使用了iview框架作为视图层框架,iview本身也提供了树形结构示例,但是其ui风格并不符合设计稿需求,没办法一开始的想法是请求外援,网上找到了jquery的著名插件ztree插件,其功能非常强大完全符合项目的功能性需求,但是由于项目是基于vue技术构建,考虑除非非常必要否则都不用jquery插件的原则,又找到了开源框架vue-ztree,它是基于ztree使用vue实现的树形结构,同样非常强大,但是ui风格依旧不符合设计稿,也没有看到可以自定义ui的地方,本着不能谈论别人的精彩,让自己永远是一个看客的技术原则,所以尝试开发了这个tree组件,由于技术浅薄所以该组件仅仅为了满足个人项目需要和自己的技术积累习惯以及给像我一样想实现自己tree组件的人一个参考写下了这篇文章。

    该组件的开发环境为:vue-cli2.x脚手架工具创建的项目,iview3.x作为组件视图层框架,vue全家桶技术,stylus作为css解析器。其实该组件也就使用了iview3.x的icon图标而已,完全可以使用自己的icon来代替。先来谈下实现思路,一开始看到项目设计稿需求决定自己写时,就想到了它的html骨架结构,它应该是一个ul>li无序列表嵌套组合,并且是一个递归循环的结构,而vue本身就支持递归组件,戳我https://cn.vuejs.org/v2/guide/components-edge-cases.html,里面有递归组件的定义要求,至此html结构构思完成,下一步是侦听tree组件事件,如何展开收起,如何侦听选择节点的点击事件,如何新增、删除等等,在这个过程里一开始想到的vue里面的父子组件的事件传递机制,但是踩了一个坑,好像对于递归组件,外层父组件不能够像平常的方式去侦听,如下的方式:

#父组件
<div class="parent-tree">
    <BasicTree @add="addNode"></BasicTree>
</div>
...addNode(target) {
console.log('监听tree组件',target);
}
#tree组件
<BasicTree>
    <template>
        .....this.$emit('add',event.target);
    </template>

通过上述方式完全监听不到tree组件往外抛出的事件,我能想到的是因为是递归组件,本身就是自身引用,既是子组件又是自己的父组件,如果像这种方式去监听往外抛出的事件需要一层层往外抛出,非常麻烦,所以路过的大神对于这种情况还望能够详细描述这个事件的传递过程。

这种方式在我这里行不通,只能另想别的办法,还好vue提供了vuex作为全局状态管理机制,所以决定所有的事件和传值都通过vuex来进行全局注入,废话不多说直接贴源码

#父组件KnowledgeQuery组件
<template>
  <div class="knowledge-query">
    <!--左侧布局-->
    <div class="left-wrapper">
      <!--搜索框-->
      <div class="search-wrapper">
        <div class="input-wrapper">
          <input type="text" v-model="keywordSearch"
                 @keyup.enter="searchKnowledge"
                 placeholder="请输入关键词搜索">
          <span class="icon" @click="delKeywordSearch"><Icon type="ios-close"/></span>
          <span class="search-btn" @click="searchKnowledge(1)">搜索</span>
        </div>
        <div class="result-wrapper" v-if="IsResultListShow">
          <ul>
            <li v-for="(item,index) in searchListData" class="item-list"
                @click="choiceValue(item.result)" :key="index">
              {{item.result}}
            </li>
          </ul>
        </div>
      </div>
      <!--内容区-->
      <div class="content-wrapper" @click="hideResultList">
        <h1 class="title-hide" v-show="!keywordSearch.length">暂无搜索条件以下为默认搜索结果</h1>
        <h1 class="title" v-show="keywordSearch.length">大约有<span
          class="num">236</span>项与&nbsp;“<span class="key">{{keywordSearch}}</span>”&nbsp;相关的搜索结果</h1>
        <ul>
          <li v-for="(result,index) in resultListData" class="result-list"
              @click="navictionUrl(1)"
              :key="index">
            <h1 class="title" v-html="result.title"></h1>
            <div class="content">
              <div class="img-wrapper" v-show="result.img.length">
                <img :src="result.img" width="124" height="84">
              </div>
              <div class="text-wrapper">
                <h1 class="top-wrapper">{{result.desc}}</h1>
                <div class="bottom-wrapper">
                  <span class="time">{{result.time}}</span>
                  <span class="classify">{{result.classify}}</span>
                  <span class="user">{{result.reportUser}}</span>
                </div>
              </div>
            </div>
          </li>
        </ul>
        <div class="page">
          <Page @on-change="searchKnowledge" :total="100" show-total/>
        </div>
      </div>
    </div>
    <!--右侧布局-->
    <div class="right-wrapper" @click="hideResultList">
      <!--添加知识库-->
      <div class="add-wrapper">
        <div class="btn-wrapper" @click="addKnowledge">
          <span class="icon-knowledge"><Icon type="ios-cloud-upload-outline"/></span>
          <span class="btn">添加知识库</span>
        </div>
        <div class="text">已有<span class="total">3166</span>份知识库资料</div>
        <h1 class="nums">363</h1>
        <p class="new-text">今日新增知识库</p>
      </div>
      <!--树形结构-->
      <div class="tree-wrapper">
        <div class="title-wrapper">
          <span class="border"></span>
          <span class="tree-title">知识库分类</span>
        </div>
        <div class="tree" ref="treeWrapper">
          <BasicTree :treeList="treeList" :funcFlag="funcFlag"></BasicTree>
        </div>
      </div>
    </div>
  </div>
</template>

<script type="text/ecmascript-6">
  import axios from 'axios';
  import { mapState, mapMutations } from 'vuex';
  import BasicTree from 'base/tree/BasicTree.vue';

  export default {
    name: 'KnowledgeQuery',
    data () {
      return {
        IsResultListShow: false, // 搜索列表显示隐藏的开关
        searchListData: [
          {result: '打印机卡纸在什么位置'},
          {result: '打印机卡纸的解决办法'},
          {result: '打印机卡纸了怎么把它拿出来'},
          {result: '电脑没法联网'},
          {result: '怎么清理电脑c盘'},
          {result: '笔记本电脑屏保损坏'}
        ],   // 存放搜索列表的数组
        resultListData: [
          {
            title: '<font color="#f14f4f">打印机卡纸</font>',
            img: 'https://gd4.alicdn.com/imgextra/i4/3275704363/TB2E2okfOFTMKJjSZFAXXckJpXa_!!3275704363.jpg',
            desc: '打印机不能打印大都是因为进出纸部件损坏,就是滚纸那个轴;纸路传感器故障,驱动部件失灵,还有可能是同时进了多张纸甚至纸张的质量差也会导致卡纸等等',
            time: '2018年12月16号',
            classify: '打印机卡纸',
            reportUser: '张云飞'
          },
          {
            title: '针式<font color="#f14f4f">打印机卡纸</font>',
            img: 'https://gd3.alicdn.com/imgextra/i3/246349854/TB2jqJVcXmWBuNjSspdXXbugXXa_!!246349854.jpg',
            desc: '打印机不能打印大都是因为进出纸部件损坏,就是滚纸那个轴;纸路传感器故障,驱动部件失灵,还有可能是同时进了多张纸甚至纸张的质量差也会导致卡纸等等',
            time: '2018年12月16号',
            classify: '打印机卡纸',
            reportUser: '张云飞'
          },
          {
            title: '<font color="#f14f4f">打印机卡纸</font>进纸器损坏',
            img: 'https://gd3.alicdn.com/imgextra/i3/246349854/TB2H9D4dkfb_uJkSmLyXXcxoXXa_!!246349854.jpg',
            desc: '打印机不能打印大都是因为进出纸部件损坏,就是滚纸那个轴;纸路传感器故障,驱动部件失灵,还有可能是同时进了多张纸甚至纸张的质量差也会导致卡纸等等',
            time: '2018年12月16号',
            classify: '打印机卡纸',
            reportUser: '张云飞'
          },
          {
            title: '喷墨<font color="#f14f4f">打印机卡纸</font>',
            img: 'https://gd1.alicdn.com/imgextra/i1/246349854/TB2xl4ZlDnI8KJjy0FfXXcdoVXa_!!246349854.png',
            desc: '打印机不能打印大都是因为进出纸部件损坏,就是滚纸那个轴;纸路传感器故障,驱动部件失灵,还有可能是同时进了多张纸甚至纸张的质量差也会导致卡纸等等',
            time: '2018年12月16号',
            classify: '打印机卡纸',
            reportUser: '张云飞'
          },
          {
            title: '激光<font color="#f14f4f">打印机卡纸</font>',
            img: 'https://gd1.alicdn.com/imgextra/i1/246349854/TB2dAnTgZnI8KJjSsziXXb8QpXa_!!246349854.jpg',
            desc: '打印机不能打印大都是因为进出纸部件损坏,就是滚纸那个轴;纸路传感器故障,驱动部件失灵,还有可能是同时进了多张纸甚至纸张的质量差也会导致卡纸等等',
            time: '2018年12月16号',
            classify: '打印机卡纸',
            reportUser: '张云飞'
          },
          {
            title: '<font color="#f14f4f">打印机卡纸</font>如何修理',
            img: 'https://gd2.alicdn.com/imgextra/i3/0/O1CN01uMwFND1W6xhSOpi3t_!!0-item_pic.jpg',
            desc: '打印机不能打印大都是因为进出纸部件损坏,就是滚纸那个轴;纸路传感器故障,驱动部件失灵,还有可能是同时进了多张纸甚至纸张的质量差也会导致卡纸等等',
            time: '2018年12月16号',
            classify: '打印机卡纸',
            reportUser: '张云飞'
          }
        ],   // 存放搜索结果列表的数组展示内容
        funcFlag: true           // 是否开启tree组件的节点增加删除功能
      };
    },
    computed: {
      ...mapState([
        'treeList' // treeList tree组件的数据源
      ]),
      // 搜索关键词的计算属性的get和set方法
      keywordSearch: {
        get: function () {
          return this.$store.state.treeNodeVal;
        },
        set: function (newValue) {
          this.setTreeValue(newValue);
        }
      }
    },
    methods: {
      ...mapMutations([
        'setTreeList',  // 请求接口设置tree组件的数据源
        'setTreeValue', // 设置store中搜索关键词treeNodeVal的值
        'delTreeValue'  // 删除同上
      ]),
      /**
       * 将中文转为unicode编码
       * @param s 中文字符串参数
       */
      toUnicode (s) {
        return s.replace(/([\u4E00-\u9FA5]|[\uFE30-\uFFA0])/g, function (newStr) {
          return '\\u' + newStr.charCodeAt(0).toString(16);
        });
      },
      showResultList () {
        this.IsResultListShow = true;
      },
      hideResultList () {
        this.IsResultListShow = false;
      },
      delKeywordSearch () {
        this.delTreeValue();
      },
      /**
       * 调用获取搜索关键词列表接口
       */
      haveSearchList () {
        console.log('调用获取搜索关键词列表接口得到searchListData的值');
      },
      /**
       * 调用搜索结果列表接口
       * @param num 页码
       */
      searchKnowledge (num) {
        if (this.keywordSearch) {
          num = !num ? 1 : num;
          console.log('调用搜索结果列表接口', num);
        }
      },
      choiceValue (result) {
        this.setTreeValue(result);
      },
      addKnowledge () {
        console.log('跳转到增加知识库界面');
      },
      /**
       * 打开新页面窗口
       * @param num 传递的参数id
       */
      navictionUrl (num) {
        let routeUrl = this.$router.resolve({
          path: '/test',
          query: {id: num}
        });
        window.open(routeUrl.href, '_blank');
      }
    },
    watch: {
      'keywordSearch' () {
        if (this.keywordSearch) {
          this.haveSearchList();
          // 模拟搜索列表的出现效果,关键点中文正则匹配
          let unicodeVal = this.toUnicode(this.keywordSearch);
          let searchArrays = [];
          this.searchListData.forEach((val) => {
            if (new RegExp(`(${unicodeVal})`).test(val.result)) {
              searchArrays.push(val);
            }
          });
          console.log(searchArrays);
          if (searchArrays.length > 1) {
            this.showResultList();
          } else {
            this.hideResultList();
          }
          this.searchKnowledge();
        }
      }
    },
    created () {
      let vm = this;
      axios.get('/api/treelist').then(res => {
        if (res.data.errno === 1) {
          vm.setTreeList(res.data.data);
        }
      });
    },
    components: {
      BasicTree
    }
  };
</script>

<style lang="stylus" rel="stylesheet/stylus" scoped>
  @import "~common/stylus/variable.styl";

  .knowledge-query
    display: flex
    margin: 16px 24px
    .left-wrapper
      flex: 1
      margin-right: 16px
      .search-wrapper
        position: relative
        margin-bottom: 16px
        .input-wrapper
          display: flex
          position: relative
          font-size: $font-size-medium-x
          color: $color-text-input
          input
            flex: 1
            height: 56px
            line-height: 56px
            padding-left: 24px
            border-top: 1.5px solid $color-blue
            border-left: 1.5px solid $color-blue
            border-bottom: 1.5px solid $color-blue
          .icon
            position: absolute
            top: 12px
            right: 22% // 设置百分比能够保证图标始终与右侧保持同等距离不会随着分辨率变化而出现错位
            font-size: 30px
          .search-btn
            display: inline-block
            flex: 0 0 20%
            width: 20%
            height: 57px
            line-height: 57px
            text-align: center
            color: $color-text-white
            background-color: $color-blue
        .result-wrapper
          position: absolute
          top: 58px
          left: 0
          width: 80%
          z-index: 100
          font-size: $font-size-medium
          background-color: $color-text-white
          box-shadow: 2px 2px 2px 0 rgba(100, 100, 100, 0.1)
          .item-list
            height: 36px
            line-height: 36px
            padding-left: 24px
            &:hover
              background-color: #f0f0f0
      .content-wrapper
        position: relative
        padding: 32px 24px 16px
        border-radius: 8px
        background: $color-text-white
        .title, .title-hide
          margin-bottom: 18px
          line-height: 14px
          font-size: $font-size-small
          color: $color-text-input
          .num
            color: $color-blue
          .key
            color: $color-red
        .result-list
          padding: 16px 0
          border-bottom: 1px dashed $color-background-t
          &:first-child
            padding-top: 0
          &:last-child
            padding-bottom: 30px
            border: none
          .title
            display: inline-block
            margin-bottom: 12px
            padding-bottom: 2px
            line-height: 18px
            font-size: $font-size-medium-x
            color: $color-text-title
            border-bottom: 1px solid $color-text-title
          .content
            display: flex
            font-size: 0
            .img-wrapper
              flex: 0 0 124px
              width: 124px
              height: 84px
              margin-right: 16px
            .text-wrapper
              flex: 1
              font-size: $font-size-small-x
              .top-wrapper
                margin: 2px 0 10px
                line-height: 20px
                color: $color-text-content
              .bottom-wrapper
                color: $color-text-input
                .classify
                  display: inline-block
                  margin: 0 18px
        .page
          font-size: $font-size-medium
    .right-wrapper
      flex: 0 0 360px
      .add-wrapper
        padding: 24px 26px
        text-align: center
        font-size: $font-size-medium
        border-radius: 8px
        background-color: $color-text-white
        .btn-wrapper
          margin: 0 10px 16px
          height: 56px
          line-height: 56px
          font-size: 0
          color: $color-text-white
          background: $color-orange
          .icon-knowledge
            display: inline-block
            width: 20px
            height: 20px
            margin-right: 6px
            font-size: $font-size-large
          .btn
            line-height: 18px
            font-size: $font-size-medium-x
        .text
          padding-bottom: 16px
          line-height: 18px
          color: $color-text-input
          border-bottom: 1px solid $color-background-t
          .total
            font-size: $font-size-large
            color: $color-orange
        .nums
          margin: 18px 0 12px
          font-size: 36px
          color: $color-purple
        .new-text
          color: $color-text-input
      .tree-wrapper
        margin-top: 16px
        border-radius: 8px
        background: $color-text-white
        .title-wrapper
          height: 58px
          line-height: 58px
          font-size: 0
          border-bottom: 1px solid $color-background-t
          .border
            display: inline-block
            position: relative
            top: 6px
            width: 4px
            height: 22px
            background: $color-blue
          .tree-title
            display: inline-block
            padding-left: 24px
            font-size: $font-size-medium
            color: $color-text-title
        .tree
          width: 360px
          height: 772px
          overflow: auto
          padding-left: 10px
          padding-bottom: 10px
          box-sizing: border-box
          &::-webkit-scrollbar
            width: 2px
            height: 2px
          &::-webkit-scrollbar-thumb
            border-radius: 5px
            -webkit-box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.2)
            background: $color-background-t
          &::-webkit-scrollbar-track
            -webkit-box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2)
            border-radius: 0
            background: $color-text-white
</style>
#tree组件BasicTree
<template>
  <div class="basic-tree">
    <ul style="padding-left: 16px;">
      <template v-for="item in treeList">
        <li v-if="item.childs==null &&item.treeNode" :key="item.treeNode.id">
          <div @click="handleValue(item.treeNode.name,item.treeNode.id)"
               :class="{'current': currentIndex === item.treeNode.id}">
            <!--<Checkbox v-model="item.treeNode.name"></Checkbox>-->
            <span class="parent-icon"><Icon :type="item.treeNode.icon"/></span>
            <span>{{item.treeNode.name}}</span>
            <div class="funcs" v-show="funcFlag">
              <span @click.stop.prevent="addTreeNode(item.treeNode.id)">
                <Icon type="ios-add"/>
              </span>
              <span @click.stop.prevent="substructTreeNode(item.treeNode.id,$event)">
                <Icon type="ios-close"/>
              </span>
            </div>
          </div>
        </li>
        <li v-if="item.childs&&item.treeNode" :key="item.id" :id='"tree"+item.treeNode.id'>
          <div class="parentli" @click="toggleTreeNodeList(item.treeNode.id)">
            <!--<Checkbox v-model="item.treeNode.name"></Checkbox>-->
            <span class="parent-icon"><Icon :type="item.treeNode.icon"/></span>
            <span>{{item.treeNode.name}}</span>
            <div class="funcs" v-show="funcFlag">
              <span @click.stop.prevent="addTreeNode(item.treeNode.id)">
                <Icon type="ios-add"/>
              </span>
            </div>
          </div>
          <BasicTree :treeList="item.childs" :funcFlag="funcFlag"></BasicTree>
        </li>
      </template>
    </ul>
  </div>
</template>

<script type="text/ecmascript-6">
  import { mapState, mapMutations } from 'vuex';

  export default {
    name: 'BasicTree',
    props: {
      /**
       * 接收tree组件数据源
       */
      treeList: {
        type: Array,
        default () {
          return [];
        }
      },
      /**
       * 是否开启节点新增删除功能
       */
      funcFlag: {
        type: Boolean,
        default: false
      }
    },
    computed: {
      ...mapState([
        'currentIndex',  // 当前选中的属性节点索引
        'treeFlag',      // 控制tree中的节点展开和收起
        'treeNodeValue'  // tree新增的节点值
      ])
    },
    methods: {
      ...mapMutations([
        'setTreeValue',     // 设置store中搜索关键词treeNodeVal的值
        'setCurrentIndex',  // 设置选中的属性节点索引值
        'setTreeFlag',      // 设置tree中的节点开关标识值
        'addStoreTreeNode', // 增加新节点
        'delStoreTreeNode', // 删除节点
        'setTreeNodeValue'  // 设置新增节点的值
      ]),
      /**
       * 设置搜索关键词的值和当前选中的索引
       * @param val 关键词的值
       * @param id  索引
       */
      handleValue (val, id) {
        this.setTreeValue(val);
        this.setCurrentIndex(id);
      },
      /**
       * 展开/收起节点
       * @param num 节点id值
       */
      toggleTreeNodeList (num) {
        let dom = document.getElementById('tree' + num);
        let subDom = dom.childNodes;
        if (this.treeFlag) {
          subDom[2].style.display = 'none';
          this.setTreeFlag();
        } else {
          subDom[2].style.display = '';
          this.setTreeFlag();
        }
      },
      /**
       * 新增节点
       * @param num 节点id值
       */
      addTreeNode (num) {
        let vm = this;
        this.$Modal.confirm({
          render: (h) => {
            return h('Input', {
              props: {
                value: this.treeNodeValue,
                autofocus: true,
                placeholder: '请输入节点名称'
              },
              on: {
                input: (val) => {
                  vm.setTreeNodeValue(val);
                }
              }
            });
          },
          onOk () {
            if (vm.treeNodeValue === '' || vm.treeNodeValue === null) {
              vm.$Message.error('节点值不能为空');
            } else {
              let obj = [];
              obj.push({id: num, name: vm.treeNodeValue});
              vm.addStoreTreeNode(obj);
            }
          }
        });
      },
      /**
       * 删除节点
       * @param num 节点id值
       * @param event 当前点击事件对象
       */
      substructTreeNode (num, event) {
        let currentParentNode = event.target.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode;
        let currentParentId = currentParentNode.getAttribute('id');
        if (new RegExp(`(tree)`).test(currentParentId)) {
          currentParentId = currentParentId.replace(RegExp.$1, '');
        }
        let finalParentId = parseInt(currentParentId);
        let numObj = {};
        numObj.num = num;
        numObj.finalParentId = finalParentId;
        this.delStoreTreeNode(numObj);
      }
    }
  };
</script>

<style lang="stylus" rel="stylesheet/stylus" scoped>
  @import "~common/stylus/variable.styl";

  .basic-tree
    width: 100%
    position: relative
    font-size: $font-size-small
    .parent-icon
      display: inline-block
      margin-left: 4px
      font-size: $font-size-large
      color: $color-blue
    .parentli
      height: 30px
      line-height: 30px
      font-size: $font-size-small-x
      background: $color-background-s
      .funcs
        display: inline-block
        margin-left: 10px
        font-size: 0
        span
          display: inline-block
          width: 10px
          height: 10px
          z-index: 100
          font-size: 20px
          &:nth-child(1)
            color: $color-blue
          &:nth-child(2)
            color: $color-red
    ul
      & > li
        padding-top: 6px
        position: relative
        left: 0
        cursor: pointer
        li
          &::before
            border-left: 1px dashed $color-text-input
          &::after
            border-top: 1px dashed $color-text-input
    li
      div
        .funcs
          display: inline-block
          margin-left: 10px
          font-size: 0
          span
            display: inline-block
            width: 10px
            height: 10px
            margin-right: 10px
            z-index: 100
            font-size: 20px
            &:nth-child(1)
              color: $color-blue
            &:nth-child(2)
              color: $color-red
        &.current
          color: $color-text-white
          background: $color-blue
          .parent-icon
            color: $color-text-white
          .funcs
            span
              color: $color-text-white
      &::before, &::after
        position: absolute
        left: -14px
        right: auto
        content: ''
      &::before
        top: 0
        bottom: 50px
        width: 1px
        height: 100%
      &::after
        width: 14px
        top: 25px
      &:last-child
        &::before
          height: 25px
      ul > li
        padding-top: 10px
        position: relative
        left: 10px

</style>
// mutations.js
.....
[type.SET_TREELIST] (state, val) {
    state.treeList = val;
  },
  [type.SET_TREEVALUE] (state, val) {
    state.treeNodeVal = val;
  },
  [type.DEL_TREEVALUE] (state) {
    state.treeNodeVal = '';
  },
  [type.SET_CURRENTINDEX] (state, val) {
    state.currentIndex = val;
  },
  [type.SET_TREEFlAG] (state) {
    state.treeFlag = !state.treeFlag;
  },
  [type.ADDSTORE_TREENODE] (state, obj) {
    let num = obj[0].id;
    let val = obj[0].name;
    let rel = util.getTreeListObjById(state.treeList, num);
    let treeTime = util.getTimeRandom();
    let finalTime = parseInt(treeTime);
    let treeObj = [];
    treeObj.push({
      treeNode: {
        id: finalTime,
        icon: 'ios-document',
        name: val
      },
      childs: null
    });
    if (rel.childs === null) {
      rel.treeNode.icon = 'ios-book';
      rel.childs = [];
    }
    rel.childs.push(treeObj[0]);
  },
  [type.DELSTORE_TREENODE] (state, obj) {
    let parentObj = util.getTreeListObjById(state.treeList, obj.finalParentId);
    let parentChildArrays = parentObj.childs;
    for (let i in parentChildArrays) {
      if (parentChildArrays[i].treeNode.id === obj.num) {
        parentChildArrays.splice(i, 1);
      }
    }
    if (!parentChildArrays.length) {
      parentObj.treeNode.icon = 'ios-document';
      parentObj.childs = null;
    }
  },
  [type.SET_TREENODEVALUE] (state, val) {
    state.treeNodeValue = val;
  }
// util.js
....
/**
 * 递归遍历树形结构
 * @param trees 数据源
 * @param id 节点id值
 * @returns {*}
 */
util.getTreeListObjById = function (trees, id) {
  if (!id || !trees || !trees.length) {
    return null;
  }
  let treeObj = null;
  for (let item of trees) {
    if (item.treeNode.id === id) {
      return item;
    }
    treeObj = util.getTreeListObjById(item.childs, id);
    if (treeObj) {
      return treeObj;
    }
  }
  return null;
};
/**
 * 获取当前时间跟上随机数由此生成永不重复的数据
 * @returns {string}
 */
util.getTimeRandom = function () {
  const now = new Date();
  let month = now.getMonth() + 1;
  let day = now.getDate();
  let hour = now.getHours();
  let minutes = now.getMinutes();
  let seconds = now.getSeconds();
  let time = now.getFullYear().toString() + month.toString() + day + hour + minutes + seconds + (Math.round(Math.random() * 89 + 100)).toString();
  return time;
};
// type.js
// 设置tree结构数据
export const SET_TREELIST = 'setTreeList';
// 设置知识库搜索框的值
export const SET_TREEVALUE = 'setTreeValue';
// 删除知识库搜索框的值
export const DEL_TREEVALUE = 'delTreeValue';
// 设置tree索引
export const SET_CURRENTINDEX = 'setCurrentIndex';
// 改变tree节点开关
export const SET_TREEFlAG = 'setTreeFlag';
// 增加tree节点
export const ADDSTORE_TREENODE = 'addStoreTreeNode';
// 删除tree节点
export const DELSTORE_TREENODE = 'delStoreTreeNode';
// 设置增加节点的值
export const SET_TREENODEVALUE = 'setTreeNodeValue';
// 模拟后台树形结构数据
{
  "tree": [
    {
      "treeNode": {
        "id": 1,
        "icon": "ios-book",
        "name": "业务知识"
      },
      "childs": [
        {
          "treeNode": {
            "id": 10,
            "icon": "ios-book",
            "name": "业务知识节点1"
          },
          "childs": [
            {
              "treeNode": {
                "id": 101,
                "icon": "ios-book",
                "name": "层级三101"
              },
              "childs": [
                {
                  "treeNode": {
                    "id": 1011,
                    "icon": "ios-document",
                    "name": "层级四1011"
                  },
                  "childs": null
                },
                {
                  "treeNode": {
                    "id": 1012,
                    "icon": "ios-document",
                    "name": "层级四1012"
                  },
                  "childs": null
                }
              ]
            },
            {
              "treeNode": {
                "id": 102,
                "icon": "ios-document",
                "name": "层级三102"
              },
              "childs": null
            },
            {
              "treeNode": {
                "id": 103,
                "icon": "ios-document",
                "name": "层级三103"
              },
              "childs": null
            }
          ]
        },
        {
          "treeNode": {
            "id": 11,
            "icon": "ios-book",
            "name": "业务知识节点2"
          },
          "childs": [
            {
              "treeNode": {
                "id": 111,
                "icon": "ios-book",
                "name": "层级三111"
              },
              "childs": [
                {
                  "treeNode": {
                    "id": 1111,
                    "icon": "ios-document",
                    "name": "层级四1111"
                  },
                  "childs": null
                },
                {
                  "treeNode": {
                    "id": 1112,
                    "icon": "ios-document",
                    "name": "层级四1112"
                  },
                  "childs": null
                }
              ]
            },
            {
              "treeNode": {
                "id": 112,
                "icon": "ios-document",
                "name": "层级三112"
              },
              "childs": null
            },
            {
              "treeNode": {
                "id": 113,
                "icon": "ios-document",
                "name": "层级三113"
              },
              "childs": null
            }
          ]
        },
        {
          "treeNode": {
            "id": 12,
            "icon": "ios-book",
            "name": "业务知识节点3"
          },
          "childs": [
            {
              "treeNode": {
                "id": 121,
                "icon": "ios-document",
                "name": "层级三121"
              },
              "childs": null
            },
            {
              "treeNode": {
                "id": 122,
                "icon": "ios-document",
                "name": "层级三122"
              },
              "childs": null
            },
            {
              "treeNode": {
                "id": 123,
                "icon": "ios-document",
                "name": "层级三123"
              },
              "childs": null
            }
          ]
        },
        {
          "treeNode": {
            "id": 13,
            "icon": "ios-book",
            "name": "业务知识节点4"
          },
          "childs": [
            {
              "treeNode": {
                "id": 131,
                "icon": "ios-document",
                "name": "层级三131"
              },
              "childs": null
            },
            {
              "treeNode": {
                "id": 132,
                "icon": "ios-document",
                "name": "层级三132"
              },
              "childs": null
            },
            {
              "treeNode": {
                "id": 133,
                "icon": "ios-document",
                "name": "层级三133"
              },
              "childs": null
            }
          ]
        },
        {
          "treeNode": {
            "id": 14,
            "icon": "ios-book",
            "name": "业务知识节点5"
          },
          "childs": [
            {
              "treeNode": {
                "id": 141,
                "icon": "ios-document",
                "name": "层级三141"
              },
              "childs": null
            },
            {
              "treeNode": {
                "id": 142,
                "icon": "ios-document",
                "name": "层级三142"
              },
              "childs": null
            },
            {
              "treeNode": {
                "id": 143,
                "icon": "ios-document",
                "name": "层级三143"
              },
              "childs": null
            }
          ]
        },
        {
          "treeNode": {
            "id": 15,
            "icon": "ios-book",
            "name": "业务知识节点6"
          },
          "childs": [
            {
              "treeNode": {
                "id": 151,
                "icon": "ios-document",
                "name": "层级三111"
              },
              "childs": null
            },
            {
              "treeNode": {
                "id": 152,
                "icon": "ios-document",
                "name": "层级三112"
              },
              "childs": null
            },
            {
              "treeNode": {
                "id": 153,
                "icon": "ios-document",
                "name": "层级三113"
              },
              "childs": null
            }
          ]
        }
      ]
    },
    {
      "treeNode": {
        "id": 2,
        "icon": "ios-book",
        "name": "技术知识"
      },
      "childs": [
        {
          "treeNode": {
            "id": 20,
            "icon": "ios-book",
            "name": "技术知识节点1"
          },
          "childs": [
            {
              "treeNode": {
                "id": 201,
                "icon": "ios-document",
                "name": "层级三201"
              },
              "childs": null
            },
            {
              "treeNode": {
                "id": 202,
                "icon": "ios-document",
                "name": "层级三202"
              },
              "childs": null
            },
            {
              "treeNode": {
                "id": 203,
                "icon": "ios-document",
                "name": "层级三203"
              },
              "childs": null
            }
          ]
        },
        {
          "treeNode": {
            "id": 21,
            "icon": "ios-book",
            "name": "技术知识节点2"
          },
          "childs": [
            {
              "treeNode": {
                "id": 211,
                "icon": "ios-document",
                "name": "层级三211"
              },
              "childs": null
            },
            {
              "treeNode": {
                "id": 212,
                "icon": "ios-document",
                "name": "层级三112"
              },
              "childs": null
            },
            {
              "treeNode": {
                "id": 213,
                "icon": "ios-document",
                "name": "层级三113"
              },
              "childs": null
            }
          ]
        },
        {
          "treeNode": {
            "id": 22,
            "icon": "ios-book",
            "name": "技术知识节点3"
          },
          "childs": [
            {
              "treeNode": {
                "id": 221,
                "icon": "ios-document",
                "name": "层级三221"
              },
              "childs": null
            },
            {
              "treeNode": {
                "id": 222,
                "icon": "ios-document",
                "name": "层级三222"
              },
              "childs": null
            },
            {
              "treeNode": {
                "id": 223,
                "icon": "ios-document",
                "name": "层级三223"
              },
              "childs": null
            }
          ]
        },
        {
          "treeNode": {
            "id": 23,
            "icon": "ios-book",
            "name": "技术知识节点4"
          },
          "childs": [
            {
              "treeNode": {
                "id": 231,
                "icon": "ios-document",
                "name": "层级三231"
              },
              "childs": null
            },
            {
              "treeNode": {
                "id": 232,
                "icon": "ios-document",
                "name": "层级三232"
              },
              "childs": null
            }
          ]
        },
        {
          "treeNode": {
            "id": 24,
            "icon": "ios-book",
            "name": "技术知识节点5"
          },
          "childs": [
            {
              "treeNode": {
                "id": 241,
                "icon": "ios-document",
                "name": "层级三241"
              },
              "childs": null
            }
          ]
        },
        {
          "treeNode": {
            "id": 25,
            "icon": "ios-book",
            "name": "技术知识节点6"
          },
          "childs": [
            {
              "treeNode": {
                "id": 251,
                "icon": "ios-document",
                "name": "层级三251"
              },
              "childs": null
            },
            {
              "treeNode": {
                "id": 252,
                "icon": "ios-document",
                "name": "层级三252"
              },
              "childs": null
            }
          ]
        }
      ]
    }
  ],
  "success": 1
}

    

新增节点会在当前选择的父节点下新增子节点,并且原来没有下一级的节点成具有下层级的节点,可以无限层级添加删除节点

 

如果想在自己的项目中使用,请自己仔细阅读源码,注释已经写的很清楚了,注意后台传过来的树形结构中必须有id为数值类型,并且永不重复,新增节点已经保证了永不重复。

该组件还有很多功能有待完善和优化,比如多层级节点复选框选择,节点上移下移,后面有时间再来慢慢完善,期望能写出一个最终可以开源的tree框架,目前源码可以自由复制修改,但必须写好引用博客地址,多谢!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值