跳转到内容

SchemaForm

SchemaForm 使用唯一的 columns Schema 生成字段,并以 Vue v-model 管理表单模型。它与表格组件共享 dataIndexvalueTypevalueEnum、校验和转换约定。

何时使用

当需要通过同一套 columns 描述普通表单、查询筛选、弹层表单或步骤表单,并统一管理初始值、校验、转换与提交时使用。

示例

异步选项、URL 同步与插槽

查看完整代码
vue
<script setup lang="ts">
import { ref } from 'vue'
import { SchemaForm, type SchemaFormColumn, type SchemaFormInstance } from 'antdv-next-pro'

type Brief = Record<string, unknown> & {
  project?: string
  owner?: string
  channel?: 'web' | 'mobile' | 'both'
  enabled?: boolean
}

const formRef = ref<SchemaFormInstance<Brief>>()
const model = ref<Partial<Brief>>({ channel: 'both', enabled: true })
const submitted = ref('等待提交')

const columns: SchemaFormColumn<Brief>[] = [
  {
    title: '项目名称',
    dataIndex: 'project',
    valueType: 'text',
    formItemProps: { rules: [{ required: true, message: '请输入项目名称' }] },
  },
  {
    title: '负责人',
    dataIndex: 'owner',
    valueType: 'text',
    dependencies: ['channel'],
  },
  {
    title: '发布渠道',
    dataIndex: 'channel',
    valueType: 'select',
    request: async () => {
      await new Promise((resolve) => setTimeout(resolve, 120))
      return [
        { label: 'Web', value: 'web' },
        { label: '移动端', value: 'mobile' },
        { label: '双端同步', value: 'both' },
      ]
    },
  },
  { title: '启用监测', dataIndex: 'enabled', valueType: 'switch' },
]

const loadInitialValues = async () => {
  await new Promise((resolve) => setTimeout(resolve, 100))
  return { owner: '林默' }
}

const onOwnerInput = (update: (value: unknown) => void, event: Event) => {
  update((event.target as HTMLInputElement).value)
}

const submit = async () => {
  try {
    const values = await formRef.value?.submit()
    submitted.value = JSON.stringify(values)
  } catch {
    submitted.value = '校验未通过'
  }
}

const fillExample = () => {
  formRef.value?.setFieldsValue({ project: '秋季增长实验', owner: 'Ada' })
}
</script>

<template>
  <div class="demo-frame vp-raw">
    <p class="demo-label">LIVE · ASYNC OPTIONS + URL SYNC + SLOTS</p>
    <SchemaForm
      ref="formRef"
      v-model="model"
      :columns="columns"
      :request="loadInitialValues"
      :url-sync="{ key: 'schema-demo' }"
      :grid="true"
      @request-error="submitted = '异步初始值加载失败'"
    >
      <template #label-project="{ column }"> {{ column.title }} · 必填 </template>
      <template #field-owner="{ value, update, dependencies }">
        <label class="owner-field">
          <input
            :value="String(value ?? '')"
            placeholder="命名字段插槽"
            @input="onOwnerInput(update, $event)"
          />
          <small>依赖渠道:{{ dependencies?.[0] ?? '未选择' }}</small>
        </label>
      </template>
      <template #submitter>
        <div class="custom-submitter">
          <button type="button" @click="fillExample">ref 填充</button>
          <button type="button" @click="formRef?.reset()">重置</button>
          <button type="button" class="primary" @click="submit">ref 提交</button>
        </div>
      </template>
    </SchemaForm>
    <p class="submit-result"><strong>结果:</strong>{{ submitted }}</p>
  </div>
</template>

<style scoped>
.owner-field {
  display: grid;
  gap: 4px;
}

.owner-field input {
  width: 100%;
  padding: 6px 11px;
  border: 1px solid #d9d9d9;
  border-radius: 6px;
  font: inherit;
}

.owner-field small,
.submit-result {
  color: #64748b;
  font-size: 12px;
}

.custom-submitter {
  display: flex;
  gap: 8px;
}

.custom-submitter button {
  padding: 6px 11px;
  border: 1px solid #bfd2e7;
  border-radius: 6px;
  background: #fff;
  color: #1768d3;
  cursor: pointer;
}

.custom-submitter .primary {
  border-color: #1768d3;
  background: #1768d3;
  color: #fff;
}
</style>

基础用法

vue
<SchemaForm ref="formRef" v-model="form" :columns="columns" @finish="save" />

initialValues、异步 request(params)、URL 与受控值会按以下优先级合并,右侧覆盖左侧:

