ProTable
ProTable targets search and browsing workflows while coordinating local/remote data, search, pagination, sorting, filters, column state, selection, and row editing.
When to use
Use it when a page needs to organize querying, data loading, pagination, column settings, selection, or row editing around a table.
Examples
Request, editing, and slots
View source
<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>Data modes
Pass dataSource or defaultDataSource for local mode. Search, sorting, filters, and pagination operate on the local rows:
<ProTable
v-model:data-source="rows"
:columns="columns"
row-key="id"
:pagination="{ defaultPageSize: 20 }"
/>Remote mode uses one fixed request contract:
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,
}
}Search, pagination, sorting, filters, and external params all feed this request. Only the most recently started concurrent request may update the table. A thrown error preserves current rows, ends loading, and emits request-error. A result with success: false is ignored.
manualRequest skips the initial request; call reload() through the component ref later. postData synchronously transforms successful data before display.
Search and collapse
search: false disables the search area. A column with dataIndex becomes searchable unless hideInSearch or search: false is set. search.transform can map one field to different request parameters.
const columns = [
{
title: 'Minimum score',
dataIndex: 'score',
valueType: 'digit',
search: {
transform: (value) => ({ minScore: value }),
},
},
]Use defaultCollapsed for uncontrolled initial state:
<ProTable :search="{ defaultCollapsed: true, span: 8, labelWidth: 'auto' }" />Use collapsed and search-collapse for controlled state:
<ProTable
:search="{ collapsed, span: 8, searchText: 'Filter', resetText: 'Clear' }"
@search-collapse="collapsed = $event"
/>span defaults to 8, or three fields in a 24-column row. Collapsed mode renders the first row only. search.onCollapse(next) is also available.
Column state and settings
The built-in column settings panel in the toolbar only toggles visibility. Control column order and fixed placement programmatically with order and fixed under each column key in columnsState (the column key takes precedence, otherwise dataIndex is used):
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" />Pair value with onChange for fully controlled state, or use defaultValue for an uncontrolled initial state. With persistenceKey, the state can be stored in localStorage or sessionStorage, including show changes made by the panel and programmatic order / fixed values.
Built-in editing
ProTable uses the same editing state machine as EditableProTable:
<ProTable
v-model:data-source="rows"
v-model:editable-keys="editableKeys"
:columns="columns"
:editable="{
type: 'multiple',
onSave: saveRow,
onCancel: cancelRow,
onDelete: deleteRow,
}"
/>Column editable can vary by row. formItemProps.rules provides sync/async validation. A custom renderFormItem(column, context) editor writes through context.update(nextValue), updating the shared editing state and EditableProTable's live v-model:value. ProTable adds an action column when no valueType: 'option' column exists.
editable.actionRender(record, actions) replaces the complete default action area. Check actions.editing, call actions.start() for a read-only row, and use save(), cancel(), or remove() while editing.
addEditRecord(record, { position, parentKey, newRecordType }) supports top/bottom insertion, tree parents, and cache/dataSource creator modes. Every new record must have a unique rowKey.
Shared field core
Search fields, editable cells, and standalone ProFormFields use the same field registry, option-request behavior, and readonly formatting:
- Search items use form-item mode.
name/labelgenerated from columndataIndex/titleoverride the same keys informItemProps. - Editable cells use the bare-control behavior behind
fieldMode="field". ColumnformItemProps.rulesstill perform validation without creating a nested FormItem. - Async options for
select,treeSelect,radio,checkbox, andsegmentedonly accept the newest request. Empty remote results are valid; failures callonFieldRequestError(error)and preserve current options.
valueType now includes treeSelect, slider, and segmented. Captcha and both Upload fields are standalone ProFormFields and have no column valueType mapping.
Continue using renderFormItem(column, context) for a fully custom search field or editor, and a column slot or render for custom readonly cells. Their priority and context.update(value) protocol are unchanged.
API
Props
| Prop | Type | Description |
|---|---|---|
columns | ProColumns<T>[] | Shared table, search, and editor description |
dataSource | T[] | Controlled local rows; supports v-model:data-source |
defaultDataSource | T[] | Uncontrolled initial rows |
request | ProRequest<T, P> | Remote Promise request |
params | P | Extra params; changes reset the page and reload |
postData | (data: T[]) => T[] | Synchronous display transform |
rowKey | keyof T | string | (record) => ProKey | Unique row identity, defaults to id |
loading | boolean | External loading state |
search | false | ProTableSearchConfig | Search and collapse options |
pagination | false | ProTablePagination | Page state and options |
options | false | ProTableOptions | Density, fullscreen, reload, settings |
toolbar | false | { title?, actions? } | Toolbar content; slots are also available |
rowSelection | false | Record<string, unknown> | Antdv Next row-selection config |
columnsState | ProColumnsStateConfig | Visibility, order, fixed-state, and persistence config |
editable | false | EditableConfig<T> | Single/multiple row editing lifecycle |
editableKeys | ProKey[] | Editing keys; supports v-model:editable-keys |
polling | number | Poll interval in milliseconds; pauses when hidden |
revalidateOnFocus | boolean | Reload on window focus |
manualRequest | boolean | Skip the initial automatic request |
scroll / size / bordered | Matching Antdv Next values | Scrolling, density, and borders |
Events
| Event | Arguments | Description |
|---|---|---|
update:data-source | rows | v-model:data-source update |
update:editable-keys | keys | v-model:editable-keys update |
data-source-change | rows, changedRecord? | Edit, creator, or delete mutation |
request-error | error | Remote request failure |
editable-error | error | Save/delete lifecycle failure |
validation-error | key, errors | Row validation failure |
search-collapse | collapsed | Search collapse change |
change | pagination, filters, sorter | Page, filter, or sort change |
selection-change | keys, rows | Row selection change |
load | rows, total | Accepted remote result |
Slots
The column key is column.key or the dot-joined dataIndex.
<ProTable :columns="columns">
<template #toolbar-title>Projects</template>
<template #toolbar-actions>
<button @click="tableRef?.reload()">Sync</button>
</template>
<template #header-name="{ column }">
{{ column.title }} · custom header
</template>
<template #cell-name="{ value, editable }">
<strong>{{ value }}</strong>
<small v-if="editable">editing</small>
</template>
</ProTable>header-${columnKey}receives{ column }.cell-${columnKey}or${columnKey}receives{ value, record, index, column, editable }.- Other slots are forwarded to the underlying Antdv Next Table.
A cell slot takes precedence over the default display and editor. Prefer a header-only slot for editable columns unless the slot handles editing itself.
Component ref
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)| Method | Description |
|---|---|
reload(resetPageIndex?) | Reload, optionally from page one |
reset() | Clear search/sort/filters and restore initial pagination |
setPageInfo(page) | Set current page or page size |
clearSelected() | Clear row selection |
fullScreen() | Enter or exit fullscreen |
scrollTo(target) | { key } scrolls to a row key, { top } scrolls by pixels; strings are keys |
startEditable(key) | Start editing |
saveEditable(key) | Validate and save; returns success |
cancelEditable(key) | Cancel and restore the original row |
addEditRecord(record, options?) | Create a row and enter edit mode |