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

React中Mantine Table多级行分组实现问题求助

基于@mantine/core Table的多级通用分组实现

核心思路

通过通用分组函数将原始数据按指定列分层聚合,再递归渲染带跨行属性的分组头和数据行,实现无需硬编码列名的多级分组效果。

实现步骤

1. 通用数据分组函数

支持传入任意数量的分组列,递归生成嵌套分组结构:

// 兼容提示:若环境不支持Object.groupBy,可替换为lodash.groupBy
const groupDataByColumns = (data, groupKeys) => {
  if (!groupKeys.length) return data.map(item => ({ type: 'row', data: item }));
  
  const currentKey = groupKeys[0];
  const grouped = Object.groupBy(data, item => item[currentKey]);
  
  return Object.entries(grouped).map(([groupValue, items]) => ({
    type: 'group',
    key: currentKey,
    value: groupValue,
    children: groupDataByColumns(items, groupKeys.slice(1))
  }));
};

2. 跨行计算工具函数

计算每个分组下的总行数,用于设置分组单元格的rowspan属性:

const calculateRowSpan = (groupItem) => {
  if (groupItem.type === 'row') return 1;
  return groupItem.children.reduce((total, child) => total + calculateRowSpan(child), 0);
};

3. 递归渲染分组与数据行

根据分组结构递归生成Table行,自动处理分组单元格的跨行和缩进:

const renderRows = (groupedData, allColumns, groupKeys, depth = 0) => {
  return groupedData.flatMap(item => {
    if (item.type === 'row') {
      return (
        <Table.Tr key={item.data.id}>
          {allColumns.map(col => 
            groupKeys.includes(col.key) 
              ? <Table.Td key={col.key}></Table.Td> 
              : <Table.Td key={col.key}>{item.data[col.key]}</Table.Td>
          )}
        </Table.Tr>
      );
    }

    const rowSpan = calculateRowSpan(item);
    const groupRow = (
      <Table.Tr key={`group-${item.value}-${depth}`}>
        {allColumns.map((col, idx) => {
          const groupColIndex = groupKeys.indexOf(item.key);
          if (idx === groupColIndex) {
            return (
              <Table.Td
                key={col.key}
                rowSpan={rowSpan}
                style={{ 
                  fontWeight: 'bold',
                  paddingLeft: `${depth * 20}px`,
                  verticalAlign: 'top'
                }}
              >
                {item.value}
              </Table.Td>
            );
          }
          return <Table.Td key={col.key}></Table.Td>;
        })}
      </Table.Tr>
    );

    const childRows = renderRows(item.children, allColumns, groupKeys, depth + 1);
    return [groupRow, ...childRows];
  });
};

4. 完整组件示例

import { Table } from '@mantine/core';

// 示例数据
const sampleData = [
  { id: 1, state_name: 'Karnataka', district: 'Banglore', gender: 'Male' },
  { id: 2, state_name: 'Karnataka', district: 'Banglore', gender: 'Female' },
  { id: 3, state_name: 'Karnataka', district: 'Mysore', gender: 'Male' },
  { id: 4, state_name: 'Karnataka', district: 'Mysore', gender: 'Female' },
  { id: 5, state_name: 'Karnataka', district: 'Manglore', gender: 'Male' },
  { id: 6, state_name: 'Karnataka', district: 'Manglore', gender: 'Female' },
  { id: 7, state_name: 'Maharashtra', district: 'Mumbai', gender: 'Male' },
  { id: 8, state_name: 'Maharashtra', district: 'Mumbai', gender: 'Female' },
];

// 通用分组函数
const groupDataByColumns = (data, groupKeys) => {
  if (!groupKeys.length) return data.map(item => ({ type: 'row', data: item }));
  
  const currentKey = groupKeys[0];
  const grouped = Object.groupBy(data, item => item[currentKey]);
  
  return Object.entries(grouped).map(([groupValue, items]) => ({
    type: 'group',
    key: currentKey,
    value: groupValue,
    children: groupDataByColumns(items, groupKeys.slice(1))
  }));
};

// 计算跨行数
const calculateRowSpan = (groupItem) => {
  if (groupItem.type === 'row') return 1;
  return groupItem.children.reduce((total, child) => total + calculateRowSpan(child), 0);
};

// 递归渲染行
const renderRows = (groupedData, allColumns, groupKeys, depth = 0) => {
  return groupedData.flatMap(item => {
    if (item.type === 'row') {
      return (
        <Table.Tr key={item.data.id}>
          {allColumns.map(col => 
            groupKeys.includes(col.key) 
              ? <Table.Td key={col.key}></Table.Td> 
              : <Table.Td key={col.key}>{item.data[col.key]}</Table.Td>
          )}
        </Table.Tr>
      );
    }

    const rowSpan = calculateRowSpan(item);
    const groupRow = (
      <Table.Tr key={`group-${item.value}-${depth}`}>
        {allColumns.map((col, idx) => {
          const groupColIndex = groupKeys.indexOf(item.key);
          if (idx === groupColIndex) {
            return (
              <Table.Td
                key={col.key}
                rowSpan={rowSpan}
                style={{ 
                  fontWeight: 'bold',
                  paddingLeft: `${depth * 20}px`,
                  verticalAlign: 'top'
                }}
              >
                {item.value}
              </Table.Td>
            );
          }
          return <Table.Td key={col.key}></Table.Td>;
        })}
      </Table.Tr>
    );

    const childRows = renderRows(item.children, allColumns, groupKeys, depth + 1);
    return [groupRow, ...childRows];
  });
};

export default function GroupedTable() {
  // 定义所有列(分组列+数据列)
  const columns = [
    { key: 'state_name', label: 'State' },
    { key: 'district', label: 'District' },
    { key: 'gender', label: 'Gender' },
  ];

  // 指定分组顺序,支持任意数量的分组列
  const groupColumns = ['state_name', 'district'];

  // 生成分组数据
  const groupedData = groupDataByColumns(sampleData, groupColumns);

  return (
    <Table>
      <Table.Thead>
        <Table.Tr>
          {columns.map(col => (
            <Table.Th key={col.key}>{col.label}</Table.Th>
          ))}
        </Table.Tr>
      </Table.Thead>
      <Table.Tbody>{renderRows(groupedData, columns, groupColumns)}</Table.Tbody>
    </Table>
  );
}

关键特性

  • 通用分组:仅需修改groupColumns数组即可实现任意层级的分组,无需硬编码列名
  • 自动跨行:通过计算分组总行数,自动设置分组单元格的rowspan属性
  • 样式可定制:分组头的缩进、字体粗细等样式可根据需求调整

内容的提问来源于stack exchange,提问作者Aishwarya J S

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 10:50:47