Antd Table 表格行的分类与动态合并

5,605 阅读3分钟

有时候需要合并 Antd Table 里面的行,并且是在不能预知哪几行需要合并的情况下。这个时候需要根据待渲染的数据,动态的合并,相邻的若干行。现在有这样一个表格:

这是代码:


import React from "react";
import "./styles.css";
import { Table } from "antd";
import "antd/dist/antd.css";

const dataSource = [
  {
    name: "小明",
    exam: "月考",
    chinese: 100,
    math: 90,
    english: 87
  },
  {
    name: "小明",
    exam: "期中",
    chinese: 98,
    math: 100,
    english: 88
  },
  {
    name: "小明",
    exam: "期末",
    chinese: 100,
    math: 100,
    english: 100
  },
  {
    name: "小红",
    exam: "月考",
    chinese: 100,
    math: 100,
    english: 100
  },
  {
    name: "小红",
    exam: "期中",
    chinese: 100,
    math: 100,
    english: 100
  },
  {
    name: "小丽",
    exam: "月考",
    chinese: 100,
    math: 100,
    english: 100
  }
];
const columns = [
  {
    key: "name",
    title: "姓名",
    dataIndex: "name",
    render: (text) => <span>{text}</span>
  },
  {
    key: "exam",
    title: "考试",
    dataIndex: "exam",
    render: (text) => <span>{text}</span>
  },
  {
    key: "chinese",
    title: "语文",
    dataIndex: "chinese",
    render: (text) => <span>{text}</span>
  },
  {
    key: "math",
    title: "数学",
    dataIndex: "math",
    render: (text) => <span>{text}</span>
  },
  {
    key: "english",
    title: "英语",
    dataIndex: "english",
    render: (text) => <span>{text}</span>
  }
];
export default function App() {

  return (
    <div className="App">
      <h2>学期考试成绩表</h2>
      <Table
        bordered={true}
        dataSource={dataSource}
        columns={columns}
        footer={null}
      />
    </div>
  );
}

我们现在需要合并表格左边姓名相同的行,如果数据是从服务器拉取的,我们并不知道有哪几个行需要合并,因此只能动态设置 Table 的 rowSpan、colSpan 来实现。

假设数据已经排好序了,也就是在这个表格里面姓名相同的行已经排在相邻的地方,下面我们实现一个 mergeRows 方法来实现合并,这个方法在需要合并的行的数据中加入 rowSpancolSpan 字段并设置相应的值,然后渲染的时候我们在 columns 中使用行的 rowSpancolSpan

我们在 App 添加一个方法:

const mergeRows = (rows, key) => {
    rows[0].rowSpan = 1;
    let idx = 0;
    return rows.slice(1).reduce(
      (mergedRows, item, index) => {
        if (item[key] === mergedRows[idx][key]) {
          mergedRows[idx].rowSpan++;
          item.colSpan = 0;
        } else {
          item.rowSpan = 1;
          idx = index + 1;
        }
        return [...mergedRows, item];
      },
      [rows[0]]
    );
  };

然后 Table 组件里的 dataSource 传入 mergeRows(dataSource, "name")

<Table
        bordered={true}
-       dataSource={dataSource}
+       dataSource={mergeRows(dataSource, "name")}
        columns={columns}
        footer={null}
      />

还需要重写 columns ,改变 name 列的 render 方法:

const columns = [
  {
    key: "name",
    title: "姓名",
    dataIndex: "name",
-   render: (text) => <span>{text}</span>
+   render: (text, record) => ({
+       children: <div style={{ fontWeight: "bold" }}>{text}</div>,
+       props: {
+         rowSpan: record.rowSpan,
+         colSpan: record.colSpan
+       }
+   })
  },
  {
    key: "exam",
    title: "考试",
    dataIndex: "exam",
    render: (text) => <span>{text}</span>
  },
  {
    key: "chinese",
    title: "语文",
    dataIndex: "chinese",
    render: (text) => <span>{text}</span>
  },
  {
    key: "math",
    title: "数学",
    dataIndex: "math",
    render: (text) => <span>{text}</span>
  },
  {
    key: "english",
    title: "英语",
    dataIndex: "english",
    render: (text) => <span>{text}</span>
  }
];

现在渲染出来的表格就合并行了:

如果数据没有实现做好排序,还可以用下面这个 classifyRows 方法排序归类好需要合并的行,之后再调用 mergeRows 合并行:

  const classifyRows = (data, classifyRule) => {
    const keys = classifyRule

    let classified = data.slice(1).reduce((ordered, row) => {
      // 从已经排好序的行中寻找一行 row1,如果这行 row1 的 keys 值分别等于当前遍历到的行的 keys 值,就把当前遍历到的这一行插入到 row1 之后
      // 否则把当前遍历到的行放在所有排好序的行的最后一行
      const index = ordered.findIndex(orderedRow =>
        keys.reduce(
          (boolean, curKey) => boolean && orderedRow[curKey] === row[curKey],
          true
        )
      )
      if (index !== -1) {
        return [...ordered.slice(0, index + 1), row, ...ordered.slice(index + 1)]
      } else { return [...ordered, row] }
    }, [data[0]])

    return classified
  }

现在我们把 dataSource 顺序打乱,比如把 小明 的一行数据放到 小红 的两条数据之间:

const dataSource = [
  {
    name: "小明",
    exam: "月考",
    chinese: 100,
    math: 90,
    english: 87
  },
  {
    name: "小明",
    exam: "期末",
    chinese: 100,
    math: 100,
    english: 100
  },
  {
    name: "小红",
    exam: "月考",
    chinese: 100,
    math: 100,
    english: 100
  },
  {
    name: "小明",
    exam: "期中",
    chinese: 98,
    math: 100,
    english: 88
  },
  {
    name: "小红",
    exam: "期中",
    chinese: 100,
    math: 100,
    english: 100
  },
  {
    name: "小丽",
    exam: "月考",
    chinese: 100,
    math: 100,
    english: 100
  }
];

如果不用 classifyRows 归类排序,那么渲染出来的表格就是:

小明 的第三条数据没有被合并到小明 的前两条数据中, 而且 小红 的两条本该被合并的数据也没有合并,下面我们在 mergeRows 之前使用 classifyRows

<Table
  bordered={true}
- dataSource={mergeRows(dataSource, "name")}
+ dataSource={
+   mergeRows(
+     classifyRows(dataSource, ['name']), 'name')
+ }
  columns={columns}
  footer={null}
/>

可以发现这条数据会被合并到小明 的前两条数据中:

演示项目地址: codesandbox.io/s/pensive-s…