Virtualized Table 虚拟化表格 el-table-v2 表头分组 多级表头的简单示例

发布于:2025-05-16 ⋅ 阅读:(15) ⋅ 点赞:(0)

注意添加这个属性,会影响到有多少个层级的表头: :header-height=“[50, 40]”,即后面的columnIndex

如果有fix的列CustomizedHeader会被调用多次,如果有多个层级的表头,也会被调用多次, 实际被调用次数是(fix数+ 1 * 表头层级数量)

以下代码均删除了JSX

TS版本代码

<template>
  <div  style="width: 100%;height: calc(100vh - 84px)">
    <el-auto-resizer>
      <template #default="{ height, width }">
        <el-table-v2
            fixed
            :columns="columns"
            :data="data"
            :sort-by="sortBy"
            :header-height="[50, 40]"
            :header-class="headerClass"
            @column-sort="onSort"
            :width="width"
            :height="height"
        >
          <template #header="props">
            <customized-header v-bind="props"></customized-header>
          </template>
        </el-table-v2>
      </template>
    </el-auto-resizer>
  </div>
</template>

<script lang="ts" setup>
import {h, ref} from 'vue'
import {TableV2FixedDir, TableV2Placeholder, TableV2SortOrder} from 'element-plus'
import type {
  HeaderClassNameGetter,
  TableV2CustomizedHeaderSlotParam,
} from 'element-plus'
import type {SortBy} from 'element-plus'



// 生成带二级表头的 columns
function generateColumns(length = 10, prefix = 'column-', props?: any) {
  return Array.from({length}).map((_, columnIndex) => ({
    ...props,
    key: `${prefix}${columnIndex}`,
    dataKey: `${prefix}${columnIndex}`,
    title: `Column ${columnIndex}`,
    width: 150,
  }))
}

function generateData(
    columns: ReturnType<typeof generateColumns>,
    length = 200,
    prefix = 'row-'
) {
  return Array.from({ length }).map((_, rowIndex) => {
    return columns.reduce(
        (rowData, column, columnIndex) => {
          rowData[column.dataKey] = `Row ${rowIndex} - Col ${columnIndex}`
          return rowData
        },
        {
          id: `${prefix}${rowIndex}`,
          parentId: null,
        }
    )
  })
}

function CustomizedHeader({cells, columns, headerIndex}) {
  if (headerIndex === 1) return cells

  const groupCells = []
  let width = 0
  let idx = 0

  columns.forEach((column, columnIndex) => {
    if (column.placeholderSign === TableV2Placeholder) {
      groupCells.push(cells[columnIndex])
    } else {
      width += cells[columnIndex].props.column.width
      idx++

      const nextColumn = columns[columnIndex + 1]
      if (
          columnIndex === columns.length - 1 ||
          nextColumn?.placeholderSign === TableV2Placeholder ||
          idx === (headerIndex === 0 ? 4 : 2)
      ) {
        groupCells.push(
            h(
                'div',
                {
                  class: 'flex items-center justify-center custom-header-cell',
                  role: 'columnheader',
                  style: {
                    ...cells[columnIndex].props.style,
                    width: `${width}px`,
                    border: `2px solid #fff`
                  }
                },
                `Group width ${width}`
            )
        )
        width = 0
        idx = 0
      }
    }
  })
  return groupCells;
}


const headerClass = ({
                       headerIndex,
                     }: Parameters<HeaderClassNameGetter<any>>[0]) => {
  if (headerIndex === 0) return 'el-primary-color'
  return ''
}

const columns = generateColumns(70)
let data = generateData(columns, 20)

columns[0].fixed = TableV2FixedDir.LEFT

for (let i = 0; i < 3; i++) columns[i].sortable = true

const sortBy = ref<SortBy>({
  key: 'column-0',
  order: TableV2SortOrder.ASC,
})

const onSort = (_sortBy: SortBy) => {
  data = data.reverse()
  sortBy.value = _sortBy
}
</script>

<style>
.el-el-table-v2__header-row .custom-header-cell {
  border-right: 1px solid var(--el-border-color);
}

