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,513評論 0 0
  • # CSS樣式規則overflow 使用HTML時,需要遵從一定的規范。CSS亦如此,要想熟練地使用CSS對網頁進...
    低調迷人的反派角色閱讀 1,022評論 0 1
  • 01|深入理解Content 01|content和替換元素 根據是否具有可替換的內容,我們也可以把元素分為替換元...
    井潤閱讀 216評論 0 0
  • 課程目標: 學會使用CSS選擇器熟記CSS樣式和外觀屬性熟練掌握CSS各種選擇器熟練掌握CSS各種選擇器熟練掌握C...
    前端陳陳陳閱讀 285評論 0 1
  • 課程目標: 學會使用CSS選擇器熟記CSS樣式和外觀屬性熟練掌握CSS各種選擇器熟練掌握CSS各種選擇器熟練掌握C...
    從小就好看閱讀 246評論 0 0