如何实现多rowSpan合并单元格表格的层级数据适配渲染
多级rowSpan合并层级表格通用实现方案
问题背景
开发带多级行合并的层级数据表格时,已完成基础表格框架搭建,但处理三层嵌套的原始数据时,仅能通过map逐层遍历、硬编码rowSpan值实现固定数据渲染,无法适配动态数据,需要通用的数据规整方案,将嵌套数据转换为可直接驱动表格渲染的结构。
原始数据结构
const CHOICE_CHECKUP_MOCK = [ { category_name: "ChoiceA", possible_count: 1, checkup_list: [ { middle_checkup_name: "ChoiceA middle list1", checkups: [ { name: "SubClass 1-1", code: "code 1-1", description: "description 1-1", price: 0, }, { name: "subClass 1-2", code: "code 1-2", description: "description 1-2", price: 0, }, ], }, { middle_checkup_name: "ChoiceA middle list2", checkups: [ { name: "checkups_name3", code: "checkups_code3", description: "checkups_description3", price: 0, }, { name: "checkups_name4", code: "checkups_code4", description: "checkups_description4", price: 0, }, ], }, ], }, { category_name: "ChoiceB", possible_count: 1, checkup_list: [ { middle_checkup_name: "ChoiceB middleClassList1", checkups: [ { name: "SubClass 1-1", code: "code 1-1", description: "description 1-1", price: 0, }, { name: "subClass 1-2", code: "code 1-2", description: "description1-2", price: 0, }, { name: "subClass 1-2", code: "code 1-2", description: "description 1-2", price: 0, }, ], }, { middle_checkup_name: "ChoiceB MiddleClassList2", checkups: [ { name: "checkups_name3", code: "checkups_code3", description: "checkups_description3", price: 0, }, { name: "checkups_name4", code: "checkups_code4", description: "checkups_description4", price: 0, }, ], }, ], }, ]
目标渲染效果

实现思路
核心是把三层嵌套数据拍平成一维行数组,拍平过程中自动计算每一层级的rowSpan,标记哪些行不需要渲染上层列的单元格,从根源上避免硬编码。
- 第一步:提前计算各层级合并行数
最内层checkups数组的长度,就是所属中间分类列的rowSpan值;每个一级分类下所有中间分类的checkups长度累加,就是一级分类列的rowSpan值。 - 第二步:嵌套遍历拍平数据
从最外层分类开始逐层向内遍历,每一条最内层的checkup明细对应表格的一行,给每行数据挂载三类字段:- 一级分类字段:仅当前行是该分类下的第一行时,携带分类名称、可选次数、rowSpan值,其余行该字段置为
null,标记为不渲染该列 - 中间分类字段:仅当前行是该中间分类下的第一行时,携带中间分类名称、rowSpan值,其余行该字段置为
null,标记为不渲染该列 - 明细字段:每一行都携带检查项的名称、编码、描述、价格信息
- 一级分类字段:仅当前行是该分类下的第一行时,携带分类名称、可选次数、rowSpan值,其余行该字段置为
- 第三步:按行渲染
遍历拍平后的一维数组生成tr标签,每个tr内判断对应列的字段:有值就渲染td并绑定rowSpan属性,值为null就直接跳过该列td的生成即可。
核心代码实现
数据转换函数
function flattenTableData(rawList) { const rowList = [] // 遍历一级分类 rawList.forEach(category => { // 计算一级分类总占行数 const categorySpan = category.checkup_list.reduce( (total, middleItem) => total + middleItem.checkups.length, 0 ) let isCategoryFirstRow = true // 遍历二级中间分类 category.checkup_list.forEach(middleItem => { const middleSpan = middleItem.checkups.length let isMiddleFirstRow = true // 遍历三级明细,每个明细生成一行 middleItem.checkups.forEach(checkupDetail => { rowList.push({ categoryCell: isCategoryFirstRow ? { name: category.category_name, count: category.possible_count, rowSpan: categorySpan } : null, middleCell: isMiddleFirstRow ? { name: middleItem.middle_checkup_name, rowSpan: middleSpan } : null, detailCell: checkupDetail }) isCategoryFirstRow = false isMiddleFirstRow = false }) }) }) return rowList }
渲染逻辑示例(JSX语法)
<tbody> {flattenTableData(CHOICE_CHECKUP_MOCK).map((row, idx) => ( <tr key={idx}> {/* 一级分类列:仅首行渲染 */} {row.categoryCell && ( <td rowSpan={row.categoryCell.rowSpan}> <p>{row.categoryCell.name}</p> <p>可选次数:{row.categoryCell.count}</p> </td> )} {/* 中间分类列:仅首行渲染 */} {row.middleCell && ( <td rowSpan={row.middleCell.rowSpan}>{row.middleCell.name}</td> )} {/* 明细列:每行渲染 */} <td>{row.detailCell.name}</td> <td>{row.detailCell.code}</td> <td>{row.detailCell.description}</td> <td>{row.detailCell.price}</td> </tr> ))} </tbody>
这个方案没有硬编码行数,不管嵌套数据里各层级有多少条子项,都能自动计算合并行数,新增、删减数据都不需要改渲染逻辑。如果后续要加更多层级的合并,只要按照同样的逻辑,在拍平时加对应层级的rowSpan计算和首行标记即可。
内容的提问来源于stack exchange,提问作者GaramSong
相关产品推荐
相关产品推荐

