輕鬆實現可擴展的樹形表格

因爲ElementUI目前還未開發樹形表格組件,也參閱了網絡上部分基於ElementUI表格封裝的開源樹形組件,都沒有找的太理想可進行二次開發的開源項目,因此就萌生了自行開發樹形表格。

本示例提供開發思路,移除了多餘的樣式,比較適合新手入門學習,若是應用於實際項目還請自行封裝。javascript

目前還僅僅實現了視覺的樹結構的層級效果和控制結構的顯示隱藏,後續還會進行不斷的完善和優化,有必要的話會對組件進行二次封裝,有點在重複造論的感受哈。css

效果圖

完整代碼

頁面(tree-table.vue)

<template>
  <div>
    TreeTable
    <el-table :data="list" :row-style="tableRowStyle" border>
      <el-table-column type="selection" width="55"></el-table-column>
      <el-table-column prop="id" label="ID" width="180">
        <template slot-scope="scope">
          <span class="collapse"
                :class="collapseClass(scope.row)"
                :style="tableRowPaddingStyle(scope.row)"
                @click="handleCollapseClick(scope.row)">
          </span>
          <span>{{ scope.row.id }}</span>
        </template>
      </el-table-column>
      <el-table-column prop="name" label="NAME"></el-table-column>
    </el-table>
  </div>
</template>

<script lang="ts">
    import {Component, Vue} from 'vue-property-decorator'
    // 引入兩個封裝好的工具方法
    import { arrayToTree } from './utils/array.js'
    import { ergodicTree } from './utils/tree.js'

    @Component
    export default class TreeTable extends Vue {
        private list: object[] = [];
        private tree: object[] = [];

        created() {
            // 準備一組含有父子級關係的一維數組方便示例測試
            // 在實際項目應用中,理應經過後端接口返回
            let _list = [
                {
                    id: 'a',
                    pid: '',
                    name: '部門a',
                    children: []
                },
                {
                    id: 'a1',
                    pid: 'a',
                    name: '子部門a1',
                    children: []
                },
                {
                    id: 'a2',
                    pid: 'a',
                    name: '子部門a2',
                    children: []
                },
                {
                    id: 'a2-1',
                    pid: 'a2',
                    name: '子部門a2-1',
                    children: []
                },
                {
                    id: 'a2-2',
                    pid: 'a2',
                    name: '子部門a2-2',
                    children: []
                },
                {
                    id: 'a3',
                    pid: 'a',
                    name: '子部門a3',
                    children: []
                },
                {
                    id: 'a3-1',
                    pid: 'a3',
                    name: '子部門a3-1',
                    children: []
                },
                {
                    id: 'b',
                    pid: '',
                    name: '部門b',
                    children: []
                },
                {
                    id: 'b1',
                    pid: 'b',
                    name: '子部門b1',
                    children: []
                },
                {
                    id: 'c',
                    pid: '',
                    name: '部門c',
                    children: []
                },
            ];
            
            // 將一維數組轉成樹形結構並存儲於tree變量
            this.tree = arrayToTree(_list);
            
            // 考慮到實際應用過程當中接口返回的數據是無序的,因此咱們對tree進行先序遍歷將節點一一插入到list變量
            this.list = [];
            ergodicTree(this.tree, (node: any) => {
                this.list.push(node);
                
                // 遍歷過程當中並對每一個節點掛載open和show屬性
                // open:控制節點的打開和關閉
                // show:控制節點的顯示和隱藏
                this.$set(node, 'open', true);
                this.$set(node, 'show', true)
            })
        }

        // 控制行的顯示和隱藏
        tableRowStyle(scope: any) {
            return {
                'display': scope.row.show ? '' : 'none'
            }
        }

        // 經過每一個節點的深度,設置行的縮進實現視覺上的層級效果
        tableRowPaddingStyle(row: any) {
            return {
                'margin-left': `${(row._depth - 1) * 24}px`
            }
        }

        // 控制展開按鈕的展開和關閉狀態
        collapseClass(row: any) {
            return {
                'collapse--open': row.open == false && row.children && row.children.length > 0,
                'collapse--close': row.open == true && row.children && row.children.length > 0
            }
        }

        // 處理展開按鈕的點擊事件
        handleCollapseClick(row: any) {
            const _open = row.open;
            // 經過節點訪問路徑控制節點的顯示隱藏,因爲內存指針的關係list和tree的節點操做都會相互影響
            ergodicTree(this.tree, (node: any) => {
                node._idPath.forEach((pathId: any) => {
                    if (pathId == row.id) {
                        this.$set(node, 'show', !_open);
                        this.$set(node, 'open', !_open)
                    }
                })
            });
            row.show = true;
            row.open = !_open;
        }
    }
</script>

<style lang="scss" scoped>
  .collapse {
    display: inline-block;
    width: 8px;
    cursor: pointer;
    margin-right: 8px;
  }

  .collapse--open:before {
    content: '+';
  }

  .collapse--close:before {
    content: '-';
  }
</style>

工具方法

考慮數組轉樹和遍歷樹都是在實際項目中都是很是經常使用的,因此這邊對這兩個方法進行了封裝。

數組轉樹結構(./utils/array.ts)

export function arrayToTree(list: object[], props = {id: 'id', pid: 'pid', children: 'children'}) {
            let tree: object[] = [];
            let map: any = {};

            let listLength = list.length;
            for (let i = 0; i < listLength; i++) {
                let node: any = list[i];
                let nodeId: any = node[props.id];
                map[nodeId] = node;
            }

            for (let i = 0; i < listLength; i++) {
                let node: any = list[i];
                let nodePid: any = node[props.pid];
                let parentNode: any = map[nodePid];
                if (parentNode) {
                    parentNode[props.children] = parentNode[props.children] || [];
                    parentNode[props.children].push(node)
                } else {
                    tree.push(node)
                }
            }

            return tree
        }

遍歷樹結構(./utils/tree.ts)

結合實際項目應用,咱們採用了先序遍歷法對樹進行遍歷,爲了方便在業務代碼裏的應用,在遍歷過程當中會對每一個節點掛載節點訪問路徑 _idPath 屬性和節點深度 _depth 屬性。
export function ergodicTree(tree: object[], callback: any = () => {}, props = {id: 'id', pid: 'pid', children: 'children'}) {
            function _ergodicTree(tree: object[], parentIdPath?: any[], depth: number = 0) {
                const treeLength = tree.length;
                for (let i = 0; i < treeLength; i++) {
                    let node: any = tree[i];
                    const _idPath: any[] = parentIdPath ? [...parentIdPath, node[props.id]] : [node[props.id]];
                    const _depth: number = depth + 1;
                    node._idPath = _idPath;
                    node._depth = _depth;
                    callback(node);
                    if (node[props.children] && node[props.children] instanceof Array) {
                        _ergodicTree(node[props.children], _idPath, _depth)
                    }
                }
            }

            _ergodicTree(tree);
            return tree;
        }
相關文章
相關標籤/搜索