table
table copied to clipboard
更复杂的分组表头
更复杂分组表头的渲染
期望渲染结果:
实际渲染结果:
示例复现的代码
import React from 'react';
import type { TableProps } from 'rc-table';
import Table from 'rc-table';
import '../../assets/index.less';
const columns: TableProps['columns'] = [
{
title: '姓名',
rowSpan: 4,
dataIndex: 'name',
key: 'name',
},
{
title: '出勤',
colSpan: 3,
rowSpan: 3,
children: [
{
title: '出勤',
dataIndex: 'attendance',
key: 'attendance',
},
{
title: '迟到',
dataIndex: 'late',
key: 'late',
},
{
title: '请假',
dataIndex: 'leave',
key: 'leave',
},
],
},
{
title: '其它',
colSpan: 4,
children: [
{
title: '年龄',
dataIndex: 'age',
key: 'age',
rowSpan: 3,
},
{
title: '住址',
colSpan: 3,
children: [
{
title: '街道',
dataIndex: 'street',
key: 'street',
rowSpan: 2,
},
{
title: '小区',
colSpan: 2,
children: [
{
title: '单元',
dataIndex: 'building',
key: 'building',
},
{
title: '门牌',
dataIndex: 'number',
key: 'number',
},
],
},
],
},
],
},
{
title: '技能',
colSpan: 2,
rowSpan: 2,
children: [
{
title: '前端',
dataIndex: 'frontend',
key: 'frontend',
rowSpan: 2,
},
{
title: '后端',
dataIndex: 'backend',
key: 'backend',
rowSpan: 2,
},
],
},
{
title: '公司',
colSpan: 2,
children: [
{
title: '地址',
dataIndex: 'companyAddress',
key: 'companyAddress',
rowSpan: 3,
},
{
title: '名称',
dataIndex: 'companyName',
key: 'companyName',
rowSpan: 3,
},
],
},
{
title: '性别',
dataIndex: 'gender',
key: 'gender',
rowSpan: 4,
},
];
const data = [
{
key: '1',
name: '胡彦斌',
attendance: 20,
late: 0,
leave: 1,
age: 32,
street: '拱墅区和睦街道',
building: 1,
number: 2033,
frontend: 'S',
backend: 'S',
companyAddress: '西湖区湖底公园',
companyName: '湖底有限公司',
gender: '男',
},
{
key: '2',
name: '胡彦祖',
attendance: 20,
late: 0,
leave: 1,
age: 42,
street: '拱墅区和睦街道',
building: 3,
number: 2035,
frontend: 'S',
backend: 'S',
companyAddress: '西湖区湖底公园',
companyName: '湖底有限公司',
gender: '男',
},
];
const Demo = () => (
<div>
<h2>grouping columns specified colSpan & rowSpan</h2>
<Table columns={columns} data={data} className="bordered" />
</div>
);
export default Demo;
不知是否有支持计划,如有可以提交相关 PR
实现复杂分组表头最小侵入方案:
已有单元测试测试通过
新增 src/utils/convertUtil.ts
interface Column {
[key: string | symbol]: any;
};
interface Options {
children: string;
colSpan: string;
rowSpan: string;
hidden: string;
}
export function convertColumns<Columns extends readonly any[] = Column[]>(
columns: Columns,
options: Partial<Options> = {},
) {
if (!Array.isArray(columns) || columns.length === 0) {
return [] as unknown as Columns;
}
const defaultOptions = {
children: 'children',
colSpan: 'colSpan',
rowSpan: 'rowSpan',
hidden: 'hidden',
};
const {
children: childrenProp,
colSpan: colSpanProp,
rowSpan: rowSpanProp,
hidden: hiddenProp,
} = Object.assign({}, defaultOptions, options);
let specified = false;
let tree = columns.map((item) => ({ ...item } as Column));
let depthCurr = 0;
let depthNext = 0;
const nodePos: {
index: number;
total: number;
} = [{
index: tree.length,
total: tree.length,
}];
const rowSpans: number[] = [];
const columnsMap = new Map<number, Column[]>();
const treeMap = new Map<Column, Column[]>();
const branchLastSet = new Set<Column>();
while (tree.length > 0) {
depthCurr = depthNext;
nodePos.splice(depthCurr + 1);
rowSpans.splice(depthCurr);
nodePos[depthCurr].index--;
if (nodePos[depthCurr].index <= 0) {
depthNext = 0;
for (let i = nodePos.length - 1; i >= 0; i--) {
if (nodePos[i].index > 0) {
depthNext = i;
break;
}
}
}
const node = tree.shift();
if (!node || typeof node !== 'object' || node[hiddenProp]) {
continue;
}
// const pathKey = nodePos.reduce((acc, { index, total }) => {
// return `${acc}-${total - 1 - index}`;
// }, 'key');
const colSpanSpecified = node[colSpanProp];
const rowSpanSpecified = node[rowSpanProp];
const colSpan = node[colSpanProp] ?? 1;
const rowSpan = node[rowSpanProp] ?? 1;
node[colSpanProp] = colSpan;
node[rowSpanProp] = rowSpan;
if (!specified && (colSpan > 1 || rowSpan > 1)) {
specified = true;
}
const parentsRowCount = rowSpans.reduce((acc, num) => acc + num, 0);
if (!columnsMap.has(parentsRowCount)) {
columnsMap.set(parentsRowCount, []);
}
columnsMap.get(parentsRowCount).push(node);
let leaf = node[childrenProp];
delete node[childrenProp];
if (Array.isArray(leaf) && leaf.length > 0) {
depthNext = depthCurr + 1;
nodePos[depthNext] = { index: leaf.length, total: leaf.length };
rowSpans[depthCurr] = rowSpan;
leaf = leaf.map((item) => ({ ...item } as Column));
node.colSpanSpecified = colSpanSpecified;
if (!treeMap.has(node)) {
treeMap.set(node, []);
}
treeMap.get(node).push(...leaf);
tree = [...leaf, ...tree];
} else {
node.rowSpanSpecified = rowSpanSpecified;
node.parentsRowCount = parentsRowCount;
branchLastSet.add(node);
}
}
if (!specified) {
return columns;
}
// correct colSpan of parent column in default state
[...treeMap.keys()].reverse().forEach((column) => {
const { colSpanSpecified } = column;
delete column.colSpanSpecified;
if (column[hiddenProp] || Number.isInteger(colSpanSpecified)) {
return;
}
const children = treeMap.get(column);
column[colSpanProp] = children.reduce((acc, item) => {
return item[hiddenProp] ? acc : acc + item[colSpanProp];
}, 0);
});
let rowCountMax = 0;
branchLastSet.forEach((column) => {
const rowCount = column[rowSpanProp] + column.parentsRowCount;
if (rowCount > rowCountMax) {
rowCountMax = rowCount;
}
});
// correct rowSpan of column in default state
branchLastSet.forEach((column) => {
const { rowSpanSpecified, parentsRowCount } = column;
if (!Number.isInteger(rowSpanSpecified)) {
column[rowSpanProp] = rowCountMax - parentsRowCount;
}
delete column.rowSpanSpecified;
delete column.parentsRowCount;
});
const keys = [...columnsMap.keys()].sort();
for (let i = keys.length - 1; i >= 1; i--) {
const parent = columnsMap.get(keys[i - 1]);
parent[0][childrenProp] = columnsMap.get(keys[i]);
}
return columnsMap.get(0) as unknown as Columns;
}
在 src/Header/Header.tsx 的 fillRowCells 之前转换 rootColumns :
diff --git a/src/Header/Header.tsx b/src/Header/Header.tsx
index f21b817b..07162562 100644
--- a/src/Header/Header.tsx
+++ b/src/Header/Header.tsx
@@ -2,6 +2,7 @@ import { useContext } from '@rc-component/context';
import * as React from 'react';
import TableContext, { responseImmutable } from '../context/TableContext';
import devRenderTimes from '../hooks/useRenderTimes';
+import { convertColumns } from '../utils/convertUtil';
import type {
CellType,
ColumnGroupType,
@@ -67,7 +68,7 @@ function parseHeaderRows<RecordType>(
}
// Generate `rows` cell data
- fillRowCells(rootColumns, 0);
+ fillRowCells(convertColumns<ColumnsType<RecordType>>(rootColumns), 0);
// Handle `rowSpan`
const rowCount = rows.length;
来个 PR?
@afc163 #1118 PR 已提交
新增复杂分组表头后会造成以下影响
-
复杂分组表头下的
colStartcolEnd计算不正确在显式指定的
colSpan或rowSpan大于或者等于 2 时 -
table 数据为空时
.rc-table-placeholder .rc-table-cell的colspan值不正确在
flattenColumns.length与实际表头列数不一致时
改动的文件可能较多,继续在 #1118 追加完善,还是等待 merge 后再提交新的 PR 呢
- [x] 复杂分组表头
- [x] 复杂分组表头下 colStart 与 colEnd 不正确的问题
- [x] 复杂分组表头下,且数据为空时 body colspan 不正确的问题
- [x] 测试
(匿了