遍历表格数据,先横向合并相邻相同的单元格,再纵向合并。允许指定要合并的列,并根据自定义条件进行合并。若未指定合并列,可通过参数控制是否默认合并所有列;若未提供合并条件,则默认相等时合并。
:span-method="toMergeColumn"
/* 合并行或列的计算方法 */
toMergeColumn({ rowIndex, columnIndex }) {
const propArr = this.fieldList.map(item => item.field)
const mergeColumns = propArr.filter(item => item !== 'aaaa7') // 需要合并的列,排除备注列
if (columnIndex === this.fieldList.length) return [1, 1] // 操作列不合并
const spanMap = this.setSpanMap(this.formList, propArr, mergeColumns)
return spanMap[rowIndex * this.fieldList.length + columnIndex]
},
/** 合并列单元格
* @description 遍历表格数据,先横向合并相邻相同的单元格,再纵向合并。允许指定要合并的列,并根据自定义条件进行合并。若未指定合并列,可通过参数控制是否默认合并所有列;若未提供合并条件,则默认相等时合并
* @description 每个单元在 spanMap 中的索引:rowIndex * 列数 + columnIndex。每一次循环,将相邻相同值的单元格在 spanMap 中的索引放进一个数组
* @description 若第一行的内容为 [1, 2, 2] --> [[0], [1, 2]]。若第二行的内容为 ['k', 'k', 'x'] --> [[3, 4], [5]]
* @description 最终第二行的 spanMap: 第一个 k, rowspan = 2, colspan = 1; 第二个 k 被合并, rowspan = 0, colspan = 0; x, rowspan = 1, colspan = 1
* @param {Array} tableData - 表格数据,每行为一个对象,键为列名
* @param {Array} props - 表头字段数组,对应 tableData 的 key 值(即prop字段)
* @param {Array|null} mergeColumns - 需要进行合并的列(列名数组),若为 null 则根据 mergeAllColumns 参数决定是否合并所有列
* @param {boolean} mergeAllColumns - 是否默认合并所有列,仅在 mergeColumns 为 null 时生效,默认为 false
* @param {Function} [mergeCondition = (a, b) => a && b && a === b] - 自定义合并条件,默认只有非空且相等的单元格才会被合并
* @returns {Object} spanMap - 记录每个单元格的 rowspan 和 colspan
*/
setSpanMap(tableData, props, mergeColumns = null, mergeAllColumns = false, mergeCondition = (a, b) => a && b && a === b) {
const rowLength = tableData.length // 表格的行数
const colLength = props.length // 表格的列数
const spanMap = {} // 记录单元格合并信息
// 如果 mergeColumns 为空,且 mergeAllColumns 为 true,则合并所有列
const columnsToMerge = mergeColumns === null ? (mergeAllColumns ? props : []) : mergeColumns
/** 处理相邻符合合并条件的单元格,并返回合并后的索引数组
* @description 输入:[{ index: 3, value: 'k'}, { index: 4, value: 'k' }, { index: 5, value: 'x' }]
* @description 输出:[[3, 4], [5]]
* @param {Array} arr - 包含 { index, value } 的对象数组
* @returns {Array} 合并后的索引数组,例如 [[0], [1, 2]]
*/
function groupAdjacent(arr) {
if (arr.length === 0) return []
const result = []
let temp = [arr[0].index]
for (let i = 1; i < arr.length; i++) {
if (mergeCondition(arr[i - 1].value, arr[i].value)) {
temp.push(arr[i].index)
} else {
result.push(temp)
temp = [arr[i].index]
}
}
result.push(temp)
return result
}
// 横向合并:先按行遍历,收集横向合并的单元格
for (let row = 0; row < rowLength; row++) {
const rowData = tableData[row]
const rowIndexAndValue = props.map((prop, col) => ({
index: row * colLength + col,
value: rowData[prop]
})).filter(({ index }) => columnsToMerge.includes(props[index % colLength]))
if (rowIndexAndValue.length > 0) {
groupAdjacent(rowIndexAndValue).forEach((group) => {
if (group.length === 1) {
spanMap[group[0]] = { rowspan: 1, colspan: 1 }
} else {
spanMap[group[0]] = { rowspan: 1, colspan: group.length }
group.slice(1).forEach((index) => {
spanMap[index] = { rowspan: 0, colspan: 0 }
})
}
})
}
}
// 纵向合并:再按列遍历,收集纵向合并的单元格
columnsToMerge.forEach((col) => {
const colIndex = props.indexOf(col)
if (colIndex === -1) return
const colIndexAndValue = tableData.map((row, rowIndex) => ({
index: rowIndex * colLength + colIndex,
value: row[col]
}))
if (colIndexAndValue.length > 0) {
groupAdjacent(colIndexAndValue).forEach((group) => {
if (group.length > 1) {
spanMap[group[0]].rowspan = group.length
group.slice(1).forEach((index) => {
spanMap[index] = { rowspan: 0, colspan: 0 }
})
}
})
}
})
// 返回合并后的索引数组
return spanMap
},
参考文章: