Skip to content

EditableProTable

EditableProTable reuses the ProTable column model, editing state machine, and validation logic for workflows where the whole table is one form field. Search, pagination, table options, and focus revalidation are disabled by default.

When to use

  • Use it when users need to add, edit, or delete multiple records continuously and submit the whole table as one controlled value.
  • Use it to reuse ProTable columns, validation, and slots while disabling browsing-oriented features such as search and pagination by default.
  • For editing a single record in a workflow that is still primarily about querying and browsing, use the built-in editing capabilities of ProTable instead.

Examples

Controlled editing, record creation, and component instance

View full code
vue
<script setup lang="ts">
import { ref } from 'vue'
import {
  EditableProTable,
  type EditableConfig,
  type EditableProTableInstance,
  type ProColumns,
  type ProKey,
  type RecordCreatorProps,
} from 'antdv-next-pro'

type Member = Record<string, unknown> & {
  id: number
  name: string
  role: '开发' | '设计' | '测试'
  allocation: number
  active: boolean
}

const tableRef = ref<EditableProTableInstance<Member>>()
const value = ref<Member[]>([
  { id: 1, name: '林默', role: '开发', allocation: 80, active: true },
  { id: 2, name: '周芮', role: '设计', allocation: 60, active: true },
])
const editableKeys = ref<ProKey[]>([])
const lastAction = ref('editableKeys 由 v-model 完整控制')
let sequence = 3

const columns: ProColumns<Member>[] = [
  {
    title: '成员',
    dataIndex: 'name',
    valueType: 'text',
    formItemProps: { rules: [{ required: true, message: '请输入成员姓名' }] },
  },
  {
    title: '角色',
    dataIndex: 'role',
    valueType: 'select',
    valueEnum: { 开发: '开发', 设计: '设计', 测试: '测试' },
  },
  { title: '投入比例', dataIndex: 'allocation', valueType: 'percent' },
  { title: '参与项目', dataIndex: 'active', valueType: 'switch' },
  { title: '操作', valueType: 'option', width: 160 },
]

const editable: EditableConfig<Member> = {
  type: 'multiple',
  async onSave(_key, record) {
    await new Promise((resolve) => setTimeout(resolve, 120))
    lastAction.value = `已保存「${record.name}」`
  },
}

const recordCreatorProps: RecordCreatorProps<Member> = {
  record: () => ({
    id: sequence++,
    name: '',
    role: '开发',
    allocation: 50,
    active: true,
  }),
  position: 'bottom',
  creatorButtonText: '添加成员',
  newRecordType: 'dataSource',
}

const editFirst = () => {
  const first = value.value[0]
  if (first && tableRef.value?.startEditable(first.id)) {
    lastAction.value = '正在编辑第一行'
  }
}

const patchFirst = () => {
  const first = value.value[0]
  if (first && tableRef.value?.setRowData(first.id, { allocation: 100 })) {
    lastAction.value = 'setRowData 已浅合并第一行'
  }
}
</script>

<template>
  <div class="demo-frame vp-raw">
    <p class="demo-label">LIVE · CONTROLLED VALUE + COMPONENT REF</p>
    <div class="demo-actions">
      <span>编辑中:{{ editableKeys.length ? editableKeys.join(', ') : '无' }}</span>
      <span>{{ lastAction }}</span>
      <button type="button" @click="editFirst">编辑第一行</button>
      <button type="button" @click="patchFirst">投入改为 100%</button>
    </div>
    <EditableProTable
      ref="tableRef"
      v-model:value="value"
      v-model:editable-keys="editableKeys"
      :columns="columns"
      :editable="editable"
      :record-creator-props="recordCreatorProps"
      :max-length="5"
      row-key="id"
      @values-change="lastAction = `完整 value 已更新,共 ${value.length} 行`"
      @editable-error="lastAction = '保存失败,编辑状态已保留'"
    >
      <template #header-role="{ column }"> {{ column.title }}(共享列插槽) </template>
    </EditableProTable>
  </div>
</template>

<style scoped>
.demo-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-bottom: 14px;
  color: #64748b;
  font-size: 12px;
}

.demo-actions button {
  padding: 5px 10px;
  border: 1px solid #bfd2e7;
  border-radius: 6px;
  background: #fff;
  color: #1768d3;
  cursor: pointer;
}
</style>

Controlled data and edit state

v-model:value is the only controlled whole-table data entry point; a conflicting dataSource prop is not exposed. defaultValue supplies uncontrolled initial rows.

vue
<EditableProTable
  v-model:value="members"
  v-model:editable-keys="editableKeys"
  :columns="columns"
  :editable="{ type: 'multiple', onSave, onDelete }"
  :record-creator-props="recordCreatorProps"
  row-key="id"
