You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 11:54:03