antd table合并复杂单元格、分组合并行、分组合并列、动态渲染列、嵌套表头

项目里遇到个需求,涉及到比较复杂的单元格合并 、嵌套表头、分组合并行、合并列等,并且数据列还是动态的,效果图如下:
在这里插入图片描述

在这里插入图片描述

可以分组设置【显示列】例如:当前组为【合同约定】,显示列为【合同节点】和【节点金额】

我们按【数据源】进行分组后,把第一组编号为【0001】的单元行合并 ,然后再插入一行【小计】,根据显示的【列数】决定合并几个【单元格】,比如我们的需求可以设置为当【合同节点】与【金额】全部显示时,显示【小计】的信息,再进行单元格合并

我们的数据源如下:

动态列数据

const mockRowData = [// {//     name: '合同约定',//     key: 'agree',//     handle: 'lt',//     handleValue: 2312313,//     color: '#FF811A',//     enable: true,//     checked: true,//     // disabled: true,//     checkable: false,//     hidden: true,//     children: [{name: '合同节点',key: 'contractNodes',handle: 'lt',handleValue: 2312313,color: '#FF811A',enable: true,checked: true,type: 1,},{name: '金额',key: 'Money',handle: 'lt',handleValue: 2312313,color: '#FF811A',enable: true,checked: true,type: 1,},{name: '节点比例',key: 'NodeRatio',handle: 'lt',handleValue: 2312313,color: '#FF811A',enable: false,checked: true,type: 1,},{name: '付款条件',key: 'paymentTerms',handle: 'lt',handleValue: 2312313,color: '#FF811A',enable: true,checked: true,type: 1,},//     ],// },// {//     name: '确认收入',//     key: 'confirmIncome',//     handle: 'lt',//     handleValue: 2312313,//     color: '#FF811A',//     enable: false,//     checked: true,//     // disabled: true,//     checkable: false,//     hidden: true,//     children: [{name: '成果提交日期',key: 'submissionDate',handle: 'lt',handleValue: 2312313,color: '#FF811A',enable: false,checked: true,type: 2,},{name: '证明文件日期',key: 'fileDate',handle: 'lt',handleValue: 2312313,color: '#FF811A',enable: true,checked: true,type: 2,},{name: '确认收入',key: 'Income',handle: 'lt',handleValue: 2312313,color: '#FF811A',enable: false,checked: true,type: 2,},{name: '确认比例',key: 'confirmationRatio',handle: 'lt',handleValue: 2312313,color: '#FF811A',enable: false,checked: true,type: 2,},//     ],// },// {//     name: '收款',//     key: 'receivePayment',//     handle: 'lt',//     handleValue: 2312313,//     color: '#FF811A',//     enable: false,//     checked: true,//     // disabled: true,//     checkable: false,//     hidden: true,//     children: [{name: '到账日期',key: 'DateofReceipt',handle: 'lt',handleValue: 2312313,color: '#FF811A',enable: false,checked: true,type: 3,},{name: '金额',key: 'Money1',handle: 'lt',handleValue: 2312313,color: '#FF811A',enable: true,checked: true,type: 3,},{name: '确认比例',key: 'confirmationRatio1',handle: 'lt',handleValue: 2312313,color: '#FF811A',enable: false,checked: true,type: 3,},//     ],// },{name: '应收款',key: 'receivables',handle: 'lt',handleValue: 2312313,color: '#FF811A',enable: false,checked: true,children: [],type: 4,},
];

数据源:

