虚拟化表格 beta
随着 Web 开发的不断演进,表格组件一直是 Web 应用中最受欢迎的组件之一,尤其是在仪表盘和数据分析领域。对于 Table V1 来说,即使只有 1000 条数据记录,由于性能不佳,使用起来也会非常烦人。
使用虚拟化表格,您可以在眨眼之间渲染大量数据。
提示
此组件 仍在测试中 ,使用风险自负。如果您发现任何错误或问题,请在 GitHub 上报告,以便我们修复。此外,还有一些 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 组件包裹表格。它将自动为您更新宽度和高度。
调整浏览器大小以查看其工作方式。
提示
请确保 `AutoResizer` 的父节点 具有固定的高度,因为它的默认高度值为 100%。或者,您可以通过将 `style` 属性传递给 `AutoResizer` 来定义它。
<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 } from 'element-plus'
import type { FunctionalComponent } from 'vue'
import type { CheckboxValueType, Column } from 'element-plus'
type SelectionCellProps = {
value: boolean
intermediate?: boolean
onChange: (value: CheckboxValueType) => void
}
const SelectionCell: FunctionalComponent<SelectionCellProps> = ({
value,
intermediate = false,
onChange,
}) => {
return (
<ElCheckbox
onChange={onChange}
modelValue={value}
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} 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}
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>
分组表头
通过自定义您的表头渲染器,您可以如此示例所示对表头进行分组。
提示
在这种情况下,我们使用了 `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 { ref } from 'vue'
import {
ElButton,
ElCheckbox,
ElIcon,
ElPopover,
TableV2FixedDir,
} 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 shouldFilter = ref(false)
const popoverRef = ref()
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()
}
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 }}>
{{
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: () => (
<ElIcon class="cursor-pointer">
<Filter />
</ElIcon>
),
}}
</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;
}
</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` 元素,因此 `colspan` 和 `rowspan` 的行为与 TableV1 有些不同。但是,通过自定义行渲染器,仍然可以实现这些功能。在本节中,我们将演示如何实现这一点。
<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<any>>[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 属性
名称 | 描述 | 类型 | 默认值 |
---|---|---|---|
cache | 预先渲染的行数,以提高性能 | number | 2 |
estimated-row-height | 用于渲染动态高度行的估计行高 | number | — |
header-class | 传递给表头容器的自定义类名 | string / Function<HeaderClassGetter> | — |
header-props | 传递给表头组件的自定义 props | object / Function<HeaderPropsGetter> | — |
header-cell-props | 传递给表头单元格组件的自定义 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 | 传递给行组件的自定义 props | object / Function<RowPropsGetter> | — |
row-height | 每一行的高度,用于计算表格的总高度 | number | 50 |
row-event-handlers | 附加到每一行的处理程序集合 | object <RowEventHandlers> | — |
cell-props | 传递给每个单元格(表头单元格除外)的额外 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',
}
type SortBy = { key: KeyType; Order: SortOrder }
type SortState = Record<KeyType, SortOrder>
常见问题
如何在第一列渲染一个带复选框的列表?
由于您被允许定义自己的单元格渲染器,您可以像 自定义单元格渲染器 示例那样自己渲染 `checkbox`,并自行维护状态。
为什么虚拟化表格提供的功能比 TableV1 少?
对于虚拟化表格,我们打算提供更少的功能,让我们的用户根据需要实现自己的功能。集成太多功能使得代码难以维护,而且对大多数用户来说,基本功能已经足够了。一些关键功能尚未开发。我们很乐意听取您的意见。加入 Discord 保持关注。