跳转到内容

ProFormFields

19 个独立字段组件为普通表单、自定义布局、ProTable 搜索与行内编辑、SchemaForm 提供同一套值绑定、选项请求和只读展示协议。默认组件自带 Antdv Next FormItem;需要只渲染控件时使用 fieldMode="field"

完整字段示例

下面的 Vue SFC 示例实际渲染全部 19 个公开字段,并演示只读态、异步选项、验证码回调、上传入口和裸控件模式。

查看完整代码
vue
<script setup lang="ts">
import type { UploadFile } from 'antdv-next'

import { Form } from 'antdv-next'
import { computed, reactive, ref } from 'vue'
import { useData } from 'vitepress'
import {
  ProFormCaptcha,
  ProFormCheckbox,
  ProFormDatePicker,
  ProFormDateRangePicker,
  ProFormDateTimePicker,
  ProFormDateTimeRangePicker,
  ProFormDigit,
  ProFormMoney,
  ProFormRadioGroup,
  ProFormSegmented,
  ProFormSelect,
  ProFormSlider,
  ProFormSwitch,
  ProFormText,
  ProFormTextArea,
  ProFormTextPassword,
  ProFormTreeSelect,
  ProFormUploadButton,
  ProFormUploadDragger,
  type ProFormDateRangeValue,
  type ProFormDateValue,
} from 'antdv-next-pro'

interface DemoModel {
  text?: string
  digit?: number | string | null
  password?: string
  textarea?: string
  captcha?: string
  date?: ProFormDateValue
  dateTime?: ProFormDateValue
  dateRange?: ProFormDateRangeValue
  dateTimeRange?: ProFormDateRangeValue
  select?: string
  treeSelect?: string
  checkbox?: string[]
  radio?: string
  slider?: number | number[]
  switch?: boolean
  uploadButton: UploadFile[]
  uploadDragger: UploadFile[]
  money?: number | string | null
  segmented?: string | number
  bareText?: string
  bareChecked?: boolean
}

const { lang } = useData()
const english = computed(() => lang.value.startsWith('en'))
const readonly = ref(false)
const captchaStatus = ref('')
const model = reactive<DemoModel>({
  text: 'Antdv Next Pro',
  digit: 2,
  password: 'vue-vapor',
  textarea: 'One field core for forms, tables, and editable cells.',
  select: 'vue',
  treeSelect: 'pro-form-fields',
  checkbox: ['docs', 'tests'],
  radio: 'named',
  slider: 68,
  switch: true,
  uploadButton: [],
  uploadDragger: [],
  money: 128,
  segmented: 'preview',
  bareText: 'fieldMode="field"',
  bareChecked: true,
})

const tr = (zh: string, en: string) => (english.value ? en : zh)
const selectOptions = computed(() => [
  { label: 'Vue 3', value: 'vue' },
  { label: 'Antdv Next', value: 'antdv-next' },
])
const treeOptions = computed(() => [
  {
    label: tr('字段体系', 'Field system'),
    value: 'fields',
    children: [
      { label: 'ProFormFields', value: 'pro-form-fields' },
      { label: 'SchemaForm', value: 'schema-form' },
    ],
  },
])
const checkboxOptions = computed(() => [
  { label: tr('文档', 'Docs'), value: 'docs' },
  { label: tr('测试', 'Tests'), value: 'tests' },
  { label: tr('构建', 'Build'), value: 'build' },
])
const radioOptions = computed(() => [
  { label: tr('具名导入', 'Named import'), value: 'named' },
  { label: tr('全局注册', 'Global registration'), value: 'global' },
])
const segmentedOptions = computed(() => [
  { label: tr('编辑', 'Edit'), value: 'edit' },
  { label: tr('预览', 'Preview'), value: 'preview' },
])

async function requestSelectOptions() {
  await Promise.resolve()
  return selectOptions.value
}

async function getCaptcha() {
  await Promise.resolve()
  captchaStatus.value = tr('发送逻辑由当前示例提供', 'The demo supplied the send callback')
}
</script>