const mockTableData = {group1: [{group: 'group1',id: 1,name: '第一建筑设计院',order: '0001',type: '外部承包',isConsortium: '是',nameSquare:'XXXXX有限公司公司名称公司名称公司名称公司名称公司名称公司名称公司名称公司名称公司名称公司名称',cstatus: '执行中',money: '60,0000,0000',value1: 12345,contractNodes: 12345,Money: 12345,NodeRatio: 12345,paymentTerms: 12345,confirmIncome: 12345,submissionDate: 12345,fileDate: 12345,Income: 12345,confirmationRatio: 12345,Money1: 879,DateofReceipt: '2024-10-25',confirmationRatio1: '20%',receivables: '',agree: '',receivePayment: '200,0000,0000',},{group: 'group1',id: 2,name: '第二建筑设计院',order: '0001',type: '外部承包',isConsortium: '是',nameSquare:'XXXXX有限公司公司名称公司名称公司名称公司名称公司名称公司名称公司名称公司名称公司名称公司名称',cstatus: '执行中',money: '60,0000,0000',value1: 1234124,contractNodes: 1234124,Money: 1234124,NodeRatio: 1234124,paymentTerms: 1234124,confirmIncome: 1234124,submissionDate: 1234124,fileDate: 1234124,Income: 1234124,confirmationRatio: 1234124,Money1: 879,DateofReceipt: '2024-10-25',confirmationRatio1: '20%',receivables: '',agree: '',receivePayment: '200,0000,0000',},{group: 'group1',id: 3,name: '第三建筑设计院',order: '0001',type: '外部承包',isConsortium: '是',nameSquare:'XXXXX有限公司公司名称公司名称公司名称公司名称公司名称公司名称公司名称公司名称公司名称公司名称',cstatus: '执行中',money: '60,0000,0000',value1: 54321,contractNodes: 54321,Money: 54321,NodeRatio: 54321,paymentTerms: 54321,confirmIncome: 54321,submissionDate: 54321,fileDate: 54321,Income: 54321,confirmationRatio: 54321,Money1: 879,DateofReceipt: '2024-10-25',confirmationRatio1: '20%',receivables: '',agree: '',receivePayment: '200,0000,0000',},],group2: [{group: 'group2',id: 4,name: '第一建筑设计院',order: '0002',type: '外部承包',isConsortium: '是',nameSquare:'XXXXX有限公司公司名称公司名称公司名称公司名称公司名称公司名称公司名称公司名称公司名称公司名称',cstatus: '执行中',money: '60,0000,0000',value1: 12345,contractNodes: 12345,Money: 12345,NodeRatio: 12345,paymentTerms: 12345,confirmIncome: 12345,submissionDate: 12345,fileDate: 12345,Income: 12345,confirmationRatio: 12345,Money1: 879,DateofReceipt: '2024-10-25',confirmationRatio1: '20%',receivables: '',agree: '',receivePayment: '200,0000,0000',},{group: 'group2',id: 5,name: '第二建筑设计院',order: '0002',type: '外部承包',isConsortium: '是',nameSquare:'XXXXX有限公司公司名称公司名称公司名称公司名称公司名称公司名称公司名称公司名称公司名称公司名称',cstatus: '执行中',money: '60,0000,0000',value1: 1234124,contractNodes: 1234124,Money: 1234124,NodeRatio: 1234124,paymentTerms: 1234124,confirmIncome: 1234124,submissionDate: 1234124,fileDate: 1234124,Income: 1234124,confirmationRatio: 1234124,Money1: 879,DateofReceipt: '2024-10-25',confirmationRatio1: '20%',receivables: '',agree: '',receivePayment: '200,0000,0000',},{group: 'group2',id: 6,name: '第三建筑设计院',order: '0002',type: '外部承包',isConsortium: '是',nameSquare:'XXXXX有限公司公司名称公司名称公司名称公司名称公司名称公司名称公司名称公司名称公司名称公司名称',cstatus: '执行中',money: '60,0000,0000',value1: 54321,contractNodes: 54321,Money: 54321,NodeRatio: 54321,paymentTerms: 54321,confirmIncome: 54321,submissionDate: 54321,fileDate: 54321,Income: 54321,confirmationRatio: 54321,Money1: 879,DateofReceipt: '2024-10-25',confirmationRatio1: '20%',receivables: '',agree: '',receivePayment: '200,0000,0000',},],
};

那接下来咱们就先看如何显示【动态列】吧~

