虚拟化表格 beta
随着 Web 开发的不断演进,表格组件一直是我们 Web 应用中最受欢迎的组件,尤其是在仪表盘、数据分析领域。对于 Table V1,即使只有 1000 条记录,其性能也可能非常糟糕,给用户带来不便。
通过虚拟化表格,您可以瞬间渲染海量数据。
提示
此组件仍处于测试阶段,请自行承担使用风险。如果您发现任何 bug 或问题,请在 GitHub 上向我们报告以便修复。此外,还有一些 API 未在本文档中提及,因为其中部分 API 尚未完全开发。
虽然虚拟化表格非常高效,但当数据量过大时,您的网络和内存大小可能会成为应用的瓶颈。因此请记住,虚拟化表格并非万能的最终解决方案,请考虑使用分页、添加过滤器等手段。
基础用法
让我们通过渲染一个包含 10 列和 1000 行的基础示例来演示虚拟化表格的性能。
<template>
<el-table-v2
:columns="columns"
:data="data"
:width="700"
:height="400"
fixed
/>
</template>
<script lang="ts" setup>
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: `Column ${columnIndex}`,
width: 150,
}))
const generateData = (
columns: ReturnType<typeof generateColumns>,
length = 200,
prefix = 'row-'
) =>
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,
}
)
})
const columns = generateColumns(10)
const data = generateData(columns, 1000)
</script>
自动调整大小
如果您不想手动向表格传递 width 和 height 属性,可以将表格组件包裹在 AutoResizer 中。它将为您自动更新宽度和高度。
调整浏览器窗口大小即可查看效果。
提示
由于其默认高度值为 100%,请确保 AutoResizer 的父节点具有固定高度。或者,您也可以通过向 AutoResizer 传递 style 属性来定义高度。
<template>
<div style="height: 400px">
<el-auto-resizer>
<template #default="{ height, width }">
<el-table-v2
:columns="columns"
:data="data"
:width="width"
:height="height"
fixed
/>
</template>
</el-auto-resizer>
</div>
</template>
<script lang="ts" setup>
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: `Column ${columnIndex}`,
width: 150,
}))
const generateData = (
columns: ReturnType<typeof generateColumns>,
length = 200,
prefix = 'row-'
) =>
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,
}
)
})
const columns = generateColumns(10)
const data = generateData(columns, 200)
</script>
自定义单元格渲染器
当然,您可以根据需求渲染表格单元格。这是一个如何自定义单元格的简单示例。
<template>
<el-table-v2
:columns="columns"
:data="data"
:width="700"
:height="400"
fixed
/>
</template>
<script lang="tsx" setup>
import { ref } from 'vue'
import dayjs from 'dayjs'
import {
ElButton,
ElIcon,
ElTag,
ElTooltip,
TableV2FixedDir,
} from 'element-plus'
import { Timer } from '@element-plus/icons-vue'
import type { Column } from 'element-plus'
let id = 0
const dataGenerator = () => ({
id: `random-id-${++id}`,
name: 'Tom',
date: '2020-10-1',
})
const columns: Column<any>[] = [
{
key: 'date',
title: 'Date',
dataKey: 'date',
width: 150,
fixed: TableV2FixedDir.LEFT,
cellRenderer: ({ cellData: date }) => (
<ElTooltip content={dayjs(date).format('YYYY/MM/DD')}>
{
<span class="flex items-center">
<ElIcon class="mr-3">
<Timer />
</ElIcon>
{dayjs(date).format('YYYY/MM/DD')}
</span>
}
</ElTooltip>
),
},
{
key: 'name',
title: 'Name',
dataKey: 'name',
width: 150,
align: 'center',
cellRenderer: ({ cellData: name }) => <ElTag>{name}</ElTag>,
},
{
key: 'operations',
title: 'Operations',
cellRenderer: () => (
<>
<ElButton size="small">Edit</ElButton>
<ElButton size="small" type="danger">
Delete
</ElButton>
</>
),
width: 150,
align: 'center',
},
]
const data = ref(Array.from({ length: 200 }).map(dataGenerator))
</script>
带选择功能的表格
使用自定义单元格渲染器为您的表格实现选择功能。
<template>
<div style="height: 400px">
<el-auto-resizer>
<template #default="{ height, width }">
<el-table-v2
:columns="columns"
:data="data"
:width="width"
:height="height"
fixed
/>
</template>
</el-auto-resizer>
</div>
</template>
<script lang="tsx" setup>
import { ref, unref } from 'vue'
import { ElCheckbox, useLocale } from 'element-plus'
import type { FunctionalComponent } from 'vue'
import type { CheckboxValueType, Column } from 'element-plus'
type SelectionCellProps = {
value: boolean
intermediate?: boolean
ariaLabel?: string
onChange: (value: CheckboxValueType) => void
}
const { t } = useLocale()
const SelectionCell: FunctionalComponent<SelectionCellProps> = ({
value,
intermediate = false,
ariaLabel,
onChange,
}) => {
return (
<ElCheckbox
onChange={onChange}
modelValue={value}
ariaLabel={ariaLabel}
indeterminate={intermediate}
/>
)
}
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: `Column ${columnIndex}`,
width: 150,
}))
const generateData = (
columns: ReturnType<typeof generateColumns>,
length = 200,
prefix = 'row-'
) =>
Array.from({ length }).map((_, rowIndex) => {
return columns.reduce(
(rowData, column, columnIndex) => {
rowData[column.dataKey] = `Row ${rowIndex} - Col ${columnIndex}`
return rowData
},
{
id: `${prefix}${rowIndex}`,
checked: false,
parentId: null,
}
)
})
const columns: Column<any>[] = generateColumns(10)
columns.unshift({
key: 'selection',
width: 50,
cellRenderer: ({ rowData }) => {
const onChange = (value: CheckboxValueType) => (rowData.checked = value)
return (
<SelectionCell
value={rowData.checked}
ariaLabel={t('el.table.selectRowLabel')}
onChange={onChange}
/>
)
},
headerCellRenderer: () => {
const _data = unref(data)
const onChange = (value: CheckboxValueType) =>
(data.value = _data.map((row) => {
row.checked = value
return row
}))
const allSelected = _data.every((row) => row.checked)
const containsChecked = _data.some((row) => row.checked)
return (
<SelectionCell
value={allSelected}
intermediate={containsChecked && !allSelected}
ariaLabel={t('el.table.selectAllLabel')}
onChange={onChange}
/>
)
},
})
const data = ref(generateData(columns, 200))
</script>
行内编辑
正如我们在上面的选择功能中所演示的,您可以使用相同的方法启用行内编辑。
<template>
<div style="height: 400px">
<el-auto-resizer>
<template #default="{ height, width }">
<el-table-v2
:columns="columns"
:data="data"
:width="width"
:height="height"
fixed
/>
</template>
</el-auto-resizer>
</div>
</template>
<script lang="tsx" setup>
import { ref, withKeys } from 'vue'
import { ElInput } from 'element-plus'
import type { FunctionalComponent } from 'vue'
import type { Column, InputInstance } from 'element-plus'
type SelectionCellProps = {
value: string
intermediate?: boolean
onChange: (value: string) => void
onBlur: () => void
onKeydownEnter: () => void
forwardRef: (el: InputInstance) => void
}
const InputCell: FunctionalComponent<SelectionCellProps> = ({
value,
onChange,
onBlur,
onKeydownEnter,
forwardRef,
}) => {
return (
<ElInput
ref={forwardRef as any}
onInput={onChange}
onBlur={onBlur}
onKeydown={withKeys(onKeydownEnter, ['enter'])}
modelValue={value}
/>
)
}
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: `Column ${columnIndex}`,
width: 150,
}))
const generateData = (
columns: ReturnType<typeof generateColumns>,
length = 200,
prefix = 'row-'
) =>
Array.from({ length }).map((_, rowIndex) => {
return columns.reduce(
(rowData, column, columnIndex) => {
rowData[column.dataKey] = `Row ${rowIndex} - Col ${columnIndex}`
return rowData
},
{
id: `${prefix}${rowIndex}`,
editing: false,
parentId: null,
}
)
})
const columns: Column<any>[] = generateColumns(10)
columns[0] = {
...columns[0],
title: 'Editable Column',
cellRenderer: ({ rowData, column }) => {
const onChange = (value: string) => {
rowData[column.dataKey!] = value
}
const onEnterEditMode = () => {
rowData.editing = true
}
const onExitEditMode = () => (rowData.editing = false)
const input = ref()
const setRef = (el) => {
input.value = el
if (el) {
el.focus?.()
}
}
return rowData.editing ? (
<InputCell
forwardRef={setRef}
value={rowData[column.dataKey!]}
onChange={onChange}
onBlur={onExitEditMode}
onKeydownEnter={onExitEditMode}
/>
) : (
<div class="table-v2-inline-editing-trigger" onClick={onEnterEditMode}>
{rowData[column.dataKey!]}
</div>
)
},
}
const data = ref(generateData(columns, 200))
</script>
<style>
.table-v2-inline-editing-trigger {
border: 1px transparent dotted;
padding: 4px;
}
.table-v2-inline-editing-trigger:hover {
border-color: var(--el-color-primary);
}
</style>
带状态的表格
您可以高亮显示您的表格内容,以区分“成功、信息、警告、危险”和其他状态。
要自定义行的外观,请使用 row-class-name 属性。例如,每 10 行使用 bg-blue-200 类高亮显示,每 5 行使用 bg-red-100 类高亮显示。
<template>
<el-table-v2
:columns="columns"
:data="data"
:row-class="rowClass"
:width="700"
:height="400"
/>
</template>
<script lang="tsx" setup>
import { ref } from 'vue'
import dayjs from 'dayjs'
import {
ElButton,
ElIcon,
ElTag,
ElTooltip,
TableV2FixedDir,
} from 'element-plus'
import { Timer } from '@element-plus/icons-vue'
import type { Column, RowClassNameGetter } from 'element-plus'
let id = 0
const dataGenerator = () => ({
id: `random-id-${++id}`,
name: 'Tom',
date: '2020-10-1',
})
const columns: Column<any>[] = [
{
key: 'date',
title: 'Date',
dataKey: 'date',
width: 150,
fixed: TableV2FixedDir.LEFT,
cellRenderer: ({ cellData: date }) => (
<ElTooltip content={dayjs(date).format('YYYY/MM/DD')}>
{
<span class="flex items-center">
<ElIcon class="mr-3">
<Timer />
</ElIcon>
{dayjs(date).format('YYYY/MM/DD')}
</span>
}
</ElTooltip>
),
},
{
key: 'name',
title: 'Name',
dataKey: 'name',
width: 150,
align: 'center',
cellRenderer: ({ cellData: name }) => <ElTag>{name}</ElTag>,
},
{
key: 'operations',
title: 'Operations',
cellRenderer: () => (
<>
<ElButton size="small">Edit</ElButton>
<ElButton size="small" type="danger">
Delete
</ElButton>
</>
),
width: 150,
align: 'center',
flexGrow: 1,
},
]
const data = ref(Array.from({ length: 200 }).map(dataGenerator))
const rowClass = ({ rowIndex }: Parameters<RowClassNameGetter<any>>[0]) => {
if (rowIndex % 10 === 5) {
return 'bg-red-100'
} else if (rowIndex % 10 === 0) {
return 'bg-blue-200'
}
return ''
}
</script>
带固定行的表格
您可以使某些行固定在表格顶部,这可以通过使用 fixed-data 属性非常容易地实现。
您可以根据滚动事件动态设置固定行,如本示例所示。
<template>
<el-table-v2
:columns="columns"
:data="tableData"
:fixed-data="fixedData"
:width="700"
:height="400"
:row-class="rowClass"
fixed
@scroll="onScroll"
/>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue'
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: `Column ${columnIndex}`,
width: 150,
}))
const generateData = (
columns: ReturnType<typeof generateColumns>,
length = 200,
prefix = 'row-'
) =>
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,
}
)
})
const columns = generateColumns(10)
const data = generateData(columns, 200)
const rowClass = ({ rowIndex }) => {
if (rowIndex < 0 || (rowIndex + 1) % 5 === 0) return 'sticky-row'
}
const stickyIndex = ref(0)
const fixedData = computed(() =>
data.slice(stickyIndex.value, stickyIndex.value + 1)
)
const tableData = computed(() => {
return data.slice(1)
})
const onScroll = ({ scrollTop }) => {
stickyIndex.value = Math.floor(scrollTop / 250) * 5
}
</script>
<style>
.el-el-table-v2__fixed-header-row {
background-color: var(--el-color-primary-light-5);
font-weight: bold;
}
</style>
带固定列的表格
如果您出于某种原因希望列固定在左侧或右侧,可以通过向表格添加特殊属性来实现。
您可以将列的 fixed 属性设置为 true(代表 FixedDir.LEFT)、FixedDir.LEFT 或 FixedDir.RIGHT。
<template>
<el-table-v2
:columns="columns"
:data="data"
:sort-by="sortBy"
:width="700"
:height="400"
fixed
@column-sort="onSort"
/>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { TableV2FixedDir, TableV2SortOrder } from 'element-plus'
import type { SortBy } from 'element-plus'
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: `Column ${columnIndex}`,
width: 150,
}))
const generateData = (
columns: ReturnType<typeof generateColumns>,
length = 200,
prefix = 'row-'
) =>
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,
}
)
})
const columns = generateColumns(10)
let data = generateData(columns, 200)
columns[0].fixed = true
columns[1].fixed = TableV2FixedDir.LEFT
columns[9].fixed = TableV2FixedDir.RIGHT
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>
表头分组
通过自定义表头渲染器,您可以像本示例中所示对表头进行分组。
提示
在这种情况下,我们使用了演练场(playground)不支持的 JSX 功能。您可以在本地环境或在线 IDE(如 codesandbox)中进行尝试。
建议您使用 JSX 编写表格组件,因为它包含 VNode 操作。
<template>
<el-table-v2
fixed
:columns="fixedColumns"
:data="data"
:header-height="[50, 40, 50]"
:header-class="headerClass"
:width="700"
:height="400"
>
<template #header="props">
<customized-header v-bind="props" />
</template>
</el-table-v2>
</template>
<script lang="tsx" setup>
import { TableV2FixedDir, TableV2Placeholder } from 'element-plus'
import type { FunctionalComponent } from 'vue'
import type {
HeaderClassNameGetter,
TableV2CustomizedHeaderSlotParam,
} from 'element-plus'
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: `Column ${columnIndex}`,
width: 150,
}))
const generateData = (
columns: ReturnType<typeof generateColumns>,
length = 200,
prefix = 'row-'
) =>
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,
}
)
})
const columns = generateColumns(15)
const data = generateData(columns, 200)
const fixedColumns = columns.map((column, columnIndex) => {
let fixed: TableV2FixedDir | undefined = undefined
if (columnIndex < 3) fixed = TableV2FixedDir.LEFT
if (columnIndex > 12) fixed = TableV2FixedDir.RIGHT
return { ...column, fixed, width: 100 }
})
const CustomizedHeader: FunctionalComponent<
TableV2CustomizedHeaderSlotParam
> = ({ cells, columns, headerIndex }) => {
if (headerIndex === 2) return cells
const groupCells = [] as typeof cells
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(
<div
class="flex items-center justify-center custom-header-cell"
role="columnheader"
style={{
...cells[columnIndex].props!.style,
width: `${width}px`,
}}
>
Group width {width}
</div>
)
width = 0
idx = 0
}
}
})
return groupCells
}
const headerClass = ({
headerIndex,
}: Parameters<HeaderClassNameGetter<any>>[0]) => {
if (headerIndex === 1) return 'el-primary-color'
return ''
}
</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>
筛选
虚拟化表格提供自定义表头渲染器,用于创建自定义表头。我们可以利用这些渲染器来实现过滤器。
<template>
<el-table-v2
fixed
:columns="fixedColumns"
:data="data"
:width="700"
:height="400"
/>
</template>
<script lang="tsx" setup>
import { computed, ref } from 'vue'
import {
ElButton,
ElCheckbox,
ElIcon,
ElPopover,
TableV2FixedDir,
useLocale,
} from 'element-plus'
import { Filter } from '@element-plus/icons-vue'
import type { HeaderCellSlotProps } from 'element-plus'
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: `Column ${columnIndex}`,
width: 150,
}))
const generateData = (
columns: ReturnType<typeof generateColumns>,
length = 200,
prefix = 'row-'
) =>
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,
}
)
})
const columns = generateColumns(10)
const data = ref(generateData(columns, 200))
const { t } = useLocale()
const shouldFilter = ref(false)
const popoverRef = ref()
const ariaLabel = computed(() => {
return t('el.table.filterLabel', { column: columns[0].title })
})
const onFilter = () => {
popoverRef.value.hide()
if (shouldFilter.value) {
data.value = generateData(columns, 100, 'filtered-')
} else {
data.value = generateData(columns, 200)
}
}
const onReset = () => {
shouldFilter.value = false
onFilter()
}
const handleShowPopover = () => {
const button = document.querySelector('.el-table-v2__demo-filter button')
;(button as HTMLElement)?.focus()
}
columns[0].headerCellRenderer = (props: HeaderCellSlotProps) => {
return (
<div class="flex items-center justify-center">
<span class="mr-2 text-xs">{props.column.title}</span>
<ElPopover
ref={popoverRef}
trigger="click"
width={200}
onAfter-enter={handleShowPopover}
>
{{
default: () => (
<div class="filter-wrapper">
<div class="filter-group">
<ElCheckbox v-model={shouldFilter.value}>
Filter Text
</ElCheckbox>
</div>
<div class="el-table-v2__demo-filter">
<ElButton text onClick={onFilter}>
Confirm
</ElButton>
<ElButton text onClick={onReset}>
Reset
</ElButton>
</div>
</div>
),
reference: () => (
<button
type="button"
aria-label={ariaLabel.value}
class="el-table-v2__demo-filter-btn"
>
<ElIcon size={14}>
<Filter />
</ElIcon>
</button>
),
}}
</ElPopover>
</div>
)
}
const fixedColumns = columns.map((column, columnIndex) => {
let fixed: TableV2FixedDir | undefined = undefined
if (columnIndex < 2) fixed = TableV2FixedDir.LEFT
if (columnIndex > 9) fixed = TableV2FixedDir.RIGHT
return { ...column, fixed, width: 100 }
})
</script>
<style>
.el-table-v2__demo-filter {
border-top: var(--el-border);
margin: 12px -12px -12px;
padding: 0 12px;
display: flex;
justify-content: space-between;
}
.el-table-v2__demo-filter-btn {
display: flex;
cursor: pointer;
padding: 0;
margin: 0;
background-color: transparent;
appearance: none;
border: none;
}
</style>
可排序
您可以使用排序状态对表格进行排序。
<template>
<el-table-v2
:columns="columns"
:data="data"
:sort-by="sortState"
:width="700"
:height="400"
fixed
@column-sort="onSort"
/>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { TableV2SortOrder } from 'element-plus'
import type { SortBy } from 'element-plus'
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: `Column ${columnIndex}`,
width: 150,
}))
const generateData = (
columns: ReturnType<typeof generateColumns>,
length = 200,
prefix = 'row-'
) =>
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,
}
)
})
const columns = generateColumns(10)
let data = generateData(columns, 200)
columns[0].sortable = true
const sortState = ref<SortBy>({
key: 'column-0',
order: TableV2SortOrder.ASC,
})
const onSort = (sortBy: SortBy) => {
console.log(sortBy)
data = data.reverse()
sortState.value = sortBy
}
</script>
受控排序
您可以根据需要定义多个可排序的列。请记住,如果您定义了多个可排序的列,UI 可能会让用户感到困惑,因为目前正在对哪一列进行排序变得不明确。
<template>
<el-table-v2
v-model:sort-state="sortState"
:columns="columns"
:data="data"
:width="700"
:height="400"
fixed
@column-sort="onSort"
/>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { TableV2SortOrder } from 'element-plus'
import type { SortBy, SortState } from 'element-plus'
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: `Column ${columnIndex}`,
width: 150,
}))
const generateData = (
columns: ReturnType<typeof generateColumns>,
length = 200,
prefix = 'row-'
) =>
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,
}
)
})
const columns = generateColumns(10)
const data = ref(generateData(columns, 200))
columns[0].sortable = true
columns[1].sortable = true
const sortState = ref<SortState>({
'column-0': TableV2SortOrder.DESC,
'column-1': TableV2SortOrder.ASC,
})
const onSort = ({ key, order }: SortBy) => {
sortState.value[key] = order
data.value = data.value.reverse()
}
</script>
十字悬停
处理大型列表时,很容易丢失当前访问的行和列。在这种情况下,使用此功能会非常有帮助。
<template>
<div style="height: 400px">
<el-auto-resizer>
<template #default="{ height, width }">
<el-table-v2
:columns="columns"
:cell-props="cellProps"
:class="kls"
:data="data"
:width="width"
:height="height"
/>
</template>
</el-auto-resizer>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: `Column ${columnIndex}`,
width: 150,
}))
const generateData = (
columns: ReturnType<typeof generateColumns>,
length = 200,
prefix = 'row-'
) =>
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,
}
)
})
const columns = generateColumns(10)
columns.unshift({
key: 'column-n-1',
width: 50,
title: 'Row No.',
cellRenderer: ({ rowIndex }) => `${rowIndex + 1}`,
align: 'center',
})
const data = generateData(columns, 200)
const cellProps = ({ columnIndex }) => {
const key = `hovering-col-${columnIndex}`
return {
['data-key']: key,
onMouseenter: () => {
kls.value = key
},
onMouseleave: () => {
kls.value = ''
},
}
}
const kls = ref<string>('')
</script>
<style>
.hovering-col-0 [data-key='hovering-col-0'],
.hovering-col-1 [data-key='hovering-col-1'],
.hovering-col-2 [data-key='hovering-col-2'],
.hovering-col-3 [data-key='hovering-col-3'],
.hovering-col-4 [data-key='hovering-col-4'],
.hovering-col-5 [data-key='hovering-col-5'],
.hovering-col-6 [data-key='hovering-col-6'],
.hovering-col-7 [data-key='hovering-col-7'],
.hovering-col-8 [data-key='hovering-col-8'],
.hovering-col-9 [data-key='hovering-col-9'],
.hovering-col-10 [data-key='hovering-col-10'] {
background: var(--el-table-row-hover-bg-color);
}
[data-key='hovering-col-0'] {
font-weight: bold;
user-select: none;
pointer-events: none;
}
</style>
列合并
虚拟化表格不使用内置的 table 元素,因此与 TableV1 相比,colspan 和 rowspan 的行为略有不同。但是,通过自定义行渲染器,这些功能仍然可以实现。在本节中,我们将演示如何实现这一点。
<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 { cloneVNode } from 'vue'
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: `Column ${columnIndex}`,
width: 150,
}))
const generateData = (
columns: ReturnType<typeof generateColumns>,
length = 200,
prefix = 'row-'
) =>
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,
}
)
})
const columns = generateColumns(10)
const data = generateData(columns, 200)
const colSpanIndex = 1
columns[colSpanIndex].colSpan = ({ rowIndex }) => (rowIndex % 4) + 1
columns[colSpanIndex].align = 'center'
const Row = ({ rowData, rowIndex, cells, columns }) => {
const colSpan = columns[colSpanIndex].colSpan({ rowData, rowIndex })
if (colSpan > 1) {
let width = Number.parseInt(cells[colSpanIndex].props.style.width)
for (let i = 1; i < colSpan; i++) {
width += Number.parseInt(cells[colSpanIndex + i].props.style.width)
cells[colSpanIndex + i] = null
}
const style = {
...cells[colSpanIndex].props.style,
width: `${width}px`,
backgroundColor: 'var(--el-color-primary-light-3)',
}
cells[colSpanIndex] = cloneVNode(cells[colSpanIndex], { style })
}
return cells
}
</script>
行合并
既然我们已经介绍了 列合并,值得注意的是我们也支持行合并。它与列合并略有不同,但基本思想是一样的。
<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 { cloneVNode } from 'vue'
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: `Column ${columnIndex}`,
width: 150,
}))
const generateData = (
columns: ReturnType<typeof generateColumns>,
length = 200,
prefix = 'row-'
) =>
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,
}
)
})
const columns = generateColumns(10)
const data = generateData(columns, 200)
const rowSpanIndex = 0
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>
行合并与列合并结合使用
我们可以将行合并和列合并结合起来,以满足您的业务需求!
<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="tsx" setup>
import { cloneVNode } from 'vue'
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: `Column ${columnIndex}`,
width: 150,
}))
const generateData = (
columns: ReturnType<typeof generateColumns>,
length = 200,
prefix = 'row-'
) =>
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,
}
)
})
const columns = generateColumns(10)
const data = generateData(columns, 200)
const colSpanIndex = 1
columns[colSpanIndex].colSpan = ({ rowIndex }) => (rowIndex % 4) + 1
columns[colSpanIndex].align = 'center'
const rowSpanIndex = 0
columns[rowSpanIndex].rowSpan = ({ rowIndex }) =>
rowIndex % 2 === 0 && rowIndex <= data.length - 2 ? 2 : 1
const Row = ({ rowData, rowIndex, cells, columns }) => {
const colSpan = columns[colSpanIndex].colSpan({ rowData, rowIndex })
if (colSpan > 1) {
let width = Number.parseInt(cells[colSpanIndex].props.style.width)
for (let i = 1; i < colSpan; i++) {
width += Number.parseInt(cells[colSpanIndex + i].props.style.width)
cells[colSpanIndex + i] = null
}
const style = {
...cells[colSpanIndex].props.style,
width: `${width}px`,
backgroundColor: 'var(--el-color-primary-light-3)',
}
cells[colSpanIndex] = cloneVNode(cells[colSpanIndex], { style })
}
const rowSpan = columns[rowSpanIndex].rowSpan({ rowData, rowIndex })
if (rowSpan > 1) {
const cell = cells[rowSpanIndex]
const style = {
...cell.props.style,
backgroundColor: 'var(--el-color-danger-light-3)',
height: `${rowSpan * 50}px`,
alignSelf: 'flex-start',
zIndex: 1,
}
cells[rowSpanIndex] = cloneVNode(cell, { style })
} else {
const style = cells[rowSpanIndex].props.style
// override the cell here for creating a pure node without pollute the style
cells[rowSpanIndex] = (
<div style={{ ...style, width: `${style.width}px` }} />
)
}
return cells
}
</script>
树形数据
虚拟表格还可以渲染树状结构的数据。通过点击箭头图标,您可以展开或折叠树节点。
<template>
<el-table-v2
v-model:expanded-row-keys="expandedRowKeys"
:columns="columns"
:data="treeData"
:width="700"
:expand-column-key="expandColumnKey"
:height="400"
fixed
@row-expand="onRowExpanded"
@expanded-rows-change="onExpandedRowsChange"
/>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue'
import { TableV2FixedDir } from 'element-plus'
import type { ExpandedRowsChangeHandler, RowExpandHandler } from 'element-plus'
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: `Column ${columnIndex}`,
width: 150,
}))
const generateData = (
columns: ReturnType<typeof generateColumns>,
length = 200,
prefix = 'row-'
) =>
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,
}
)
})
const columns = generateColumns(10).map((column, columnIndex) => {
let fixed!: TableV2FixedDir
if (columnIndex < 2) fixed = TableV2FixedDir.LEFT
if (columnIndex > 8) fixed = TableV2FixedDir.RIGHT
return { ...column, fixed }
})
const data = generateData(columns, 200)
const expandColumnKey = 'column-0'
// add some sub items
for (let i = 0; i < 50; i++) {
data.push(
{
...data[0],
id: `${data[0].id}-sub-${i}`,
parentId: data[0].id,
[expandColumnKey]: `Sub ${i}`,
},
{
...data[2],
id: `${data[2].id}-sub-${i}`,
parentId: data[2].id,
[expandColumnKey]: `Sub ${i}`,
},
{
...data[2],
id: `${data[2].id}-sub-sub-${i}`,
parentId: `${data[2].id}-sub-${i}`,
[expandColumnKey]: `Sub-Sub ${i}`,
}
)
}
function unflatten(
data: ReturnType<typeof generateData>,
rootId = null,
dataKey = 'id',
parentKey = 'parentId'
) {
const tree: any[] = []
const childrenMap = {}
for (const datum of data) {
const item = { ...datum }
const id = item[dataKey]
const parentId = item[parentKey]
if (Array.isArray(item.children)) {
childrenMap[id] = item.children.concat(childrenMap[id] || [])
} else if (!childrenMap[id]) {
childrenMap[id] = []
}
item.children = childrenMap[id]
if (parentId !== undefined && parentId !== rootId) {
if (!childrenMap[parentId]) childrenMap[parentId] = []
childrenMap[parentId].push(item)
} else {
tree.push(item)
}
}
return tree
}
const treeData = computed(() => unflatten(data))
const expandedRowKeys = ref<string[]>([])
const onRowExpanded = ({ expanded }: Parameters<RowExpandHandler>[0]) => {
console.log('Expanded:', expanded)
}
const onExpandedRowsChange = (
expandedKeys: Parameters<ExpandedRowsChangeHandler>[0]
) => {
console.log(expandedKeys)
}
</script>
动态高度行
虚拟表格能够渲染具有动态高度的行。如果您正在处理数据且不确定内容大小,此功能非常适合渲染根据内容高度调整的行。要启用此功能,请传入 estimated-row-height 属性。估计高度越接近实际内容,渲染体验越流畅。
提示
每行的高度在渲染行时进行动态测量。因此,如果您尝试显示大量数据,UI 可能会出现跳动。
<template>
<el-table-v2
:columns="columns"
:data="data"
:sort-by="sort"
:estimated-row-height="40"
:width="700"
:height="400"
fixed
@column-sort="onColumnSort"
/>
</template>
<script lang="tsx" setup>
import { ref } from 'vue'
import {
ElButton,
ElTag,
TableV2FixedDir,
TableV2SortOrder,
} from 'element-plus'
import type { Column, SortBy } from 'element-plus'
const longText =
'Quaerat ipsam necessitatibus eum quibusdam est id voluptatem cumque mollitia.'
const midText = 'Corrupti doloremque a quos vero delectus consequatur.'
const shortText = 'Eius optio fugiat.'
const textList = [shortText, midText, longText]
// generate random number in range 0 to 2
let id = 0
const dataGenerator = () => ({
id: `random-${++id}`,
name: 'Tom',
date: '2016-05-03',
description: textList[Math.floor(Math.random() * 3)],
})
const columns: Column<any>[] = [
{
key: 'id',
title: 'Id',
dataKey: 'id',
width: 150,
sortable: true,
fixed: TableV2FixedDir.LEFT,
},
{
key: 'name',
title: 'Name',
dataKey: 'name',
width: 150,
align: 'center',
cellRenderer: ({ cellData: name }) => <ElTag>{name}</ElTag>,
},
{
key: 'description',
title: 'Description',
dataKey: 'description',
width: 150,
cellRenderer: ({ cellData: description }) => (
<div style="padding: 10px 0;">{description}</div>
),
},
{
key: 'operations',
title: 'Operations',
cellRenderer: () => (
<>
<ElButton size="small">Edit</ElButton>
<ElButton size="small" type="danger">
Delete
</ElButton>
</>
),
width: 150,
align: 'center',
},
]
const data = ref(
Array.from({ length: 200 })
.map(dataGenerator)
.sort((a, b) => (a.name > b.name ? 1 : -1))
)
const sort = ref<SortBy>({ key: 'name', order: TableV2SortOrder.ASC })
const onColumnSort = (sortBy: SortBy) => {
const order = sortBy.order === 'asc' ? 1 : -1
const dataClone = [...data.value]
dataClone.sort((a, b) => (a[sortBy.key] > b[sortBy.key] ? order : -order))
sort.value = sortBy
data.value = dataClone
}
</script>
详情视图
利用动态高度渲染,您还可以在表格中显示详情视图。
<template>
<el-table-v2
:columns="columns"
:data="data"
:estimated-row-height="50"
:expand-column-key="columns[0].key"
:width="700"
:height="400"
>
<template #row="props">
<Row v-bind="props" />
</template>
</el-table-v2>
</template>
<script lang="tsx" setup>
import { ref } from 'vue'
const detailedText = `Velit sed aspernatur tempora. Natus consequatur officiis dicta vel assumenda.
Itaque est temporibus minus quis. Ipsum commodiab porro vel voluptas illum.
Qui quam nulla et dolore autem itaque est.
Id consequatur ipsum ea fuga et odit eligendi impedit.
Maiores officiis occaecati et magnam et sapiente est velit sunt.
Non et tempore temporibus. Excepturi et quos. Minus distinctio aut.
Voluptatem ea excepturi omnis vel. Non aperiam sit sed laboriosam eaque omnis deleniti.
Est molestiae omnis non et nulla repudiandae fuga sit.`
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: `Column ${columnIndex}`,
width: 150,
}))
const generateData = (
columns: ReturnType<typeof generateColumns>,
length = 200,
prefix = 'row-'
) =>
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,
}
)
})
const columns = generateColumns(10)
const data = ref(
generateData(columns, 200).map((data) => {
data.children = [
{
id: `${data.id}-detail-content`,
detail: detailedText,
},
]
return data
})
)
const Row = ({ cells, rowData }) => {
if (rowData.detail) return <div class="p-6">{rowData.detail}</div>
return cells
}
Row.inheritAttrs = false
</script>
<style>
.el-table-v2__row-depth-0 {
height: 50px;
}
.el-table-v2__cell-text {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>
自定义页脚
当您想要显示总结性消息或信息时,可以渲染自定义页脚。
<template>
<el-table-v2
:columns="columns"
:data="data"
:row-height="40"
:width="700"
:height="400"
:footer-height="50"
fixed
>
<template #footer
><div
class="flex items-center"
style="
justify-content: center;
height: 100%;
background-color: var(--el-color-primary-light-7);
"
>
Display a message in the footer
</div>
</template>
</el-table-v2>
</template>
<script lang="ts" setup>
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: `Column ${columnIndex}`,
width: 150,
}))
const generateData = (
columns: ReturnType<typeof generateColumns>,
length = 200,
prefix = 'row-'
) =>
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,
}
)
})
const columns = generateColumns(10)
const data = generateData(columns, 200)
</script>
自定义空状态渲染器
渲染自定义空状态元素。
无数据
<template>
<el-table-v2
:columns="columns"
:data="[]"
:row-height="40"
:width="700"
:height="400"
:footer-height="50"
>
<template #empty>
<div class="flex items-center justify-center h-100%">
<el-empty />
</div>
</template>
</el-table-v2>
</template>
<script lang="tsx" setup>
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: `Column ${columnIndex}`,
width: 150,
}))
const columns = generateColumns(10)
</script>
遮罩层
当您想显示加载指示器或其他内容时,可以在表格上方渲染遮罩层。
<template>
<el-table-v2
:columns="columns"
:data="data"
:row-height="40"
:width="700"
:height="400"
>
<template #overlay>
<div
class="el-loading-mask"
style="display: flex; align-items: center; justify-content: center"
>
<el-icon class="is-loading" color="var(--el-color-primary)" :size="26">
<loading-icon />
</el-icon>
</div>
</template>
</el-table-v2>
</template>
<script lang="ts" setup>
import { Loading as LoadingIcon } from '@element-plus/icons-vue'
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: `Column ${columnIndex}`,
width: 150,
}))
const generateData = (
columns: ReturnType<typeof generateColumns>,
length = 200,
prefix = 'row-'
) =>
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,
}
)
})
const columns = generateColumns(10)
const data = generateData(columns, 200)
</script>
<style>
.example-showcase .el-table-v2__overlay {
z-index: 9;
}
</style>
手动滚动
使用 Table V2 提供的方法,以指定的偏移量/行数进行手动/程序化滚动。
提示
scrollToRow 的第二个参数是滚动策略,默认值为 auto,它会自行计算滚动位置。如果您希望滚动到特定位置,可以自行定义策略。可选值为 "auto" | "center" | "end" | "start" | "smart"
smart 和 auto 的区别在于 auto 是 smart 滚动策略的一个子集。
<template>
<div class="mb-4 flex items-center">
<el-form-item label="Scroll pixels" class="mr-4">
<el-input v-model="scrollDelta" />
</el-form-item>
<el-form-item label="Scroll rows">
<el-input v-model="scrollRows" />
</el-form-item>
</div>
<div class="mb-4 flex items-center">
<el-button @click="scrollByPixels"> Scroll by pixels </el-button>
<el-button @click="scrollByRows"> Scroll by rows </el-button>
</div>
<div style="height: 400px">
<el-auto-resizer>
<template #default="{ height, width }">
<el-table-v2
ref="tableRef"
:columns="columns"
:data="data"
:width="width"
:height="height"
fixed
/>
</template>
</el-auto-resizer>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import type { TableV2Instance } from 'element-plus'
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: `Column ${columnIndex}`,
width: 150,
}))
const generateData = (
columns: ReturnType<typeof generateColumns>,
length = 200,
prefix = 'row-'
) =>
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,
}
)
})
const columns = generateColumns(10)
const data = generateData(columns, 200)
const tableRef = ref<TableV2Instance>()
const scrollDelta = ref(200)
const scrollRows = ref(10)
function scrollByPixels() {
tableRef.value?.scrollToTop(scrollDelta.value)
}
function scrollByRows() {
tableRef.value?.scrollToRow(scrollRows.value)
}
</script>
TableV2 API
TableV2 属性
| 名称 | 描述 | 类型 | 默认值 |
|---|---|---|---|
| cache | 提前渲染的行数,以提高性能 | number | 2 |
| estimated-row-height | 渲染动态高度行时的预估行高 | number | — |
| header-class | 传递给表头包装器的自定义类名 | string / Function<HeaderClassGetter> | — |
| header-props | 传递给表头组件的自定义属性名 | object / Function<HeaderPropsGetter> | — |
| header-cell-props | 传递给表头单元格组件的自定义属性名 | object / Function<HeaderCellPropsGetter> | — |
| header-height | 表头的高度由 height 设置。如果给定数组,它将渲染长度等于该数组长度的表头行 | number/ number[] | 50 |
| footer-height | 页脚元素的高度,如果提供,将计入表格总高度的计算。 | number | 0 |
| row-class | 传递给行包装器的自定义类名 | string / Function<RowClassGetter> | — |
| row-key | 每行的键,如果不提供,将使用行的索引 | string / Symbol / number | id |
| row-props | 传递给行组件的自定义属性名 | object / Function<RowPropsGetter> | — |
| row-height | 每行的高度,用于计算表格的总高度 | number | 50 |
| row-event-handlers | 附加到每行的一组处理器 | object<RowEventHandlers> | — |
| cell-props | 传递给每个单元格(表头单元格除外)的额外属性 | object / Function<CellPropsGetter> | — |
| columns | 列定义的数组。 | Column[] | — |
| data | 要在表格中渲染的数据数组。 | Data[] | [] |
| data-getter | 自定义从数据源获取数据的方法。 | Function<DataGetter<T>> | — |
| fixed-data | 用于在主内容上方和表头下方渲染行的数据 | object<Data> | — |
| expand-column-key | 指示哪一列是可展开的列键 | string | — |
| expanded-row-keys | 已展开行的键数组,可与 v-model 配合使用 | KeyType[] | — |
| default-expanded-row-keys | 默认展开行的键数组,非响应式 | KeyType[] | — |
| class | 虚拟表格的类名,将应用于所有三个表格(左、右、主) | string / array / object | — |
| fixed | 标志指示表格列宽是固定还是弹性的。 | boolean | false |
| width 必填 | 表格宽度 | number | — |
| height 必填 | 表格高度 | number | — |
| max-height | 表格最大高度 | number | — |
| indent-size | 树形表格的水平缩进 | number | 12 |
| h-scrollbar-size | 表格水平滚动条的大小,用于防止水平和垂直滚动条重叠 | number | 6 |
| v-scrollbar-size | 表格垂直滚动条的大小,用于防止水平和垂直滚动条重叠 | number | 6 |
| scrollbar-always-on | 如果为 true,滚动条将始终显示,而不仅仅是在鼠标置于表格上方时显示 | boolean | false |
| sort-by | 排序指示器 | object<SortBy> | {} |
| sort-state | 多列排序指示器 | object<SortState> | undefined |
TableV2 插槽
| 名称 | 类型 |
|---|---|
| cell | object<CellSlotProps> |
| header | object<HeaderSlotProps> |
| header-cell | object<HeaderCellSlotProps> |
| row | object<RowSlotProps> |
| footer | — |
| empty | — |
| overlay | — |
TableV2 事件
| 名称 | 描述 | 参数 |
|---|---|---|
| column-sort | 列排序时调用 | object<ColumnSortParam> |
| expanded-rows-change | 展开行变更时调用 | KeyType[] |
| end-reached | 滚动到表格末尾时调用。回调包含剩余距离,通常是滚动条高度。 | Function |
| scroll | 滚动后调用 | object<ScrollParams> |
| rows-rendered | 行渲染后调用 | object<RowsRenderedParams> |
| row-expand | 点击箭头图标展开/折叠树节点时调用 | object<RowExpandParams> |
TableV2 暴露的方法
| 方法 | 描述 | 参数 |
|---|---|---|
| scrollTo | 滚动到给定位置 | Function |
| scrollToLeft | 滚动到给定的水平位置 | Function |
| scrollToTop | 滚动到给定的垂直位置 | Function |
| scrollToRow | 使用指定的滚动策略滚动到给定行 | Function |
提示
注意这些是 JavaScript 对象,因此您不能对这些属性使用短横线命名法(kebab-case)
列属性
| 名称 | 描述 | 类型 | 默认值 |
|---|---|---|---|
| align | 表格单元格内容的对齐方式 | Alignment | left |
| class | 列的类名 | string | — |
| key | 唯一标识 | KeyType | — |
| dataKey | 数据的唯一标识 | KeyType | — |
| fixed | 列的固定方向 | boolean / FixedDir | false |
| flexGrow | CSSProperties flex grow,仅在非固定表格中有效 | number | 0 |
| flexShrink | CSSProperties flex shrink,仅在非固定表格中有效 | number | 1 |
| headerClass | 用于自定义表头列类名 | string | — |
| hidden | 列是否不可见 | boolean | — |
| style | 自定义列单元格样式,将与网格单元格样式合并 | object | — |
| sortable | 指示该列是否可排序 | boolean | — |
| title | 在表头单元格中渲染的默认文本 | string | — |
| maxWidth | 列的最大宽度 | number | — |
| minWidth | 列的最小宽度 | number | — |
| width 必填 | 列的宽度 | number | — |
| cellRenderer | 自定义单元格渲染器 | VueComponent / (props: CellRenderProps) => VNode | — |
| headerCellRenderer | 自定义表头渲染器 | VueComponent / (props: HeaderRenderProps) => VNode | — |
类型定义
显示类型声明
type HeaderClassGetter = (param: {
columns: Column<any>[]
headerIndex: number
}) => string
type HeaderPropsGetter = (param: {
columns: Column<any>[]
headerIndex: number
}) => Record<string, any>
type HeaderCellPropsGetter = (param: {
columns: Column<any>[]
column: Column<any>
columnIndex: number
headerIndex: number
style: CSSProperties
}) => Record<string, any>
type RowClassGetter = (param: {
columns: Column<any>[]
rowData: any
rowIndex: number
}) => string
type RowPropsGetter = (param: {
columns: Column<any>[]
rowData: any
rowIndex: number
}) => Record<string, any>
type CellPropsGetter = (param: {
column: Column<any>
columns: Column<any>[]
columnIndex: number
cellData: any
rowData: any
rowIndex: number
}) => void
type DataGetterParams<T> = {
columns: Column<T>[]
column: Column<T>
columnIndex: number
} & RowCommonParams
type DataGetter<T> = (params: DataGetterParams<T>) => T
type CellRenderProps<T> = {
cellData: T
column: Column<T>
columns: Column<T>[]
columnIndex: number
rowData: any
rowIndex: number
}
type HeaderRenderProps<T> = {
column: Column<T>
columns: Column<T>[]
columnIndex: number
headerIndex: number
}
type ScrollParams = {
xAxisScrollDir: 'forward' | 'backward'
scrollLeft: number
yAxisScrollDir: 'forward' | 'backward'
scrollTop: number
}
type CellSlotProps<T> = {
column: Column<T>
columns: Column<T>[]
columnIndex: number
depth: number
style: CSSProperties
rowData: any
rowIndex: number
isScrolling: boolean
expandIconProps?:
| {
rowData: any
rowIndex: number
onExpand: (expand: boolean) => void
}
| undefined
}
type HeaderSlotProps = {
cells: VNode[]
columns: Column<any>[]
headerIndex: number
}
type HeaderCellSlotProps = {
class: string
columns: Column<any>[]
column: Column<any>
columnIndex: number
headerIndex: number
style: CSSProperties
headerCellProps?: any
sortBy: SortBy
sortState?: SortState | undefined
onColumnSorted: (e: MouseEvent) => void
}
type RowCommonParams = {
rowData: any
rowIndex: number
}
type RowEventHandlerParams = {
rowKey: KeyType
event: Event
} & RowCommonParams
type RowEventHandler = (params: RowEventHandlerParams) => void
type RowEventHandlers = {
onClick?: RowEventHandler
onContextmenu?: RowEventHandler
onDblclick?: RowEventHandler
onMouseenter?: RowEventHandler
onMouseleave?: RowEventHandler
}
type RowsRenderedParams = {
rowCacheStart: number
rowCacheEnd: number
rowVisibleStart: number
rowVisibleEnd: number
}
type RowSlotProps = {
columns: Column<any>[]
rowData: any
columnIndex: number
rowIndex: number
data: any
key: number | string
isScrolling?: boolean
style: CSSProperties
}
type RowExpandParams = {
expanded: boolean
rowKey: KeyType
} & RowCommonParams
type Data = {
[key: KeyType]: any
children?: Array<any>
}
type FixedData = Data
type KeyType = string | number | symbol
type ColumnSortParam<T> = { column: Column<T>; key: KeyType; order: SortOrder }
enum SortOrder {
ASC = 'asc',
DESC = 'desc',
}
enum Alignment {
LEFT = 'left',
CENTER = 'center',
RIGHT = 'right',
}
type SortBy = { key: KeyType; Order: SortOrder }
type SortState = Record<KeyType, SortOrder>常见问题
如何在第一列渲染带复选框的列表?
既然允许定义自己的单元格渲染器,您可以像示例 自定义单元格渲染器 中所做的那样自行渲染 checkbox,并自行维护状态。
为什么虚拟化表格提供的功能比 TableV1 少?
对于虚拟化表格,我们打算提供较少的功能,让用户根据需要实现自己的功能。整合过多的功能会使代码难以维护,且对于大多数用户来说,基础功能已经足够。一些关键功能尚未开发。我们很乐意听取您的意见。加入 Discord 以保持关注。