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

实现flattenColumns函数:将嵌套树形表格列转换为带表头层级的扁平二维结构

实现flattenColumns函数:将嵌套树形表格列转换为带表头层级的扁平二维结构

嘿,我完全get到你要解决的问题了——把嵌套的表格列转换成适配HTML表头的二维结构,每一行对应表头的一个层级,还要精准处理占位符和跨列(colSpan)。你之前的尝试已经搭了基础,但确实没搞定占位符的对齐问题,这也是这个需求里最绕的核心点。

先把核心需求捋清楚:最终的二维数组里,每一行对应表头的一个层级,最底层是所有叶子列;上层的容器列要占据下属所有叶子列的宽度,其他位置用空占位符填充,确保和底层叶子列的位置严格对齐。

解决方案思路

要实现这个效果,我们得先摸透每个叶子列的完整归属路径(比如City的路径是Details → Address → City),再基于这些路径来构建每一层表头:

  1. 收集所有叶子的完整路径:递归遍历列结构,记录每个叶子列从根节点到自身的所有父容器+自身的路径。
  2. 确定表头总层数:最长路径的长度就是表头的总行数(比如你给的例子里最长路径是3层,所以表头有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" }
  ]
]

代码关键点解释

  1. collectLeafPaths:递归遍历所有列,把每个叶子的完整路径存起来,这是后续构建表头的核心依据——我们需要明确每个叶子属于哪些父容器。
  2. 逐行构建逻辑:
    • 最底层(最后一行)直接输出所有叶子列,不需要占位符。
    • 上层遇到容器节点时,计算它包含的叶子数量(连续相同路径前缀的数量),设置colSpan;遇到叶子列的上层位置,用空占位符填充,确保和底层对齐。
  3. 合并连续容器:通过遍历叶子路径,合并连续的同容器节点,避免重复添加容器列,同时精准计算跨列数。

这样得到的二维结构可以直接用于生成HTML的<thead>部分,循环生成<tr>和<th>标签即可,colSpan属性也能直接复用。

备注:内容来源于stack exchange,提问作者vixalien

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.14 16:58:01