Preface {#preface}
表格行合并是一个非常常用的功能,每次都手写十分的麻烦,故做一下简单的封装。
效果图 {#效果图}
使用步骤 {#使用步骤}
- 在获取到表格数据之后,调用
generateTableDataSpanMap()
函数,生成关于表格行合并的Map
。- 第一个参数是表格的数组数据。
- 第二个参数是要合并的对象属性,可以配置成包含子数组的形式,子数组内的属性必须要子数组之外的属性完全相同才会合并。
- 返回值是
Map
对象,键的组成方式是行序号-列属性
;如第 1 行,name
属性列即为:1-name
,对应的值就是要合并的行列配置数组,如:[1, 1]
// 生成行合并信息 map,合并 age、sex、score1、score2 列,其中 score1,score2 要在 age和sex 都相等的前提下才会计算是否要合并
const tableDataSpanMap = ref(null)
tableDataSpanMap.value = generateTableDataSpanMap(tableData.value, ['age', 'sex', ['score1', 'score2']])
- 在 Element Plus 表格合并方法
:span-method="handleSpanMethod"
中从生成的Map
对象中取数据即可
/**
* 表格行合并方法
*
* @param rowIndex 行号,从 0 开始
* @param column 列信息,column.property 为列名
* @returns 合并信息
*/
const handleSpanMethod = ({rowIndex, column}) => {
return tableDataSpanMap.value.get(`${rowIndex}-${column.property}`)
}
封装方法 {#封装方法}
完整的封装方法如下:
/** * 生成表格行合并 map * * @param tableData 表格数据 * @param spanColumnArr 要合并的列属性数组,数组中可以包含子数组,子数组中的属性需要父数组中的所有属性都相同才会合并 * (也就是分组的意思,只会进行组内合并) * @return map 键:行号-列属性名(如:第一行 username 属性名为 1-username,第五行 password 属性名为 5-password) * 值:数组:[行合并个数, 列合并个数] */ export const generateTableDataSpanMap = (tableData, spanColumnArr) => { const spanColumnMap = new Map() /* 转换存放要合并列名的数组为map,键为当前要合并的列名,值为上一层列名数组 如: ['a', 'b', ['c', 'd', ['e']], 'f'] => { 'a': [], 'b': [], 'f': [], 'c': ['a', 'b', 'f'], 'd': ['a', 'b', 'f'], 'e': ['a', 'b', 'f', 'c', 'd'] } 列 c 可以合并的前提是:列 a、b、f 值相同且列 c 本身的值也要相同 列 e 可以合并的前提是:列 a、b、f、c、d 值相同且列 e 本身的值也要相同 */ const convertSpanColumnArr = (outSpanColumnArr, innerSpanColumnArr) => { const tempOutSpanColumnArr = [] const tempInnerSpanColumnArr = [] for (const item of innerSpanColumnArr) { if (typeof item === 'string') { // 外层列 tempOutSpanColumnArr.push(item) } else if (item instanceof Array) { // 内层列 tempInnerSpanColumnArr.push(...item) } }
tempOutSpanColumnArr.forEach(item => spanColumnMap.set(item, outSpanColumnArr)) if (tempInnerSpanColumnArr.length > 0) { convertSpanColumnArr(outSpanColumnArr.concat(tempOutSpanColumnArr), tempInnerSpanColumnArr) } } // 转换存放要合并列名的数组为map convertSpanColumnArr([], spanColumnArr) // 存放表格合并信息 map const tableSpanMap = new Map() for (const [rowIndex, row] of tableData.entries()) { // 遍历每一行 for (const columnName in row) { // 遍历每一行中对象的每一个属性 if (!spanColumnMap.has(columnName)) { // 当前列属性不需要合并 tableSpanMap.set(`${rowIndex}-${columnName}`, [1, 1]) continue } if (rowIndex > 0 && tableData[rowIndex][columnName] === tableData[rowIndex - 1][columnName]) { // 当前单元格值与上一行对应的单元格值相同,则此单元格不显示 let spanBool = true for (const item of spanColumnMap.get(columnName)) { if (tableData[rowIndex][item] !== tableData[rowIndex - 1][item]) { // 父合并属性的值不相同也不合并 spanBool = false } } if (spanBool) { tableSpanMap.set(`${rowIndex}-${columnName}`, [0, 0]) continue } } let rowSpan = 1 outFor: for (let index = rowIndex; index < tableData.length - 1; index++) { if (tableData[rowIndex][columnName] !== tableData[index + 1][columnName]) { // 当前单元格值和下一行对应的单元格值不相同 tableSpanMap.set(`${rowIndex}-${columnName}`, [rowSpan, 1]) break } for (const item of spanColumnMap.get(columnName)) { if (tableData[rowIndex][item] !== tableData[index + 1][item]) { // 父合并属性的值不相同也不合并 tableSpanMap.set(`${rowIndex}-${columnName}`, [rowSpan, 1]) // 结束外层循环 break outFor } } rowSpan++ } if (!tableSpanMap.has(`${rowIndex}-${columnName}`)) { // 上面的 for 循环中没有设置值才在这里设置 tableSpanMap.set(`${rowIndex}-${columnName}`, [rowSpan, 1]) } } } return tableSpanMap
}
完整案例 {#完整案例}
实现效果图的完整案例如下:
<template> <el-table :data="tableData" :span-method="handleSpanMethod" border> <el-table-column label="姓名" prop="name"/> <el-table-column label="年龄" prop="age"/> <el-table-column label="性别" prop="sex"/> <el-table-column label="分数1" prop="score1"/> <el-table-column label="分数2" prop="score2"/> </el-table> </template>
<script setup> import {ref} from 'vue' import {generateTableDataSpanMap} from '@/tool-box/element-plus-table'
// 表格数据 const tableData = ref([ { name: '张三', age: 18, sex: '男', score1: 99, score2: 88 }, { name: '李四', age: 18, sex: '男', score1: 100, score2: 88 }, { name: '王五', age: 20, sex: '女', score1: 104, score2: 62 }, { name: '赵六', age: 18, sex: '女', score1: 104, score2: 62 }, { name: '钱七', age: 18, sex: '男', score1: 99, score2: 88 }, ])
// 生成行合并信息 map,合并 age、sex、score1、score2 列,其中 score1,score2 要在 age和sex 都相等的前提下才会计算是否要合并 const tableDataSpanMap = ref(null) tableDataSpanMap.value = generateTableDataSpanMap(tableData.value, ['age', 'sex', ['score1', 'score2']])
/**
- 表格行合并方法
- @param rowIndex 行号,从 0 开始
- @param column 列信息,column.property 为列名
- @returns 合并信息 */ const handleSpanMethod = ({rowIndex, column}) => { return tableDataSpanMap.value.get(
${rowIndex}-${column.property}
) } </script><style scoped lang="scss"> .el-table--border, .el-table--group { box-sizing: border-box; border: 1px solid black !important; }
:deep(.el-table__cell) { border-color: black !important; } </style>