/>

editable.type is single or multiple. onSave, onCancel, and onDelete may return Promises. Returning false from save or delete keeps the current edit/data state. Column-level editable can vary by record.

Validation and lifecycle

Place validation in column formItemProps.rules:

ts
const columns: ProColumns<Member>[] = [
  {
    title: 'Name',
    dataIndex: 'name',
    valueType: 'text',
    formItemProps: {
      rules: [{ required: true, message: 'Enter a name' }],
    },
  },
]

const editable: EditableConfig<Member> = {
  type: 'multiple',
  async onSave(key, record, origin) {
    await api.save(record)
  },
  async onDelete(key, record) {
    await api.remove(record.id)
    return true
  },
}

saveEditable returns false when validation fails. Lifecycle exceptions emit editable-error. The component-level formItemProps wraps the whole EditableProTable in an Antdv Next FormItem for outer-form integration.

Record creator

ts
const recordCreatorProps: RecordCreatorProps<Member> = {
  record: () => ({
    id: crypto.randomUUID(),
    name: '',
  }),
  position: 'bottom',
  parentKey: undefined,
  newRecordType: 'dataSource',
  creatorButtonText: 'Add member',
}
FieldDescription
recordRecord or factory called for each creation
positiontop / bottom; defaults to bottom
parentKeyParent for a new tree row
newRecordTypedataSource writes immediately; cache writes after a successful save
creatorButtonTextCreator button label

Every new row must have a unique rowKey. maxLength counts the flattened tree, hides the button at the limit, and is also enforced by ref-based addEditRecord.

Shared field core and FormItem boundary

EditableProTable does not maintain a separate field map. It reuses the bare-control core from ProFormFields through ProTable. treeSelect, slider, and segmented work in editable columns like existing valueType values, while checked fields and async options share the same v-model, race, and error behavior.

Column formItemProps only provide cell validation and field configuration; they do not create a nested FormItem. Root-level formItemProps only wrap the complete EditableProTable in one outer FormItem. Custom editors still use column renderFormItem and write drafts through context.update(value).

API

Props

PropTypeDescription
columnsProColumns<T>[]Shared ProTable column model
value / defaultValueT[]Controlled value and uncontrolled initial value
editableKeysProKey[]Current editing keys; supports two-way binding
editablefalse | EditableConfig<T>Editing mode and lifecycle
recordCreatorPropsfalse | RecordCreatorProps<T>Creator configuration
maxLengthnumberMaximum row count
formItemPropsRecord<string, unknown>Outer Antdv Next FormItem props
onValuesChange(values, changedRecord) => voidWhole-table change callback
onTableChange(pagination, filters, sorter) => voidTable-state callback
request / params / postDataSame as ProTableOptional remote initialization/refresh
toolbar / rowSelection / columnsStateSame as ProTableOpt into related capabilities
polling / manualRequestSame as ProTableRemote request controls
scroll / size / borderedSame as ProTableTable presentation

Events

onValuesChange / onTableChange props and @values-change / @table-change are two syntaxes for the same Vue listener channel. Choose one syntax; every change is dispatched exactly once, so do not bind the same handler through both forms:

EventArgumentsDescription
update:valuerowsv-model:value update
update:editable-keyskeysv-model:editable-keys update
values-changerows, changedRecordAny row mutation
table-changepagination, filters, sorterTable-state change
request-errorerrorRemote request failure
editable-errorerrorEditing lifecycle failure

Slots

EditableProTable forwards every slot to its inner ProTable:

  • toolbar-title and toolbar-actions.
  • header-${columnKey}.
  • cell-${columnKey} or ${columnKey} with { value, record, index, column, editable }.
  • Other slots supported by the underlying Antdv Next Table.

Component instance

EditableProTableInstance<T> includes all ProTable methods and adds whole-table access:

ts
const editableRef = ref<EditableProTableInstance<Member>>()

editableRef.value?.startEditable(memberId)
const first = editableRef.value?.getRowData(0)
const all = editableRef.value?.getRowsData()
editableRef.value?.setRowData(memberId, { allocation: 100 })
MethodDescription
getRowData(indexOrKey)Numeric input resolves an exact row key first, then a top-level index
getRowsData()Read a copy of all current rows
setRowData(indexOrKey, value)Resolve with the same key/index rule, shallow-merge, and update v-model:value

Inherited methods are reload, reset, setPageInfo, clearSelected, fullScreen, scrollTo, startEditable, saveEditable, cancelEditable, and addEditRecord.

Released under the MIT License