<template>
  <div class="demo-frame pro-form-fields-demo vp-raw">
    <div class="demo-heading">
      <div>
        <p class="demo-label">LIVE · 19 PRO FORM FIELDS</p>
        <p class="demo-note">
          {{
            tr(
              '所有字段共享相同的表单项、裸控件、只读与选项协议。',
              'Every field shares the same form-item, bare-control, readonly, and option protocols.',
            )
          }}
        </p>
      </div>
      <button type="button" @click="readonly = !readonly">
        {{ readonly ? tr('返回编辑', 'Edit values') : tr('查看只读', 'Readonly view') }}
      </button>
    </div>

    <Form :model="model" layout="vertical">
      <div class="field-grid">
        <ProFormText
          v-model="model.text"
          :label="tr('文本', 'Text')"
          :readonly="readonly"
          :field-props="{ allowClear: true }"
        >
          <template #extra>
            {{ tr('默认包含 FormItem', 'FormItem is included by default') }}
          </template>
        </ProFormText>

        <ProFormDigit v-model="model.digit" :label="tr('数字', 'Digit')" :readonly="readonly" />

        <ProFormTextPassword
          v-model="model.password"
          :label="tr('密码', 'Password')"
          :readonly="readonly"
        />

        <ProFormTextArea
          v-model="model.textarea"
          class="span-2"
          :label="tr('多行文本', 'Text area')"
          :readonly="readonly"
          :field-props="{ rows: 3 }"
        />

        <ProFormCaptcha
          v-model="model.captcha"
          class="span-2"
          :label="tr('验证码', 'Captcha')"
          :readonly="readonly"
          :on-get-captcha="getCaptcha"
          :count-down="5"
          :captcha-text="tr('发送演示验证码', 'Send demo code')"
        />

        <ProFormDatePicker v-model="model.date" :label="tr('日期', 'Date')" :readonly="readonly" />

        <ProFormDateTimePicker
          v-model="model.dateTime"
          :label="tr('日期时间', 'Date time')"
          :readonly="readonly"
        />

        <ProFormDateRangePicker
          v-model="model.dateRange"
          :label="tr('日期区间', 'Date range')"
          :readonly="readonly"
        />

        <ProFormDateTimeRangePicker
          v-model="model.dateTimeRange"
          :label="tr('日期时间区间', 'Date-time range')"
          :readonly="readonly"
        />

        <ProFormSelect
          v-model="model.select"
          :label="tr('异步选择', 'Async select')"
          :readonly="readonly"
          :request="requestSelectOptions"
        />

        <ProFormTreeSelect
          v-model="model.treeSelect"
          :label="tr('树选择', 'Tree select')"
          :readonly="readonly"
          :options="treeOptions"
        />

        <ProFormCheckbox
          v-model="model.checkbox"
          :label="tr('复选组', 'Checkbox group')"
          :readonly="readonly"
          :options="checkboxOptions"
          layout="vertical"
        />

        <ProFormRadioGroup
          v-model="model.radio"
          :label="tr('单选组', 'Radio group')"
          :readonly="readonly"
          :options="radioOptions"
        />

        <ProFormSlider
          v-model="model.slider"
          :label="tr('滑块', 'Slider')"
          :readonly="readonly"
          :field-props="{ min: 0, max: 100 }"
        />

        <ProFormSwitch v-model="model.switch" :label="tr('开关', 'Switch')" :readonly="readonly" />

        <ProFormMoney v-model="model.money" :label="tr('金额', 'Money')" :readonly="readonly" />

        <ProFormSegmented
          v-model="model.segmented"
          :label="tr('分段选择', 'Segmented')"
          :readonly="readonly"
          :options="segmentedOptions"
        />

        <ProFormUploadButton
          v-model="model.uploadButton"
          :label="tr('按钮上传', 'Upload button')"
          :readonly="readonly"
          :field-props="{ maxCount: 1 }"
        />

        <ProFormUploadDragger
          v-model="model.uploadDragger"
          class="span-2"
          :label="tr('拖拽上传', 'Upload dragger')"
          :readonly="readonly"
          :field-props="{ multiple: true }"
        />
      </div>
    </Form>

    <div class="bare-fields">
      <strong>{{ tr('裸控件模式', 'Bare-control mode') }}</strong>
      <ProFormText
        v-model="model.bareText"
        field-mode="field"
        :readonly="readonly"
        :placeholder="tr('不会生成 FormItem', 'No FormItem is rendered')"
      />
      <ProFormCheckbox v-model="model.bareChecked" field-mode="field" :readonly="readonly">
        {{
          tr(
            'checked / update:checked 已桥接到 v-model',
            'checked / update:checked bridged to v-model',
          )
        }}
      </ProFormCheckbox>
    </div>

    <p v-if="captchaStatus" class="demo-status">{{ captchaStatus }}</p>
  </div>
