VUE實現Studio管理後臺(七):樹形結構,文件樹,節點樹共用一套代碼NodeTree

本次介紹的內容,稍稍複雜了一點,用VUE實現樹形結構。目前這個屬性結構尚未編輯功能,僅僅是展現。明天再開一篇文章,介紹如何增長編輯功能,標題都想好了。先看今天的展現效果:css

構建樹必須用到遞歸,使用slot這種直觀明瞭的方式,已經行不通了。只能經過屬性參數,傳遞一個樹形的數據結構給組件,傳入的數據結構大體是這個樣子:html

[
        {
          title:‘頁面 ’
          selected:false,
          opened:false,
          isFolder:true,
          children:[
            {
              title:'index.html',
              selected:false,
              opened:false,
              icon:"far fa-file-code",
            },
            {
              title:'product.html',
              selected:false,
              opened:false,
              icon:"far fa-file-code",
            },
          ],
        },
        {
          title:‘樣式’
          selected:false,
          opened:false,
          isFolder:true,
          children:[
            {
              title:'style.css',
              selected:false,
              opened:false,
              icon:"far fa-file-code",
            },
          ],
        },
]

 

每一個節點經過children嵌套子節點。須要注意的是,咱們但願這顆樹是能夠被編輯的,能夠增長、刪除、編輯其節點,因此須要數據的雙向綁定,不能經過普通屬性props傳遞給組件,而是經過v-model傳遞。
RXEditor項目中,只有兩個地方用到了樹形結構,要製做的組件知足這兩處需求就能夠,由於不是構建一個通用類庫,就能夠相對簡單些。這兩處地方一處用於展現並編輯文件目錄結構,一處是節點樹,純顯示,沒有編輯功能。文件樹只有葉子節點能夠被選中,節點樹全部節點均可以被選中。都是單選,無複選需求。
給這個控件取個大氣的名字,叫NodeTree吧,先看如何使用NodeTree。
第一處調用: vue

<NodeTree v-model="files" 
:openIcon="'fas fa-folder-open'" 
:closeIcon="'fas fa-folder'" >
</NodeTree>

 

第二處調用:node

<NodeTree v-model="nodes" 
:openIcon="'fas fa-caret-down'" 
:closeIcon="'fas fa-caret-right'" 
:leafIcon="''"
:folderCanbeSelected = 'true'>
</NodeTree>

經過v-model傳遞樹形數據結構,openIcon是節點展開時的圖標,closeIcion是節點閉合時的圖標,leafIcon是沒有子節點時的圖標。這些圖標若是不設置,會有缺省值,是文件夾跟文件的樣子。爲了增長可擴展性,樹形數據結構也能夠放置圖標,數據結構裏的圖標設置優先級高,能夠覆蓋控件的設置。明白個原理,想作成什麼樣子,看本身的項目需求。folderCanbeSelected 參數是指含有子節點的節點(好比文件夾)是否能夠被選中。git

在src目錄下新建tree目錄,放兩個文件:github

NodeTree是樹形控件,TreeNode是樹形控件內部的節點,名字稍微優勢繞,可是是我喜歡的命名方式。數據結構

NodeTree.vue的代碼(省略CSS):ui

<template>
  <div class="node-tree">
    <TreeNode v-for = "(node, i) in inputValue" 
      :key = "i" 
      v-model = "inputValue[i]"
      :openIcon = "openIcon"
      :closeIcon = "closeIcon"
      :leafIcon = "leafIcon"
      :folderCanbeSelected = "folderCanbeSelected"
      @nodeSelected = "nodeSelected"
      ></TreeNode>
  </div>
</template>

<script>
import TreeNode from "./TreeNode.vue"