text
initialValues < request result < URL values < modelValue

合并后的快照也是 reset() 的目标。paramsrequestinitialValuesurlSync 变化时会重新初始化;并发初始化只接纳最后一次结果。请求失败会回退到 initialValues + modelValue,并触发 request-errorerror

ts
const loadInitialValues = async (params?: Record<string, unknown>) => {
  const project = await api.project(params?.projectId)
  return { owner: project.owner, channel: project.channel }
}

valueType 与异步选项

类型生成控件/结构
text / textarea / passwordInput / Textarea / Password
digit / money / percentInputNumber
select / treeSelect / radioSelect / TreeSelect / RadioGroup
checkbox / switchCheckbox(Group) / Switch
slider / segmentedSlider / Segmented
date / dateTimeDatePicker
dateRange / dateTimeRangeDateRangePicker
time / timeRangeTimePicker / TimeRangePicker
group / formSet字段分组
formList可新增、删除的动态列表
divider分隔线
dependency依赖字段或自定义联动区域

selecttreeSelectradiocheckboxsegmented 可使用 valueEnum、顶层/fieldProps 选项,或列级异步 request。TreeSelect 的底层选项属性为 treeData

ts
const channelColumn: SchemaFormColumn<Brief> = {
  title: '发布渠道',
  dataIndex: 'channel',
  valueType: 'select',
  params: { enabled: true },
  request: async (params) => {
    const items = await api.channels(params)
    return items.map((item) => ({
      label: item.name,
      value: item.code,
      disabled: !item.available,
    }))
  },
}

异步选项会在 requestparams 变化时重载,并同样只接纳最后一次结果。

SchemaForm、ProTable 搜索/编辑与独立 ProFormFields共用同一字段注册表和控件核心,因此选项请求、只读展示与 treeSelectslidersegmented 的行为保持一致。timeRange 也可直接生成时间区间控件。Captcha、UploadButton、UploadDragger 只提供独立组件,不映射为 valueType

布局类型

layoutType场景
Form标准表单
Embed无额外弹层的嵌入式表单
ModalFormModal,使用 v-model:open
DrawerFormDrawer,使用 v-model:open
QueryFilter网格化行内查询表单
LightFilter轻量行内筛选
StepForm单步骤视图,使用 v-model:current
StepsForm带 Steps 导航的多步骤表单

既可以设置 layout-type,也可直接导入同名组件:

vue
<script setup lang="ts">
import { ModalForm, StepsForm } from 'antdv-next-pro'
</script>

<template>
  <ModalForm v-model="form" v-model:open="open" :columns="columns" />
  <StepsForm v-model="form" v-model:current="current" :columns="stepColumns" />
</template>

多步骤 Schema 要求顶层列全部是带子列的 groupformSet;每个顶层分组成为一步。next() 会先校验当前步骤,最后一步提交完整结果。Steps 标题允许直接点击返回已访问的前序步骤;点击后续步骤只会触发一次 next(),校验成功后前进一步,不能绕过当前步骤校验。

组合字段与动态 Schema

ts
const columns: SchemaFormColumn<Project>[] = [
  {
    title: '基本信息',
    valueType: 'group',
    columns: [
      { title: '名称', dataIndex: 'name', valueType: 'text' },
      { title: '类型', dataIndex: 'kind', valueType: 'select', valueEnum: kinds },
    ],
  },
  {
    title: '联系人',
    dataIndex: 'contacts',
    valueType: 'formList',
    fieldProps: {
      creatorButtonText: '添加联系人',
      removeText: '移除',
      initialValue: { name: '', email: '' },
    },
    columns: [
      { title: '姓名', dataIndex: 'name', valueType: 'text' },
      { title: '邮箱', dataIndex: 'email', valueType: 'text' },
    ],
  },
]

columns 可以是 computed 结果;依赖外部状态增删列即可生成动态字段。dependencies 会把依赖值传给字段插槽,dependency 搭配 renderFormItem 可渲染联动区域。

普通字段的扩展优先级为:动态字段插槽(field-${path}${path} 或列 key)→ renderFormItemcolumn.component → 默认 valueType 控件。column.component 仍由 SchemaForm 的 FormItem 包裹,并接收 valuemodelValuedisabled 以及对应更新监听器;需要完全接管内容时使用字段插槽或 renderFormItem

值转换

convertValue 只处理进入表单的数据,包括初始化、外部 v-model 更新和 setFieldsValuetransform 只在 submit() 时处理提交输出。validate() 仅校验并返回表单中的原始值,不执行 transform