</template>

<style scoped>
.demo-heading {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 20px;
}

.demo-heading .demo-label,
.demo-note {
  margin: 0;
}

.demo-note {
  margin-top: 5px;
  color: var(--vp-c-text-2);
  font-size: 13px;
}

.demo-heading button {
  flex: none;
  padding: 6px 12px;
  border: 1px solid var(--bubbles-border);
  border-radius: 7px;
  background: var(--vp-c-bg);
  color: var(--vp-c-brand-1);
  cursor: pointer;
}

.field-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: 20px;
}

.span-2 {
  grid-column: span 2;
}

.bare-fields {
  display: grid;
  grid-template-columns: minmax(120px, 0.45fr) minmax(220px, 1fr) minmax(220px, 1fr);
  align-items: center;
  gap: 14px;
  margin-top: 4px;
  padding-top: 18px;
  border-top: 1px solid var(--bubbles-border);
}

.demo-status {
  margin: 14px 0 0;
  color: var(--vp-c-text-2);
  font-size: 12px;
}

@media (max-width: 720px) {
  .field-grid,
  .bare-fields {
    grid-template-columns: 1fr;
  }

  .span-2 {
    grid-column: auto;
  }

  .demo-heading {
    flex-direction: column;
  }
}
</style>

导入与命名

19 个模板友好名称均支持具名导入;安装默认插件后也会注册为同名全局组件。

ts
import {
  ProFormCaptcha,
  ProFormCheckbox,
  ProFormDatePicker,
  ProFormDateRangePicker,
  ProFormDateTimePicker,
  ProFormDateTimeRangePicker,
  ProFormDigit,
  ProFormMoney,
  ProFormRadioGroup,
  ProFormSegmented,
  ProFormSelect,
  ProFormSlider,
  ProFormSwitch,
  ProFormText,
  ProFormTextArea,
  ProFormTextPassword,
  ProFormTreeSelect,
  ProFormUploadButton,
  ProFormUploadDragger,
} from 'antdv-next-pro'

为对应 Ant Design Pro Components 的组合命名,同时提供:

ts
ProFormText.Password === ProFormTextPassword
ProFormRadio.Group === ProFormRadioGroup

Vue 模板推荐直接使用 ProFormTextPasswordProFormRadioGroupProFormRadio 是只读命名空间对象,不是第 20 个字段组件,也不会作为带点名称全局注册。

通用 API

Props

Prop类型默认值说明
modelValue由组件决定undefined标准 v-model
namestring | number | readonly (string | number)[]FormItem 字段名,支持嵌套路径
labelVNodeChildFormItem 标签
rulesAntdv Next FormItem rules校验规则
fieldMode'form-item' | 'field''form-item'渲染表单项或裸控件
fieldProps对应基础控件 Props传给 Input、Select 等基础控件
formItemPropsAntdv Next FormItem Props只传给 FormItem
disabledbooleanfalse保持控件外观并禁止交互
readonlybooleanfalse改用统一只读展示
emptyTextVNodeChild'-'undefinednull、空字符串的占位
readonlyRender(value) => VNodeChild自定义只读输出;返回空值时回退默认格式化

表单项与裸控件

fieldMode="form-item" 是默认模式,namelabelrulesformItemProps 作用于组件创建的 FormItem。

vue
<ProFormText
  v-model="form.title"
  name="title"
  label="标题"
  :rules="[{ required: true, message: '请输入标题' }]"
/>

当父级已经提供 FormItem,或字段位于表格单元格、自定义网格中时使用裸控件模式。此时不会创建 FormItem,namelabelrulesformItemProps 也不参与渲染。

vue
<ProFormText v-model="keyword" field-mode="field" placeholder="只渲染 Input" />

属性优先级与 attrs

同名普通属性按以下顺序取值:

  1. 组件顶层显式属性。
  2. fieldPropsformItemProps 中的同名属性。
  3. 组件默认值。

因此顶层 disabled 会覆盖 fieldProps.disabled,顶层 label 会覆盖 formItemProps.label。内部受控值与更新监听器始终由字段核心写入,使用者回调会被组合,不会覆盖 v-model 更新。

