最近項目中須要用到樹形表格來描述部門、區域之間的父子展開關係。可是已經在項目中使用的Vue的成熟組件ElementUI以及iViewUI組件都沒有提供相應的樹形表格組件,無奈找了其餘替代方案也都被pass掉了,只能從改造現有組件放面着手。css
在網上也找到了一些實踐案例:http://blog.csdn.net/s8460049/article/details/61414751vue
第一種方案就是原做者介紹的,即將具備層級關係的數據進行提早處理。好比: 數據結構爲:數組
[ { id: 1, parentId: 0, name: '測試1', age: 18, sex: '男', children: [ { id: 2, parentId: 1, name: '測試2', age: 22, sex: '男' } ] }, { id: 3, parentId: 0, name: '測試3', age: 23, sex: '女', children: [ { id: 4, parentId: 3, name: '測試4', age: 22, sex: '男' }, { id: 5, parentId: 3, name: '測試5', age: 25, sex: '男' }, { id: 6, parentId: 3, name: '測試6', age: 26, sex: '女', children: [ { id: 7, parentId: 6, name: '測試7', age: 27, sex: '男' } ] } ] }, { id: 18, parentId: 0, name: '測試8', age: 18, sex: '男' } ]
這樣能夠經過數據轉換方法,把每一條數據從它的父級中取出來,把樹形結構數據轉換成數組數據。瀏覽器
dataTranslate.js內容:數據結構
import Vue from 'vue' function DataTransfer (data) { if (!(this instanceof DataTransfer)) { return new DataTransfer(data, null, null) } } DataTransfer.treeToArray = function (data, parent, level, expandedAll) { let tmp = [] Array.from(data).forEach(function (record) { if (record._expanded === undefined) { Vue.set(record, '_expanded', expandedAll) } if (parent) { Vue.set(record, '_parent', parent) } let _level = 0 if (level !== undefined && level !== null) { _level = level + 1 } Vue.set(record, '_level', _level) tmp.push(record) if (record.children && record.children.length > 0) { let children = DataTransfer.treeToArray(record.children, record, _level, expandedAll) tmp = tmp.concat(children) } }) return tmp } export default DataTransfer
有了進行數據轉換的方法以後,開始正式些數據TreeGrid.vue組件:app
<template> <el-table :data="data" border style="width: 100%" :row-style="showTr"> <el-table-column v-for="(column, index) in columns" :key="column.dataIndex" :label="column.text"> <template scope="scope"> <span v-if="spaceIconShow(index)" v-for="(space, levelIndex) in scope.row._level" class="ms-tree-space"></span> <button class="button is-outlined is-primary is-small" v-if="toggleIconShow(index,scope.row)" @click="toggle(scope.$index)"> <i v-if="!scope.row._expanded" class="el-icon-caret-right" aria-hidden="true"></i> <i v-if="scope.row._expanded" class="el-icon-caret-bottom" aria-hidden="true"></i> </button> <span v-else-if="index===0" class="ms-tree-space"></span> {{scope.row[column.dataIndex]}} </template> </el-table-column> <el-table-column label="操做" v-if="treeType === 'normal'" width="260"> <template scope="scope"> <button type="button" class="el-button el-button--default el-button--small"> <router-link :to="{ path: requestUrl + 'edit', query: {id: scope.row.Oid} }" tag="span"> 編輯 </router-link> </button> <el-button size="small" type="danger" @click="handleDelete()"> 刪除 </el-button> <button type="button" class="el-button el-button--success el-button--small"> <router-link :to="{ path: requestUrl, query: {parentId: scope.row.parentOId} }" tag="span"> 添加下級樹結構 </router-link> </button> </template> </el-table-column> </el-table> </template> <script> import DataTransfer from '../utils/dataTranslate.js' import Vue from 'vue' export default { name: 'tree-grid', props: { // 該屬性是確認父組件傳過來的數據是否已是樹形結構了,若是是,則不須要進行樹形格式化 treeStructure: { type: Boolean, default: function () { return false } }, // 這是相應的字段展現 columns: { type: Array, default: function () { return [] } }, // 這是數據源 dataSource: { type: Array, default: function () { return [] } }, // 這個做用是根據本身需求來的,好比在操做中涉及相關按鈕編輯,刪除等,須要向服務端發送請求,則能夠把url傳過來 requestUrl: { type: String, default: function () { return '' } }, // 這個是是否展現操做列 treeType: { type: String, default: function () { return 'normal' } }, // 是否默認展開全部樹 defaultExpandAll: { type: Boolean, default: function () { return false } } }, data () { return {} }, computed: { // 格式化數據源 data: function () { let me = this if (me.treeStructure) { let data = DataTransfer.treeToArray(me.dataSource, null, null, me.defaultExpandAll) console.log(data) return data } return me.dataSource } }, methods: { // 顯示行 showTr: function (row, index) { let show = (row._parent ? (row._parent._expanded && row._parent._show) : true) row._show = show return show ? '' : 'display:none;' }, // 展開下級 toggle: function (trIndex) { let me = this let record = me.data[trIndex] record._expanded = !record._expanded }, // 顯示層級關係的空格和圖標 spaceIconShow (index) { let me = this if (me.treeStructure && index === 0) { return true } return false }, // 點擊展開和關閉的時候,圖標的切換 toggleIconShow (index, record) { let me = this if (me.treeStructure && index === 0 && record.children && record.children.length > 0) { return true } return false }, handleDelete () { this.$confirm('此操做將永久刪除該記錄, 是否繼續?', '提示', { confirmButtonText: '肯定', cancelButtonText: '取消', type: 'error' }).then(() => { this.$message({ type: 'success', message: '刪除成功!' }) }).catch(() => { this.$message({ type: 'info', message: '已取消刪除' }) }) } } } </script> <style scoped> .ms-tree-space{position: relative; top: 1px; display: inline-block; font-family: 'Glyphicons Halflings'; font-style: normal; font-weight: 400; line-height: 1; width: 18px; height: 14px;} .ms-tree-space::before{content: ""} table td{ line-height: 26px; } </style>
寫好了樹形表格組件,使用方式和普通的Vue組件使用方法相同:框架
<template> <div class="hello"> <tree-grid :columns="columns" :tree-structure="true" :data-source="dataSource"></tree-grid> </div> </template> <script> import {TreeGrid} from './TreeGrid' export default { name: 'hello', data () { return { columns: [ { text: '姓名', dataIndex: 'name' }, { text: '年齡', dataIndex: 'age' }, { text: '性別', dataIndex: 'sex' } ], dataSource: [ { id: 1, parentId: 0, name: '測試1', age: 18, sex: '男', children: [ { id: 2, parentId: 1, name: '測試2', age: 22, sex: '男' } ] }, { id: 3, parentId: 0, name: '測試3', age: 23, sex: '女', children: [ { id: 4, parentId: 3, name: '測試4', age: 22, sex: '男' }, { id: 5, parentId: 3, name: '測試5', age: 25, sex: '男' }, { id: 6, parentId: 3, name: '測試6', age: 26, sex: '女', children: [ { id: 7, parentId: 6, name: '測試7', age: 27, sex: '男' } ] } ] }, { id: 18, parentId: 0, name: '測試8', age: 18, sex: '男' } ] } }, components: { TreeGrid } } </script>
以上就是實現樹形表格的方法,提早把樹形表格數據處理成數組數據,而後針對父級和子集分別增長不一樣的表示,以實現不一樣的表格首行顯示效果。測試
可是,該組件將說有數據都加在到Table中,而後採用操做css樣式"display:none"的方式進行隱藏和顯示。數量比較時候卻是能夠徹底知足使用,可是若是數據量超過100條或者更多就會出現頁面卡頓的現象。this
第二種方式在原方法的基礎上進行操做,原理就是基於MVVM框架vue的數據驅動原理。採用操做數據的方式執行子級數據的顯示隱藏。url
根據ElementUI 的 Table組件會根據數據變化進行加載的原理,經過只傳入須要展現的數據的方式,來提升瀏覽器渲染的速度。
直接上代碼TreeGrid.vue:
<template> <div class="table-content"> <el-table :data="TableDate" border style="width: 18.82rem"> <el-table-column label="部門名稱" min-width="400"> <template scope="scope"> <span v-for="(space, levelIndex) in scope.row._level" :key="levelIndex" class="ms-tree-space"></span> <span class="button is-outlined is-primary is-small" v-if="toggleIconShow(scope.row)" @click="toggle(scope.row)"> <i v-if="!scope.row._expanded" class="el-icon-arrow-right" aria-hidden="true"></i> <i v-if="scope.row._expanded" class="el-icon-arrow-down" aria-hidden="true"></i> </span> <span v-else class="ms-tree-space"></span> <span :title="scope.row.dpmName"> {{ scope.row.dpmName }} </span> </template> </el-table-column> <el-table-column label="組織機構代碼" min-width="300"> <template scope="scope"> <span :title="scope.row.dpmAdc"> {{ scope.row.dpmAdc }} </span> </template> </el-table-column> <el-table-column label="所屬地區" min-width="300"> <template scope="scope"> <span :title="scope.row.areaName"> {{ scope.row.areaName }} </span> </template> </el-table-column> <el-table-column label="上級部門" min-width="315"> <template scope="scope"> <span :title="scope.row.parentName"> {{ scope.row.parentName }} </span> </template> </el-table-column> </el-table> </div> </template> <script> import {deepCopy} from "../utils/util.js" Array.prototype.removeByValue = function(val) { //對數組原型添加刪除指定項的方法 for(var i=0; i<this.length; i++) { if(this[i] == val) { this.splice(i, 1); break; } } }; export default { name: 'TreeGrid', components: { }, data(){ return { TableDate:[] } }, computed:{ allData(){ let me = this; let newData = deepCopy(me.$store.getters.Data); return newData; } }, watch: { allData(val){ this.TableDate = deepCopy(val); } }, methods: { toggleIconShow (record) { /** * 點擊展開和關閉的時候,圖標的切換 */ let me = this; if (record.children && record.children.length > 0) { return true } return false }, toggle(rowData) { let me = this; /** * 展開下級 */ let childLen = rowData.children.length; if(rowData._expanded){ let dataArr=[]; dataArr.push(rowData); let arr = me.getChildFlowId(dataArr,[]); for(let i=0; i < childLen; i++){ me.TableDate.map((value)=>{ if(arr.indexOf(value.parentId) > -1){ me.TableDate.removeByValue(value); } }); } } else { rowData.children = me.setSpaceIcon(rowData.children,rowData._level); let index = me.TableDate.indexOf(rowData); let pre = me.TableDate.slice(0,index+1); let last = me.TableDate.slice(index+1); let concatChildren = pre.concat(rowData.children); me.TableDate = concatChildren.concat(last); } rowData._expanded = !rowData._expanded; }, getChildFlowId(data,emptyArr){ // 獲取子級的flowId let me = this; Array.from(data).forEach((record)=>{ emptyArr.push(record.flowId); if(record.children&&record.children.length > 0){ let childFlowIdArr = me.getChildFlowId(record.children,emptyArr); emptyArr.concat(childFlowIdArr); } }); return emptyArr; }, setSpaceIcon(data,level){ // 設置第一列的空格和方向按鈕 let me = this; let _level = 0; data.forEach((value)=>{ value._expanded = false; if(level !== undefined && level !== null){ _level = level + 1; } else { _level = 1; } value._level = _level; if(value.children&&value.children.length > 0){ me.setSpaceIcon(value.children, _level); } }); return data; } } } </script>
雖然上了大段的代碼,不過也有不少不細緻的地方。重點仍是理解實現的方式,儘可能減小須要寫個方法或者組件的時候,在網上Google一下就拿過來用,更多的應該是理解其中的原理,而且本身進行實踐才能進步。
不明白的地方,歡迎提問交流!