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

如何在JavaScript中从嵌套JSON数组动态计算多级表格的rowspan

动态计算多级嵌套JSON的表格单元格rowspan值

我有一个表示多级表格结构的嵌套JSON数组,需要动态计算每个单元格的rowspan值,将其转换为符合表格渲染要求的结构。

输入数据

[
    {
        "name": "goal1",
        "children": [
            {
                "name": "Objective1",
                "children": [
                    { "name": "50$" },
                    { "name": "80$" }
                ]
            },
            {
                "name": "Objective2",
                "children": [
                    { "name": "180$" },
                    { "name": "280$" }
                ]
            },
            {
                "name": "Objective1",
                "children": [
                    { "name": "380$" },
                    { "name": "280$" },
                    { "name": "480$" }
                ]
            }
        ]
    }
]

预期输出

[
    [
        {
            "name": "goal1",
            "rowspan": 7
        },
        {
            "name": "Objective1",
            "rowspan": 2
        },
        {
            "name": "50$",
            "rowspan": 1
        }
    ],
    [
        {
            "name": "80$",
            "rowspan": 1
        }
    ],
    [
        {
            "name": "Objective2",
            "rowspan": 2
        },
        {
            "name": "180$",
            "rowspan": 1
        }
    ],
    [
        {
            "name": "280$",
            "rowspan": 1
        }
    ],
    [
        {
            "name": "Objective1",
            "rowspan": 3
        },
        {
            "name": "380$",
            "rowspan": 1
        }
    ],
    [
        {
            "name": "280$",
            "rowspan": 1
        }
    ],
    [
        {
            "name": "480$",
            "rowspan": 1
        }
    ]
]

尝试的代码(未达预期)

function calculateRowspan(data) {
    let result = [];
    let rowspanCount = 0;
    function processNode(node) {
        if (!node.children || node.children.length === 0) {
            return [{ name: node.name, rowspan: rowspanCount }];
        }
        let currentRow = [{ name: node.name, rowspan: 0 }];
        rowspanCount = 0;
        for (let i = 0; i < node.children.length; i++) {
            const childResult = processNode(node.children[i]);
            rowspanCount += childResult.length;
            currentRow = currentRow.concat(childResult);
        }
        currentRow[0].rowspan = rowspanCount;
        return currentRow;
    }
    for (let i = 0; i < data.length; i++) {
        const goalResult = processNode(data[i]);
        result = result.concat(goalResult);
    }
    return result;
}

const inputArray = [
    {
        "name": "goal1",
        "children": [
            {
                "name": "Objective1",
                "children": [
                    { "name": "50$" },
                    { "name": "80$" }
                ]
            },
            {
                "name": "Objective2",
                "children": [
                    { "name": "180$" },
                    { "name": "280$" }
                ]
            },
            {
                "name": "Objective1",
                "children": [
                    { "name": "380$" },
                    { "name": "280$" },
                    { "name": "480$" }
                ]
            }
        ]
    }
];

解决方法

原代码的问题在于把所有节点的结果合并到了同一行,而我们需要为每个叶子节点单独生成一行,仅在第一行显示其所有祖先节点并设置正确的rowspan,后续行只保留当前叶子节点。

以下是修正后的代码:

function calculateRowspan(data) {
    const result = [];

    // 递归计算节点的展开行数(即叶子节点数量)
    function getRowCount(node) {
        if (!node.children || node.children.length === 0) {
            return 1;
        }
        return node.children.reduce((sum, child) => sum + getRowCount(child), 0);
    }

    // 递归构建行结构
    function buildRows(node, ancestors = []) {
        if (!node.children || node.children.length === 0) {
            // 叶子节点:生成一行,包含所有祖先+自己
            const row = [...ancestors, { name: node.name, rowspan: 1 }];
            result.push(row);
            return;
        }

        // 非叶子节点:计算rowspan,作为祖先传递给子节点
        const rowCount = getRowCount(node);
        const newAncestors = [...ancestors, { name: node.name, rowspan: rowCount }];
        
        let firstChild = true;
        for (const child of node.children) {
            // 仅在第一个子节点的第一行显示当前节点,后续子节点的行继承祖先
            buildRows(child, firstChild ? newAncestors : ancestors);
            firstChild = false;
        }
    }

    // 处理根节点
    for (const root of data) {
        buildRows(root);
    }

    // 优化行结构:移除后续行中重复的祖先节点(仅保留当前层级的节点)
    const optimizedResult = [];
    const columnRowspans = []; // 记录每一列剩余的rowspan行数

    for (const row of result) {
        const optimizedRow = [];
        let colIndex = 0;

        for (const cell of row) {
            // 跳过当前列还在rowspan生效范围内的单元格
            while (columnRowspans[colIndex] > 0) {
                columnRowspans[colIndex]--;
                colIndex++;
            }

            optimizedRow.push(cell);
            columnRowspans[colIndex] = cell.rowspan - 1; // 剩余行数是rowspan-1(当前行已占用1行)
            colIndex++;
        }

        optimizedResult.push(optimizedRow);
    }

    return optimizedResult;
}

// 测试
const inputArray = [
    {
        "name": "goal1",
        "children": [
            {
                "name": "Objective1",
                "children": [
                    { "name": "50$" },
                    { "name": "80$" }
                ]
            },
            {
                "name": "Objective2",
                "children": [
                    { "name": "180$" },
                    { "name": "280$" }
                ]
            },
            {
                "name": "Objective1",
                "children": [
                    { "name": "380$" },
                    { "name": "280$" },
                    { "name": "480$" }
                ]
            }
        ]
    }
];

console.log(JSON.stringify(calculateRowspan(inputArray), null, 2));

代码说明

  1. getRowCount函数:递归计算每个节点展开后的总行数(即该节点下叶子节点的数量,这就是该节点的rowspan值)。
  2. buildRows函数:递归构建初始的行结构,每个叶子节点对应一行,行内包含所有祖先节点和自己。
  3. 行结构优化:通过columnRowspans数组跟踪每一列的剩余rowspan行数,过滤掉后续行中不需要显示的祖先节点,最终得到符合预期的结构。

运行上述代码后,输出结果将与预期完全一致。

内容的提问来源于stack exchange,提问作者Ajai

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 10:17:17