fieldProps.class/style 作用于基础控件,formItemProps.class/style 作用于 FormItem。普通 classstyledata-*aria-* 在表单项模式落到 FormItem,在 field 模式落到基础控件。

v-model 桥接

所有公开组件对外都使用 modelValue / update:modelValue,字段核心会适配基础控件的不同协议:

组件类型基础控件协议ProFormFields 对外协议
Input、InputNumber、日期、Select、Radio、Slider、Segmentedvalue / update:valuev-model
单 Checkbox、Switchchecked / update:checkedv-model
Upload、UploadDraggerfileList / update:fileListv-model

fieldProps['onUpdate:value']fieldProps['onUpdate:checked']fieldProps['onUpdate:fileList'] 可与组件 v-model 同时使用,每次更新各调用一次。

事件

事件适用组件说明
update:modelValue全部规范化后的字段值
change全部原样转发基础控件的 change 参数
requestErrorSelect、TreeSelect、Checkbox 组、RadioGroup、Segmented选项请求失败;已有选项不会被清空
captchaErrorCaptcha回调 reject 或返回 falsefalse 原样作为取消结果
drop两个 Upload原样转发拖拽事件

底层事件顺序为:内部模型更新 → fieldProps 处理器 → 包装组件 emit。

插槽与实例方法

labelextrahelptooltip 是 FormItem 插槽;其他具名插槽和默认插槽会转发给基础控件。fieldMode="field" 时不渲染 FormItem 专属插槽。

  • Captcha 增加 captcha 插槽,参数为 { seconds }
  • UploadButton 的默认插槽替换上传按钮内容。
  • UploadDragger 的默认插槽替换拖拽区内容。
  • 选项类组件通过 ref 暴露 refresh(): Promise<void>
  • Captcha 通过 ref 暴露 resetCountdown(): void

选项、请求与只读

optionsvalueEnumrequest

Select、TreeSelect、Checkbox 组、RadioGroup 与 Segmented 使用相同协议:

vue
<ProFormSelect
  ref="selectRef"
  v-model="owner"
  :options="fallbackOptions"
  :params="{ teamId }"
  :request="loadOwners"
  @request-error="reportError"
/>
  • 存在 request 时立即请求;第一次成功前可以显示本地回退选项。
  • 远程请求成功后结果成为权威来源,[] 也是有效结果,不会回退到本地选项。
  • 没有 request 时,优先级为顶层 optionsfieldProps.options(TreeSelect 为 treeData)→ valueEnum
  • 请求失败保留最后一次成功结果;从未成功时保留本地回退,并触发 requestError
  • requestparams 变化会刷新;并发时只有最后一次请求可以更新状态。

选项会按 label ?? text ?? title ?? name ?? value 解析标签,按 value ?? key ?? id 解析值。若 valueEnum 需要严格保留数值 key,请使用 ReadonlyMap,或在枚举项中显式写 value

ts
const levels = new Map([
  [1, { text: '一级' }],
  [2, { text: '二级' }],
])

const alternate = {
  1: { text: '一级', value: 1 },
}

只读与重置

readonly 使用统一的 ReadonlyField,不会依赖各基础控件不同的只读实现。选项显示 label,数组使用逗号连接,日期区间使用 ~,密码显示掩码,金额保留前缀,上传显示文件名。0false 不会被当作空值。

字段都是受控组件:父级重置并传入新的 modelValue 后会立即更新。选项缓存不会因值重置而清空;Captcha 的倒计时需调用 resetCountdown() 显式取消。

19 个组件

以下各项都可以在完整字段示例中直接操作。

ProFormText

单行文本字段,modelValuestring | undefined,基础控件是 Input。无额外 Pro 默认值;allowClearprefixsuffixmaxlength 等通过 fieldProps 传入。

ProFormDigit

数值字段,modelValuenumber | string | null。默认 min=0precision=2;顶层 min / precision 优先于 fieldProps,传 false 可不向 InputNumber 下发对应限制。

vue
<ProFormDigit v-model="count" :min="false" :precision="0" />

ProFormTextPassword

密码输入,modelValuestring | undefined,基础控件是 InputPassword。只读非空值固定显示 ••••••。可使用模板友好名称 ProFormTextPassword,也可在脚本或模板中使用 ProFormText.Password

