element-plus虚拟表格合并问题&解决方法

现象

element-plus的虚拟表格合并某列后滚动会导致合并的列显示异常,如下:


虚拟表格合并异常.gif

合并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
现在就可以得到我们所需要的合并效果啦


image.png

完整代码如下:

<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

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 使用flex和div布局实现表格的解决方法(类似天眼查那种) 因为业务需求,几个地方用到类似天眼查工商信息那种表格...
    mudssky阅读 2,554评论 0 0
  • # CSS样式规则overflow 使用HTML时,需要遵从一定的规范。CSS亦如此,要想熟练地使用CSS对网页进...
    低调迷人的反派角色阅读 1,035评论 0 1
  • 01|深入理解Content 01|content和替换元素 根据是否具有可替换的内容,我们也可以把元素分为替换元...
    井润阅读 234评论 0 0
  • 课程目标: 学会使用CSS选择器熟记CSS样式和外观属性熟练掌握CSS各种选择器熟练掌握CSS各种选择器熟练掌握C...
    前端陈陈陈阅读 288评论 0 1
  • 课程目标: 学会使用CSS选择器熟记CSS样式和外观属性熟练掌握CSS各种选择器熟练掌握CSS各种选择器熟练掌握C...
    从小就好看阅读 250评论 0 0