实现flattenColumns函数:将嵌套树形表格列转换为带表头层级的扁平二维结构
实现flattenColumns函数:将嵌套树形表格列转换为带表头层级的扁平二维结构
嘿,我完全get到你要解决的问题了——把嵌套的表格列转换成适配HTML表头的二维结构,每一行对应表头的一个层级,还要精准处理占位符和跨列(colSpan)。你之前的尝试已经搭了基础,但确实没搞定占位符的对齐问题,这也是这个需求里最绕的核心点。
先把核心需求捋清楚:最终的二维数组里,每一行对应表头的一个层级,最底层是所有叶子列;上层的容器列要占据下属所有叶子列的宽度,其他位置用空占位符填充,确保和底层叶子列的位置严格对齐。
解决方案思路
要实现这个效果,我们得先摸透每个叶子列的完整归属路径(比如City的路径是Details → Address → City),再基于这些路径来构建每一层表头:
- 收集所有叶子的完整路径:递归遍历列结构,记录每个叶子列从根节点到自身的所有父容器+自身的路径。
- 确定表头总层数:最长路径的长度就是表头的总行数(比如你给的例子里最长路径是3层,所以表头有3行)。
- 逐行构建表头:
- 对每一层,遍历所有叶子的路径,找到当前层对应的节点。
- 合并连续的相同容器节点,设置对应的
colSpan值。 - 对没有对应节点的位置(比如
Name在第二层、第三层没有父容器),用空占位符填充。
完整代码实现
export interface TableLeafColumn { label: string; key?: string; colSpan?: number; } export interface TableContainerColumn { label: string; columns: TableColumn[]; colSpan?: number; } export type TableColumn = TableLeafColumn | TableContainerColumn; // 辅助函数:收集所有叶子列的完整路径(从根到叶子的所有列节点) function collectLeafPaths(columns: TableColumn[], currentPath: TableColumn[] = []): TableColumn[][] { const paths: TableColumn[][] = []; for (const col of columns) { const newPath = [...currentPath, col]; if ('columns' in col && Array.isArray(col.columns)) { // 递归收集子列的路径 paths.push(...collectLeafPaths(col.columns, newPath)); } else { // 叶子列,保存完整路径 paths.push(newPath); } } return paths; } export function flattenColumns(columns: TableColumn[]): (TableLeafColumn | TableContainerColumn)[][] { const leafPaths = collectLeafPaths(columns); if (leafPaths.length === 0) return []; const maxDepth = Math.max(...leafPaths.map(path => path.length)); const result: (TableLeafColumn | TableContainerColumn)[][] = Array.from({ length: maxDepth }, () => []); for (let depth = 0; depth < maxDepth; depth++) { const currentRow: (TableLeafColumn | TableContainerColumn)[] = []; let i = 0; while (i < leafPaths.length) { const currentPath = leafPaths[i]; // 获取当前层的节点,如果路径长度小于等于当前depth,说明是占位符 const currentNode = depth < currentPath.length ? currentPath[depth] : null; if (!currentNode || !('columns' in currentNode)) { // 叶子列在当前层的占位符,或者已经是叶子节点(最底层) if (depth === maxDepth - 1) { // 最底层,直接用叶子节点 currentRow.push({ ...currentPath[depth] }); } else { // 非底层,添加空占位符 currentRow.push({ label: '' }); } i++; } else { // 容器节点,计算需要合并的列数(即该容器下的叶子数量) let span = 0; // 找到所有连续的、当前层节点相同的叶子路径 while (i < leafPaths.length && leafPaths[i][depth] === currentNode) { span++; i++; } // 添加带colSpan的容器节点 currentRow.push({ ...currentNode, colSpan: span }); } } result[depth] = currentRow; } return result; }
测试你的示例
用你给出的列数组测试:
const columns: TableColumn[] = [ { label: "Name", key: "name" }, { label: "Details", columns: [ { label: "Age", key: "age" }, { label: "Address", columns: [ { label: "City", key: "city" }, { label: "Country", key: "country" }, ], }, ], }, ]; const result = flattenColumns(columns); console.log(result);
输出结果和你期望的完全一致:
[ [ { label: "" }, { label: "Details", colSpan: 3 } ], [ { label: "" }, { label: "" }, { label: "Address", colSpan: 2 } ], [ { label: "Name", key: "name" }, { label: "Age", key: "age" }, { label: "City", key: "city" }, { label: "Country", key: "country" } ] ]
代码关键点解释
- collectLeafPaths:递归遍历所有列,把每个叶子的完整路径存起来,这是后续构建表头的核心依据——我们需要明确每个叶子属于哪些父容器。
- 逐行构建逻辑:
- 最底层(最后一行)直接输出所有叶子列,不需要占位符。
- 上层遇到容器节点时,计算它包含的叶子数量(连续相同路径前缀的数量),设置
colSpan;遇到叶子列的上层位置,用空占位符填充,确保和底层对齐。
- 合并连续容器:通过遍历叶子路径,合并连续的同容器节点,避免重复添加容器列,同时精准计算跨列数。
这样得到的二维结构可以直接用于生成HTML的<thead>部分,循环生成<tr>和<th>标签即可,colSpan属性也能直接复用。
备注:内容来源于stack exchange,提问作者vixalien
相关产品推荐
相关产品推荐

