- 想要实现的效果图
3. 具体代码实现如下
js
<template>
<div style="padding: 20px;">
<table style="width: 100%; border-collapse: collapse;" border="1">
<thead>
<tr>
<th :colspan="maxLevel" style="text-align: center;">活动类型</th>
<th v-for="item in columns" style="text-align: center;">{{item.name}}</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, index) in tableRows" :key="index">
<!-- 渲染每个单元格 -->
<template v-for="(cell, cellIndex) in row.cells" :key="cellIndex">
<td style="text-align: center" v-if="cell.show" :rowspan="cell.rowspan" :colspan="cell.colspan">
{{ cell.name }}
</td>
</template>
<!-- 渲染人数 -->
<td v-for="item in columns">{{ row[item.prop] }}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
const columns = [
{
prop: "count",
name: "活动人数",
},
{
prop: "count1",
name: "活动人数1",
},
]
const maxLevel = ref(5);
// 模拟的树形数据
const treeData = ref([
{
name: "一级活动1",
count: 1,
count1: 2,
children: [],
},
{
name: "一级活动2",
count: 0, // 一级节点本身不显示具体人数,由子节点决定行数
children: [
{
name: "二级活动1",
count: 1,
children: [],
},
{
name: "二级活动2",
count: 0, // 二级节点本身不显示具体人数,由子节点决定行数
children: [
{
name: "三级活动1",
count: 1,
children: [],
},
{
name: "三级活动2",
count: 4,
count1: 5,
children: [],
},
{
name: "三级活动3",
count: 1,
children: [],
}
],
}
],
},
{
name: "一级活动3",
count: 1,
children: [
{
name: "二级活动3",
count: 1,
children: [],
},
{
name: "二级活动4",
count: 1,
children: [
{
name: "三级活动4",
count: 1,
children: [],
},
],
},
{
name: "二级活动5",
count: 1,
children: [
{
name: "三级活动5",
count: 1,
children: [
{
name: "四级活动1",
count: 1,
children: [],
},
{
name: "四级活动2",
count: 1,
children: [
{
name: "五级活动1",
count: 1,
children: [],
},
],
},
],
},
],
},
],
},
]);
// 计算属性,用于生成表格行数据
const tableRows = computed(() => {
const rows = [];
// 递归函数,用于处理节点并生成行
// 返回该节点及其子树所占用的行数
const processNode = (node, level, cells) => {
const isLeaf = node.children.length === 0;
let totalRowspan = 0;
debugger
if (isLeaf) {
// 叶子节点:创建新行
const newRowCells = [...cells];
newRowCells[level - 1] = {
name: node.name,
rowspan: 1,
colspan: maxLevel.value - level + 1,
show: true
};
// 填充之前的空单元格
for (let i = 0; i < level - 1; i++) {
if (!newRowCells[i]) {
newRowCells[i] = { show: false };
}
}
rows.push({
...node,
cells: newRowCells,
});
return 1; // 叶子节点贡献1行
} else {
// 非叶子节点
const currentCells = [...cells];
// 为当前节点预留一个单元格位置,但暂不设置 rowspan 和 show
currentCells[level - 1] = {
name: node.name,
// rowspan 和 show 将在处理完所有子节点后设置
colspan: 1,
};
// 递归处理所有子节点
for (const child of node.children) {
const childRows = processNode(child, level + 1, currentCells);
totalRowspan += childRows;
}
// 处理完所有子节点后,更新当前节点的单元格信息
// 找到当前节点对应的行(第一个子节点所在的行)
if (rows.length > 0) {
const firstChildRowIndex = rows.length - totalRowspan;
if (firstChildRowIndex >= 0) {
rows[firstChildRowIndex].cells[level - 1] = {
name: node.name,
rowspan: totalRowspan,
colspan: 1,
show: true
};
// 确保之前的单元格也正确设置(如果被覆盖)
for (let i = 0; i < level - 1; i++) {
if (!rows[firstChildRowIndex].cells[i]) {
rows[firstChildRowIndex].cells[i] = { show: false };
}
}
}
}
return totalRowspan;
}
};
// 遍历所有根节点
treeData.value.forEach(node => {
processNode(node, 1, []);
});
return rows;
});
</script>
<style scoped>
/* 可以在这里添加一些样式 */
</style>