- 使用的antd vue 版本(v4.26)
- 对于开发中的业务报表需求,我们经常会遇到,合并表格单元格的场景,通过这些场景总结出一些可用的方法。
- 主要涉及表格数据的行,列,行列,合并场景。
分析antd vue table 文档的中的操作方法:
- 表头只支持列合并,使用 column 里的 colSpan 进行设置。
- 表格支持行/列合并,使用 render 里的单元格属性 colSpan 或者 rowSpan 设置为 0 时,设置的表格不会渲染。
- 通过column配置项中的customCell|Function(record, rowIndex, column)进行映射数据进行合并。
示例:
使用行合并方法对指定的列进行合并(columns可能是拍平数组,可能是树数组,最后进行递归法兼容两种情况)
js
/**
* @desc 动态指定那一列 进行合并行
* @param {Array} dataSource 表格数据
* @param {Array} columns 表格列配置
* @param {Array} mergeKeys 需要合并的列字段数组
*/
export const mergeRows = (dataSource, columns, mergeKeys) => {
for (let i = 0; i < mergeKeys.length; i++) {
const mergeInfo = {};
const key = mergeKeys[i];
// 遍历数据源,计算合并信息
for (let j = 0; j < dataSource.length; j++) {
const currentValue = dataSource[j][key];
if (j === 0) {
mergeInfo[j] = {
rowSpan: 1,
value: currentValue,
};
} else {
const prevValue = dataSource[j - 1][key];
if (currentValue === prevValue) {
mergeInfo[j] = {
rowSpan: 0,
value: currentValue,
};
// 向前查找第一个 并更新连续相同值的最早单元格的rowSpan
let start = j - 1;
while (start >= 0 && mergeInfo[start].value === currentValue) {
start--;
}
const firstIndex = start + 1; // 连续值的起始位置
mergeInfo[firstIndex].rowSpan++;
} else {
mergeInfo[j] = {
rowSpan: 1,
value: currentValue,
};
}
}
}
// 递归寻找 columns中的key === mergeKeys[i] 列,添加合并信息
const findMergeCols = (cols, targetKey) => {
for (let k = 0; k < cols.length; k++) {
if (cols[k].key === targetKey) {
// 重写 customCell 方法
cols[k].customCell = (record, index) => {
const { rowSpan } = mergeInfo[index];
return {
rowSpan,
};
};
return;
}
if (cols[k]?.children && cols[k]?.children.length) {
findMergeCols(cols[k].children, targetKey);
}
}
};
findMergeCols(columns, mergeKeys[i]);
}
return columns;
};
js
在表格数据请求完进行方法调用对类别('kinds')进行行合并
state.tableData = res?.data
mergeRows(state.tableData,columns,['kinds'])
- 行列都合并
- 首先我们需要对指标名称下构造多个相同列数据
- 其次我们对后端数据进行约定多列时,进行返回
[{target:['vave','vave-1]},{target:['vave','vave-2]},['vave','vave-2]}]
- 通过customCell的方法 我们进行构造合并行和列的二维数组,再进行数据映射合并
- html渲染通过插槽索引 映射数值
js
// 已知指标名称是行列合并
unknownMergeCols(state.tableData, columns2, ['target']);
/**
* @desc 不常规的处理列 合并
*/
export const unknownMergeCols = (dataSource, columns, targetArr) => {
for (let i = 0; i < targetArr.length; i++) {
const targetKey = targetArr[i];
// 计算最大数组长度
const max_length = dataSource.reduce((max, item) => {
const value = item[targetKey];
return Math.max(max, Array.isArray(value) ? value.length : 0);
}, 0);
// 递归查找并插入列
const findMergeCols = (cols) => {
for (let k = 0; k < cols.length; k++) {
const col = cols[k];
if (col.key === targetKey) {
const arr = [];
// selfIndex 增加列索引便于html渲染定位 colSpan合并表头
for (let m = 0; m < max_length; m++) {
arr.push({ ...col, selfIndex: m, colSpan: m === 0 ? max_length : 0 });
}
// 替换目标列为 所有副本arr 跳过已插入的副本,避免重复处理
cols.splice(k, 1, ...arr);
break;
}
// 递归处理子列
if (col.children?.length) {
findMergeCols(col.children);
}
}
};
findMergeCols(columns);
}
// return columns;
};
构造合并二维数组
js
/**
* @desc 构造合并信息的二维数组
*/
export const generateMergeInfo = (dataSource, columns) => {
const mergeInfo = [];
// 初始化 行合并信息
for (let j = 0; j < columns.length; j++) {
mergeInfo[j] = [];
for (let i = 0; i < dataSource.length; i++) {
if (i === 0) {
// 第一个位置 处理 初始化为 1
mergeInfo[j][i] = { rowSpan: 1 };
} else {
// 后续其他位置处理 和上一个不同的值 初始化为 1,和上一个相同的值 初始化为 0
const prevValue = dataSource[i - 1][columns[j].key][j];
const currentValue = dataSource[i][columns[j].key][j];
if (currentValue === prevValue) {
mergeInfo[j][i] = { rowSpan: 0 };
// 向前查找第一个 并更新连续相同值的最早单元格的rowSpan
let start = i - 1;
while (start >= 0 && dataSource[start][columns[j].key][j] === currentValue) {
start--;
}
const firstIndex = start + 1; // 连续值的起始位置
mergeInfo[j][firstIndex].rowSpan++;
} else {
mergeInfo[j][i] = { rowSpan: 1 };
}
}
}
}
console.log(mergeInfo, '....mergeInfo');
// 初始化 列合并信息
for (let i = 0; i < dataSource.length; i++) {
for (let j = 0; j < columns.length; j++) {
if (j === 0) {
// 初始化第一个位置 初始化为 1
mergeInfo[j][i].colSpan = 1;
} else {
// 后续其他位置处理 和上一个不同的值 初始化为 1,和上一个相同的值 初始化为 0
const prevValue = dataSource[i][columns[j - 1].key][j - 1];
const currentValue = dataSource[i][columns[j].key][j];
if (prevValue === currentValue) {
// 修正为当前单元格
mergeInfo[j][i].colSpan = 0;
// 向前查找第一个 并更新连续相同值的最早单元格的colSpan
let start = j - 1;
while (start >= 0 && dataSource[i][columns[start].key][start] === currentValue) {
start--;
}
const firstIndex = start + 1; // 连续值的起始位置
// 修正为正确的索引
mergeInfo[firstIndex][i].colSpan++;
} else {
mergeInfo[j][i].colSpan = 1;
}
}
}
}
console.log(mergeInfo, '....mergeInfo2');
return mergeInfo;
};
// 打印合并二维数组的内容
[[
{rowSpan: 1, colSpan: 2}, {rowSpan: 3, colSpan: 1},
{rowSpan: 0, colSpan: 1}, {rowSpan: 0, colSpan: 1},
{rowSpan: 1, colSpan: 2}, {rowSpan: 1, colSpan: 2}],
[
{rowSpan: 1, colSpan: 0}, {rowSpan: 1, colSpan: 1},
{rowSpan: 1, colSpan: 1}, {rowSpan: 1, colSpan: 1},
{rowSpan: 1, colSpan: 0}, {rowSpan: 1, colSpan: 0}
]]
重写方法
js
// 对构造复制的列columns 进行增加customCell方法
arr.forEach((item, colIndex) => {
item.customCell = (record, rowIndex) => {
const { rowSpan, colSpan } = mergeInfo[colIndex][rowIndex];
return {
rowSpan,
colSpan,
};
};
});
// 表格渲染 数据
<a-table
:columns="columns"
:data-source="tableData"
bordered
size="small"
:pagination="false"
:loading="loading"
:scroll="{ y: 540 }"
>
<template #bodyCell="{ column, record, index }">
<template v-if="column.key === 'target'">
{{ record.target[column.selfIndex] }}
</template>
</template>
</a-table>
总结:遇到问题不要慌,请看文档思考。 "遇事不决,可问春风。 --剑来"