.el-el-table-v2__header-row .custom-header-cell:last-child {
  border-right: none;
}

.el-primary-color {
  background-color: var(--el-color-primary);
  color: var(--el-color-white);
  font-size: 14px;
  font-weight: bold;
}

.el-primary-color .custom-header-cell {
  padding: 0 4px;
}
</style>

JS版本

<template>
  <div style="width: 100%;height: calc(100vh - 84px)">
    <el-auto-resizer>
      <template #default="{ height, width }">
        <el-table-v2
            fixed
            :columns="columns"
            :data="data"
            :sort-by="sortBy"
            :header-height="[50, 40]"
            :header-class="headerClass"
            @column-sort="onSort"
            :width="width"
            :height="height"
        >
          <template #header="props">
            <CustomizedHeader v-bind="props"></CustomizedHeader>
          </template>
        </el-table-v2>
      </template>
    </el-auto-resizer>
  </div>
</template>

<script setup>
import { h, ref } from 'vue'
import {
  TableV2FixedDir,
  TableV2Placeholder,
  TableV2SortOrder
} from 'element-plus'

// 生成带二级表头的 columns
function generateColumns(length = 10, prefix = 'column-') {
  return Array.from({ length }).map((_, columnIndex) => ({
    key: `${prefix}${columnIndex}`,
    dataKey: `${prefix}${columnIndex}`,
    title: `Column ${columnIndex}`,
    width: 150
  }))
}

function generateData(columns, length = 200, prefix = 'row-') {
  return Array.from({ length }).map((_, rowIndex) => {
    return columns.reduce(
        (rowData, column, columnIndex) => {
          rowData[column.dataKey] = `Row ${rowIndex} - Col ${columnIndex}`
          return rowData
        },
        {
          id: `${prefix}${rowIndex}`,
          parentId: null
        }
    )
  })
}

function CustomizedHeader({ cells, columns, headerIndex }) {
  if (headerIndex === 1) return cells

  const groupCells = []
  let width = 0
  let idx = 0

  columns.forEach((column, columnIndex) => {
    if (column.placeholderSign === TableV2Placeholder) {
      groupCells.push(cells[columnIndex])
    } else {
      width += cells[columnIndex].props.column.width
      idx++

      const nextColumn = columns[columnIndex + 1]
      if (
          columnIndex === columns.length - 1 ||
          nextColumn?.placeholderSign === TableV2Placeholder ||
          idx === (headerIndex === 0 ? 4 : 2)
      ) {
        groupCells.push(
            h(
                'div',
                {
                  class: 'flex items-center justify-center custom-header-cell',
                  role: 'columnheader',
                  style: {
                    ...cells[columnIndex].props.style,
                    width: `${width}px`,
                    border: `2px solid #fff`
                  }
                },
                `Group width ${width}`
            )
        )
        width = 0
        idx = 0
      }
    }
  })
  return groupCells
}


const headerClass = ({ headerIndex }) => {
  return headerIndex === 0 ? 'el-primary-color' : ''
}

const columns = generateColumns(70)
let data = generateData(columns, 20)

columns[0].fixed = TableV2FixedDir.LEFT

for (let i = 0; i < 3; i++) {
  columns[i].sortable = true
}

const sortBy = ref({
  key: 'column-0',
  order: TableV2SortOrder.ASC
})

const onSort = (_sortBy) => {
  // 创建新数组以保持响应性
  data = [...data].reverse()
  sortBy.value = _sortBy
}

</script>

<style>
.el-el-table-v2__header-row .custom-header-cell {
  border-right: 1px solid var(--el-border-color);
}

.el-el-table-v2__header-row .custom-header-cell:last-child {
  border-right: none;
}

.el-primary-color {
  background-color: var(--el-color-primary);
  color: var(--el-color-white);
  font-size: 14px;
  font-weight: bold;
}

.el-primary-color .custom-header-cell {
  padding: 0 4px;
}
</style>

网站公告

今日签到

点亮在社区的每一天
去签到