ts
const columns = [
  {
    title: '时间范围',
    dataIndex: 'range',
    valueType: 'dateRange',
    convertValue: (value) => value?.map(dayjs),
    transform: (value) => ({
      startedAt: value?.[0]?.toISOString(),
      endedAt: value?.[1]?.toISOString(),
    }),
  },
]

transform 返回对象时会合并到 submit() 的最终结果;返回普通值时保留原 dataIndex。因此可以先用 validate() 读取校验后的原始表单值,再用 submit() 获取面向接口的转换结果。

URL 同步

vue
<!-- 每个字段写入 query -->
<SchemaForm :url-sync="true" />

<!-- 完整模型以 JSON 写入 filters 参数 -->
<SchemaForm :url-sync="{ key: 'filters' }" />

<!-- 每个字段写入 hash -->
<SchemaForm :url-sync="{ mode: 'hash' }" />

字段模式会删除 URL 中的空值;命名 key 模式存储完整模型。组件监听 popstate / hashchange 并回填表单,适合可分享的筛选条件。URL 值在初始化时覆盖 request 结果,但仍会被显式 modelValue 覆盖。

API

属性

Prop类型说明
columnsSchemaFormColumn<T>[]唯一 Schema 入口
modelValuePartial<T>标准 v-model
initialValuesPartial<T>初始化与重置基线
request(params?) => Promise<Partial<T>>异步初始值
paramsRecord<string, unknown>初始化请求参数
layoutTypeSchemaFormLayoutType表单布局,默认 Form
openbooleanModal/Drawer 打开状态,支持 v-model:open
currentnumber步骤索引,支持 v-model:current
title / width文本或尺寸弹层标题和宽度
labelCol / wrapperColAntdv Next Form 配置标签与控件布局
gridboolean使用响应式 Row/Col 网格
readonlyboolean全表单只读
urlSyncboolean | { key?, mode? }query/hash 同步
submitterfalse | { submitText?, resetText? }默认操作区
styleCSSProperties根容器样式

列还支持 componentcolPropsrowPropstooltipextra

事件

事件参数说明
update:model-valuevalues默认 v-model 更新
update:openopen弹层双向绑定
update:currentcurrent步骤双向绑定
changevalues任意字段变化
values-changechanged, valueschangedpathvalue
submit / finishvalues校验与 transform 后的结果
resetvalues恢复初始化快照
open / close组件方法或弹层交互
current-changecurrent当前步骤变化
request-errorerror异步初始值失败
errorerror初始化或校验失败

插槽

字段路径以点连接,例如 ['profile', 'name'] 对应 profile.name

vue
<SchemaForm ref="formRef" v-model="form" :columns="columns">
  <template #label-project="{ column }">
    {{ column.title }} *
  </template>

  <template #field-owner="{ value, update, dependencies }">
    <OwnerPicker
      :model-value="value"
      :channel="dependencies[0]"
      @update:model-value="update"
    />
  </template>

  <template #submitter="{ values, current }">
    <button @click="formRef?.prev()">上一步</button>
    <button @click="formRef?.submit()">提交第 {{ current + 1 }} 步</button>
  </template>
</SchemaForm>
插槽参数
field-${path}${path} 或列 key{ value, record, column, dependencies, update }
label-${path}{ column, record }
submitter{ values, current }
trigger{ open, openForm, closeForm }
title{ title, open, values, close }
footer{ values, submitting, submit, reset, close }
step-title{ title, index, current, step, steps, values }
step-content{ current, step, steps, columns, values, content }
step-actions{ current, step, steps, values, hasPrevious, hasNext, submitting, next, prev, submit, reset }

字段插槽中的 update(nextValue) 会更新表单、v-model、URL 和相关事件。 triggertitlefooter 用于 ModalForm / DrawerFormstep-titlestep-contentstep-actions 用于 StepForm / StepsFormstep-contentcontent() 可渲染当前步骤的默认字段。

组件实例

ts
const formRef = ref<SchemaFormInstance<Brief>>()

formRef.value?.setFieldsValue({ owner: 'Ada' })
const raw = formRef.value?.getFieldsValue()
const output = await formRef.value?.submit()
await formRef.value?.next()
方法说明
validate()校验并返回原始表单值,不执行 transform
reset()恢复初始化快照
getFieldsValue()获取当前表单原始值
setFieldsValue(values)浅层/嵌套合并传入字段
submit()校验、执行 transform,触发 submitfinish 并返回转换结果
open() / close()控制 Modal/Drawer
next() / prev()控制步骤;next 返回是否成功前进

基于 MIT 许可发布