VueJS如何基于5层深度关联数组动态生成带rowspan的表格
解决方案
核心问题修正
你当前代码的最大问题是嵌套了<tr>标签,HTML规范中<tr>只能作为<thead>、<tbody>、<tfoot>的直接子元素,嵌套<tr>会导致DOM结构完全错乱,根本无法实现rowspan合并效果,首先要改掉这个错误写法。
实现步骤
1. 定义动态层级配置 + 计算合并行数
写计算属性做层级可配置化,先判断当前数据是否存在districts字段动态调整遍历层级,同时提前计算每个父节点对应的rowspan(即该节点下所有末级数据行的总数量)。
computed: { // 动态层级配置,根据是否有districts字段自动调整层级顺序 levelConfig() { const baseLevels = [ { key: 'countries', label: '国家', childKey: 'cities' }, { key: 'cities', label: '城市', childKey: this.hasDistricts ? 'districts' : 'languages' }, ] if (this.hasDistricts) { baseLevels.push({ key: 'districts', label: '行政区', childKey: 'languages' }) } baseLevels.push( { key: 'languages', label: '语言', childKey: 'quarters' }, { key: 'quarters', label: '季度', childKey: 'parallels' }, { key: 'parallels', label: '指标', isLast: true } ) return baseLevels }, // 判断当前数据是否存在districts层级 hasDistricts() { if (!this.itemsReal?.countries) return false const firstCountry = Object.values(this.itemsReal.countries)[0] const firstCity = firstCountry?.cities ? Object.values(firstCountry.cities)[0] : null return !!firstCity?.districts }, // 扁平化表格行数据,同时计算每个层级的rowspan值 tableRows() { const rows = [] // 递归遍历所有层级,统计每个父节点的末级行数作为rowspan const traverse = (node, level, parentInfo = {}) => { // 末级指标层直接生成行数据 if (this.levelConfig[level].isLast) { rows.push({ ...parentInfo, values: Object.values(node).map(item => item.value ?? '') }) return 1 } const childKey = this.levelConfig[level].childKey let totalChildRows = 0 const children = Object.values(node) children.forEach(child => { const childRows = traverse(child[childKey], level + 1, { ...parentInfo, [this.levelConfig[level].key]: { title: child.title, rowspan: 0 } }) totalChildRows += childRows parentInfo[this.levelConfig[level].key].rowspan = childRows }) return totalChildRows } traverse(this.itemsReal.countries, 0) // 标记同一层级同一父节点只在第一行渲染单元格,避免重复输出 const renderedKeys = new Set() rows.forEach((row, index) => { Object.keys(row).forEach(key => { const uniqueKey = `${key}_${row[key].title}_${index - row[key].rowspan}` row[key].show = !renderedKeys.has(uniqueKey) if (row[key].show) renderedKeys.add(uniqueKey) }) }) return rows }, // 右侧动态表头,可根据数据源自定义拓展 tableHeaders() { return ['tst1', 'tst2', 'tst3', 'tst4', 'tst5'] } }
2. 模板部分重写
不要嵌套<tr>,统一在<tbody>下循环行,每个单元格判断是否需要渲染,绑定对应的rowspan值即可:
<template> <b-table> <!-- 表头部分 --> <template #thead> <b-tr> <b-th v-for="level in levelConfig.filter(l => !l.isLast)" :key="level.key"> {{ level.label }} </b-th> <b-th v-for="header in tableHeaders" :key="header"> {{ header }} </b-th> </b-tr> </template> <!-- 表格主体 --> <template #tbody> <b-tr v-for="(row, index) in tableRows" :key="index"> <!-- 左侧层级单元格,只有第一行显示,绑定rowspan --> <b-td v-for="level in levelConfig.filter(l => !l.isLast)" :key="level.key" v-if="row[level.key].show" :rowspan="row[level.key].rowspan" > {{ row[level.key].title }} </b-td> <!-- 右侧数值单元格 --> <b-td v-for="(value, idx) in row.values" :key="idx" class="value-cells"> {{ value }} </b-td> </b-tr> </template> </b-table> </template>
关键优化点
- 层级配置适配动态结构:开启过滤器新增/删除层级时,只需要修改
levelConfig配置即可,不需要重写模板逻辑 - 提前在计算属性计算rowspan,避免模板内实时计算导致的性能问题和渲染错误
- 完全符合HTML表格规范,没有嵌套
<tr>的问题,rowspan合并效果可以正常生效
内容的提问来源于stack exchange,提问作者Powerless
相关产品推荐
相关产品推荐

