基於Vue的組織架構樹組件

因爲公司業務需求,須要開發一個展現組織架構的樹組件(公司的項目是基於Vue)。在GitHub上找了半天,這類組件很少,也沒有符合業務需求的組件,因此決定本身造輪子!javascript

分析

  • 既然是樹,那麼每一個節點都應該是相同的組件
  • 節點下面套節點,因此節點組件應該是一個遞歸組件

那麼,問題來了。遞歸組件怎麼寫?css

遞歸組件

Vue官方文檔是這樣說的:html

組件在它的模板內能夠遞歸地調用本身。不過,只有當它有 name 選項時才能夠這麼作vue

接下來,咱們來寫一個樹節點遞歸組件:java

<template>
    <div class="org-tree-node">
    	<div class="org-tree-node-label">{{data.label}}</div>
    	
    	<div class="org-tree-node-children" v-if="data.children">
    	    <org-tree-node v-for="node in data.children" :data="node" :key="data.id"></org-tree-node>
    	</div>
    </div>
</template>

<script> export default { name: 'OrgTreeNode', props: { data: Object } } </script>

<style> /* ... */ </style>
複製代碼

而後渲染這個這個組件,效果以下node

至此,一個簡單的組織架構樹組件就完成了。git

然而,事情還遠遠沒有結束。。。github

需求說:節點的label要支持定製,樹要支持水平展現!element-ui

所以,咱們對遞歸組件做以下修改:數據結構

<template>
    <div class="org-tree-node">
    	<div class="org-tree-node-label">
    	    <slot>{{data.label}}</slot>
    	</div>
    	
    	<div class="org-tree-node-children" v-if="data.children">
    	    <org-tree-node v-for="node in data.children" :data="node" :key="data.id"></org-tree-node>
    	</div>
    </div>
</template>

<script> export default { name: 'OrgTreeNode', props: { data: Object } } </script>

<style> /* ... */ </style>
複製代碼

咱們使用slot插槽來支持label可定製,可是問題又來了:咱們發現只有第一層級的節點label能定製,嵌套的子節點不能有效的傳遞slot插槽。上網查了半天,仍然沒有結果,因而再看官方文檔。發現有個函數式組件。因爲以前使用過element-uitree組件,受到啓發,就想到了能夠像element-uitree組件同樣傳一個renderContent函數,由調用者本身渲染節點label,這樣就達到了節點定製的目的!

函數式組件

接下來,咱們將樹節點模板組件改形成函數式組件。編寫node.js:

  1. 首先咱們實現一個render函數

    export const render = (h, context) => {
      const {props} = context
      return renderNode(h, props.data, context)
    }
    複製代碼
  2. 實現renderNode函數

    export const renderNode = (h, data, context) => {
      const {props} = context
      const childNodes = []
    
      childNodes.push(renderLabel(h, data, context))
    
      if (props.data.children && props.data.children.length) {
        childNodes.push(renderChildren(h, props.data.children, context))
      }
    
      return h('div', {
        domProps: {
          className: 'org-tree-node'
        }
      }, childNodes)
    }
    複製代碼
  3. 實現renderLabel函數。節點label定製關鍵在這裏:

    export const renderLabel = (h, data, context) => {
      const {props} = context
      const renderContent = props.renderContent
      const childNodes = []
    
      // 節點label定製,由調用者傳入的renderContent實現
      if (typeof renderContent === 'function') {
        let vnode = renderContent(h, props.data)
    
        vnode && childNodes.push(vnode)
      } else {
        childNodes.push(props.data.label)
      }
    
      return h('div', {
        domProps: {
          className: 'org-tree-node-label'
        }
      }, childNodes)
    }
    複製代碼
  4. 實現renderChildren函數。這裏遞歸調用renderNode,實現了遞歸組件

    export const renderChildren = (h, list, context) => {
      if (Array.isArray(list) && list.length) {
        const children = list.map(item => {
          return renderNode(h, item, context)
        })
    
        return h('div', {
          domProps: {
            className: 'org-tree-node-children'
          }
        }, children)
      }
      return ''
    }
    複製代碼

至此咱們的render函數完成了,接下來使用render函數定義函數式組件。在tree組件裏面聲明:

<template>
    <!-- ... -->
</template>

<script> import render from './node.js' export default { name: 'OrgTree', components: { OrgTreeNode: { render, // 定義函數式組件 functional: true } } } </script>
複製代碼

至此咱們的函數式組件改造完成了,至於水平顯示用樣式控制就能夠了。

CSS樣式

樣式使用less預編譯。節點之間的線條採用了 :before:after僞元素的border繪製

功能擴展

  • 添加了 labelClassName 屬性,以支持對節點label的樣式定製
  • 添加了 labelWidth 屬性,用於限制節點label的寬度
  • 添加了 props 屬性,參考element-uitree組件的props屬性,以支持複雜的數據結構
  • 添加了 collapsable 屬性,以支持子節點的展開和摺疊(展開和摺疊操做需調用者實現)

剛開始採用了flex佈局,可是要兼容IE9,後來改爲了display: table佈局

最終效果

  • default

  • horizontal

問題總結

  • 能夠定義一個樹的store,存儲每一個節點狀態,這樣就能夠在內部維護樹節點的展開可收起狀態

最後附上源碼傳送門:github.com/hukaibaihu/… !

參考資料

github.com/HigorSilvaR…

相關文章
相關標籤/搜索