antd Table通过onCell属性实现表格自定义合并
前段时间迭代不是很忙,抽空写了个表格合并的函数。这边对此进行简单记录一下。
主要实现的功能如下:
(ps:该函数需要搭配antd Table使用)
1.表格数据处理:按照某列处理为相同数据展示在一起
2.单行列的表格数据合并,对于某列进行指定行范围的列合并, 或对于某行进行指定列范围的行合并
3.整体表格的数据合并,对于指定的两个区域,分别进行行合并和列合并
原理说明如下:
通过 colSpan,rowSpan 进行行列合并。 需要合并的列需添加onCell属性,rowSpan命名格式为:record['rowSpan' + 列key], colSpan命名格式为:record['colSpan' + 列key] 给Table传入columns时,可通过如下columnsTitle给columns添加onCell属性(<Table columns={this.columnsTitle(columns)} />) columnsTitle = (column = []) => { // 给columns根据key动态增加onCell属性 return column.map(item => { item.onCell = record => ({ rowSpan: record['rowSpan' + item.key], colSpan: record['colSpan' + item.key] }); return item; }); };
一些传参说明:
| 属性项 | 说明 | type | 可选 | required | default | 详细说明 | | :---------- | :-------------------------------- | :----- | :--- | :------- | :------ | :-------------------------------------------------- | | data | 表格数据 Table-dataSource | Array | - | true | - | | columns | 表格列配置 Table-columns | Array | - | true | - | | mergeMethod | 表示操作类型 行/列合并 或处理数据 | String | - | true | - | 'column' / 'row' / 'handleData' / 'handleMultiData' | | markNum | 指定合并的行列 | Number | - | false | - | 单数据合并及处理数据时必填(从0初始) | | mergeScope | 合并的范围,指定行列范围 | Array | - | - false | - | 单数据及多数据合并时必填 | | rowRefer | 正在合并的行数 | Number | - | - false | - | 行合并 (mergeMethod = 'row')合并时必填 |
对于mergeScope的详细说明:
- 单列('column'): 表示按照从 mergeScope[0]行至 mergeScope[1]行的列合并 例如:[2,5] - 单行('row'): 表示按照从 mergeScope[0]列至 mergeScope[1]列的行合并 例如: [2,5] - 多行列合并('handleMultiData'): mergeScope 内元素也是数组形式[[], []] 例如: mergeScope: [[1,2,1,4], [3,5,1,5]] - - 四个值分别表示起始列,结束列,起始行,结束行 - - mergeScope[0]表示列合并范围:第一列至第二列进行列合并(从第一行到第四行的列合并) - - mergeScope[1]表示行合并范围:第一行至第五行进行行合并(从第三列到第五列的行合并); - - 当只需要进行多列合并,第二个参数可传[], eg: [[1,2,1,4], []]; - - 当只需要进行多行合并,第一个参数可传[], eg: [[], [3,5,1,5]]
对于markNum的说明:
markNum: - 单列及处理数据时传需要处理的列数 - 单行合并时传合并范围的起始列,eg:mergeScope: [2,5] -> markNum: 2
传参参考:
mergeMethod合并类型: 'column' 'row' 'handleData' 'handleMultiData': 1.'handleData'- 按照某列进行数据处理 - rowSpanData({data, columns, mergeMethod, markNum}) 2.'column'- 某列进行指定行范围数据合并 - rowSpanData({data, columns, mergeMethod, markNum, mergeScope}) 3.'row' - 某行进行指定列范围数据合并 - rowSpanData({data, columns, mergeMethod, markNum, mergeScope, rowRefer}) 4.'handleMultiData' - 同时进行多行列数据的合并 - rowSpanData({data, columns, mergeMethod, mergeScope})
---------------------------------------------------------------------------手动分割线---------------------------------------------------------------------------------------------------------------------
代码实现如下:
只要引入rowSpanData函数即可进行使用
// 对于表格数据进行计算处理 // 列表合并相同列 // 表头只支持列合并,使用 column 里的 colSpan 进行设置。 // 表格支持行/列合并,使用 render 里的单元格属性 colSpan 或者 rowSpan 设值为 0 时,设置的表格不会渲染。 // 表格的合并操作主要就是通过设置colSpan属性&rowSpan属性 const mergeRows = (data: any[], colName: string, mergeScope: any[]): any[] => { const startMergeSite = mergeScope[0]; const endMergeSite = mergeScope[1]; let idx = 0; let handledata = data.slice(startMergeSite, endMergeSite + 1); try { const filterData = handledata.find( // 判断是否已经进行过该colName的行列合并,不接受某行列进行同时行列合并 item => item['colSpan' + colName] || item['colSpan' + colName] === 0 || item['rowSpan' + colName] || item['rowSpan' + colName] === 0 ); if (filterData) { handledata = data.slice(startMergeSite, endMergeSite + 1); throw Error(''); } else { data[startMergeSite]['rowSpan' + colName] = 1; handledata = data.slice(startMergeSite + 1, endMergeSite + 1); handledata = handledata.reduce( (mergedRows, item, index) => { if (item[colName] === mergedRows[idx][colName]) { mergedRows[idx]['rowSpan' + colName]++; item['colSpan' + colName] = 0; } else { item['rowSpan' + colName] = 1; idx = index + 1; } return [...mergedRows, item]; }, [data[startMergeSite]] ); } } catch (e) { console.log('该行列已被合并过,不支持列合并'); } // 进行原始数组的拼接 data.splice(startMergeSite, endMergeSite - startMergeSite + 1, ...handledata); return data; }; // 行合并 const mergeCells = ( data: any[], colName: string, rowRefer: number, mergeScope: number[], columnList: string[] ): any[] => { const startMergeSite = mergeScope[0]; const endMergeSite = mergeScope[1]; if (Array.isArray(data) && data.length === 0) return []; const dataArr = [...data[rowRefer]]; // 需要处理的该行数据 let handleData = dataArr; try { const handledata = dataArr.find( // 异常行列合并捕获 item => item['colSpan' + colName] || item['colSpan' + colName] === 0 || item['rowSpan' + colName] || item['rowSpan' + colName] === 0 ); if (handledata) { throw Error(''); } else { handleData = dataArr.reduce((result, item) => { let idx = 1; const handleColumnList = columnList.slice(startMergeSite + 1, endMergeSite + 1); Object.entries(item).forEach(item1 => { if (item1[0] !== colName && item1[1] === item[colName] && handleColumnList.includes(item1[0])) { item['rowSpan' + item1[0]] = 0; idx++; item['colSpan' + colName] = idx; // 需要占据的表格个数 } }); return [...result, ...item]; }, []); } } catch (e) { console.log('该行列已被合并过,不支持行合并'); } data.splice(rowRefer, 1, ...handleData); return data; }; // 数据处理 const classifyRows = (data: any[], classifyRule: string): any[] => { // 对于表格数据根据key进行排序处理 const keys = [classifyRule]; const 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; }; interface TableParams { data?: any[]; mergeMethod?: string; markNum?: string | number; mergeScope?: any[]; columns?: any[]; rowRefer?: number; } const rowSpanData = ({ data = [], columns, mergeMethod = 'column', markNum, mergeScope, rowRefer }: TableParams = {}): any[] => { // 列表合并相同行 rowSpanData: (data, colName) // 列表合并相同行 rowSpanData: (data, colName) ————————》升级一下:row 一行某几列合并、col 一列 某几行合并、columns 表头多级处理 (只是对数据处理) let result; // 获取表格columns数组的key集合 const columnList = Array.isArray(columns) && columns.map(item => item.key); const colName = columnList && columnList[markNum]; if (mergeMethod === 'column') { result = mergeRows(data, colName, mergeScope); } else if (mergeMethod === 'row') { result = mergeCells(data, colName, rowRefer, mergeScope, columnList); } else if (mergeMethod === 'handleData') { result = classifyRows(data, colName); } else if (mergeMethod === 'handleMultiData') { // 表格整体处理 const colMergeScope = mergeScope[0]; const rowMergeScope = mergeScope[1]; let handleMergeData = data; // 先进行列合并 // 为【】时不进行列处理 if (Array.isArray(colMergeScope) && colMergeScope.length) { const startColMergeCol = colMergeScope[0]; const endColMergeCol = colMergeScope[1]; const startRowMergeCol = colMergeScope[2]; const endRowMergeCol = colMergeScope[3]; for (let i = startColMergeCol; i <= endColMergeCol; i++) { handleMergeData = mergeRows(handleMergeData, columnList[i], [startRowMergeCol, endRowMergeCol]); } } // 再进行行合并 // 为【】时不进行行处理 if (Array.isArray(rowMergeScope) && rowMergeScope.length) { const startColMergeRow = rowMergeScope[0]; const endColMergeRow = rowMergeScope[1]; const startRowMergeRow = rowMergeScope[2]; const endRowMergeRow = rowMergeScope[3]; for (let i = startRowMergeRow; i <= endRowMergeRow; i++) { handleMergeData = mergeCells( handleMergeData, columnList[startColMergeRow], i, [startColMergeRow, endColMergeRow], columnList ); } } result = handleMergeData; } return result; }; export default rowSpanData;
实现的效果如下:
个人总结:
基本大致如上,虽然只是一个简单的函数完成,却也花了一点时间,菜是原罪。
在不忙的时候开始写,刚准备写的时候就开始版本迭代,陆陆续续的开发,到开完完成之后,写完给同事看看会不会用,又被同事伙伴找出了几个需要优化的点并且给了我很好的意见,对于参数的传递需要贴切用户而不是便于开发者,对于错误的异常抛出避免表格错乱等等问题。改进了一波,终于写完了,完结撒花。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构