1.显示动态列

 const columns = useCallback(() => {if (rowData && rowData.length) {console.log(rowData);const list = [// 先写入非动态列{title: '合同编号',dataIndex: 'order',width: 50,render: (text) => {return (<divclassName="ellipsis-item"title={text}style={{ width: '100px' }}>{text}</div>);},onCell: mergeRow,},// 循环动态列,{title: '收款',className: 'type3Column',children: [...flattenData(rowData, 0).filter((item) => item.checked).filter((item) => item.type === 3).map((item) => {return {title: (<div className="ellipsis-item" title={item.name}>{item.name}</div>),dataIndex: item.key,className: 'type3Column',width: 100,render: (text) => {return (<divstyle={{background: getColumnColor(item.enable,text,item.handle,item.handleValue,item.color,),}}className="value-item">{text}</div>);},};}),],},];console.log('list', list);return list;}}, [rowData]);

2.合并列

思路

我们的列是动态变换的,所以合并的列数也是动态合并的,那我们就需要在每次变化的时候再去判断需要合并几列

1.先将【原始数据源】分组处理,计算每组的【小计】金额 ,然后写入【表格数据源】里
2.循环动态列,将列也分别写入三个数组里
3.判断我们【每组】【动态列】中是否符合我们的需求,是否要显示【小计】信息,设置【要合并的列】为1
4.如果存在,判断本组有无其他列显示,有的话【要合并的列数+1】

处理列数&计算要合并的列数
  const handleColumnData = (row) => {console.log('row=======', row);let type1list = [];// 分组列1let type2list = []; // 分组列2let type3list = []; // 分组列3let haveAgreeTotal = false;  // 分组列1是否有【小计】let haveIncomeTotal = false;  // 分组列1是否有【小计】let haveReciveTotal = false;  // 分组列1是否有【小计】let AgreeColspan = 0;  // 分组列1要合并的列数let IncomeColspan = 0;  // 分组列1要合并的列数let ReciveColspan = 0;  // 分组列1要合并的列数row.forEach((item) => {if (item.type === 1) {type1list.push(item.key);} else if (item.type === 2) {type2list.push(item.key);} else if (item.type === 3) {type3list.push(item.key);}});// console.log('type1', type1list)// console.log('type2', type2list)// console.log('type3', type3list)if (type1list.length > 0) {// 如果同时存在【合同节点】和【金额】字段,显示小计if (type1list.includes('contractNodes') &&type1list.includes('Money')) {haveAgreeTotal = true;AgreeColspan = 1;if (type1list.includes('NodeRatio')) {AgreeColspan += 1;}if (type1list.includes('paymentTerms')) {AgreeColspan += 1;}}}if (type2list.length > 0) {// 如果同时存在【成果提交日期】和【确认收入】字段,显示小计if (type2list.includes('submissionDate') &&type2list.includes('Income')) {haveIncomeTotal = true;IncomeColspan = 1;if (type2list.includes('fileDate')) {IncomeColspan += 1;}if (type2list.includes('confirmationRatio')) {IncomeColspan += 1;}}}if (type3list.length > 0) {// 如果同时存在【到账日期】和【金额】字段,显示小计if (type3list.includes('DateofReceipt') &&type3list.includes('Money1')) {haveReciveTotal = true;ReciveColspan = 1;if (type3list.includes('confirmationRatio1')) {ReciveColspan += 1;}}}// console.log('haveAgreeTotal, haveIncomeTotal, haveReciveTotal', haveAgreeTotal, haveIncomeTotal, haveReciveTotal)// console.log('AgreeColspan, IncomeColspan, ReciveColspan', AgreeColspan, IncomeColspan, ReciveColspan)return {haveAgreeTotal,haveIncomeTotal,haveReciveTotal,AgreeColspan,IncomeColspan,ReciveColspan,};};
  useEffect(() => {// setTableData(mockTableData)let source = formatData(mockTableData, rowData);setDataSource(source);}, []);
根据需求写入【小计】行
 const formatData = (data, rowData) => {let obj = handleColumnData(rowData);let resTableData = [];const {haveAgreeTotal,haveIncomeTotal,haveReciveTotal,AgreeColspan,IncomeColspan,ReciveColspan,} = obj;// 是否有任意一组列存在小计,存在则写入新一行// 循环列表,把数据分组写入// 写入小计行,根据列标识写入要合并的列数// 渲染时判断标识行,写入列数,其余为0if (haveAgreeTotal || haveIncomeTotal || haveReciveTotal) {Object.keys(data).forEach((key) => {let group = data[key];let nodeSum = 0;let IncomeSum = 0;let Money1Sum = 0;group.forEach((item) => {resTableData.push(item);nodeSum += item.NodeRatio;IncomeSum += item.Income;Money1Sum += item.Money1;});let totalRecord = {group: key,id: '',order: '',type: '',isConsortium: '',nameSquare: '',cstatus: '',money: '',value1: '',confirmIncome: '',receivables: '',agree: '',receivePayment: '',};if (haveAgreeTotal) {totalRecord.contractNodes = '小计';totalRecord.Money = nodeSum;totalRecord.NodeRatio = nodeSum;totalRecord.paymentTerms = nodeSum;totalRecord.AgreeColspan = AgreeColspan;}if (haveIncomeTotal) {totalRecord.submissionDate = '小计';totalRecord.Income = IncomeSum;totalRecord.fileDate = IncomeSum;totalRecord.confirmationRatio = IncomeSum;totalRecord.IncomeColspan = IncomeColspan;}if (haveReciveTotal) {totalRecord.DateofReceipt = '小计';totalRecord.Money1 = Money1Sum;totalRecord.confirmationRatio1 = Money1Sum;totalRecord.ReciveColspan = ReciveColspan;}// console.log('recordtotal', totalRecord)resTableData.push(totalRecord);});} else {Object.keys(data).forEach((key) => {let group = data[key];group.forEach((item) => {resTableData.push(item);});});}
return resTableData};
渲染表格

此处我们要注意的是!当前合并单元格行数、列数设置好后,我们要把【被合并的行列】都设置为0!!并且【其他的行和列】的合并数也要都为0!不然表格会出现错位问题!

我们把cloumns列设置

  {title: '合同约定',className: 'type1Column',children: [...flattenData(rowData, 0).filter((item) => item.checked).filter((item) => item.type === 1).map((item) => {return {title: (<div className="ellipsis-item" title={item.name}>{item.name}</div>),dataIndex: item.key,className: 'type1Column',width: 100,render: (text, record, index) => {if (record.AgreeColspan) { // 判断是否是小计行if (item.key === 'Money') {return {children: handleNode(item, text, 'total_back_pink'),props: { colSpan: record.AgreeColspan }, // 合并"小计"行从第二列开始的所有列};} else if (item.key === 'contractNodes') {return {children: handleNode(item, text, 'total_back_pink'),props: { colSpan: 1 }, // "小计"列不合并};} else {return {children: handleNode(item, text, 'total_back_pink'),props: { colSpan: 0 }, // 当前组其他列设置为0,不会出现表格错位情况};}} else {return {children: handleNode(item, text),props: { colSpan: 1 }, // 非"小计"行全部不合并};}},};}),],},

这样就实现了【分组合并列】、【动态渲染列】啦!
在这里插入图片描述

2.合并行

思路

循环【数据源】,从第一行开始判断,如果【当前行】与【下一行】是同一组,那【rowSpan】 + 1,【被合并的行】记得要设置为0哦

const formatData = (data, rowData) => {// 处理要合并的列...// 处理要合并的行let processedData = [...resTableData];let prevFinishNum;let currentIndexForSameProperty;for (let i = 0; i < processedData.length; i++) {const currentItem = processedData[i];if (i === 0 || currentItem.group !== prevFinishNum) {// 新的组开始,重置计数器,并将上一组最后一项的rowSpan设置好if (prevFinishNum !== undefined &&currentIndexForSameProperty !== undefined) {processedData[currentIndexForSameProperty].rowSpan =i - currentIndexForSameProperty;}currentIndexForSameProperty = i;currentItem.rowSpan = 1;currentItem.opcaty = '0'; // 标识是不是被合并的行} else {// 相同组内,累加rowSpanprocessedData[currentIndexForSameProperty].rowSpan += 1;currentItem.opcaty = '1'; // 标识是被合并的行}prevFinishNum = currentItem.group;}// 处理数组的最后一项if (currentIndexForSameProperty !== undefined &&currentIndexForSameProperty < processedData.length - 1) {processedData[currentIndexForSameProperty].rowSpan =processedData.length - currentIndexForSameProperty;}return processedData;}

这样就实现了【合并行】啦!
在这里插入图片描述

3.嵌套表头

这个在 antd官网 都有,就是给columns设置children就行了,咱们在上面【渲染动态列】的代码里也有提到 ~
在这里插入图片描述

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/445836.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

文件完整性监控:如何提高企业的数据安全性

企业网络庞大而复杂&#xff0c;需要处理大量关键业务数据&#xff0c;这些敏感文件在企业网络中不断传输&#xff0c;并由多个用户和实体存储、共享和访问。FIM 工具或具有 FIM 功能的 SIEM 解决方案使企业能够跟踪未经授权的文件更改、对敏感信息的恶意访问、数据篡改尝试和内…

VSCode 使用 EmmyLua 对lua进行调试

时间&#xff1a;2024年10月 其他&#xff1a;win10&#xff0c;EmmyLua v0.8.20 参考&#xff1a;https://blog.csdn.net/ShenHaoDeHao/article/details/140268354 有几个概念搞清楚就好理解了。一般开发中&#xff0c;我们编写的lua文件由宿主程序的来解析、执行&#xff1…

[Linux#65][TCP] 详解 延迟应答 | 捎带应答 | 流量控制 | 拥塞控制

目录 一、延迟应答 二、捎带应答 三. 流量控制 总结 四. 拥塞控制 1. 拥塞控制 2. 慢启动机制&#xff1a; 3.思考 4.拥塞避免算法 5. 快速恢复算法 一、延迟应答 1. 立即应答问题 接收数据的主机若立刻返回ACK应答&#xff0c;可能返回的窗口较小。例如&#xff1…

RabbitMQ 入门(三)SpringAMQP

一、Spring AMQP 简介 SpringAMQP是基于RabbitMQ封装的一套模板&#xff0c;并且还利用SpringBoot对其实现了自动装配&#xff0c;使用起来非常方便。 SpringAmqp的官方地址&#xff1a;https://spring.io/projects/spring-amqp SpringAMQP提供了三个功能&#xff1a; - 自动…

【嵌入式软件-STM32】STM32简介

目录 一、STM32定义 二、STM32用途 三、STM32特点 四、STM32 四个系列 五、了解ARM 六、芯片解释 七、片上资源 八、命名规则 九、系统结构 内核 Flash DMA 外设种类和分布 十、引脚定义 类型 名称 引脚 十一、启动配置 十二、STM32最小系统电路 STM32及供电 供电引脚 滤波电容…

FFmpeg的简单使用【Windows】--- 视频倒叙播放

实现功能 点击【选择文件】按钮可以选择视频&#xff0c;当点击【开始处理】按钮之后&#xff0c;会先将视频上传到服务器&#xff0c;然后开始进行视频倒叙播放的处理&#xff0c;当视频处理完毕之后会将输出的文件路径返回&#xff0c;同时在页面中将处理好的视频展示出来。…

【特赞-注册安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞…

低代码开发技术:驱动MES系统创新与制造业数字化转型的融合之路

低代码开发与生产管理MES系统的融合&#xff0c;是当今制造业数字化转型的一个重要趋势。以下是对这一融合现象的详细分析&#xff1a; 一、低代码开发的概念与特点 低代码开发是一种通过图形化界面和预构建模块来简化应用程序开发过程的方法。它允许开发人员使用拖放组件和最…

【视觉分割新SOTA|论文解读2】一种最先进的图像分割模型——Segment Anything Model (SAM)模型架构!

【视觉分割新SOTA|论文解读2】一种最先进的图像分割模型——Segment Anything Model (SAM)模型架构&#xff01; 【视觉分割新SOTA|论文解读2】一种最先进的图像分割模型——Segment Anything Model (SAM)模型架构&#xff01; 文章目录 【视觉分割新SOTA|论文解读2】一种最先…

Matlab详细学习教程 MATLAB使用教程与知识点总结

Matlab语言教程 章节目录 一、Matlab简介与基础操作 二、变量与数据类型 三、矩阵与数组操作 四、基本数学运算与函数 五、图形绘制与数据可视化 六、控制流与逻辑运算 七、脚本与函数编写 八、数据导入与导出 九、Matlab应用实例分析 一、Matlab简介与基础操作 重点内容知识…

DM8数据库用户和表空间管理

1 说明 DM8用户管理和表空间管理常用的管理命令&#xff0c;包括创建、修改和查看信息操作等。 2 用户管理 2.1 创建用户 创建一个用户lu9up&#xff0c;密码为"admin2024."&#xff0c;未制定表空间&#xff0c;使用默认的表空间main。 SQL> create user lu…

八大排序--08快速排序

现有 arr {4,8,9,2,7}数组&#xff0c;请用快速排序的方式实现从小到大排序&#xff1a; 方法&#xff1a; 1.定义待排序数组中的第一个值为基准数&#xff1b; 2.定义j游标&#xff0c;从后向前移动找到第一个比基准数小的值停下&#xff1b; 3.定义i游标&#xff0c;从后向…

黑马程序员-redis项目实践笔记1

目录 一、 基于Session实现登录 发送验证码 验证用户输入验证码 校验登录状态 Redis代替Session登录 发送验证码修改 验证用户输入验证码 登录拦截器的优化 二、 商铺查询缓存 缓存更新策略 数据库和缓存不一致解决方案 缓存更新策略的最佳实践方案 实现商铺缓…

大数据毕业设计选题推荐-音乐数据分析系统-音乐推荐系统-Python数据可视化-Hive-Hadoop-Spark

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

【C语言】使用结构体实现位段

文章目录 一、什么是位段二、位段的内存分配1.位段内存分配规则练习1练习2 三、位段的跨平台问题四、位段的应用五、位段使用的注意事项 一、什么是位段 在上一节中我们讲解了结构体&#xff0c;而位段的声明和结构是类似的&#xff0c;它们有两个不同之处&#xff0c;如下&…

Kubernetes(K8s)部署

主机名ip角色docker-harbor.revkarl.org172.25.254.250harbor仓库k8s-master172.25.254.100master&#xff0c;k8s集群控制节点k8s-node1172.25.254.10worker&#xff0c;k8s集群工作节点k8s-node2172.25.254.20worker&#xff0c;k8s集群工作节点 注意&#xff1a; 所有节点禁…

信息系统运维管理方案,运维建设文档,运维平台建设方案,软件硬件中间件运维方案,信息安全管理(原件word,PPT,excel)

建设方案目录&#xff1a; 1、智慧运维系统建设背景 2、智慧运维系统建设目标 3、智慧运维系统建设内容 4、智慧运维系统建设技术 5、智慧运维系统建设流程 6、智慧运维系统建设收益 企业对运维管理的需求&#xff1a; 1、提高运维效率&#xff1a;降低运维成本&#xff0c;提高…

vue从0开始的项目搭建(含环境配置)

一、环境准备 下载node.js 检查node.js版本 替换npm下载源 1.下载node.js: Node.js — 在任何地方运行 JavaScript (nodejs.org) 2.查看版本: windowsr输入cmd进入输入node -v命令查看版本号是否出现确认是否安装 2.替换npm下载源: npm config set registry https://reg…

【JS试题】对象键排序问题的神仙试题

前言 题目如下&#xff1a; const obj { a: 0 } obj[1] 0 obj[obj.a] obj.a const values Object.values(obj) obj[values[1]] obj.a console.log(obj);在此之前需要先了解 JS 对象键的排序问题&#xff0c;JS会对对象的属性进行处理&#xff0c;把所有 Number类型 和 数…

c++基础-去掉空格

#include <algorithm> #include <string> #include <cctype> // 用于std::isspace std::string removeSpaces(std::string str) {str.erase(std::remove_if(str.begin(), str.end(), ::isspace), str.end());return str; }int main() {string str &quo…