引言
在管理后台开发中,树形结构数据的展示是一个常见需求。Element UI 的 el-table 组件提供了强大的树形表格功能,通过 tree-props 配置可以轻松实现。本文将详细介绍如何基于 parentId 字段构建树形表格,包括数据预处理、表格列定义和树形渲染逻辑。
一、数据预处理:构建树形结构
1.1 数据结构设计
假设我们有一个部门数据,每个部门包含以下字段:
id: 部门IDparentId: 父部门ID(根节点的parentId为null或0)name: 部门名称orderNum: 排序字段- 其他业务字段...
1.2 树形结构构建算法
我们需要将扁平数据转换为树形结构,以下是实现代码:
javascript
/**
* 将扁平数据转换为树形结构
* @param {Array} data 原始数据
* @param {String} idKey 主键字段名
* @param {String} parentKey 父键字段名
* @param {String} childrenKey 子节点字段名
* @returns {Array} 树形结构数据
*/
function buildTree(data, idKey = 'id', parentKey = 'parentDefId', childrenKey = 'children') {
const map = {};
const tree = [];
// 首先将所有数据存入map,键为id
data.forEach(item => {
map[item[idKey]] = { ...item, [childrenKey]: [] };
});
// 遍历map,构建树形结构
Object.keys(map).forEach(id => {
const node = map[id];
const parentId = node[parentKey];
if (parentId === null || parentId === undefined || parentId === 0) {
// 根节点
tree.push(node);
} else {
// 子节点,添加到父节点的children中
if (map[parentId]) {
map[parentId][childrenKey].push(node);
}
}
});
return tree;
}
1.3 排序处理
如果需要按 orderNum 排序,可以修改算法:
javascript
function buildSortedTree(data, idKey = 'id', parentKey = 'parentDefId', childrenKey = 'children') {
const map = {};
const tree = [];
// 第一步:将所有数据存入map
data.forEach(item => {
map[item[idKey]] = { ...item, [childrenKey]: [] };
});
// 第二步:构建树形结构
Object.keys(map).forEach(id => {
const node = map[id];
const parentId = node[parentKey];
if (parentId === null || parentId === undefined || parentId === 0) {
tree.push(node);
} else {
if (map[parentId]) {
map[parentId][childrenKey].push(node);
}
}
});
// 第三步:递归排序
function sortTree(nodes) {
if (!nodes || nodes.length === 0) return;
// 对当前层级排序
nodes.sort((a, b) => (a.orderNum || 0) - (b.orderNum || 0));
// 递归排序子节点
nodes.forEach(node => {
if (node[childrenKey] && node[childrenKey].length > 0) {
sortTree(node[childrenKey]);
}
});
}
sortTree(tree);
return tree;
}
二、el-table 树形表格配置
2.1 基本表格配置
vue
<template>
<el-table
:data="tableData"
row-key="id"
border
default-expand-all
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
style="width: 100%">
<el-table-column prop="name" label="部门名称" width="180"></el-table-column>
<el-table-column prop="orderNum" label="排序" width="100"></el-table-column>
<el-table-column prop="description" label="描述"></el-table-column>
</el-table>
</template>
<script>
export default {
data() {
return {
flatData: [
{ id: 1, parentDefId: null, name: '总公司', orderNum: 1, description: '总公司描述' },
{ id: 2, parentDefId: 1, name: '技术部', orderNum: 1, description: '技术部描述' },
{ id: 3, parentDefId: 1, name: '市场部', orderNum: 2, description: '市场部描述' },
{ id: 4, parentDefId: 2, name: '前端组', orderNum: 1, description: '前端组描述' },
{ id: 5, parentDefId: 2, name: '后端组', orderNum: 2, description: '后端组描述' },
],
tableData: []
}
},
created() {
this.tableData = this.buildSortedTree(this.flatData);
},
methods: {
buildSortedTree(data) {
// 使用前面定义的buildSortedTree方法
return buildSortedTree(data);
}
}
}
</script>
2.2 关键配置说明
-
row-key: 必须设置为唯一标识字段(通常是id)
-
tree-props:
children: 指定子节点字段名(默认为children)hasChildren: 指定是否有子节点的字段名(可选)
-
default-expand-all: 是否默认展开所有节点
三、高级功能实现
3.1 懒加载子节点
对于大数据量的树形表格,可以使用懒加载方式:
vue
<template>
<el-table
:data="tableData"
row-key="id"
border
lazy
:load="loadChildren"
:tree-props="{children: 'children', hasChildren: 'hasChildren', isLeaf: 'leaf'}"
style="width: 100%">
<!-- 列定义 -->
</el-table>
</template>
<script>
export default {
data() {
return {
tableData: [
{ id: 1, parentDefId: null, name: '总公司', hasChildren: true },
{ id: 2, parentDefId: 1, name: '技术部', hasChildren: true },
{ id: 3, parentDefId: 1, name: '市场部', hasChildren: false, leaf: true }
]
}
},
methods: {
async loadChildren(tree, treeNode, resolve) {
// 模拟API请求
setTimeout(() => {
const children = [
{ id: 4, parentDefId: 2, name: '前端组', hasChildren: false, leaf: true },
{ id: 5, parentDefId: 2, name: '后端组', hasChildren: false, leaf: true }
];
resolve(children);
}, 1000);
}
}
}
</script>
3.2 自定义展开/折叠图标
vue
<el-table
<!-- 其他配置 -->
:indent="20">
<template #default="{row, $index}">
<span v-if="row.children && row.children.length"
@click="toggleExpand(row)"
style="cursor: pointer; margin-right: 5px;">
<i :class="row._expanded ? 'el-icon-arrow-down' : 'el-icon-arrow-right'"></i>
</span>
<span v-else style="margin-right: 20px;"></span>
{{ row.name }}
</template>
</el-table>
<script>
export default {
methods: {
toggleExpand(row) {
// 这里需要手动维护_expanded状态
// 实际项目中建议使用状态管理或直接操作DOM
this.$set(row, '_expanded', !row._expanded);
// 可能需要手动触发重新渲染
}
}
}
</script>
四、完整示例代码
vue
<template>
<div class="tree-table-container">
<el-table
:data="tableData"
row-key="id"
border
default-expand-all
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
style="width: 100%">
<el-table-column label="部门名称" width="180">
<template #default="{row}">
<span :style="{paddingLeft: (row.level * 20) + 'px'}">
<i v-if="row.children && row.children.length"
class="el-icon-folder"
style="color: #f0ad4e; margin-right: 5px;"></i>
<i v-else class="el-icon-document" style="color: #5bc0de; margin-right: 5px;"></i>
{{ row.name }}
</span>
</template>
</el-table-column>
<el-table-column prop="orderNum" label="排序" width="100" align="center"></el-table-column>
<el-table-column prop="description" label="描述"></el-table-column>
<el-table-column label="操作" width="150" align="center">
<template #default="{row}">
<el-button size="mini" @click="handleAddChild(row)">添加子部门</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
function buildTreeWithLevel(data, idKey = 'id', parentKey = 'parentDefId', childrenKey = 'children', level = 0) {
const map = {};
const tree = [];
data.forEach(item => {
map[item[idKey]] = { ...item, [childrenKey]: [], level };
});
Object.keys(map).forEach(id => {
const node = map[id];
const parentId = node[parentKey];
if (parentId === null || parentId === undefined || parentId === 0) {
tree.push(node);
} else {
if (map[parentId]) {
// 设置子节点的level为父节点level+1
const childNode = { ...node, level: map[parentId].level + 1 };
map[parentId][childrenKey].push(childNode);
}
}
});
return tree;
}
export default {
data() {
return {
flatData: [
{ id: 1, parentDefId: null, name: '总公司', orderNum: 1, description: '总公司描述' },
{ id: 2, parentDefId: 1, name: '技术部', orderNum: 1, description: '技术部描述' },
{ id: 3, parentDefId: 1, name: '市场部', orderNum: 2, description: '市场部描述' },
{ id: 4, parentDefId: 2, name: '前端组', orderNum: 1, description: '前端组描述' },
{ id: 5, parentDefId: 2, name: '后端组', orderNum: 2, description: '后端组描述' },
],
tableData: []
}
},
created() {
this.tableData = buildTreeWithLevel(this.flatData);
},
methods: {
handleAddChild(parentRow) {
const newId = Date.now(); // 简单生成ID,实际项目应使用更可靠的方案
const newChild = {
id: newId,
parentDefId: parentRow.id,
name: '新部门',
orderNum: parentRow.children.length + 1,
description: '新部门描述',
level: parentRow.level + 1
};
// 实际项目中应该调用API添加,这里简单模拟
this.flatData.push(newChild);
// 重新构建树
this.tableData = buildTreeWithLevel(this.flatData);
this.$message.success(`成功添加子部门: ${newChild.name}`);
}
}
}
</script>
<style scoped>
.tree-table-container {
padding: 20px;
}
</style>
五、总结与优化建议
- 数据预处理:将扁平数据转换为树形结构是关键步骤
- 树形配置:正确配置
row-key和tree-props是树形表格的基础 - 性能优化:对于大数据量考虑使用懒加载