1. 程式人生 > >Vue樹形結構操作

Vue樹形結構操作

        樹形結構是一種常用的資料結構,使用Vue怎麼來渲染呢?要把樹結構的每一個節點都渲染成dom,需要對樹結構進行遞迴遍歷。Vue元件可以通過name選項的設定來遞迴的呼叫自己,因此渲染起來很方便。  

        本文簡單實現了一下樹結構的基本增刪改等操作,實現效果可以在 http://wintc.top 檢視,後續還會繼續對樹結構渲染(比如拖拽操作、大資料量渲染效率等)進行探索。

程式碼比較簡單,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。

時常翻翻官網總會有意想不到的收穫~。~