树形结构是一种常用的数据结构,使用Vue怎么来渲染呢?要把树结构的每一个节点都渲染成dom,需要对树结构进行递归遍历。Vue组件可以通过name选项的设置来递归的调用自己,因此渲染起来很方便。  

        本文简单实现了一下树结构的基本增删改等操作,后续还会继续对树结构渲染(比如拖拽操作、大数据量渲染效率等)进行探索。

代码比较简单,MyTree组件:

<template>
  <div class="my-tree">
    <div
      class="brother"
      v-for="(data, idx) of treeData"
      :key="idx">
      <div class="node">
        <span @click="data.expand=!data.expand">
          <Button class="node-expand" type="text" icon="chevron-down" v-if="data.expand"></Button>
          <Button class="node-expand" type="text" icon="chevron-right" v-else></Button>
        </span>
        <input class="node-name" v-model="data.name" />
        <span class="node-menu">
          <span class="menu-item" title="添加同级节点" @click.stop="$emit('addBrother', $event, data)">
            <Icon type="plus-round"></Icon>
          </span>
          <span class="menu-item" title="添加下级节点" @click.stop="$emit('addChild', $event, data)">
            <Icon type="ios-plus-outline"></Icon>
          </span>
          <span class="menu-item" title=“删除” @click.stop="$emit('deleteNode', $event, data)">
            <Icon type="trash-a"></Icon>
          </span>
        </span>
      </div>
      <div
        class="children"
        v-if="data.children && data.children.length"
        v-show="data.expand">
        <my-tree
          @addBrother="addBrother"
          @addChild="addChild"
          @deleteNode="deleteNode"
          :treeData="data.children">
        </my-tree>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'my-tree',
  props: {
    treeData: {
      type: Array,
      default: () => [{
        id: 1,
        name: '一级节点1',
        expand: true,
        children: [{
          id: 2,
          expand: true,
          name: '二级节点1'
        }]
      },
      {
        id: 3,
        expand: true,
        name: '一级节点2',
        children: [{
          id: 4,
          expand: true,
          name: '二级节点1'
        },
        {
          id: 5,
          expand: true,
          name: '二级节点2',
          children: [{
            id: 6,
            expand: true,
            name: '三级节点'
          }]
        }]
      }]
    }
  },
  methods: {
    addBrother (event, data) {
      this.$emit('addBrother', event, data)
    },
    addChild (event, data) {
      this.$emit('addChild', event, data)
    },
    deleteNode (event, data) {
      this.$emit('deleteNode', event, data)
    }
  }
}
</script>

<style lang="stylus" scoped>
.children
  position relative
  padding-left 20px
.node-expand
  width 1.5rem
  height 1.5rem
  padding-left 0
  padding-right 0
  padding-bottom 0
  padding-top 0
  &:focus
    box-shadow none

.node-menu
  width 3rem
  display flex
  justify-content space-around
  .menu-item
    &:hover
      cursor pointer

.brother
  display flex
  flex-direction column
  .node
    height 1.5rem
    display flex
    align-items center
    .node-name
      // border none
      // background none
      overflow-x visible
      &:focus
        outline none
</style>

组件使用,Tree.vue页面代码:

<template>
  <div class="tree">
    <div class="tree-title">
      树结构
    </div>
    <my-tree
      @addBrother="addBrother"
      @addChild="addChild"
      @deleteNode="deleteNode">
    </my-tree>
  </div>
</template>

<script>
import MyTree from '@/components/MyTree'

export default {
  name: 'tree',
  components: {
    'my-tree': MyTree
  },
  data () {
    return {
      id: 100
    }
  },
  methods: {
    addBrother (event, data) {
      let parentData = this.getParentData(event.target)
      console.log(parentData)
      if (parentData) {
        let index = parentData.indexOf(data)
        if (index !== -1) {
          parentData.splice(index + 1, 0, this.newNode())
        }
      }
    },
    addChild (event, data) {
      if (!data.children) {
        this.$set(data, 'children', [])
      }
      data.children.push(this.newNode())
    },
    deleteNode (event, data) {
      let parentData = this.getParentData(event.target)
      if (parentData) {
        let index = parentData.indexOf(data)
        if (index !== -1) {
          parentData.splice(index, 1)
        }
      }
    },
    newNode () {
      let id = this.id++
      return {
        id,
        name: '新节点' + id,
        expand: true,
        children: []
      }
    },
    getParentData (node) {
      while (node && node.tagName !== 'BODY') {
        if (node.__vue__ && node.__vue__.$options.name === 'my-tree') {
          return node.__vue__.treeData
        }
        node = node.parentNode
      }
      return null
    }
  }
}
</script>

<style lang="stylus" scoped>
.tree
  padding 3rem 2rem
  text-align left
  .tree-title
    border-bottom 1px solid gray
    padding-bottom 0.5rem
    margin-bottom 1rem
</style>

这里使用了一些iView的图标标签,可以简单的使用iView来实现漂亮的效果,可以到其iView网了解使用方法。

学习前端也几个月了,在开始使用Vue的很长一段时间里,对Vue一些选项的作用不甚了解,在Vue网都有比较清楚的介绍,比如递归渲染用到的name属性:

name

  • 类型string

  • 限制:只有作为组件选项时起作用。

  • 详细

    允许组件模板递归地调用自身。注意,组件在全局用 Vue.component() 注册时,全局 ID 自动作为组件的 name。

时常翻翻官网总会有意想不到的收获~。~

 

Logo

前往低代码交流专区

更多推荐