ProFormTextArea

多行文本,modelValuestring | undefinedrowsautoSizemaxlength 等 TextArea 属性通过 fieldProps 传入。

ProFormCaptcha

验证码输入由 Input 与发送按钮组成。onGetCaptcha 必填;countDown 默认 60 秒,按钮默认文案为“获取验证码”。回调 resolve 且结果不为 false 时开始倒计时,reject 或 false 会恢复按钮并触发 captchaError

组件不读取手机号、不发送网络请求、不持久化验证码;业务侧必须提供 onGetCaptcha。loading、倒计时、disabled、readonly 任一成立时都会阻止重复点击。

ProFormDatePicker

日期选择器,值保持 Antdv Next 的 Dayjs 协议,不自动转换为字符串或原生 Date。格式、禁用日期、面板等属性通过 fieldProps 传入。

ProFormDateTimePicker

日期时间选择器,值保持 Dayjs;默认 showTime=true,可通过顶层 showTimefieldProps.showTime 传入具体配置。

ProFormDateRangePicker

日期区间选择器,值为 Dayjs 区间或 null;清空时不会合成空字符串。范围预设、禁用日期等通过 fieldProps 传入。

ProFormDateTimeRangePicker

日期时间区间选择器,值保持 Dayjs 区间,默认 showTime=true

ProFormSelect

选择字段,支持 optionsvalueEnumrequestparamsrequestErrormode="multiple" / "tags" 等能力通过 fieldProps 使用。ref 暴露 refresh()

ProFormTreeSelect

树选择字段,共用选项请求协议。标准 option 会映射为带 titlelabelvaluedisabledchildren 的树节点;调用者提供的 treeDatafieldNames 等仍可通过 fieldProps 使用。

ProFormCheckbox

没有选项来源时渲染单 Checkbox,内部 checked 自动桥接到 v-model;存在 optionsvalueEnumrequestfieldProps.options 时渲染 CheckboxGroup。layout 默认为 horizontal,也可设为 vertical

ProFormRadioGroup

单选组,共用选项请求协议;方向、button style 等能力通过底层 RadioGroup 的 fieldProps 使用。模板推荐 ProFormRadioGroup,同时提供 ProFormRadio.Group 命名关系。

ProFormSlider

滑块字段,modelValuenumber | number[] | undefinedrangemarksminmaxsteptooltip 均通过 fieldProps 透传。

ProFormSwitch

开关字段对外使用 v-model,内部使用 checked / update:checkedcheckedValueunCheckedValue 原样保留,不会强制把自定义值转换为 Boolean。

ProFormUploadButton

按钮式上传入口,modelValueUploadFile[],内部桥接 fileList。默认槽可替换按钮内容;actioncustomRequestbeforeUploadheadersdatamultipleacceptmaxCountitemRender 等通过 fieldProps 传入。

组件库不提供上传后端。没有 actioncustomRequest 时,内部 beforeUpload 返回 false 以阻止向未知地址发送,但仍执行使用者提供的 beforeUpload

ProFormUploadDragger

拖拽式上传入口,与 UploadButton 使用同一个 UploadFile[]fileList 和无后端协议。默认槽替换整个拖拽区,drop 事件原样转发。

ProFormMoney

金额字段,modelValuenumber | string | null。默认前缀为 ¥;顶层 prefixfieldProps.prefix 可覆盖,传 false 可关闭。formatterparserstringMode 直接透传给 InputNumber。

ProFormSegmented

分段选择字段,共用选项请求协议,值为 string | number。没有选项来源时向基础控件传入空数组;选项的 disabled 与其他扩展字段会被保留。

与上层组件共用字段核心

独立字段与上层组件使用同一注册表和 FieldControl:

  • ProTable 搜索项使用表单项模式,可编辑单元格使用裸控件模式。
  • SchemaForm 保留自己的 FormItem、嵌套路径、动态插槽和组合字段,仅默认控件分支复用裸控件核心。
  • EditableProTable 通过 ProTable 间接复用,不创建第三套字段映射。

本期 valueType 新增 treeSelectslidersegmented。Captcha、UploadButton、UploadDragger 只作为独立组件提供,不映射为 valueType,也不内置后端。

基于 MIT 许可发布