提要
最近項目中需要用到樹形表格來描述部門、區域之間的父子展開關系。但是已經在項目中使用的Vue的成熟組件ElementUI以及iViewUI組件都沒有提供相應的樹形表格組件,無奈找了其他替代方案也都被pass掉了,只能從改造現有組件放面着手。
在網上也找到了一些實踐案例:http://blog.csdn.net/s8460049/article/details/61414751
第一種方案
第一種方案就是原作者介紹的,即將具有層級關系的數據進行提前處理。比如: 數據結構為:
[ { 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組件:
<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條或者更多就會出現頁面卡頓的現象。
第二種方式
第二種方式在原方法的基礎上進行操作,原理就是基於MVVM框架vue的數據驅動原理。采用操作數據的方式執行子級數據的顯示隱藏。
根據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一下就拿過來用,更多的應該是理解其中的原理,並且自己進行實踐才能進步。
不明白的地方,歡迎提問交流!

