ProTable
ProTable 面向查询和浏览场景,统一管理本地/远程数据、搜索、分页、排序、筛选、列状态、选择和行编辑。
何时使用
当页面需要围绕表格统一组织查询、数据加载、分页、列设置、选择或行编辑时使用。
示例
综合示例
查看完整代码
<script setup lang="ts">
import { ref } from 'vue'
import {
ProTable,
type EditableConfig,
type ProColumns,
type ProKey,
type ProRequest,
type ProTableInstance,
} from 'antdv-next-pro'
type Project = Record<string, unknown> & {
id: number
name: string
owner: string
status: 'running' | 'done'
budget: number
}
type Query = Record<string, unknown> & {
name?: string
owner?: string
status?: Project['status']
minBudget?: number
}
const projects: Project[] = [
{ id: 1, name: '增长驾驶舱', owner: '林默', status: 'running', budget: 80 },
{ id: 2, name: '会员洞察', owner: '周芮', status: 'done', budget: 45 },
{ id: 3, name: '留存预警', owner: '孟晴', status: 'running', budget: 60 },
{ id: 4, name: '区域经营', owner: '方屿', status: 'done', budget: 35 },
]
const tableRef = ref<ProTableInstance<Project>>()
const visibleRows = ref<Project[]>([])
const editableKeys = ref<ProKey[]>([])
const collapsed = ref(true)
const lastAction = ref('可展开查询区,也可通过 ref 刷新或进入编辑')
const columns: ProColumns<Project>[] = [
{ title: '#', valueType: 'indexBorder', width: 56, search: false },
{
title: '项目',
dataIndex: 'name',
valueType: 'text',
formItemProps: { rules: [{ required: true, message: '请输入项目名称' }] },
},
{ title: '负责人', dataIndex: 'owner', valueType: 'text' },
{
title: '状态',
dataIndex: 'status',
valueType: 'select',
editable: false,
valueEnum: {
running: { text: '进行中', status: 'processing' },
done: { text: '已完成', status: 'success' },
},
},
{
title: '最低预算',
dataIndex: 'budget',
valueType: 'money',
search: { transform: (value) => ({ minBudget: value }) },
},
]
const request: ProRequest<Project, Query> = async (params) => {
await new Promise((resolve) => setTimeout(resolve, 180))
const name = String(params.name ?? '').toLowerCase()
const owner = String(params.owner ?? '').toLowerCase()
const data = projects.filter(
(item) =>
(!name || item.name.toLowerCase().includes(name)) &&
(!owner || item.owner.toLowerCase().includes(owner)) &&
(!params.status || item.status === params.status) &&
(!params.minBudget || item.budget >= Number(params.minBudget)),
)
return { data, total: data.length, success: true }
}
const editable: EditableConfig<Project> = {
type: 'multiple',
async onSave(_key, record) {
await new Promise((resolve) => setTimeout(resolve, 120))
lastAction.value = `已保存「${record.name}」`
},
}
const editFirst = () => {
const first = visibleRows.value[0]
if (first && tableRef.value?.startEditable(first.id)) {
lastAction.value = `正在编辑「${first.name}」`
}
}
const reload = async () => {
await tableRef.value?.reload()
lastAction.value = '已通过组件 ref 重新请求'
}
</script>
<template>
<div class="demo-frame vp-raw">
<p class="demo-label">LIVE · REQUEST + EDITABLE + SLOTS</p>
<div class="demo-actions">
<span>{{ lastAction }}</span>
<button type="button" @click="editFirst">编辑第一行</button>
</div>
<ProTable
ref="tableRef"
v-model:data-source="visibleRows"
v-model:editable-keys="editableKeys"
:columns="columns"
:request="request"
:editable="editable"
row-key="id"
:pagination="false"
:row-selection="{}"
:search="{ collapsed, span: 8, labelWidth: 'auto' }"
:options="{ reload: true, setting: true }"
@search-collapse="collapsed = $event"
@request-error="lastAction = '请求失败,现有数据已保留'"
>
<template #toolbar-title> 项目清单 · {{ visibleRows.length }} 条 </template>
<template #toolbar-actions>
<button class="slot-button" type="button" @click="reload">ref 刷新</button>
</template>
<template #cell-status="{ value }">
<span :class="['status-chip', `is-${value}`]">
{{ value === 'running' ? '进行中' : '已完成' }}
</span>
</template>
</ProTable>
</div>
</template>
<style scoped>
.demo-actions {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 14px;
color: #64748b;
font-size: 13px;
}
.demo-actions button,
.slot-button {
padding: 5px 10px;
border: 1px solid #bfd2e7;
border-radius: 6px;
background: #fff;
color: #1768d3;
cursor: pointer;
}
.status-chip {
display: inline-flex;
padding: 2px 8px;
border-radius: 999px;
font-size: 12px;
}
.status-chip.is-running {
background: #e6f7f7;
color: #087b84;
}
.status-chip.is-done {
background: #eef5ff;
color: #1768d3;
}
</style>数据模式
本地模式传入 dataSource 或 defaultDataSource。搜索、排序、筛选和分页会直接作用于本地数据:
<ProTable
v-model:data-source="rows"
:columns="columns"
row-key="id"
:pagination="{ defaultPageSize: 20 }"
/>远程模式使用固定请求契约:
import type { ProRequest } from 'antdv-next-pro'
const request: ProRequest<User, Query> = async (params, sort, filter) => {
const result = await api.list({ ...params, sort, filter })
return {
data: result.items,
total: result.total,
success: true,
}
}搜索、分页、排序、筛选和外部 params 变化都会进入同一请求。并发请求只接纳最后发起的结果;请求抛错时保留当前数据、结束 loading,并触发 request-error。返回 success: false 的结果不会覆盖数据。
manualRequest 可阻止首次自动请求,之后通过组件 ref 的 reload() 发起。postData 在展示前同步转换成功数据。
搜索区与折叠
search: false 关闭查询区。默认情况下,包含 dataIndex 且没有 hideInSearch / search: false 的列会生成搜索项;search.transform 可将一个字段转换为多个请求参数。
const columns = [
{
title: '最低分',
dataIndex: 'score',
valueType: 'digit',
search: {
transform: (value) => ({ minScore: value }),
},
},
]非受控折叠只需设置初始值:
<ProTable :search="{ defaultCollapsed: true, span: 8, labelWidth: 'auto' }" />受控折叠使用 collapsed 和 search-collapse:
<ProTable
:search="{ collapsed, span: 8, searchText: '筛选', resetText: '清空' }"
@search-collapse="collapsed = $event"
/>span 默认是 8,即 24 栅格下一行 3 项;折叠时仅保留首行。也可通过 search.onCollapse(next) 接收状态变化。
列状态与列设置
工具栏中的内建列设置面板只提供列显隐开关。列顺序和固定位置由 columnsState 中每个列 key(优先使用列 key,否则使用 dataIndex)对应的 order、fixed 编程控制:
const columnsState = ref<Record<string, ProColumnsState>>({
name: { show: true, order: 10, fixed: 'left' },
status: { show: true, order: 20 },
actions: { show: true, order: 30, fixed: 'right' },
})
const tableColumnsState = computed<ProColumnsStateConfig>(() => ({
value: columnsState.value,
onChange: (next) => {
columnsState.value = next
},
persistenceKey: 'users-table-columns',
persistenceType: 'localStorage',
}))<ProTable :columns-state="tableColumnsState" />完全受控时将 value 与 onChange 配对;非受控初值使用 defaultValue。设置 persistenceKey 后,状态可写入 localStorage 或 sessionStorage,包括面板产生的 show 变化以及编程设置的 order、fixed。
ProTable 内建编辑
ProTable 自身即可使用与 EditableProTable 相同的编辑状态机:
<ProTable
v-model:data-source="rows"
v-model:editable-keys="editableKeys"
:columns="columns"
:editable="{
type: 'multiple',
onSave: saveRow,
onCancel: cancelRow,
onDelete: deleteRow,
}"
/>列上的 editable 可按记录控制;formItemProps.rules 提供异步或同步校验;renderFormItem(column, context) 可自定义编辑器,并通过 context.update(nextValue) 写入共享编辑状态、触发 EditableProTable 的实时 v-model:value。没有 valueType: 'option' 列时,组件会自动补充操作列。
editable.actionRender(record, actions) 可完全替换默认操作区。actions.editing 表示当前行状态;未编辑时调用 actions.start(),编辑中可使用 save()、cancel() 和 remove()。
addEditRecord(record, { position, parentKey, newRecordType }) 支持顶部/底部创建、树形 parentKey 以及 cache/dataSource 两种新记录策略。新记录必须具有唯一 rowKey。
共用字段核心
搜索区、可编辑单元格和独立 ProFormFields 使用同一字段注册表、选项请求和只读格式化逻辑:
- 搜索项使用表单项模式,列级
dataIndex/title生成的name/label会覆盖formItemProps中的同名值。 - 可编辑单元格使用
fieldMode="field"对应的裸控件能力,校验仍由列级formItemProps.rules执行,不会嵌套第二个 FormItem。 select、treeSelect、radio、checkbox、segmented的异步选项只接纳最后一次请求;空远程结果有效,失败时调用onFieldRequestError(error)并保留现有选项。
valueType 现支持 treeSelect、slider、segmented。Captcha 与两个 Upload 是独立 ProFormFields,不提供列 valueType 映射。
需要完全自定义搜索项或编辑器时继续使用 renderFormItem(column, context);需要自定义只读单元格时使用列插槽或 render。这些入口的优先级和 context.update(value) 协议不变。
API
属性
| Prop | 类型 | 说明 |
|---|---|---|
columns | ProColumns<T>[] | 表格、搜索和编辑的统一列描述 |
dataSource | T[] | 受控本地数据,支持 v-model:data-source |
defaultDataSource | T[] | 非受控初始数据 |
request | ProRequest<T, P> | 远程 Promise 请求 |
params | P | 额外请求参数,变化时回到第一页并重载 |
postData | (data: T[]) => T[] | 展示前同步转换 |
rowKey | keyof T | string | (record) => ProKey | 行唯一标识,默认 id |
loading | boolean | 叠加外部 loading |
search | false | ProTableSearchConfig | 搜索区和折叠配置 |
pagination | false | ProTablePagination | 分页、页大小和选项 |
options | false | ProTableOptions | 密度、全屏、刷新和列设置 |
toolbar | false | { title?, actions? } | 工具栏内容,也可使用插槽 |
rowSelection | false | Record<string, unknown> | Antdv Next 行选择配置 |
columnsState | ProColumnsStateConfig | 列显隐、顺序、固定和持久化配置 |
editable | false | EditableConfig<T> | 单行/多行编辑与生命周期 |
editableKeys | ProKey[] | 编辑行 key,支持 v-model:editable-keys |
polling | number | 轮询间隔(毫秒),页面隐藏时暂停 |
revalidateOnFocus | boolean | 窗口重新聚焦时请求 |
manualRequest | boolean | 不执行首次自动请求 |
scroll / size / bordered | Antdv Next 对应值 | 滚动、密度和边框 |
事件
| 事件 | 参数 | 说明 |
|---|---|---|
update:data-source | rows | v-model:data-source 更新 |
update:editable-keys | keys | v-model:editable-keys 更新 |
data-source-change | rows, changedRecord? | 编辑、新增或删除导致数据变化 |
request-error | error | 远程请求失败 |
editable-error | error | 保存/删除生命周期抛错 |
validation-error | key, errors | 行编辑校验失败 |
search-collapse | collapsed | 搜索区折叠状态变化 |
change | pagination, filters, sorter | 分页、筛选或排序变化 |
selection-change | keys, rows | 行选择变化 |
load | rows, total | 成功接纳远程结果 |
插槽
列 key 取 column.key,否则取点连接后的 dataIndex。
<ProTable :columns="columns">
<template #toolbar-title>项目列表</template>
<template #toolbar-actions>
<button @click="tableRef?.reload()">同步</button>
</template>
<template #header-name="{ column }">
{{ column.title }} · 自定义表头
</template>
<template #cell-name="{ value, record, editable }">
<strong>{{ value }}</strong>
<small v-if="editable">编辑中</small>
</template>
</ProTable>header-${columnKey}:参数为{ column }。cell-${columnKey}或直接${columnKey}:参数为{ value, record, index, column, editable }。- 其他插槽继续透传给底层 Antdv Next Table。
单元格插槽优先于默认只读展示和编辑器;需要行内编辑的列通常只自定义表头,或在插槽中自行处理编辑态。
组件实例
import type { ProTableInstance } from 'antdv-next-pro'
const tableRef = ref<ProTableInstance<User>>()
await tableRef.value?.reload(true)
tableRef.value?.setPageInfo({ current: 2, pageSize: 50 })
tableRef.value?.clearSelected()
tableRef.value?.startEditable(userId)
await tableRef.value?.saveEditable(userId)| 方法 | 说明 |
|---|---|
reload(resetPageIndex?) | 重新请求,可选回到第一页 |
reset() | 清空搜索/排序/筛选并恢复初始分页 |
setPageInfo(page) | 更新当前页或页大小 |
clearSelected() | 清空行选择 |
fullScreen() | 进入/退出全屏 |
scrollTo(target) | { key } 滚动到 rowKey,{ top } 按像素滚动;字符串直接视为 key |
startEditable(key) | 开始编辑 |
saveEditable(key) | 校验并保存,返回是否成功 |
cancelEditable(key) | 取消并恢复原记录 |
addEditRecord(record, options?) | 创建并进入编辑态 |