export default {
  name: 'FileTree',
  props: {
    value: { default: []},
    openIcon:{ default: 'fas fa-folder-open'},
    closeIcon:{ default: 'fas fa-folder'},
    leafIcon:{ default: 'fas fa-file' },
    folderCanbeSelected:{ default:false }
  },
  components:{
    TreeNode
  },
  data() {
    return {
    };
  },

  computed:{
    inputValue: {
        get:function() {
          return this.value;
        },
        set:function(val) {
          this.$emit('input', val);
        },
    },
  },

  methods: {
    nodeSelected(selectedNode){
      this.inputValue.forEach(child=>{
        this.resetSelected(selectedNode, child)
      })
      this.$emit('nodeSelected', selectedNode)
    },

    //遞歸充置選擇狀態
    resetSelected(selectedNode, node){
      node.selected = (node === selectedNode)
      if(node.children){
        node.children.forEach(child=>{
          this.resetSelected(selectedNode, child)
        })
      }
    }
  },
}
</script>

 

這個代碼邏輯很簡單,就是接收外面參數,循環調用TreeNode。要自定義v-model的話,須要用到屬性(props)value,計算屬性inputValue用於修改value,具體原理,能夠參考VUE官方文檔。
須要特殊注意的是nodeSelected事件,這個事件在子節點產生,經過冒泡的方式層層往父節點發送,最後到達NodeTree組件。NodeTree組件再經過$emit方法,分發到外層調用組件。
此次實現的控件是單選,排他的,須要遞歸調用resetSelected方法消除其它節點的選中狀態。this

TreeNode組件的代碼以下(省略CSS,如須要,請到GIthub獲取):spa

<template>
  <div class="tree-node" :class="inputValue.selected ? 'selected' :''"

  >
    <div class="node-title" 
      @click="click"  
      @contextmenu.prevent = 'onContextMenu'
    >
      <div  class="node-icon" @click="iconClick">
        <i v-show="icon" :class="icon"></i>
      </div>
      {{inputValue.title}}
    </div>
    <div v-show="showChild" class="children-nodes">
      <TreeNode v-for="(child, i) in inputValue.children" 
        :openIcon = "openIcon"
        :closeIcon = "closeIcon"
        :leafIcon = "leafIcon"
        :key="i" 
        :folderCanbeSelected = "folderCanbeSelected"
        v-model="inputValue.children[i]"
        @nodeSelected = "nodeSelected"
      ></TreeNode>
    </div>
  </div>
</template>

<script>
export default {
  name: 'TreeNode',
  props: {
    value: { default: {}},
    openIcon:{ default: 'fas fa-folder-open'},
    closeIcon:{ default: 'fas fa-folder'},
    leafIcon:{ default: 'fas fa-file' },
    folderCanbeSelected:{default: false},
  },
  data() {
    return {
    }
  },

  computed:{
    inputValue: {
        get:function() {
          return this.value;
        },
        set:function(val) {
          this.$emit('input', val);
        },
    },

    icon(){
      if(this.hasChildren){
        return this.inputValue.opened ? this.openIcon : this.closeIcon
      }
      return this.inputValue.icon !== undefined ? this.inputValue.icon : this.leafIcon
    },

    showChild(){
      return this.hasChildren && this.inputValue.opened
    },

    hasChildren(){
      return this.inputValue.children
         &&this.inputValue.children.length > 0
    },
  },

  methods: {
    click(){
      if((this.hasChildren && this.folderCanbeSelected) || !this.hasChildren){
        this.inputValue.selected = true
        this.$emit('nodeSelected', this.inputValue)
      }
      else {
        this.inputValue.opened = !this.inputValue.opened
      }
    },

    iconClick(event){
      if(this.hasChildren && this.folderCanbeSelected){
        event.stopPropagation()
        this.inputValue.opened = !this.inputValue.opened
      }
    },

    nodeSelected(node){
      this.$emit('nodeSelected', node)
    },

    onContextMenu(event){
      console.log(event)
    }
  },

}
</script>

父組件調用時經過v-mode,把整個節點的數據傳入該控件。該組件遞歸調用自身,從而造成樹形結構。三個狀態:opened(展開),closed(閉合),selected(選中)存於model數據中,這樣在控件外部,經過修改model,也能夠控制節點狀態。

本功能介紹完畢,代碼請自行到github獲取相應歷史版本:
https://github.com/vularsoft/studio-ui

相關文章
相關標籤/搜索