使用 antd 的表格组件实施交叉表(动态合并单元格)
最编程
2024-07-06 09:07:09
...
最终效果如下图
主要难点在于左侧模拟的表头,需要自己合并,用到的是rowSpan属性。
数据:
export default [
{'dataSource1': 26.99,'dataSource2': 13.01,'type': 'Female','type2': 'Smoker','week': '周五','type3': 'Dinner','peoCount': 4},
{'dataSource1': 16.19,'dataSource2': 1.02,'type': 'Female','type2': 'Smoker','week': '周日','type3': 'Lunch','peoCount': 3},
{'dataSource1': 16.91,'dataSource2': 3.01,'type': 'Female','type2': 'Smoker','week': '周三','type3': 'Lunch','peoCount': 2},
{'dataSource1': 13.19,'dataSource2': 5.01,'type': 'Female','type2': 'Non-Smoker','week': '周四','type3': 'Dinner','peoCount': 3},
{'dataSource1': 16.99,'dataSource2': 1.01,'type': 'Female','type2': 'Non-Smoker','week': '周日','type3': 'Dinner','peoCount': 3},
{'dataSource1': 17.99,'dataSource2': 2.011,'type': 'Female','type2': 'Non-Smoker','week': '周日','type3': 'Lunch','peoCount': 1},
{'dataSource1': 15.99,'dataSource2': 1.021,'type': 'Female','type2': 'Non-Smoker','week': '周五','type3': 'Lunch','peoCount': 4},
{'dataSource1': 36.99,'dataSource2': 14.01,'type': 'Female','type2': 'Non-Smoker','week': '周一','type3': 'Lunch','peoCount': 3},
{'dataSource1': 12.99,'dataSource2': 8.01,'type': 'Male','type2': 'Smoker','week': '周二','type3': 'Dinner','peoCount': 3},
{'dataSource1': 19.99,'dataSource2': 12.01,'type': 'Male','type2': 'Smoker','week': '周日','type3': 'Dinner','peoCount': 2},
{'dataSource1': 10.34,'dataSource2': 1.03,'type': 'Male','type2': 'Non-Smoker','week': '周一','type3': 'Lunch','peoCount': 4},
{'dataSource1': 13.99,'dataSource2': 2.01,'type': 'Male','type2': 'Non-Smoker','week': '周一','type3': 'Dinner','peoCount': 3},
];
组件:
import React from 'react';
import {Table} from "antd";
import data from './data'; // data 是指上面的demo数据
class TestReport extends React.Component {
state = {
columnTree: [
{title: '数据1', key: 'dataSource1'},
{title: 'Other', key: 'Other', children: [
{title: 'Age', key: 'Age'}, {title: 'Address', key: 'Address', children: [
{ title: 'Test', key: 'Test' }, { title: 'Block', key: 'Block', children: [
{title: '数据2', key: 'dataSource2'}, {title: 'Door No.'}
]}
]}]
},
{title: 'Company', key: 'Company', children: [
{title: '人数', key: 'peoCount'}, {title: 'Company Name', key: 'Company Name'}
]}
], // 横向的表头
groupColumnKeys: ['type', 'type2', 'type3', 'week'], // 左侧表头的key
};
// 递归生成上面的表头
generateColumns = (item) => {
const columns = {};
if (item.children) {
columns.children = item.children.map(this.generateColumns);
}
columns.title = item.title;
columns.key = item.key;
columns.dataIndex = item.key;
columns.width = 120;
return columns;
};
// 递归处理左侧表头合并的值
generateRowSpan = (row, rowIndex, key, rowSpan, testMap, colIndex) => {
let mapKey = `${rowIndex}-${key}`;
let preKey = `${rowIndex - 1}-${key}`;
// 如果父级值为空,则代表是第一列, 递归获取合并的数量
if (testMap[mapKey].parentValue == null) {
// 如果到了第一行,则结束递归,并设置当前行的rowSpan
if (rowIndex === 0) {
testMap[mapKey].rowSpan = rowSpan;
return rowSpan;
}
if (testMap[mapKey].value === testMap[preKey].value) {
// 如果当前值 和 上一个值相同 则合并,并继续往前找
testMap[mapKey].rowSpan = 0;
return this.generateRowSpan(data[rowIndex - 1], rowIndex - 1, key, rowSpan + 1, testMap);
} else {
// 如果当前行和上一行不同,则结束递归,并设置当前行的rowSpan
testMap[mapKey].rowSpan = rowSpan;
return rowSpan;
}
} else {
if (rowIndex === 0) {
testMap[mapKey].rowSpan = rowSpan;
return rowSpan;
}
let preColKey = this.state.groupColumnKeys[colIndex - 1];
let preColMapKey = `${rowIndex}-${preColKey}`;
let preColRowKey = `${rowIndex - 1}-${preColKey}`;
// 如果当前值相同 父级值也相同时 合并, 否则不合并
if (testMap[mapKey].value === testMap[preKey].value) {
if (testMap[preColMapKey].value === testMap[preColRowKey].value) {
testMap[mapKey].rowSpan = 0;
return this.generateRowSpan(data[rowIndex - 1], rowIndex - 1, key, rowSpan + 1, testMap, colIndex);
} else {
testMap[mapKey].rowSpan = rowSpan;
return rowSpan;
}
} else {
testMap[mapKey].rowSpan = rowSpan;
return rowSpan;
}
}
};
render() {
const {groupColumnKeys, columnTree} = this.state;
const testMap = {};
const dataArray = data;
// 循环生成map, 获取每一行的当前值与父级值
groupColumnKeys.forEach((key, gIndex) => {
data.forEach((data, dIndex) => {
testMap[`${dIndex}-${key}`] = {rowSpan: 1, value: data[key], parentValue: gIndex === 0 ? null : dataArray[dIndex][groupColumnKeys[gIndex-1]]};
this.generateRowSpan(data, dIndex, key, 1, testMap, gIndex);
});
});
let newColumns = [];
for (let i = 0; i < groupColumnKeys.length; i++) {
newColumns.push({ title: '',
key: groupColumnKeys[i],
colSpan: i === 0 ? groupColumnKeys.length : 0,
width: 120,
className: 'tableHeader',
render(_, row, rowIndex) {
return {
children: row[groupColumnKeys[i]],
props: {
rowSpan: testMap[`${rowIndex}-${groupColumnKeys[i]}`].rowSpan,
},
};
}
});
}
let test = columnTree.map(this.generateColumns);
newColumns = [...newColumns, ...test];
return (
<div style={{ padding: 20 }}>
<Table
dataSource={dataArray}
columns={newColumns}
bordered
pagination={false}
/>
</div>
);
}
}
export default TestReport;