跳转到内容

ProTable

ProTable 面向查询和浏览场景,统一管理本地/远程数据、搜索、分页、排序、筛选、列状态、选择和行编辑。

何时使用

当页面需要围绕表格统一组织查询、数据加载、分页、列设置、选择或行编辑时使用。

示例

综合示例

查看完整代码
vue
<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>

数据模式

本地模式传入 dataSourcedefaultDataSource。搜索、排序、筛选和分页会直接作用于本地数据:

vue
<ProTable
  v-model:data-source="rows"
  :columns="columns"
  row-key="id"
  :pagination="{ defaultPageSize: 20 }"
/>

远程模式使用固定请求契约:

ts
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 可将一个字段转换为多个请求参数。

ts
const columns = [
  {
    title: '最低分',
    dataIndex: 'score',
    valueType: 'digit',
    search: {
      transform: (value) => ({ minScore: value }),
    },
  },
]

非受控折叠只需设置初始值:

vue
<ProTable :search="{ defaultCollapsed: true, span: 8, labelWidth: 'auto' }" />

受控折叠使用 collapsedsearch-collapse

vue
<ProTable
  :search="{ collapsed, span: 8, searchText: '筛选', resetText: '清空' }"
  @search-collapse="collapsed = $event"
/>

span 默认是 8,即 24 栅格下一行 3 项;折叠时仅保留首行。也可通过 search.onCollapse(next) 接收状态变化。

列状态与列设置

工具栏中的内建列设置面板只提供列显隐开关。列顺序和固定位置由 columnsState 中每个列 key(优先使用列 key,否则使用 dataIndex)对应的 orderfixed 编程控制:

ts
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',
}))
vue
<ProTable :columns-state="tableColumnsState" />

完全受控时将 valueonChange 配对;非受控初值使用 defaultValue。设置 persistenceKey 后,状态可写入 localStoragesessionStorage,包括面板产生的 show 变化以及编程设置的 orderfixed

ProTable 内建编辑

ProTable 自身即可使用与 EditableProTable 相同的编辑状态机:

vue
<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。
  • selecttreeSelectradiocheckboxsegmented 的异步选项只接纳最后一次请求;空远程结果有效,失败时调用 onFieldRequestError(error) 并保留现有选项。

valueType 现支持 treeSelectslidersegmented。Captcha 与两个 Upload 是独立 ProFormFields,不提供列 valueType 映射。

需要完全自定义搜索项或编辑器时继续使用 renderFormItem(column, context);需要自定义只读单元格时使用列插槽或 render。这些入口的优先级和 context.update(value) 协议不变。

API

属性

Prop类型说明
columnsProColumns<T>[]表格、搜索和编辑的统一列描述
dataSourceT[]受控本地数据,支持 v-model:data-source
defaultDataSourceT[]非受控初始数据
requestProRequest<T, P>远程 Promise 请求
paramsP额外请求参数,变化时回到第一页并重载
postData(data: T[]) => T[]展示前同步转换
rowKeykeyof T | string | (record) => ProKey行唯一标识,默认 id
loadingboolean叠加外部 loading
searchfalse | ProTableSearchConfig搜索区和折叠配置
paginationfalse | ProTablePagination分页、页大小和选项
optionsfalse | ProTableOptions密度、全屏、刷新和列设置
toolbarfalse | { title?, actions? }工具栏内容,也可使用插槽
rowSelectionfalse | Record<string, unknown>Antdv Next 行选择配置
columnsStateProColumnsStateConfig列显隐、顺序、固定和持久化配置
editablefalse | EditableConfig<T>单行/多行编辑与生命周期
editableKeysProKey[]编辑行 key,支持 v-model:editable-keys
pollingnumber轮询间隔(毫秒),页面隐藏时暂停
revalidateOnFocusboolean窗口重新聚焦时请求
manualRequestboolean不执行首次自动请求
scroll / size / borderedAntdv Next 对应值滚动、密度和边框

事件

事件参数说明
update:data-sourcerowsv-model:data-source 更新
update:editable-keyskeysv-model:editable-keys 更新
data-source-changerows, changedRecord?编辑、新增或删除导致数据变化
request-errorerror远程请求失败
editable-errorerror保存/删除生命周期抛错
validation-errorkey, errors行编辑校验失败
search-collapsecollapsed搜索区折叠状态变化
changepagination, filters, sorter分页、筛选或排序变化
selection-changekeys, rows行选择变化
loadrows, total成功接纳远程结果

插槽

列 key 取 column.key,否则取点连接后的 dataIndex

vue
<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。

单元格插槽优先于默认只读展示和编辑器;需要行内编辑的列通常只自定义表头,或在插槽中自行处理编辑态。

组件实例

ts
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?)创建并进入编辑态

基于 MIT 许可发布