现象
element-plus的虚拟表格合并某列后滚动会导致合并的列显示异常,如下:
合并8列时,当滚动到合并的第一行消失,会失去合并效果
<template>
<el-table-v2 fixed :columns="columns" :data="data" :width="700" :height="400" >
<template #row="props">
<Row v-bind="props" />
</template>
</el-table-v2>
</template>
<script lang="ts" setup>
import { ref, h, reactive, cloneVNode } from 'vue'
const generateData = (
columns,
length = 200,
) =>
Array.from({ length }).map((_, index) => {
return {
id: index,
name: `name${index}`,
other: `other${index}`,
parentId: null
}
})
const columns = [
{
key: 'id',
dataKey: 'id',
title: 'id',
width: 200,
maxWidth: '400',
},
{
key: 'name',
dataKey: 'name',
title: 'name',
width: 200,
},
{
key: 'other',
dataKey: 'other',
title: 'other',
width: 200,
},
]
const data = generateData(columns, 100)
const rowSpanIndex = 0
//根据rowIndex来计算rowSpanIndex值
columns[rowSpanIndex].rowSpan = ({ rowIndex }) =>
rowIndex % 2 === 0 && rowIndex <= data.length - 2 ? 2 : 1
const Row = ({ rowData, rowIndex, cells, columns }) => {
const rowSpan = columns[rowSpanIndex].rowSpan({ rowData, rowIndex })
if (rowSpan > 1) {
const cell = cells[rowSpanIndex]
const style = {
...cell.props.style,
backgroundColor: 'var(--el-color-primary-light-3)',
height: `${rowSpan * 50 - 1}px`, //重新计算高度
alignSelf: 'flex-start', //设置自身的对齐方式,不能省略
zIndex: 1,
}
cells[rowSpanIndex] = cloneVNode(cell, { style })
}
return cells
}
</script>
原因
首先我们需要知道虚拟列表的原理:如果总共有1w条数据需要渲染到表格上,一次性渲染会导致渲染时间过长,所以我们只渲染当前需要看到的s到s+k行(s为起始值,会随着滚动的高度改变,k为实际渲染的条数,由表格的高度决定),所以我们实际上只渲染了k行。
而element-plus列合并的原理为:假设要合并8行,将第1行的高度*8,第2-7不变,将第1行覆盖着2-7行。
当第1行超出了渲染范围时(不在s到s+k之间),就不会再渲染了,而当第一行消失就会导致2-7行不会被覆盖,就出现了上述现象
解决办法
1. 增加cache值
<template>
<el-table-v2 :cache="100" >
//...
</el-table-v2>
</template>
当你的合并的列不是动态的值且合并的行数没有很多时,可以通过增加cache值来增加实际渲染行数
这样做的缺点是:
● 实际渲染太多会导致渲染时间变长
● 并不能完全阻止上述异常的发生,只能减少它发生的次数(如果合并的第一行刚好就在实际渲染的最后一行呢?)
2. 放弃官方的合并方法,通过改变单元格的border来做到模拟合并
官方合并方法为通过改变cell的height来进行覆盖,而我们通过改变border来实现合并。
改变border的意思是:比如我要合并n行,那么只用将第1至n-1行的border-bottom去掉或者将第2至n行的border-top去掉即可实现合并的效果
这里以去掉border-top合并8行为例,我们要找出不需要去掉border-top的行,那么剩下的行就必须有border-top
● 首先得去掉每行的border样式(包括header)
● 将需要合并的列中,不需要合并和需要合并的的第一行添加border-top(第1行不用添加border-top,因为会和header的border重叠)
● 由于需要合并的列值都是一样的,这时我们只需要将最上方的值显示,其他值设为空即可(如果要合并到中间,只需将中间值显示,其他值设空)
● 所有cell加上border-right,border添加border-bottom
现在就可以得到我们所需要的合并效果啦
完整代码如下:
<template>
<el-table-v2
fixed
:columns="columns"
:data="data"
:width="700"
:height="400"
row-class="span-row"
header-class="span-header"
>
<template #row="props">
<Row v-bind="props"/>
</template>
</el-table-v2>
</template>
<script lang="ts" setup>
import { ref, h, reactive, cloneVNode } from 'vue'
const generateData = (
columns,
length = 200,
) =>
Array.from({ length }).map((_, index) => {
return {
id: index,
name: `name${index}`,
other: `other${index}`,
parentId: null
}
})
const columns = [
{
key: 'id',
dataKey: 'id',
title: 'id',
width: 200,
maxWidth: '400',
},
{
key: 'name',
dataKey: 'name',
title: 'name',
width: 200,
},
{
key: 'other',
dataKey: 'other',
title: 'other',
width: 200,
},
]
const data = generateData(columns, 100)
//让name列合并,每4行合并一次
const spanKeyIndex = 1
//找到需要border-top的那列
const getSpanMap = (data) => {
const map = {}
let i = 0;
while(i < data.length) {
if(i % 4 === 0) {
map[i] = true
}
else {
data[i]['name'] = ""
}
i+= 1;
}
return map
}
const spanKeyMap = getSpanMap(data)
console.log(spanKeyMap)
const Row = ({ rowData, rowIndex, cells, columns }) => {
console.log(cells)
for(let i = 0; i < cells.length; i++) {
const cell = cells[i];
const style = {
...cell.props.style,
borderRight: "1px solid #ebeef5"
}
//第0行如果有border-top会和header的border重叠
if((i === spanKeyIndex && !spanKeyMap[rowIndex]) || rowIndex === 0) {
//处理需要合并的列
style.borderTop = "0px"
} else {
style.borderTop = "1px solid #ebeef5"
}
cells[i] = cloneVNode(cell, { style })
}
return cells
}
</script>
<style>
.span-row, .span-header {
border-bottom: 0px
}
</style>
<style scoped>
.el-table-v2 :deep(.el-table-v2__header-cell){
border-right: 1px solid #ebeef5;
border-bottom: 1px solid #ebeef5;
}
</style>
目前已发现的缺点:
● 合并的区域换行问题:如果合并了1-5行,内容显示在第3行,如果第三行内容过多换行就只能在第三行的范围换行
所以上面的这种方法只针对于内容比较少的表格,对于内容需要换行的表格我们可以用下面这种方法
3. 向上对齐
当内容向上对齐时,我们可以使用element官方的合并方法,只需要加大cache值,但还是会有合并过多行时,滚动到下面之后第一行不存在导致合并失效的问题,这时我们可以借鉴上一种方法:通过样式隐藏所有的border,只有最后一行显示border