Virtualized Table 虚拟化表格
基础用法
让我们演示虚拟化表的性能,用 10 列和 1000 行渲染一个基本示例。
<template>
<div style="height: 400px">
<w-table-v2
:columns="columns"
:data="data"
:width="700"
:height="400"
fixed
/>
</div>
</template>
<script lang="ts" setup>
const columnTitles = [
'患者编号',
'姓名',
'性别',
'年龄',
'科室',
'主治医生',
'诊断',
'入院日期',
'体温(℃)',
'状态',
]
const patients = [
{
name: '张伟',
gender: '男',
age: 56,
dept: '心血管内科',
doctor: '周建国',
diagnosis: '高血压2级',
date: '2026-08-01',
temp: 36.8,
status: '在院',
},
{
name: '李娜',
gender: '女',
age: 48,
dept: '呼吸内科',
doctor: '吴晓芳',
diagnosis: '社区获得性肺炎',
date: '2026-08-03',
temp: 38.2,
status: '在院',
},
{
name: '王强',
gender: '男',
age: 32,
dept: '骨科',
doctor: '郑海涛',
diagnosis: '右胫骨骨折',
date: '2026-07-28',
temp: 36.5,
status: '在院',
},
{
name: '刘秀英',
gender: '女',
age: 67,
dept: '神经内科',
doctor: '王淑华',
diagnosis: '脑梗死后遗症',
date: '2026-07-15',
temp: 36.9,
status: '转科',
},
{
name: '陈静',
gender: '女',
age: 29,
dept: '妇产科',
doctor: '林志强',
diagnosis: '妊娠期糖尿病',
date: '2026-08-05',
temp: 36.7,
status: '在院',
},
{
name: '杨帆',
gender: '男',
age: 41,
dept: '消化内科',
doctor: '周建国',
diagnosis: '急性胰腺炎',
date: '2026-08-06',
temp: 37.6,
status: '病危',
},
{
name: '赵敏',
gender: '女',
age: 53,
dept: '内分泌科',
doctor: '郑雅琴',
diagnosis: '2型糖尿病',
date: '2026-07-20',
temp: 36.4,
status: '出院',
},
{
name: '孙丽华',
gender: '女',
age: 71,
dept: '心血管内科',
doctor: '周建国',
diagnosis: '冠心病',
date: '2026-08-02',
temp: 37.1,
status: '在院',
},
]
const getCellValue = (rowIndex: number, columnIndex: number) => {
const patient = patients[rowIndex % patients.length]
const values = [
`HZ2026${String(rowIndex + 1).padStart(4, '0')}`,
patient.name,
patient.gender,
patient.age,
patient.dept,
patient.doctor,
patient.diagnosis,
patient.date,
patient.temp,
patient.status,
]
return values[columnIndex % values.length]
}
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: columnTitles[columnIndex % columnTitles.length],
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] = getCellValue(rowIndex, columnIndex)
return rowData
},
{
id: `${prefix}${rowIndex}`,
parentId: null,
}
)
})
const columns = generateColumns(10)
const data = generateData(columns, 1000)
</script>
自动调整大小、设置滚动条尺寸
如果不想手动向表格传递 width 和 height 属性,可以使用 AutoResizer 对表格组件进行封装。 这会自动为你更新宽度和高度。
尝试调整您的浏览器大小来看看它是如何工作的。
TIP
由于 AutoResizer 组件的默认高度是 100%,所以请确保该组件的父元素拥有固定的高度值。 或者,您可以通过将 style 属性传递到 AutoResizer 来定义它。
<template>
<div style="height: 400px">
<w-auto-resizer>
<template #default="{ height, width }">
<w-table-v2
:columns="columns"
:data="data"
:width="width"
:h-scrollbar-size="8"
:scrollbar-size="10"
:height="height"
fixed
/>
</template>
</w-auto-resizer>
</div>
</template>
<script lang="ts" setup>
const columnTitles = [
'患者编号',
'姓名',
'性别',
'年龄',
'科室',
'主治医生',
'诊断',
'入院日期',
'体温(℃)',
'状态',
]
const patients = [
{
name: '张伟',
gender: '男',
age: 56,
dept: '心血管内科',
doctor: '周建国',
diagnosis: '高血压2级',
date: '2026-08-01',
temp: 36.8,
status: '在院',
},
{
name: '李娜',
gender: '女',
age: 48,
dept: '呼吸内科',
doctor: '吴晓芳',
diagnosis: '社区获得性肺炎',
date: '2026-08-03',
temp: 38.2,
status: '在院',
},
{
name: '王强',
gender: '男',
age: 32,
dept: '骨科',
doctor: '郑海涛',
diagnosis: '右胫骨骨折',
date: '2026-07-28',
temp: 36.5,
status: '在院',
},
{
name: '刘秀英',
gender: '女',
age: 67,
dept: '神经内科',
doctor: '王淑华',
diagnosis: '脑梗死后遗症',
date: '2026-07-15',
temp: 36.9,
status: '转科',
},
{
name: '陈静',
gender: '女',
age: 29,
dept: '妇产科',
doctor: '林志强',
diagnosis: '妊娠期糖尿病',
date: '2026-08-05',
temp: 36.7,
status: '在院',
},
{
name: '杨帆',
gender: '男',
age: 41,
dept: '消化内科',
doctor: '周建国',
diagnosis: '急性胰腺炎',
date: '2026-08-06',
temp: 37.6,
status: '病危',
},
{
name: '赵敏',
gender: '女',
age: 53,
dept: '内分泌科',
doctor: '郑雅琴',
diagnosis: '2型糖尿病',
date: '2026-07-20',
temp: 36.4,
status: '出院',
},
{
name: '孙丽华',
gender: '女',
age: 71,
dept: '心血管内科',
doctor: '周建国',
diagnosis: '冠心病',
date: '2026-08-02',
temp: 37.1,
status: '在院',
},
]
const getCellValue = (rowIndex: number, columnIndex: number) => {
const patient = patients[rowIndex % patients.length]
const values = [
`HZ2026${String(rowIndex + 1).padStart(4, '0')}`,
patient.name,
patient.gender,
patient.age,
patient.dept,
patient.doctor,
patient.diagnosis,
patient.date,
patient.temp,
patient.status,
]
return values[columnIndex % values.length]
}
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: columnTitles[columnIndex % columnTitles.length],
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] = getCellValue(rowIndex, columnIndex)
return rowData
},
{
id: `${prefix}${rowIndex}`,
parentId: null,
}
)
})
const columns = generateColumns(10)
const data = generateData(columns, 200)
</script>
自定义单元格渲染器
当然,您可以根据您的需要呈现表格单元格。 这是如何自定义您的单元格的简单例子。
<template>
<div style="height: 400px">
<w-auto-resizer>
<template #default="{ height, width }">
<w-table-v2
:columns="columns"
:data="data"
:width="width"
:height="height"
fixed
/>
</template>
</w-auto-resizer>
</div>
</template>
<script lang="tsx" setup>
import { ref } from 'vue'
import dayjs from 'dayjs'
import {
TableV2FixedDir,
WButton,
WIcon,
WTag,
WTooltip,
} from 'win-design-next'
import { Time } from '@win-design-next/icons-vue'
import type { Column } from 'win-design-next'
const patientNames = [
'张伟',
'李娜',
'王强',
'刘秀英',
'陈静',
'杨帆',
'赵敏',
'孙丽华',
]
const admissionDates = [
'2026-08-01',
'2026-08-03',
'2026-07-28',
'2026-07-15',
'2026-08-05',
'2026-08-06',
'2026-07-20',
'2026-08-02',
]
const depts = [
'心血管内科',
'呼吸内科',
'骨科',
'神经内科',
'妇产科',
'消化内科',
'内分泌科',
'心血管内科',
]
const doctors = [
'周建国',
'吴晓芳',
'郑海涛',
'王淑华',
'林志强',
'周建国',
'郑雅琴',
'周建国',
]
const diagnoses = [
'高血压2级',
'社区获得性肺炎',
'右胫骨骨折',
'脑梗死后遗症',
'妊娠期糖尿病',
'急性胰腺炎',
'2型糖尿病',
'冠心病',
]
const genders = ['男', '女', '男', '女', '女', '男', '女', '女']
const ages = [56, 48, 32, 67, 29, 41, 53, 71]
const temperatures = [36.8, 38.2, 36.5, 36.9, 36.7, 37.6, 36.4, 37.1]
const statuses = [
'在院',
'在院',
'在院',
'转科',
'在院',
'病危',
'出院',
'在院',
]
let id = 0
const dataGenerator = () => ({
id: `random-id-${++id}`,
name: patientNames[(id - 1) % patientNames.length],
date: admissionDates[(id - 1) % admissionDates.length],
gender: genders[(id - 1) % genders.length],
age: ages[(id - 1) % ages.length],
dept: depts[(id - 1) % depts.length],
doctor: doctors[(id - 1) % doctors.length],
diagnosis: diagnoses[(id - 1) % diagnoses.length],
temperature: temperatures[(id - 1) % temperatures.length],
status: statuses[(id - 1) % statuses.length],
})
const columns: Column<any>[] = [
{
key: 'date',
title: '入院日期',
dataKey: 'date',
width: 150,
fixed: TableV2FixedDir.LEFT,
cellRenderer: ({ cellData: date }) => (
<WTooltip content={dayjs(date).format('YYYY/MM/DD')}>
{
<span class="flex items-center">
<WIcon class="mr-3">
<Time />
</WIcon>
{dayjs(date).format('YYYY/MM/DD')}
</span>
}
</WTooltip>
),
},
{
key: 'name',
title: '患者姓名',
dataKey: 'name',
width: 350,
align: 'center',
cellRenderer: ({ cellData: name }) => <WTag>{name}</WTag>,
},
{
key: 'gender',
title: '性别',
dataKey: 'gender',
width: 100,
},
{
key: 'age',
title: '年龄',
dataKey: 'age',
width: 100,
},
{
key: 'dept',
title: '科室',
dataKey: 'dept',
width: 150,
},
{
key: 'doctor',
title: '主治医生',
dataKey: 'doctor',
width: 150,
},
{
key: 'diagnosis',
title: '诊断',
dataKey: 'diagnosis',
width: 150,
},
{
key: 'temperature',
title: '体温(℃)',
dataKey: 'temperature',
width: 120,
},
{
key: 'status',
title: '状态',
dataKey: 'status',
width: 120,
},
{
key: 'operations',
title: '操作',
cellRenderer: () => (
<>
<WButton text size="small" type="primary">
编辑
</WButton>
<WButton text size="small" type="danger">
删除
</WButton>
</>
),
width: 250,
align: 'center',
},
]
const data = ref(Array.from({ length: 200 }).map(dataGenerator))
</script>
带有选择的表格
使用自定义的单元格渲染来给表格组件添加选择的能力。
<template>
<div style="height: 400px">
<w-auto-resizer>
<template #default="{ height, width }">
<w-table-v2
:columns="columns"
:data="data"
:width="width"
:height="height"
fixed
/>
</template>
</w-auto-resizer>
</div>
</template>
<script lang="tsx" setup>
import { ref, unref } from 'vue'
import { WCheckbox } from 'win-design-next'
import type { FunctionalComponent } from 'vue'
import type { CheckboxValueType, Column } from 'win-design-next'
type SelectionCellProps = {
value: boolean
intermediate?: boolean
onChange: (value: CheckboxValueType) => void
}
const SelectionCell: FunctionalComponent<SelectionCellProps> = ({
value,
intermediate = false,
onChange,
}) => {
return (
<WCheckbox
onChange={onChange}
modelValue={value}
indeterminate={intermediate}
/>
)
}
const columnTitles = [
'患者编号',
'姓名',
'性别',
'年龄',
'科室',
'主治医生',
'诊断',
'入院日期',
'体温(℃)',
'状态',
]
const patients = [
{
name: '张伟',
gender: '男',
age: 56,
dept: '心血管内科',
doctor: '周建国',
diagnosis: '高血压2级',
date: '2026-08-01',
temp: 36.8,
status: '在院',
},
{
name: '李娜',
gender: '女',
age: 48,
dept: '呼吸内科',
doctor: '吴晓芳',
diagnosis: '社区获得性肺炎',
date: '2026-08-03',
temp: 38.2,
status: '在院',
},
{
name: '王强',
gender: '男',
age: 32,
dept: '骨科',
doctor: '郑海涛',
diagnosis: '右胫骨骨折',
date: '2026-07-28',
temp: 36.5,
status: '在院',
},
{
name: '刘秀英',
gender: '女',
age: 67,
dept: '神经内科',
doctor: '王淑华',
diagnosis: '脑梗死后遗症',
date: '2026-07-15',
temp: 36.9,
status: '转科',
},
{
name: '陈静',
gender: '女',
age: 29,
dept: '妇产科',
doctor: '林志强',
diagnosis: '妊娠期糖尿病',
date: '2026-08-05',
temp: 36.7,
status: '在院',
},
{
name: '杨帆',
gender: '男',
age: 41,
dept: '消化内科',
doctor: '周建国',
diagnosis: '急性胰腺炎',
date: '2026-08-06',
temp: 37.6,
status: '病危',
},
{
name: '赵敏',
gender: '女',
age: 53,
dept: '内分泌科',
doctor: '郑雅琴',
diagnosis: '2型糖尿病',
date: '2026-07-20',
temp: 36.4,
status: '出院',
},
{
name: '孙丽华',
gender: '女',
age: 71,
dept: '心血管内科',
doctor: '周建国',
diagnosis: '冠心病',
date: '2026-08-02',
temp: 37.1,
status: '在院',
},
]
const getCellValue = (rowIndex: number, columnIndex: number) => {
const patient = patients[rowIndex % patients.length]
const values = [
`HZ2026${String(rowIndex + 1).padStart(4, '0')}`,
patient.name,
patient.gender,
patient.age,
patient.dept,
patient.doctor,
patient.diagnosis,
patient.date,
patient.temp,
patient.status,
]
return values[columnIndex % values.length]
}
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: columnTitles[columnIndex % columnTitles.length],
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] = getCellValue(rowIndex, columnIndex)
return rowData
},
{
id: `${prefix}${rowIndex}`,
checked: false,
parentId: null,
}
)
})
const columns: Column<any>[] = generateColumns(10)
columns.unshift({
key: 'selection',
width: 50,
align: 'center',
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">
<w-auto-resizer>
<template #default="{ height, width }">
<w-table-v2
:columns="columns"
:data="data"
:width="width"
:height="height"
fixed
/>
</template>
</w-auto-resizer>
</div>
</template>
<script lang="tsx" setup>
import { ref, withKeys } from 'vue'
import { WInput } from 'win-design-next'
import type { FunctionalComponent } from 'vue'
import type { Column, InputInstance } from 'win-design-next'
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 (
<WInput
ref={forwardRef as any}
onInput={onChange}
onBlur={onBlur}
onKeydown={withKeys(onKeydownEnter, ['enter'])}
modelValue={value}
/>
)
}
const columnTitles = [
'患者编号',
'姓名',
'性别',
'年龄',
'科室',
'主治医生',
'诊断',
'入院日期',
'体温(℃)',
'状态',
]
const patients = [
{
name: '张伟',
gender: '男',
age: 56,
dept: '心血管内科',
doctor: '周建国',
diagnosis: '高血压2级',
date: '2026-08-01',
temp: 36.8,
status: '在院',
},
{
name: '李娜',
gender: '女',
age: 48,
dept: '呼吸内科',
doctor: '吴晓芳',
diagnosis: '社区获得性肺炎',
date: '2026-08-03',
temp: 38.2,
status: '在院',
},
{
name: '王强',
gender: '男',
age: 32,
dept: '骨科',
doctor: '郑海涛',
diagnosis: '右胫骨骨折',
date: '2026-07-28',
temp: 36.5,
status: '在院',
},
{
name: '刘秀英',
gender: '女',
age: 67,
dept: '神经内科',
doctor: '王淑华',
diagnosis: '脑梗死后遗症',
date: '2026-07-15',
temp: 36.9,
status: '转科',
},
{
name: '陈静',
gender: '女',
age: 29,
dept: '妇产科',
doctor: '林志强',
diagnosis: '妊娠期糖尿病',
date: '2026-08-05',
temp: 36.7,
status: '在院',
},
{
name: '杨帆',
gender: '男',
age: 41,
dept: '消化内科',
doctor: '周建国',
diagnosis: '急性胰腺炎',
date: '2026-08-06',
temp: 37.6,
status: '病危',
},
{
name: '赵敏',
gender: '女',
age: 53,
dept: '内分泌科',
doctor: '郑雅琴',
diagnosis: '2型糖尿病',
date: '2026-07-20',
temp: 36.4,
status: '出院',
},
{
name: '孙丽华',
gender: '女',
age: 71,
dept: '心血管内科',
doctor: '周建国',
diagnosis: '冠心病',
date: '2026-08-02',
temp: 37.1,
status: '在院',
},
]
const getCellValue = (rowIndex: number, columnIndex: number) => {
const patient = patients[rowIndex % patients.length]
const values = [
`HZ2026${String(rowIndex + 1).padStart(4, '0')}`,
patient.name,
patient.gender,
patient.age,
patient.dept,
patient.doctor,
patient.diagnosis,
patient.date,
patient.temp,
patient.status,
]
return values[columnIndex % values.length]
}
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: columnTitles[columnIndex % columnTitles.length],
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] = getCellValue(rowIndex, columnIndex)
return rowData
},
{
id: `${prefix}${rowIndex}`,
editing: false,
parentId: null,
}
)
})
const columns: Column<any>[] = generateColumns(10)
columns[4] = {
...columns[4],
title: '科室(点击编辑)',
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(--w3-color-primary);
}
</style>
带状态的表格
可将表格内容 highlight 显示,方便区分「成功、信息、警告、危险」等内容。
要自定义行的外观,请使用 row-class-name 属性。 举个例子,每 10 行会自动添加 bg-blue-200 类名,每 5 行会添加 bg-red-100 类名。
<template>
<div style="height: 400px">
<w-auto-resizer>
<template #default="{ height, width }">
<w-table-v2
:columns="columns"
:data="data"
:row-class="rowClass"
:width="width"
:height="height"
fixed
/>
</template>
</w-auto-resizer>
</div>
</template>
<script lang="tsx" setup>
import { ref } from 'vue'
import dayjs from 'dayjs'
import {
TableV2FixedDir,
WButton,
WIcon,
WTag,
WTooltip,
} from 'win-design-next'
import { Time } from '@win-design-next/icons-vue'
import type { Column, RowClassNameGetter } from 'win-design-next'
const patientNames = [
'张伟',
'李娜',
'王强',
'刘秀英',
'陈静',
'杨帆',
'赵敏',
'孙丽华',
]
const admissionDates = [
'2026-08-01',
'2026-08-03',
'2026-07-28',
'2026-07-15',
'2026-08-05',
'2026-08-06',
'2026-07-20',
'2026-08-02',
]
const depts = [
'心血管内科',
'呼吸内科',
'骨科',
'神经内科',
'妇产科',
'消化内科',
'内分泌科',
'心血管内科',
]
const doctors = [
'周建国',
'吴晓芳',
'郑海涛',
'王淑华',
'林志强',
'周建国',
'郑雅琴',
'周建国',
]
const diagnoses = [
'高血压2级',
'社区获得性肺炎',
'右胫骨骨折',
'脑梗死后遗症',
'妊娠期糖尿病',
'急性胰腺炎',
'2型糖尿病',
'冠心病',
]
const genders = ['男', '女', '男', '女', '女', '男', '女', '女']
const ages = [56, 48, 32, 67, 29, 41, 53, 71]
const temperatures = [36.8, 38.2, 36.5, 36.9, 36.7, 37.6, 36.4, 37.1]
const statuses = [
'在院',
'在院',
'在院',
'转科',
'在院',
'病危',
'出院',
'在院',
]
let id = 0
const dataGenerator = () => ({
id: `random-id-${++id}`,
name: patientNames[(id - 1) % patientNames.length],
date: admissionDates[(id - 1) % admissionDates.length],
gender: genders[(id - 1) % genders.length],
age: ages[(id - 1) % ages.length],
dept: depts[(id - 1) % depts.length],
doctor: doctors[(id - 1) % doctors.length],
diagnosis: diagnoses[(id - 1) % diagnoses.length],
temperature: temperatures[(id - 1) % temperatures.length],
status: statuses[(id - 1) % statuses.length],
})
const columns: Column<any>[] = [
{
key: 'date',
title: '入院日期',
dataKey: 'date',
width: 250,
fixed: TableV2FixedDir.LEFT,
cellRenderer: ({ cellData: date }) => (
<WTooltip content={dayjs(date).format('YYYY/MM/DD')}>
{
<span class="flex items-center">
<WIcon class="mr-3">
<Time />
</WIcon>
{dayjs(date).format('YYYY/MM/DD')}
</span>
}
</WTooltip>
),
},
{
key: 'name',
title: '患者姓名',
dataKey: 'name',
width: 250,
align: 'center',
cellRenderer: ({ cellData: name }) => <WTag>{name}</WTag>,
},
{
key: 'gender',
title: '性别',
dataKey: 'gender',
width: 100,
},
{
key: 'age',
title: '年龄',
dataKey: 'age',
width: 100,
},
{
key: 'dept',
title: '科室',
dataKey: 'dept',
width: 150,
},
{
key: 'doctor',
title: '主治医生',
dataKey: 'doctor',
width: 150,
},
{
key: 'diagnosis',
title: '诊断',
dataKey: 'diagnosis',
width: 150,
},
{
key: 'temperature',
title: '体温(℃)',
dataKey: 'temperature',
width: 120,
},
{
key: 'status',
title: '状态',
dataKey: 'status',
width: 120,
},
{
key: 'operations',
title: '操作',
cellRenderer: () => (
<>
<WButton text size="small" type="primary">
编辑
</WButton>
<WButton text size="small" type="danger">
删除
</WButton>
</>
),
width: 250,
align: 'center',
},
]
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>
<div style="height: 400px">
<w-auto-resizer>
<template #default="{ height, width }">
<w-table-v2
:columns="columns"
:data="tableData"
:fixed-data="fixedData"
:width="width"
:height="height"
:row-class="rowClass"
fixed
@scroll="onScroll"
/>
</template>
</w-auto-resizer>
</div>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue'
const columnTitles = [
'患者编号',
'姓名',
'性别',
'年龄',
'科室',
'主治医生',
'诊断',
'入院日期',
'体温(℃)',
'状态',
]
const patients = [
{
name: '张伟',
gender: '男',
age: 56,
dept: '心血管内科',
doctor: '周建国',
diagnosis: '高血压2级',
date: '2026-08-01',
temp: 36.8,
status: '在院',
},
{
name: '李娜',
gender: '女',
age: 48,
dept: '呼吸内科',
doctor: '吴晓芳',
diagnosis: '社区获得性肺炎',
date: '2026-08-03',
temp: 38.2,
status: '在院',
},
{
name: '王强',
gender: '男',
age: 32,
dept: '骨科',
doctor: '郑海涛',
diagnosis: '右胫骨骨折',
date: '2026-07-28',
temp: 36.5,
status: '在院',
},
{
name: '刘秀英',
gender: '女',
age: 67,
dept: '神经内科',
doctor: '王淑华',
diagnosis: '脑梗死后遗症',
date: '2026-07-15',
temp: 36.9,
status: '转科',
},
{
name: '陈静',
gender: '女',
age: 29,
dept: '妇产科',
doctor: '林志强',
diagnosis: '妊娠期糖尿病',
date: '2026-08-05',
temp: 36.7,
status: '在院',
},
{
name: '杨帆',
gender: '男',
age: 41,
dept: '消化内科',
doctor: '周建国',
diagnosis: '急性胰腺炎',
date: '2026-08-06',
temp: 37.6,
status: '病危',
},
{
name: '赵敏',
gender: '女',
age: 53,
dept: '内分泌科',
doctor: '郑雅琴',
diagnosis: '2型糖尿病',
date: '2026-07-20',
temp: 36.4,
status: '出院',
},
{
name: '孙丽华',
gender: '女',
age: 71,
dept: '心血管内科',
doctor: '周建国',
diagnosis: '冠心病',
date: '2026-08-02',
temp: 37.1,
status: '在院',
},
]
const getCellValue = (rowIndex: number, columnIndex: number) => {
const patient = patients[rowIndex % patients.length]
const values = [
`HZ2026${String(rowIndex + 1).padStart(4, '0')}`,
patient.name,
patient.gender,
patient.age,
patient.dept,
patient.doctor,
patient.diagnosis,
patient.date,
patient.temp,
patient.status,
]
return values[columnIndex % values.length]
}
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: columnTitles[columnIndex % columnTitles.length],
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] = getCellValue(rowIndex, 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>
.w3-w-table-v2__fixed-header-row {
background-color: var(--w3-color-primary-hover);
font-weight: bold;
color: var(--w3-color-white);
}
</style>
固定列表格
如果您想要有列粘贴左侧或右侧的某种原因。 您可以通过向表中添加特殊属性来实现这一点。
您可以设置该行的 fixed 属性为 true (代表FixedDir.LEFT)、FixedDir.LEFT 或 FixedDir.RIGHT
<template>
<div style="height: 400px">
<w-auto-resizer>
<template #default="{ height, width }">
<w-table-v2
:columns="columns"
:data="data"
:sort-by="sortBy"
:width="width"
:height="height"
fixed
@column-sort="onSort"
/>
</template>
</w-auto-resizer>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { TableV2FixedDir, TableV2SortOrder } from 'win-design-next'
import type { SortBy } from 'win-design-next'
const columnTitles = [
'患者编号',
'姓名',
'性别',
'年龄',
'科室',
'主治医生',
'诊断',
'入院日期',
'体温(℃)',
'状态',
]
const patients = [
{
name: '张伟',
gender: '男',
age: 56,
dept: '心血管内科',
doctor: '周建国',
diagnosis: '高血压2级',
date: '2026-08-01',
temp: 36.8,
status: '在院',
},
{
name: '李娜',
gender: '女',
age: 48,
dept: '呼吸内科',
doctor: '吴晓芳',
diagnosis: '社区获得性肺炎',
date: '2026-08-03',
temp: 38.2,
status: '在院',
},
{
name: '王强',
gender: '男',
age: 32,
dept: '骨科',
doctor: '郑海涛',
diagnosis: '右胫骨骨折',
date: '2026-07-28',
temp: 36.5,
status: '在院',
},
{
name: '刘秀英',
gender: '女',
age: 67,
dept: '神经内科',
doctor: '王淑华',
diagnosis: '脑梗死后遗症',
date: '2026-07-15',
temp: 36.9,
status: '转科',
},
{
name: '陈静',
gender: '女',
age: 29,
dept: '妇产科',
doctor: '林志强',
diagnosis: '妊娠期糖尿病',
date: '2026-08-05',
temp: 36.7,
status: '在院',
},
{
name: '杨帆',
gender: '男',
age: 41,
dept: '消化内科',
doctor: '周建国',
diagnosis: '急性胰腺炎',
date: '2026-08-06',
temp: 37.6,
status: '病危',
},
{
name: '赵敏',
gender: '女',
age: 53,
dept: '内分泌科',
doctor: '郑雅琴',
diagnosis: '2型糖尿病',
date: '2026-07-20',
temp: 36.4,
status: '出院',
},
{
name: '孙丽华',
gender: '女',
age: 71,
dept: '心血管内科',
doctor: '周建国',
diagnosis: '冠心病',
date: '2026-08-02',
temp: 37.1,
status: '在院',
},
]
const getCellValue = (rowIndex: number, columnIndex: number) => {
const patient = patients[rowIndex % patients.length]
const values = [
`HZ2026${String(rowIndex + 1).padStart(4, '0')}`,
patient.name,
patient.gender,
patient.age,
patient.dept,
patient.doctor,
patient.diagnosis,
patient.date,
patient.temp,
patient.status,
]
return values[columnIndex % values.length]
}
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: columnTitles[columnIndex % columnTitles.length],
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] = getCellValue(rowIndex, 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>
表头分组
正如这个示例,通过自定义表头渲染以将表头分组。
<template>
<div style="height: 400px">
<w-auto-resizer>
<template #default="{ height, width }">
<w-table-v2
fixed
:columns="fixedColumns"
:data="data"
:header-height="[50, 40, 50]"
:header-class="headerClass"
:width="width"
:height="height"
>
<template #header="props">
<customized-header v-bind="props" />
</template>
</w-table-v2>
</template>
</w-auto-resizer>
</div>
</template>
<script lang="tsx" setup>
import { TableV2FixedDir, TableV2Placeholder } from 'win-design-next'
import type { FunctionalComponent } from 'vue'
import type {
HeaderClassNameGetter,
TableV2CustomizedHeaderSlotParam,
} from 'win-design-next'
const columnTitles = [
'患者编号',
'姓名',
'性别',
'年龄',
'科室',
'主治医生',
'诊断',
'入院日期',
'体温(℃)',
'状态',
]
const patients = [
{
name: '张伟',
gender: '男',
age: 56,
dept: '心血管内科',
doctor: '周建国',
diagnosis: '高血压2级',
date: '2026-08-01',
temp: 36.8,
status: '在院',
},
{
name: '李娜',
gender: '女',
age: 48,
dept: '呼吸内科',
doctor: '吴晓芳',
diagnosis: '社区获得性肺炎',
date: '2026-08-03',
temp: 38.2,
status: '在院',
},
{
name: '王强',
gender: '男',
age: 32,
dept: '骨科',
doctor: '郑海涛',
diagnosis: '右胫骨骨折',
date: '2026-07-28',
temp: 36.5,
status: '在院',
},
{
name: '刘秀英',
gender: '女',
age: 67,
dept: '神经内科',
doctor: '王淑华',
diagnosis: '脑梗死后遗症',
date: '2026-07-15',
temp: 36.9,
status: '转科',
},
{
name: '陈静',
gender: '女',
age: 29,
dept: '妇产科',
doctor: '林志强',
diagnosis: '妊娠期糖尿病',
date: '2026-08-05',
temp: 36.7,
status: '在院',
},
{
name: '杨帆',
gender: '男',
age: 41,
dept: '消化内科',
doctor: '周建国',
diagnosis: '急性胰腺炎',
date: '2026-08-06',
temp: 37.6,
status: '病危',
},
{
name: '赵敏',
gender: '女',
age: 53,
dept: '内分泌科',
doctor: '郑雅琴',
diagnosis: '2型糖尿病',
date: '2026-07-20',
temp: 36.4,
status: '出院',
},
{
name: '孙丽华',
gender: '女',
age: 71,
dept: '心血管内科',
doctor: '周建国',
diagnosis: '冠心病',
date: '2026-08-02',
temp: 37.1,
status: '在院',
},
]
const getCellValue = (rowIndex: number, columnIndex: number) => {
const patient = patients[rowIndex % patients.length]
const values = [
`HZ2026${String(rowIndex + 1).padStart(4, '0')}`,
patient.name,
patient.gender,
patient.age,
patient.dept,
patient.doctor,
patient.diagnosis,
patient.date,
patient.temp,
patient.status,
]
return values[columnIndex % values.length]
}
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: columnTitles[columnIndex % columnTitles.length],
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] = getCellValue(rowIndex, 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 groupTitles = [
['患者信息', '诊疗信息', '住院信息', '费用信息'],
['患者标识', '人口学特征', '就诊信息', '诊疗记录', '体征状态'],
]
const CustomizedHeader: FunctionalComponent<
TableV2CustomizedHeaderSlotParam
> = ({ cells, columns, headerIndex }) => {
if (headerIndex === 2) return cells
const groupCells = [] as typeof cells
let width = 0
let idx = 0
let groupIndex = 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)
) {
const titles = groupTitles[headerIndex]
groupCells.push(
<div
class="flex items-center justify-center custom-header-cell"
role="columnheader"
style={{
...cells[columnIndex].props!.style,
width: `${width}px`,
}}
>
{`${titles[groupIndex % titles.length]}(宽 ${width}px)`}
</div>
)
width = 0
idx = 0
groupIndex++
}
}
})
return groupCells
}
const headerClass = ({
headerIndex,
}: Parameters<HeaderClassNameGetter<any>>[0]) => {
if (headerIndex === 1) return 'w3-primary-color'
return ''
}
</script>
<style>
.w3-w-table-v2__header-row .custom-header-cell {
border-right: 1px solid var(--w3-border-color);
}
.w3-w-table-v2__header-row .custom-header-cell:last-child {
border-right: none;
}
.w3-primary-color {
background-color: var(--w3-color-primary);
color: var(--w3-color-white);
font-size: 14px;
font-weight: bold;
}
.w3-primary-color .custom-header-cell {
padding: 0 4px;
}
</style>
过滤器
虚拟表格提供自定义页眉渲染器以创建自定义标题。 然后我们可以利用这些来渲染过滤器。
<template>
<div style="height: 400px">
<w-auto-resizer>
<template #default="{ height, width }">
<w-table-v2
fixed
:columns="fixedColumns"
:data="data"
:width="width"
:height="height"
/>
</template>
</w-auto-resizer>
</div>
</template>
<script lang="tsx" setup>
import { ref } from 'vue'
import {
TableV2FixedDir,
WButton,
WCheckbox,
WIcon,
WPopover,
} from 'win-design-next'
import { Filter } from '@win-design-next/icons-vue'
import type { HeaderCellSlotProps } from 'win-design-next'
const columnTitles = [
'患者编号',
'姓名',
'性别',
'年龄',
'科室',
'主治医生',
'诊断',
'入院日期',
'体温(℃)',
'状态',
]
const patients = [
{
name: '张伟',
gender: '男',
age: 56,
dept: '心血管内科',
doctor: '周建国',
diagnosis: '高血压2级',
date: '2026-08-01',
temp: 36.8,
status: '在院',
},
{
name: '李娜',
gender: '女',
age: 48,
dept: '呼吸内科',
doctor: '吴晓芳',
diagnosis: '社区获得性肺炎',
date: '2026-08-03',
temp: 38.2,
status: '在院',
},
{
name: '王强',
gender: '男',
age: 32,
dept: '骨科',
doctor: '郑海涛',
diagnosis: '右胫骨骨折',
date: '2026-07-28',
temp: 36.5,
status: '在院',
},
{
name: '刘秀英',
gender: '女',
age: 67,
dept: '神经内科',
doctor: '王淑华',
diagnosis: '脑梗死后遗症',
date: '2026-07-15',
temp: 36.9,
status: '转科',
},
{
name: '陈静',
gender: '女',
age: 29,
dept: '妇产科',
doctor: '林志强',
diagnosis: '妊娠期糖尿病',
date: '2026-08-05',
temp: 36.7,
status: '在院',
},
{
name: '杨帆',
gender: '男',
age: 41,
dept: '消化内科',
doctor: '周建国',
diagnosis: '急性胰腺炎',
date: '2026-08-06',
temp: 37.6,
status: '病危',
},
{
name: '赵敏',
gender: '女',
age: 53,
dept: '内分泌科',
doctor: '郑雅琴',
diagnosis: '2型糖尿病',
date: '2026-07-20',
temp: 36.4,
status: '出院',
},
{
name: '孙丽华',
gender: '女',
age: 71,
dept: '心血管内科',
doctor: '周建国',
diagnosis: '冠心病',
date: '2026-08-02',
temp: 37.1,
status: '在院',
},
]
const getCellValue = (rowIndex: number, columnIndex: number) => {
const patient = patients[rowIndex % patients.length]
const values = [
`HZ2026${String(rowIndex + 1).padStart(4, '0')}`,
patient.name,
patient.gender,
patient.age,
patient.dept,
patient.doctor,
patient.diagnosis,
patient.date,
patient.temp,
patient.status,
]
return values[columnIndex % values.length]
}
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: columnTitles[columnIndex % columnTitles.length],
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] = getCellValue(rowIndex, 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, 200).filter(
(row) => row['column-9'] === '在院'
)
} 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>
<WPopover ref={popoverRef} trigger="click" {...{ width: 200 }}>
{{
default: () => (
<div class="filter-wrapper">
<div class="filter-group">
<WCheckbox v-model={shouldFilter.value}>
仅显示在院患者
</WCheckbox>
</div>
<div class="w3-table-v2__demo-filter">
<WButton text onClick={onFilter}>
确定
</WButton>
<WButton text onClick={onReset}>
重置
</WButton>
</div>
</div>
),
reference: () => (
<WIcon class="cursor-pointer">
<Filter />
</WIcon>
),
}}
</WPopover>
</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>
.w3-table-v2__demo-filter {
border-top: var(--w3-border);
margin: 12px -12px -12px;
padding: 0 12px;
display: flex;
justify-content: space-between;
}
</style>
可排序表格
您可以使用排序状态来对表格进行排序。
<template>
<div style="height: 400px">
<w-auto-resizer>
<template #default="{ height, width }">
<w-table-v2
:columns="columns"
:data="data"
:sort-by="sortState"
:width="width"
:height="height"
fixed
@column-sort="onSort"
/>
</template>
</w-auto-resizer>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { TableV2SortOrder } from 'win-design-next'
import type { SortBy } from 'win-design-next'
const columnTitles = [
'患者编号',
'姓名',
'性别',
'年龄',
'科室',
'主治医生',
'诊断',
'入院日期',
'体温(℃)',
'状态',
]
const patients = [
{
name: '张伟',
gender: '男',
age: 56,
dept: '心血管内科',
doctor: '周建国',
diagnosis: '高血压2级',
date: '2026-08-01',
temp: 36.8,
status: '在院',
},
{
name: '李娜',
gender: '女',
age: 48,
dept: '呼吸内科',
doctor: '吴晓芳',
diagnosis: '社区获得性肺炎',
date: '2026-08-03',
temp: 38.2,
status: '在院',
},
{
name: '王强',
gender: '男',
age: 32,
dept: '骨科',
doctor: '郑海涛',
diagnosis: '右胫骨骨折',
date: '2026-07-28',
temp: 36.5,
status: '在院',
},
{
name: '刘秀英',
gender: '女',
age: 67,
dept: '神经内科',
doctor: '王淑华',
diagnosis: '脑梗死后遗症',
date: '2026-07-15',
temp: 36.9,
status: '转科',
},
{
name: '陈静',
gender: '女',
age: 29,
dept: '妇产科',
doctor: '林志强',
diagnosis: '妊娠期糖尿病',
date: '2026-08-05',
temp: 36.7,
status: '在院',
},
{
name: '杨帆',
gender: '男',
age: 41,
dept: '消化内科',
doctor: '周建国',
diagnosis: '急性胰腺炎',
date: '2026-08-06',
temp: 37.6,
status: '病危',
},
{
name: '赵敏',
gender: '女',
age: 53,
dept: '内分泌科',
doctor: '郑雅琴',
diagnosis: '2型糖尿病',
date: '2026-07-20',
temp: 36.4,
status: '出院',
},
{
name: '孙丽华',
gender: '女',
age: 71,
dept: '心血管内科',
doctor: '周建国',
diagnosis: '冠心病',
date: '2026-08-02',
temp: 37.1,
status: '在院',
},
]
const getCellValue = (rowIndex: number, columnIndex: number) => {
const patient = patients[rowIndex % patients.length]
const values = [
`HZ2026${String(rowIndex + 1).padStart(4, '0')}`,
patient.name,
patient.gender,
patient.age,
patient.dept,
patient.doctor,
patient.diagnosis,
patient.date,
patient.temp,
patient.status,
]
return values[columnIndex % values.length]
}
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: columnTitles[columnIndex % columnTitles.length],
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] = getCellValue(rowIndex, 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>
<div style="height: 400px">
<w-auto-resizer>
<template #default="{ height, width }">
<w-table-v2
v-model:sort-state="sortState"
:columns="columns"
:data="data"
:width="width"
:height="height"
fixed
@column-sort="onSort"
/>
</template>
</w-auto-resizer>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { TableV2SortOrder } from 'win-design-next'
import type { SortBy, SortState } from 'win-design-next'
const columnTitles = [
'患者编号',
'姓名',
'性别',
'年龄',
'科室',
'主治医生',
'诊断',
'入院日期',
'体温(℃)',
'状态',
]
const patients = [
{
name: '张伟',
gender: '男',
age: 56,
dept: '心血管内科',
doctor: '周建国',
diagnosis: '高血压2级',
date: '2026-08-01',
temp: 36.8,
status: '在院',
},
{
name: '李娜',
gender: '女',
age: 48,
dept: '呼吸内科',
doctor: '吴晓芳',
diagnosis: '社区获得性肺炎',
date: '2026-08-03',
temp: 38.2,
status: '在院',
},
{
name: '王强',
gender: '男',
age: 32,
dept: '骨科',
doctor: '郑海涛',
diagnosis: '右胫骨骨折',
date: '2026-07-28',
temp: 36.5,
status: '在院',
},
{
name: '刘秀英',
gender: '女',
age: 67,
dept: '神经内科',
doctor: '王淑华',
diagnosis: '脑梗死后遗症',
date: '2026-07-15',
temp: 36.9,
status: '转科',
},
{
name: '陈静',
gender: '女',
age: 29,
dept: '妇产科',
doctor: '林志强',
diagnosis: '妊娠期糖尿病',
date: '2026-08-05',
temp: 36.7,
status: '在院',
},
{
name: '杨帆',
gender: '男',
age: 41,
dept: '消化内科',
doctor: '周建国',
diagnosis: '急性胰腺炎',
date: '2026-08-06',
temp: 37.6,
status: '病危',
},
{
name: '赵敏',
gender: '女',
age: 53,
dept: '内分泌科',
doctor: '郑雅琴',
diagnosis: '2型糖尿病',
date: '2026-07-20',
temp: 36.4,
status: '出院',
},
{
name: '孙丽华',
gender: '女',
age: 71,
dept: '心血管内科',
doctor: '周建国',
diagnosis: '冠心病',
date: '2026-08-02',
temp: 37.1,
status: '在院',
},
]
const getCellValue = (rowIndex: number, columnIndex: number) => {
const patient = patients[rowIndex % patients.length]
const values = [
`HZ2026${String(rowIndex + 1).padStart(4, '0')}`,
patient.name,
patient.gender,
patient.age,
patient.dept,
patient.doctor,
patient.diagnosis,
patient.date,
patient.temp,
patient.status,
]
return values[columnIndex % values.length]
}
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: columnTitles[columnIndex % columnTitles.length],
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] = getCellValue(rowIndex, 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">
<w-auto-resizer>
<template #default="{ height, width }">
<w-table-v2
:columns="columns"
:cell-props="cellProps"
:class="kls"
:data="data"
:width="width"
:height="height"
fixed
/>
</template>
</w-auto-resizer>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const columnTitles = [
'患者编号',
'姓名',
'性别',
'年龄',
'科室',
'主治医生',
'诊断',
'入院日期',
'体温(℃)',
'状态',
]
const patients = [
{
name: '张伟',
gender: '男',
age: 56,
dept: '心血管内科',
doctor: '周建国',
diagnosis: '高血压2级',
date: '2026-08-01',
temp: 36.8,
status: '在院',
},
{
name: '李娜',
gender: '女',
age: 48,
dept: '呼吸内科',
doctor: '吴晓芳',
diagnosis: '社区获得性肺炎',
date: '2026-08-03',
temp: 38.2,
status: '在院',
},
{
name: '王强',
gender: '男',
age: 32,
dept: '骨科',
doctor: '郑海涛',
diagnosis: '右胫骨骨折',
date: '2026-07-28',
temp: 36.5,
status: '在院',
},
{
name: '刘秀英',
gender: '女',
age: 67,
dept: '神经内科',
doctor: '王淑华',
diagnosis: '脑梗死后遗症',
date: '2026-07-15',
temp: 36.9,
status: '转科',
},
{
name: '陈静',
gender: '女',
age: 29,
dept: '妇产科',
doctor: '林志强',
diagnosis: '妊娠期糖尿病',
date: '2026-08-05',
temp: 36.7,
status: '在院',
},
{
name: '杨帆',
gender: '男',
age: 41,
dept: '消化内科',
doctor: '周建国',
diagnosis: '急性胰腺炎',
date: '2026-08-06',
temp: 37.6,
status: '病危',
},
{
name: '赵敏',
gender: '女',
age: 53,
dept: '内分泌科',
doctor: '郑雅琴',
diagnosis: '2型糖尿病',
date: '2026-07-20',
temp: 36.4,
status: '出院',
},
{
name: '孙丽华',
gender: '女',
age: 71,
dept: '心血管内科',
doctor: '周建国',
diagnosis: '冠心病',
date: '2026-08-02',
temp: 37.1,
status: '在院',
},
]
const getCellValue = (rowIndex: number, columnIndex: number) => {
const patient = patients[rowIndex % patients.length]
const values = [
`HZ2026${String(rowIndex + 1).padStart(4, '0')}`,
patient.name,
patient.gender,
patient.age,
patient.dept,
patient.doctor,
patient.diagnosis,
patient.date,
patient.temp,
patient.status,
]
return values[columnIndex % values.length]
}
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: columnTitles[columnIndex % columnTitles.length],
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] = getCellValue(rowIndex, columnIndex)
return rowData
},
{
id: `${prefix}${rowIndex}`,
parentId: null,
}
)
})
const columns = generateColumns(10)
columns.unshift({
key: 'column-n-1',
width: 80,
title: '序号',
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(--w3-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>
<div style="height: 400px">
<w-auto-resizer>
<template #default="{ height, width }">
<w-table-v2
fixed
:columns="columns"
:data="data"
:width="width"
:height="height"
>
<template #row="props">
<Row v-bind="props" />
</template>
</w-table-v2>
</template>
</w-auto-resizer>
</div>
</template>
<script lang="ts" setup>
import { cloneVNode } from 'vue'
const columnTitles = [
'患者编号',
'姓名',
'性别',
'年龄',
'科室',
'主治医生',
'诊断',
'入院日期',
'体温(℃)',
'状态',
]
const patients = [
{
name: '张伟',
gender: '男',
age: 56,
dept: '心血管内科',
doctor: '周建国',
diagnosis: '高血压2级',
date: '2026-08-01',
temp: 36.8,
status: '在院',
},
{
name: '李娜',
gender: '女',
age: 48,
dept: '呼吸内科',
doctor: '吴晓芳',
diagnosis: '社区获得性肺炎',
date: '2026-08-03',
temp: 38.2,
status: '在院',
},
{
name: '王强',
gender: '男',
age: 32,
dept: '骨科',
doctor: '郑海涛',
diagnosis: '右胫骨骨折',
date: '2026-07-28',
temp: 36.5,
status: '在院',
},
{
name: '刘秀英',
gender: '女',
age: 67,
dept: '神经内科',
doctor: '王淑华',
diagnosis: '脑梗死后遗症',
date: '2026-07-15',
temp: 36.9,
status: '转科',
},
{
name: '陈静',
gender: '女',
age: 29,
dept: '妇产科',
doctor: '林志强',
diagnosis: '妊娠期糖尿病',
date: '2026-08-05',
temp: 36.7,
status: '在院',
},
{
name: '杨帆',
gender: '男',
age: 41,
dept: '消化内科',
doctor: '周建国',
diagnosis: '急性胰腺炎',
date: '2026-08-06',
temp: 37.6,
status: '病危',
},
{
name: '赵敏',
gender: '女',
age: 53,
dept: '内分泌科',
doctor: '郑雅琴',
diagnosis: '2型糖尿病',
date: '2026-07-20',
temp: 36.4,
status: '出院',
},
{
name: '孙丽华',
gender: '女',
age: 71,
dept: '心血管内科',
doctor: '周建国',
diagnosis: '冠心病',
date: '2026-08-02',
temp: 37.1,
status: '在院',
},
]
const getCellValue = (rowIndex: number, columnIndex: number) => {
const patient = patients[rowIndex % patients.length]
const values = [
`HZ2026${String(rowIndex + 1).padStart(4, '0')}`,
patient.name,
patient.gender,
patient.age,
patient.dept,
patient.doctor,
patient.diagnosis,
patient.date,
patient.temp,
patient.status,
]
return values[columnIndex % values.length]
}
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: columnTitles[columnIndex % columnTitles.length],
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] = getCellValue(rowIndex, 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(--w3-color-primary-hover)',
color: 'var(--w3-color-white)',
}
cells[colSpanIndex] = cloneVNode(cells[colSpanIndex], { style })
}
return cells
}
</script>
纵跨行
既然我们已经覆盖了 Colspan,那我们也可以覆盖 Row 也是没问题的。 它与 colspan 略有不同,但是 的想法基本上是一样的。
<template>
<div style="height: 400px">
<w-auto-resizer>
<template #default="{ height, width }">
<w-table-v2
fixed
:columns="columns"
:data="data"
:width="width"
:height="height"
>
<template #row="props">
<Row v-bind="props" />
</template>
</w-table-v2>
</template>
</w-auto-resizer>
</div>
</template>
<script lang="ts" setup>
import { cloneVNode } from 'vue'
const columnTitles = [
'患者编号',
'姓名',
'性别',
'年龄',
'科室',
'主治医生',
'诊断',
'入院日期',
'体温(℃)',
'状态',
]
const patients = [
{
name: '张伟',
gender: '男',
age: 56,
dept: '心血管内科',
doctor: '周建国',
diagnosis: '高血压2级',
date: '2026-08-01',
temp: 36.8,
status: '在院',
},
{
name: '李娜',
gender: '女',
age: 48,
dept: '呼吸内科',
doctor: '吴晓芳',
diagnosis: '社区获得性肺炎',
date: '2026-08-03',
temp: 38.2,
status: '在院',
},
{
name: '王强',
gender: '男',
age: 32,
dept: '骨科',
doctor: '郑海涛',
diagnosis: '右胫骨骨折',
date: '2026-07-28',
temp: 36.5,
status: '在院',
},
{
name: '刘秀英',
gender: '女',
age: 67,
dept: '神经内科',
doctor: '王淑华',
diagnosis: '脑梗死后遗症',
date: '2026-07-15',
temp: 36.9,
status: '转科',
},
{
name: '陈静',
gender: '女',
age: 29,
dept: '妇产科',
doctor: '林志强',
diagnosis: '妊娠期糖尿病',
date: '2026-08-05',
temp: 36.7,
status: '在院',
},
{
name: '杨帆',
gender: '男',
age: 41,
dept: '消化内科',
doctor: '周建国',
diagnosis: '急性胰腺炎',
date: '2026-08-06',
temp: 37.6,
status: '病危',
},
{
name: '赵敏',
gender: '女',
age: 53,
dept: '内分泌科',
doctor: '郑雅琴',
diagnosis: '2型糖尿病',
date: '2026-07-20',
temp: 36.4,
status: '出院',
},
{
name: '孙丽华',
gender: '女',
age: 71,
dept: '心血管内科',
doctor: '周建国',
diagnosis: '冠心病',
date: '2026-08-02',
temp: 37.1,
status: '在院',
},
]
const getCellValue = (rowIndex: number, columnIndex: number) => {
const patient = patients[rowIndex % patients.length]
const values = [
`HZ2026${String(rowIndex + 1).padStart(4, '0')}`,
patient.name,
patient.gender,
patient.age,
patient.dept,
patient.doctor,
patient.diagnosis,
patient.date,
patient.temp,
patient.status,
]
return values[columnIndex % values.length]
}
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: columnTitles[columnIndex % columnTitles.length],
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] = getCellValue(rowIndex, 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(--w3-color-primary-hover)',
height: `${rowSpan * 32 - 1}px`,
alignSelf: 'flex-start',
zIndex: 1,
color: 'var(--w3-color-white)',
}
cells[rowSpanIndex] = cloneVNode(cell, { style })
}
return cells
}
</script>
同时跨行和跨列
我们当然可以同时使用横跨列与纵跨行来满足您的业务需求!
<template>
<div style="height: 400px">
<w-auto-resizer>
<template #default="{ height, width }">
<w-table-v2
fixed
:columns="columns"
:data="data"
:width="width"
:height="height"
>
<template #row="props">
<Row v-bind="props" />
</template>
</w-table-v2>
</template>
</w-auto-resizer>
</div>
</template>
<script lang="tsx" setup>
import { cloneVNode } from 'vue'
const columnTitles = [
'患者编号',
'姓名',
'性别',
'年龄',
'科室',
'主治医生',
'诊断',
'入院日期',
'体温(℃)',
'状态',
]
const patients = [
{
name: '张伟',
gender: '男',
age: 56,
dept: '心血管内科',
doctor: '周建国',
diagnosis: '高血压2级',
date: '2026-08-01',
temp: 36.8,
status: '在院',
},
{
name: '李娜',
gender: '女',
age: 48,
dept: '呼吸内科',
doctor: '吴晓芳',
diagnosis: '社区获得性肺炎',
date: '2026-08-03',
temp: 38.2,
status: '在院',
},
{
name: '王强',
gender: '男',
age: 32,
dept: '骨科',
doctor: '郑海涛',
diagnosis: '右胫骨骨折',
date: '2026-07-28',
temp: 36.5,
status: '在院',
},
{
name: '刘秀英',
gender: '女',
age: 67,
dept: '神经内科',
doctor: '王淑华',
diagnosis: '脑梗死后遗症',
date: '2026-07-15',
temp: 36.9,
status: '转科',
},
{
name: '陈静',
gender: '女',
age: 29,
dept: '妇产科',
doctor: '林志强',
diagnosis: '妊娠期糖尿病',
date: '2026-08-05',
temp: 36.7,
status: '在院',
},
{
name: '杨帆',
gender: '男',
age: 41,
dept: '消化内科',
doctor: '周建国',
diagnosis: '急性胰腺炎',
date: '2026-08-06',
temp: 37.6,
status: '病危',
},
{
name: '赵敏',
gender: '女',
age: 53,
dept: '内分泌科',
doctor: '郑雅琴',
diagnosis: '2型糖尿病',
date: '2026-07-20',
temp: 36.4,
status: '出院',
},
{
name: '孙丽华',
gender: '女',
age: 71,
dept: '心血管内科',
doctor: '周建国',
diagnosis: '冠心病',
date: '2026-08-02',
temp: 37.1,
status: '在院',
},
]
const getCellValue = (rowIndex: number, columnIndex: number) => {
const patient = patients[rowIndex % patients.length]
const values = [
`HZ2026${String(rowIndex + 1).padStart(4, '0')}`,
patient.name,
patient.gender,
patient.age,
patient.dept,
patient.doctor,
patient.diagnosis,
patient.date,
patient.temp,
patient.status,
]
return values[columnIndex % values.length]
}
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: columnTitles[columnIndex % columnTitles.length],
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] = getCellValue(rowIndex, 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(--w3-color-primary-hover)',
color: 'var(--w3-color-white)',
}
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(--w3-color-danger-hover)',
height: `${rowSpan * 32}px`,
alignSelf: 'flex-start',
zIndex: 1,
color: 'var(--w3-color-white)',
}
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>
<div style="height: 400px">
<w-auto-resizer>
<template #default="{ height, width }">
<w-table-v2
v-model:expanded-row-keys="expandedRowKeys"
:columns="columns"
:data="treeData"
:width="width"
:expand-column-key="expandColumnKey"
:height="height"
fixed
@row-expand="onRowExpanded"
@expanded-rows-change="onExpandedRowsChange"
/>
</template>
</w-auto-resizer>
</div>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue'
import { TableV2FixedDir } from 'win-design-next'
import type {
ExpandedRowsChangeHandler,
RowExpandHandler,
} from 'win-design-next'
const columnTitles = [
'患者编号',
'姓名',
'性别',
'年龄',
'科室',
'主治医生',
'诊断',
'入院日期',
'体温(℃)',
'状态',
]
const patients = [
{
name: '张伟',
gender: '男',
age: 56,
dept: '心血管内科',
doctor: '周建国',
diagnosis: '高血压2级',
date: '2026-08-01',
temp: 36.8,
status: '在院',
},
{
name: '李娜',
gender: '女',
age: 48,
dept: '呼吸内科',
doctor: '吴晓芳',
diagnosis: '社区获得性肺炎',
date: '2026-08-03',
temp: 38.2,
status: '在院',
},
{
name: '王强',
gender: '男',
age: 32,
dept: '骨科',
doctor: '郑海涛',
diagnosis: '右胫骨骨折',
date: '2026-07-28',
temp: 36.5,
status: '在院',
},
{
name: '刘秀英',
gender: '女',
age: 67,
dept: '神经内科',
doctor: '王淑华',
diagnosis: '脑梗死后遗症',
date: '2026-07-15',
temp: 36.9,
status: '转科',
},
{
name: '陈静',
gender: '女',
age: 29,
dept: '妇产科',
doctor: '林志强',
diagnosis: '妊娠期糖尿病',
date: '2026-08-05',
temp: 36.7,
status: '在院',
},
{
name: '杨帆',
gender: '男',
age: 41,
dept: '消化内科',
doctor: '周建国',
diagnosis: '急性胰腺炎',
date: '2026-08-06',
temp: 37.6,
status: '病危',
},
{
name: '赵敏',
gender: '女',
age: 53,
dept: '内分泌科',
doctor: '郑雅琴',
diagnosis: '2型糖尿病',
date: '2026-07-20',
temp: 36.4,
status: '出院',
},
{
name: '孙丽华',
gender: '女',
age: 71,
dept: '心血管内科',
doctor: '周建国',
diagnosis: '冠心病',
date: '2026-08-02',
temp: 37.1,
status: '在院',
},
]
const getCellValue = (rowIndex: number, columnIndex: number) => {
const patient = patients[rowIndex % patients.length]
const values = [
`HZ2026${String(rowIndex + 1).padStart(4, '0')}`,
patient.name,
patient.gender,
patient.age,
patient.dept,
patient.doctor,
patient.diagnosis,
patient.date,
patient.temp,
patient.status,
]
return values[columnIndex % values.length]
}
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: columnTitles[columnIndex % columnTitles.length],
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] = getCellValue(rowIndex, 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]: `病程记录 ${i + 1}`,
},
{
...data[2],
id: `${data[2].id}-sub-${i}`,
parentId: data[2].id,
[expandColumnKey]: `病程记录 ${i + 1}`,
},
{
...data[2],
id: `${data[2].id}-sub-sub-${i}`,
parentId: `${data[2].id}-sub-${i}`,
[expandColumnKey]: `复查记录 ${i + 1}`,
}
)
}
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 属性。 估计高度越接近实际内容,渲染体验就越顺。
TIP
每行高度在渲染过程中动态测量。 因此,如果您试图显示大量数据, UI 可能会 抖动。
<template>
<div style="height: 400px">
<w-auto-resizer>
<template #default="{ height, width }">
<w-table-v2
:columns="columns"
:data="data"
:sort-by="sort"
:estimated-row-height="40"
:width="width"
:height="height"
fixed
@column-sort="onColumnSort"
/>
</template>
</w-auto-resizer>
</div>
</template>
<script lang="tsx" setup>
import { ref } from 'vue'
import {
TableV2FixedDir,
TableV2SortOrder,
WButton,
WTag,
} from 'win-design-next'
import type { Column, SortBy } from 'win-design-next'
const longText =
'患者神志清楚,精神可,主诉偶有胸闷心悸,心电监护未见明显异常,继续当前抗凝治疗方案,密切观察病情变化并准确记录二十四小时出入量。'
const midText =
'术后第二天,切口愈合良好,体温正常,遵医嘱流质饮食并适当下床活动。'
const shortText = '生命体征平稳,继续观察。'
const textList = [shortText, midText, longText]
// generate random number in range 0 to 2
const patientNames = [
'张伟',
'李娜',
'王强',
'刘秀英',
'陈静',
'杨帆',
'赵敏',
'孙丽华',
]
const depts = [
'心血管内科',
'呼吸内科',
'骨科',
'神经内科',
'妇产科',
'消化内科',
'内分泌科',
'心血管内科',
]
const doctors = [
'周建国',
'吴晓芳',
'郑海涛',
'王淑华',
'林志强',
'周建国',
'郑雅琴',
'周建国',
]
const diagnoses = [
'高血压2级',
'社区获得性肺炎',
'右胫骨骨折',
'脑梗死后遗症',
'妊娠期糖尿病',
'急性胰腺炎',
'2型糖尿病',
'冠心病',
]
let id = 0
const dataGenerator = () => ({
id: `HZ2026${String(++id).padStart(4, '0')}`,
name: patientNames[(id - 1) % patientNames.length],
dept: depts[(id - 1) % depts.length],
doctor: doctors[(id - 1) % doctors.length],
date: '2026-08-01',
diagnosis: diagnoses[(id - 1) % diagnoses.length],
description: textList[Math.floor(Math.random() * 3)],
})
const columns: Column<any>[] = [
{
key: 'id',
title: '患者编号',
dataKey: 'id',
width: 150,
sortable: true,
fixed: TableV2FixedDir.LEFT,
},
{
key: 'name',
title: '患者姓名',
dataKey: 'name',
width: 150,
align: 'center',
cellRenderer: ({ cellData: name }) => <WTag>{name}</WTag>,
},
{
key: 'dept',
title: '科室',
dataKey: 'dept',
width: 150,
},
{
key: 'doctor',
title: '主治医生',
dataKey: 'doctor',
width: 150,
},
{
key: 'description',
title: '病情描述',
dataKey: 'description',
width: 150,
cellRenderer: ({ cellData: description }) => (
<div style="padding: 10px 0;">{description}</div>
),
},
{
key: 'diagnosis',
title: '诊断',
dataKey: 'diagnosis',
width: 200,
},
{
key: 'operations',
title: '操作',
cellRenderer: () => (
<>
<WButton text size="small" type="primary">
编辑
</WButton>
<WButton text size="small" type="danger">
删除
</WButton>
</>
),
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>
<div style="height: 400px">
<w-auto-resizer>
<template #default="{ height, width }">
<w-table-v2
:columns="columns"
:data="data"
:estimated-row-height="50"
:expand-column-key="columns[0].key"
:width="width"
:height="height"
fixed
>
<template #row="props">
<Row v-bind="props" />
</template>
</w-table-v2>
</template>
</w-auto-resizer>
</div>
</template>
<script lang="tsx" setup>
import { ref } from 'vue'
const detailedText = `入院后完善心电图、血常规、生化等检查,心电监护示窦性心律,血压控制在 130/85mmHg 左右。
主诉偶有头晕、胸闷,无恶心呕吐,夜间睡眠尚可,已交代卧床休息及防跌倒注意事项。
目前给予降压、抗凝等对症支持治疗,密切观察病情变化,准确记录 24 小时出入量。
责任护士每两小时巡视一次,指导低盐低脂饮食,协助完成生活护理与心理疏导。
继续监测体温及生命体征变化,如出现异常及时通知值班医生处理。
出院前需复查心电图与肝肾功能,评估恢复情况后安排后续随访计划。`
const columnTitles = [
'患者编号',
'姓名',
'性别',
'年龄',
'科室',
'主治医生',
'诊断',
'入院日期',
'体温(℃)',
'状态',
]
const patients = [
{
name: '张伟',
gender: '男',
age: 56,
dept: '心血管内科',
doctor: '周建国',
diagnosis: '高血压2级',
date: '2026-08-01',
temp: 36.8,
status: '在院',
},
{
name: '李娜',
gender: '女',
age: 48,
dept: '呼吸内科',
doctor: '吴晓芳',
diagnosis: '社区获得性肺炎',
date: '2026-08-03',
temp: 38.2,
status: '在院',
},
{
name: '王强',
gender: '男',
age: 32,
dept: '骨科',
doctor: '郑海涛',
diagnosis: '右胫骨骨折',
date: '2026-07-28',
temp: 36.5,
status: '在院',
},
{
name: '刘秀英',
gender: '女',
age: 67,
dept: '神经内科',
doctor: '王淑华',
diagnosis: '脑梗死后遗症',
date: '2026-07-15',
temp: 36.9,
status: '转科',
},
{
name: '陈静',
gender: '女',
age: 29,
dept: '妇产科',
doctor: '林志强',
diagnosis: '妊娠期糖尿病',
date: '2026-08-05',
temp: 36.7,
status: '在院',
},
{
name: '杨帆',
gender: '男',
age: 41,
dept: '消化内科',
doctor: '周建国',
diagnosis: '急性胰腺炎',
date: '2026-08-06',
temp: 37.6,
status: '病危',
},
{
name: '赵敏',
gender: '女',
age: 53,
dept: '内分泌科',
doctor: '郑雅琴',
diagnosis: '2型糖尿病',
date: '2026-07-20',
temp: 36.4,
status: '出院',
},
{
name: '孙丽华',
gender: '女',
age: 71,
dept: '心血管内科',
doctor: '周建国',
diagnosis: '冠心病',
date: '2026-08-02',
temp: 37.1,
status: '在院',
},
]
const getCellValue = (rowIndex: number, columnIndex: number) => {
const patient = patients[rowIndex % patients.length]
const values = [
`HZ2026${String(rowIndex + 1).padStart(4, '0')}`,
patient.name,
patient.gender,
patient.age,
patient.dept,
patient.doctor,
patient.diagnosis,
patient.date,
patient.temp,
patient.status,
]
return values[columnIndex % values.length]
}
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: columnTitles[columnIndex % columnTitles.length],
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] = getCellValue(rowIndex, 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>
.w3-table-v2__row-depth-0 {
height: 50px;
}
.w3-table-v2__cell-text {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>
自定义页脚
自定义表格 footer, 通常用来展示一些汇总数据和信息。
<template>
<div style="height: 400px">
<w-auto-resizer>
<template #default="{ height, width }">
<w-table-v2
:columns="columns"
:data="data"
:row-height="40"
:width="width"
:height="height"
:footer-height="50"
fixed
>
<template #footer
><div
class="flex items-center"
style="
justify-content: center;
height: 100%;
background-color: var(--w3-color-primary-active-bg);
"
>
共 200 名住院患者,数据统计截至 2026-08-20
</div>
</template>
</w-table-v2>
</template>
</w-auto-resizer>
</div>
</template>
<script lang="ts" setup>
const columnTitles = [
'患者编号',
'姓名',
'性别',
'年龄',
'科室',
'主治医生',
'诊断',
'入院日期',
'体温(℃)',
'状态',
]
const patients = [
{
name: '张伟',
gender: '男',
age: 56,
dept: '心血管内科',
doctor: '周建国',
diagnosis: '高血压2级',
date: '2026-08-01',
temp: 36.8,
status: '在院',
},
{
name: '李娜',
gender: '女',
age: 48,
dept: '呼吸内科',
doctor: '吴晓芳',
diagnosis: '社区获得性肺炎',
date: '2026-08-03',
temp: 38.2,
status: '在院',
},
{
name: '王强',
gender: '男',
age: 32,
dept: '骨科',
doctor: '郑海涛',
diagnosis: '右胫骨骨折',
date: '2026-07-28',
temp: 36.5,
status: '在院',
},
{
name: '刘秀英',
gender: '女',
age: 67,
dept: '神经内科',
doctor: '王淑华',
diagnosis: '脑梗死后遗症',
date: '2026-07-15',
temp: 36.9,
status: '转科',
},
{
name: '陈静',
gender: '女',
age: 29,
dept: '妇产科',
doctor: '林志强',
diagnosis: '妊娠期糖尿病',
date: '2026-08-05',
temp: 36.7,
status: '在院',
},
{
name: '杨帆',
gender: '男',
age: 41,
dept: '消化内科',
doctor: '周建国',
diagnosis: '急性胰腺炎',
date: '2026-08-06',
temp: 37.6,
status: '病危',
},
{
name: '赵敏',
gender: '女',
age: 53,
dept: '内分泌科',
doctor: '郑雅琴',
diagnosis: '2型糖尿病',
date: '2026-07-20',
temp: 36.4,
status: '出院',
},
{
name: '孙丽华',
gender: '女',
age: 71,
dept: '心血管内科',
doctor: '周建国',
diagnosis: '冠心病',
date: '2026-08-02',
temp: 37.1,
status: '在院',
},
]
const getCellValue = (rowIndex: number, columnIndex: number) => {
const patient = patients[rowIndex % patients.length]
const values = [
`HZ2026${String(rowIndex + 1).padStart(4, '0')}`,
patient.name,
patient.gender,
patient.age,
patient.dept,
patient.doctor,
patient.diagnosis,
patient.date,
patient.temp,
patient.status,
]
return values[columnIndex % values.length]
}
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: columnTitles[columnIndex % columnTitles.length],
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] = getCellValue(rowIndex, columnIndex)
return rowData
},
{
id: `${prefix}${rowIndex}`,
parentId: null,
}
)
})
const columns = generateColumns(10)
const data = generateData(columns, 200)
</script>
自定义空元素渲染器
渲染自定义的空元素
暂无数据
<template>
<div style="height: 400px">
<w-auto-resizer>
<template #default="{ height, width }">
<w-table-v2
:columns="columns"
:data="[]"
:row-height="40"
:width="width"
:height="height"
:footer-height="50"
>
<template #empty>
<div class="flex items-center justify-center h-100%">
<w-empty />
</div>
</template>
</w-table-v2>
</template>
</w-auto-resizer>
</div>
</template>
<script lang="tsx" setup>
const columnTitles = [
'患者编号',
'姓名',
'性别',
'年龄',
'科室',
'主治医生',
'诊断',
'入院日期',
'体温(℃)',
'状态',
]
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: columnTitles[columnIndex % columnTitles.length],
width: 150,
}))
const columns = generateColumns(10)
</script>
浮动遮罩层
当您想要显示加载指示器之类的浮动元素,可以通过渲染一个浮动在表格之上的遮罩层来实现。
<template>
<div style="height: 400px">
<w-auto-resizer>
<template #default="{ height, width }">
<w-table-v2
:columns="columns"
:data="data"
:row-height="40"
:width="width"
:height="height"
fixed
>
<template #overlay>
<div
class="w3-loading-mask"
style="
display: flex;
align-items: center;
justify-content: center;
"
>
<w-icon
class="is-loading"
color="var(--w3-color-primary)"
:size="26"
>
<loading-icon />
</w-icon>
</div>
</template>
</w-table-v2>
</template>
</w-auto-resizer>
</div>
</template>
<script lang="ts" setup>
import { Loading as LoadingIcon } from '@win-design-next/icons-vue'
const columnTitles = [
'患者编号',
'姓名',
'性别',
'年龄',
'科室',
'主治医生',
'诊断',
'入院日期',
'体温(℃)',
'状态',
]
const patients = [
{
name: '张伟',
gender: '男',
age: 56,
dept: '心血管内科',
doctor: '周建国',
diagnosis: '高血压2级',
date: '2026-08-01',
temp: 36.8,
status: '在院',
},
{
name: '李娜',
gender: '女',
age: 48,
dept: '呼吸内科',
doctor: '吴晓芳',
diagnosis: '社区获得性肺炎',
date: '2026-08-03',
temp: 38.2,
status: '在院',
},
{
name: '王强',
gender: '男',
age: 32,
dept: '骨科',
doctor: '郑海涛',
diagnosis: '右胫骨骨折',
date: '2026-07-28',
temp: 36.5,
status: '在院',
},
{
name: '刘秀英',
gender: '女',
age: 67,
dept: '神经内科',
doctor: '王淑华',
diagnosis: '脑梗死后遗症',
date: '2026-07-15',
temp: 36.9,
status: '转科',
},
{
name: '陈静',
gender: '女',
age: 29,
dept: '妇产科',
doctor: '林志强',
diagnosis: '妊娠期糖尿病',
date: '2026-08-05',
temp: 36.7,
status: '在院',
},
{
name: '杨帆',
gender: '男',
age: 41,
dept: '消化内科',
doctor: '周建国',
diagnosis: '急性胰腺炎',
date: '2026-08-06',
temp: 37.6,
status: '病危',
},
{
name: '赵敏',
gender: '女',
age: 53,
dept: '内分泌科',
doctor: '郑雅琴',
diagnosis: '2型糖尿病',
date: '2026-07-20',
temp: 36.4,
status: '出院',
},
{
name: '孙丽华',
gender: '女',
age: 71,
dept: '心血管内科',
doctor: '周建国',
diagnosis: '冠心病',
date: '2026-08-02',
temp: 37.1,
status: '在院',
},
]
const getCellValue = (rowIndex: number, columnIndex: number) => {
const patient = patients[rowIndex % patients.length]
const values = [
`HZ2026${String(rowIndex + 1).padStart(4, '0')}`,
patient.name,
patient.gender,
patient.age,
patient.dept,
patient.doctor,
patient.diagnosis,
patient.date,
patient.temp,
patient.status,
]
return values[columnIndex % values.length]
}
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: columnTitles[columnIndex % columnTitles.length],
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] = getCellValue(rowIndex, columnIndex)
return rowData
},
{
id: `${prefix}${rowIndex}`,
parentId: null,
}
)
})
const columns = generateColumns(10)
const data = generateData(columns, 200)
</script>
<style>
.example-showcase .w3-table-v2__overlay {
z-index: 9;
}
</style>
手动滚动
使用 Table V2 暴露的方法可以进行手动或编程式的滚动到指定的偏移量或者行。
TIP
scrollToRow 的第二个参数代表滚动策略,计算了要滚动的位置,其默认值是 auto。 如果你想要滚动到某个特定位置,你可以自己定义战略。 可用的选项是 "auto" | "center" | "end" | "start" | "smart"
smart 和auto 之间的区别是, auto 是 smart 滚动策略的子集。
<template>
<div class="mb-4 flex items-center">
<w-form-item label="滚动像素" class="mr-4">
<w-input v-model="scrollDelta" />
</w-form-item>
<w-form-item label="滚动行数">
<w-input v-model="scrollRows" />
</w-form-item>
</div>
<div class="mb-4 flex items-center">
<w-button @click="scrollByPixels"> 按像素滚动 </w-button>
<w-button @click="scrollByRows"> 按行滚动 </w-button>
</div>
<div style="height: 400px">
<w-auto-resizer>
<template #default="{ height, width }">
<w-table-v2
ref="tableRef"
:columns="columns"
:data="data"
:width="width"
:height="height"
fixed
/>
</template>
</w-auto-resizer>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import type { TableV2Instance } from 'win-design-next'
const columnTitles = [
'患者编号',
'姓名',
'性别',
'年龄',
'科室',
'主治医生',
'诊断',
'入院日期',
'体温(℃)',
'状态',
]
const patients = [
{
name: '张伟',
gender: '男',
age: 56,
dept: '心血管内科',
doctor: '周建国',
diagnosis: '高血压2级',
date: '2026-08-01',
temp: 36.8,
status: '在院',
},
{
name: '李娜',
gender: '女',
age: 48,
dept: '呼吸内科',
doctor: '吴晓芳',
diagnosis: '社区获得性肺炎',
date: '2026-08-03',
temp: 38.2,
status: '在院',
},
{
name: '王强',
gender: '男',
age: 32,
dept: '骨科',
doctor: '郑海涛',
diagnosis: '右胫骨骨折',
date: '2026-07-28',
temp: 36.5,
status: '在院',
},
{
name: '刘秀英',
gender: '女',
age: 67,
dept: '神经内科',
doctor: '王淑华',
diagnosis: '脑梗死后遗症',
date: '2026-07-15',
temp: 36.9,
status: '转科',
},
{
name: '陈静',
gender: '女',
age: 29,
dept: '妇产科',
doctor: '林志强',
diagnosis: '妊娠期糖尿病',
date: '2026-08-05',
temp: 36.7,
status: '在院',
},
{
name: '杨帆',
gender: '男',
age: 41,
dept: '消化内科',
doctor: '周建国',
diagnosis: '急性胰腺炎',
date: '2026-08-06',
temp: 37.6,
status: '病危',
},
{
name: '赵敏',
gender: '女',
age: 53,
dept: '内分泌科',
doctor: '郑雅琴',
diagnosis: '2型糖尿病',
date: '2026-07-20',
temp: 36.4,
status: '出院',
},
{
name: '孙丽华',
gender: '女',
age: 71,
dept: '心血管内科',
doctor: '周建国',
diagnosis: '冠心病',
date: '2026-08-02',
temp: 37.1,
status: '在院',
},
]
const getCellValue = (rowIndex: number, columnIndex: number) => {
const patient = patients[rowIndex % patients.length]
const values = [
`HZ2026${String(rowIndex + 1).padStart(4, '0')}`,
patient.name,
patient.gender,
patient.age,
patient.dept,
patient.doctor,
patient.diagnosis,
patient.date,
patient.temp,
patient.status,
]
return values[columnIndex % values.length]
}
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: columnTitles[columnIndex % columnTitles.length],
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] = getCellValue(rowIndex, 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
Attributes
| 属性名 | 描述说明 | 类型 | 默认值 |
|---|---|---|---|
| cache | 为了更好的渲染效果预先多加载的行数 | number | 2 |
| estimated-row-height | 渲染动态的单元格的预估高度 | number | — |
| header-class | header 部分的自定义 class 名 | string / Function<HeaderClassGetter> | — |
| header-props | header 部分的自定义 props 名 | object / Function<HeaderPropsGetter> | — |
| header-cell-props | header cell 部分的自定义 props 名 | object / Function<HeaderCellPropsGetter> | — |
| header-height | Header 的高度由height设置。 如果传入数组,它会使 header row 等于数组长度 | number/ number[] | 50 |
| footer-height | Footer 部分的高度,当传入值时,这部分将被计算入 table 的高度里 | number | 0 |
| row-class | row wrapper 部分的自定义 class 名 | string / Function<RowClassGetter> | — |
| row-key | 每行的 key 值,如果不提供,将使用索引 index 代替 | string / Symbol / number | id |
| row-props | row component 部分的自定义 class 名 | object / Function<RowPropsGetter> | — |
| row-height | 每行的高度, 用于计算表的总高度 | number | 50 |
| cell-props | 每个单元格 cell 的自定义 props (除了 header cell 以外) | object / Function<CellPropsGetter> | — |
| columns | 列 column 的配置数组 | Column[] | — |
| data | 要在表中渲染的数据数组 | Data[] | [] |
| data-getter | 一个自定义方法从数据源获取数据 | Function<DataGetter<T>> | — |
| fixed-data | 渲染行在表格主内容上方和 header 下方区域的数据 | object<Data> | — |
| expand-column-key | 列的 key 来标记哪个行可以被展开 | string | — |
| expanded-row-keys | 存放行展开状态的 key 的数组,可以和 v-model 搭配使用 | KeyType[] | — |
| default-expanded-row-keys | 默认展开的行的 key 的数组, 这个数据不是响应式的 | KeyType[] | — |
| class | 表格的类名称,将应用于表格的全部的三个部分 (左、右、主) | string / array / object | — |
| fixed | 单元格宽度是自适应还是固定 | boolean | false |
| width required | 表格的宽度 | number | — |
| height required | 表格的高度 | number | — |
| max-height | 表格的最大高度 | number | — |
| indent-size | 树形表的水平缩进 | number | 12 |
| h-scrollbar-size | 配置表格的水平滚动条大小,防止水平和垂直滚动条重叠。 | number | 10 |
| scrollbar-size | 配置表格的垂直滚动条大小,防止水平和垂直滚动条重叠。 | number | 10 |
| scrollbar-always-on | 如果开启,滚动条将一直显示,反之只会在鼠标经过时显示。 | boolean | false |
| sort-by | 排序方式 | object<SortBy> | {} |
| sort-state | 多个排序 | object<SortState> | undefined |
Slots
| 插槽名 | 参数 |
|---|---|
| cell | object<CellSlotProps> |
| header | object<HeaderSlotProps> |
| header-cell | object<HeaderCellSlotProps> |
| row | object<RowSlotProps> |
| footer | — |
| empty | — |
| overlay | — |
Events
| 事件名 | 描述 | 参数 |
|---|---|---|
| column-sort | 列排序时调用 | object<ColumnSortParam> |
| expanded-rows-change | 行展开状态改变时触发 | KeyType[] |
| end-reached | 到达表格末尾时触发 | — |
| scroll | 表格被用户滚动后触发 | object<ScrollParams> |
| rows-rendered | 当行被渲染后触发 | object<RowsRenderedParams> |
| row-expand | 点击箭头图标展开/折叠树节点时触发 | object<RowExpandParams> |
| row-event-handlers | 当每行添加了一系列事件处理器时触发 | object<RowEventHandlers> |
Methods
| 事件名 | 描述 | 参数 |
|---|---|---|
| scrollTo | 滚动到给定位置 | Function |
| scrollToLeft | 滚动到给定的水平位置 | Function |
| scrollToTop | 滚动到给定的垂直位置 | Function |
| scrollToRow | 使用给定的滚动策略滚动至指定行 | Function |
TIP
请注意:这些是 JavaScript 对象,所以您 不能使用 短横线命名法(kebab-case)来处理这些属性
Column API
Attribute
| 属性名 | 描述 | 类型 | 默认值 |
|---|---|---|---|
| align | 表格单元格内容对齐方式 | Alignment | left |
| class | 列的类名 | string | — |
| key | 唯一标志 | KeyType | — |
| dataKey | data 的唯一标志符 | KeyType | — |
| fixed | 固定列位置 | boolean / FixedDir | false |
| flexGrow | CSS 属性 flex grow, 仅当不是固定表时才生效 | number | 0 |
| flexShrink | CSS 属性 flex shrink, 仅当不是固定表时才生效 | number | 1 |
| headerClass | 自定义 header 头部类名 | string | — |
| hidden | 此列是否不可见 | boolean | — |
| style | 自定义列单元格的类名,将会与 gird 单元格合并 | object | — |
| sortable | 设置列是否可排序 | boolean | — |
| title | Header 头部单元格中的默认文本 | string | — |
| maxWidth | 列的最大宽度 | number | — |
| minWidth | 列的最小宽度 | number | — |
| width required | 列宽度 | number | — |
| cellRenderer | 自定义单元格渲染器 | VueComponent / (props: CellRenderProps) => VNode | — |
| headerCellRenderer | 自定义头部渲染器 | VueComponent / (props: HeaderRenderProps) => VNode | — |
Type Declarations
显示类型声明
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,并自行管理其状态。
Copyright 2025 Winning Health 版权所有

