欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

使用 antd 的表格组件实施交叉表(动态合并单元格)

最编程 2024-07-06 09:07:09
...

最终效果如下图


image.png

主要难点在于左侧模拟的表头,需要自己合并,用到的是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;