現象
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