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>