跳转到内容

ProForm 高级表单

ProForm 是核心组件之一,高性能表单控件,自带数据域管理。包含数据录入、校验以及对应样式。解决复杂表单控件数据联动,使用配置解决大部分业务问题,使复杂功能简单化实现。ProForm 是根据 JSON Schema 来生成表单。ProForm 会根据 fieldType 来映射成不同的表单项

何时使用 ProForm?

当你想快速实现一个表单但不想花太多时间去布局时 ProForm 是最好的选择。

ProForm 是基于 antdv Form 的JSON Schema封装,但是在其之上还增加一些预设行为和多种布局。这些布局之间可以无缝切换,并且拥有公共的 API。

布局使用场景
ProForm标准 Form,增加了 onFinish 中自动 loading 和根据 request 自动获取默认值的功能。通过设置 layoutType 可以实现所有布局。
ProModalForm|ProDrawerForm在 ProForm 的基础上增加了 trigger ,无需维护 open 状态。
ProQueryFilter一般用于作为筛选表单,需要配合其他数据展示组件使用。
ProStepsForm分步表单,需要配置 steps 生成步骤使用。

使用演示

分步表单、Modal 表单、Drawer 表单、查询表单等多种 layout 可以覆盖大部分的使用场景,让我们脱离复杂而且繁琐的表单布局工作,用更少的代码完成更多的功能。

基本使用

标签与表单项布局

除了 ProQueryFilter 这样固定布局的表单样式,其他表单布局支持配置与 antdv 一致的二种布局方式,不支持inline布局方式。

栅格化布局

同时支持在 ProForm, ProModalForm, ProDrawerForm, ProStepsForm 中使用,ProQueryFilter 默认使用栅格化布局,无法更改。

表单联动

表单联动是及其常用的功能,由于场景非常多,实现时比较复杂,组件内部支持了各种联动方式,可以满足大多数业务场景。大多数情况下,你不再需要使用onChange事件手动处理表单联动。

  1. 使用表单项linkage配置,支持cleardisabledhidden三种配置,还支持函数方式使用,例如:{hidden: ['name'], clear: ['name'], disabled: ['name']}
  2. 使用函数,表单项很多配置都支持函数类型,如:titleoptionsformItemPropsreadonlydisabledrulesfieldPropsrenderrenderFormItem
  3. 使用表单项dependencies配置,此配置只针对使用request请求的Field有效,会根据dependencies配置的namePath值变化重新请求,非常适合下拉多级联动功能。
表单项部分配置类型
ts
{
  linkage?: {
    disabled?: Key[] | ((value: any, formData: any) => Key[])
    hidden?: Key[] | ((value: any, formData: any) => Key[])
    clear?: NamePath[] | ((value: any, formData: any) => NamePath[])
  };
  title?: ProVNode | ((formData: T) => ProVNode);
  options?:
    | (string | number | RequestOptionsType)[]
    | ((formData: T, rowData?: Entity) => (string | number | RequestOptionsType)[]);
  formItemProps?:
    | Record<string, any>
    | ((formData: T, config: ProFormItemType<T, FieldType>) => Record<string, any>);
  readonly?: ((formData: T, rowData?: Entity) => boolean) | boolean
  disabled?: ((formData: T, rowData?: Entity) => boolean) | boolean
  rules?:
    | ((formData: T, action: ProFormActionType) => FormItemProps['rules'])
    | FormItemProps['rules']
  fieldProps?: ((formData: T) => Record<string, any>) | Record<string, any>
  renderFormItem?:
    | string
    | ((config: {
        value: any
        onChange: <T = any>(value: T, ...args: any[]) => void
        defaultDom: VNode
        formData: T
        action: ProFormActionType
      }) => VNode | string)
  render?: ProVNode | ((formData: T) => ProVNode)
  /** @name 依赖字段的name,暂时只在拥有 request 的项目中生效,会自动注入到 params 中 */
  dependencies?: NamePath[]
}

表单方法调用

你可以通过 formRef 获取到表单实例的引用,通过引用可以调用表单方法实现表单重置,设置表单,获取表单值等功能。仅在 ProForm标准模式下ProQueryFilter 中支持。

ProModalForm|ProDrawerForm 浮层表单中,由于默认浮层内部的Form是不渲染的,只有打开后才渲染,关闭后销毁,所以无法直接获取到表单实例,可以通过init 自定义事件的第二个参数 action 调用表单方法。

ProStepsForm 分步表单中,无法直接获取内部表单实例,通过formRef.value.formArrayRef[current]方式获取当前步骤表单实例。

表单项配置onChange事件最后一个参数都是action,可以在onChange事件内直接使用表单方法,不需要使用formRef,这个所有布局都支持。

自定义提交

ProForm 默认内置验证提交,只需要在finish回调事件中执行业务逻辑即可,比如执行网络请求,当然完全可以自定义提交逻辑。

自定义提交按钮需要使用 submitter.rendersubmitter插槽 来进行自定义,使用 submitter.render 自定义按钮时可以使用jsx|tsx的方式或使用h函数,submitter.render和submitter插槽的参数完全一致。

ProForm 支持修改提交按钮到位置,使用 submitter.teleport 配置可以实现提交按钮放到你想要的任意位置,teleport 使用的vue3内置的组件Teleport

  1. 使用 submitter.onSubmit 事件自定义提交,如果返回一个 false,会阻止默认提交,当然如果你确定不使用默认提交时,请一定要返回false,否会默认提交也会执行。

  2. 使用submitter插槽自定义提交按钮配合validateFields表单实例方法,实现完全自定义。

submitter 配置类型
ts
type SubmitterProps<T = Record<string, any>> = {
  /** @name 提交方法 */
  onSubmit?: (formData: T, action?: ProFormActionType) => Promise<boolean | void> | boolean | void
  /** @name 重置方法 */
  onReset?: (formData: T, action?: ProFormActionType) => Promise<boolean | void> | boolean | void
  /** @name 搜索的配置,一般用来配置文本 */
  searchConfig?: {
    /** @name 重置按钮的文本 */
    resetText?: ProVNode
    /** @name 提交按钮的文本 */
    submitText?: ProVNode
  }
  /** @name 提交按钮的 props */
  submitButtonProps?: false | (ButtonProps & { preventDefault?: boolean })
  /** @name 重置按钮的 props */
  resetButtonProps?: false | (ButtonProps & { preventDefault?: boolean })
  /** @name 操作按钮渲染位置 */
  teleport?: string | HTMLElement
  /** @name 反转提交及重置按钮 */
  reverse?: boolean
  /** @name grid布局下 */
  colProps?: ColProps
  /** @name 自定义操作的渲染 */
  render?:
    | ((paramer: {
        props: SubmitterProps & {
          submit: () => void
          reset: () => void
        }
        action: ProFormActionType
        defaultDoms: VNode | VNode[]
      }) => VNode | false)
    | false
}

自定义渲染

ProForm 内置了很多Field,基本能满足大多数业务场景,当然任何组件不支持自定义,那是不完美的,ProForm 不仅仅支持自定义 Field,还支持自定义title、extra、render等,并且所有都支持使用函数|插槽 两种方式实现,且参数一致。

自定义Field支持局部和全局配置,局部使用renderFormItem配置,全局使用registerField注册。如果自定义组件如果希望 Form.Item 进行校验展示,你需要 const {id, onFieldChange, onFieldBlur} = useInjectFormItemContext() 注入,并调用相应的方法。

支持Antv FormItem的'extra'、'help'自定义插槽,使用方式:formItemProps: {extra: '::custom-item-extra', help: '::custom-item-help'}

支持Antv Form Field的自定义插槽,如:Input|Select|Checkbox,根据定义的fieldType使用,使用方式:fieldProps: {addonAfter: '::addonAfter', suffix: '::suffix'}

  1. 使用函数时,需要返回 jsx|tsxh函数 的VNode,((params: { formData: T }) => VNode)
  2. 使用插槽时,没有固定的slot name,内部约定使用::slot-name开头的字符串代表使用自定义插槽,如:title: '::custom-title',需要在 ProForm 下使用插槽,<template #custom-title={formData}>我是自定义标题</template>

提示

在 script setup 下使用jsx|tsx时,需要设置语言<script setup lang="tsx">

全局注册:

ts
import { registerField } from 'pro-design-vue'
import CustomField from '@/components/custom-field'
registerField('custom-field', CustomField)

使用时fieldType:'custom-field'即可,当然全局注册组件也需要满足一些基本条件,参考下方详情。

custom-item.vue
vue
<script setup lang="ts">
import {
  ProTable,
  type ProTableColumnsType,
  type ProTableRowSelection,
  type ProFormItemType,
} from 'pro-design-vue'
import { type PropType } from 'vue'
import { Form } from 'ant-design-vue'

defineProps({
  value: {
    type: Array as PropType<string[]>,
    default: () => [],
  },
  onChange: {
    type: Function as PropType<ProFormItemType['onChange']>,
  },
})
const emit = defineEmits(['change', 'update:value'])

const formItemContext = Form.useInjectFormItemContext()

const columns: ProTableColumnsType = [
  {
    title: 'id',
    dataIndex: 'id',
    width: 100,
  },
  {
    title: '名称',
    dataIndex: 'name',
    width: 200,
  },
]

const dataSource = [
  { id: '1', name: 'pro design' },
  { id: '2', name: 'pro design admin' },
  { id: '3', name: 'pro design vue' },
]

const rowSelection: ProTableRowSelection = {
  type: 'checkbox',
  onChange(selectedRowKeys) {
    emit('change', selectedRowKeys)
    emit('update:value', selectedRowKeys)
    formItemContext.onFieldChange()
  },
}
</script>

<template>
  <ProTable
    :selected-row-keys="value"
    :row-selection="rowSelection"
    :columns="columns"
    :tool-bar="false"
    :card-props="false"
    :pagination="false"
    :data-source="dataSource"
  >
  </ProTable>
</template>

<style scoped lang="less"></style>
全局注册的自定义Field,此处是参考代码
vue
<script setup lang="ts">
import { ref, useAttrs, computed, PropType } from 'vue'
import { Form, Input } from 'ant-design-vue'
import { ProFieldReadonly, ProFormItemType, ProModal } from 'pro-design-vue'
import { SCron } from '@micro-front/components'
import type { SCronInstance } from '@micro-front/components'

defineOptions({
  name: 'CronField',
})

const props = defineProps({
  value: {
    type: String,
    default: '',
  },
  placeholder: {
    type: String,
    default: '请输入',
  },
  readonly: {
    type: Boolean,
    default: undefined,
  },
  readonlyProps: {
    type: Object as PropType<ProFormItemType['readonlyProps']>,
    default: undefined,
  },
  onChange: {
    type: Function as PropType<ProFormItemType['onChange']>,
  },
})
const emit = defineEmits(['change', 'update:value'])
const attrs = useAttrs()
const cronRef = ref<SCronInstance>()
const open = ref(false)
const cronValue = ref('')
const formItemContext = Form.useInjectFormItemContext()

const internalValue = computed({
  get: () => {
    return props.value
  },
  set: (val) => {
    emit('change', val)
    emit('update:value', val)
  },
})

const onInputChange = (e: any) => {
  emit('change', e.target.value)
  emit('update:value', e.target.value)
  formItemContext.onFieldChange()
}

const onShowCron = () => {
  open.value = true
  cronValue.value = internalValue.value
}

const onConfirm = () => {
  const cronValue = cronRef.value?.getCurrentCrobValue()
  emit('change', cronValue)
  emit('update:value', cronValue)
  formItemContext.onFieldChange()
  return true
}
</script>

<template>
  <template v-if="readonly">
    <ProFieldReadonly :text="internalValue" v-bind="readonlyProps" />
  </template>
  <template v-else>
    <Input
      v-model:value="internalValue"
      v-bind="attrs"
      :placeholder="placeholder"
      @change="onInputChange"
    >
      <template #addonAfter>
        <a style="font-size: 12px" @click="onShowCron">配置</a>
      </template>
    </Input>
    <ProModal title="配置Cron表达式" v-model:open="open" destroy-on-close @confirm="onConfirm">
      <SCron ref="cronRef" v-model:value="cronValue" style="height: 506px" />
    </ProModal>
  </template>
</template>

API

Props

参数说明类型默认值
colon配置 Form.Item 的 colon 的默认值 (只有在属性 layout 为 horizontal 时有效)booleantrue
disabled设置全部表单项禁用,表单项单独配置优先级更高booleanfalse
hideRequired-mark隐藏所有表单项的必选标记booleanfalse
label-alignlabel 标签的文本对齐方式left | right'right'
label-collabel 标签布局,Antv Col 的所有属性,设置 span offset 值,如 {span: 3, offset: 12}sm: {span: 3, offset: 12}ColProps-
label-wraplabel 标签的文本换行方式booleanfalse
layout表单布局horizontal|vertical'horizontal'
form-key表单key,默认不需要配置,系统自动递增string-
wrapper-col需要为输入控件设置布局样式时,使用该属性,用法同 labelColColProps-
loading表单受控加载动画,默认表单内部控制,提交表单时触发boolean-
submit-on-loading提交时是否显示加载动画(包含表单主体、提交按钮)booleantrue
show-loading是否显示加载动画(包含提交、加载初始值)booleantrue
omit-nilProForm 会自动清空 null 和 undefined 的数据,如果你约定了 nil 代表某种数据,可以设置为 false 关闭此功能booleantrue
readonly设置全部表单项只读,表单项单独配置优先级更高booleanfalse
readonly-props全部只读表单项的配置,与表单项单独配置合并处理{ tooltip?: boolean | string; copy?: boolean; emptyText?: string; ellipsis?: boolean; autoLine?: boolean}{ emptyText: '-', ellipsis: true }
grid表单布局的栅格化,基于行(row)和列(col)来定义信息区块的外部框架booleanfalse
col-props在开启 grid 模式时传递给 Col,表单项单独配置优先级更高,支持Antv Col 的所有属性ColProps{ xs: 24 }
row-props在开启 grid 模式时传递给 Row,表单项(group、formList、formSet)单独配置优先级更高,支持Antv Row 的所有属性RowProps{ gutter: 16 }
initial-values表单默认值,只有初始化以及重置时生效object-
request获取表单默认值的网络请求,返回值会和initialValues合并(params)=>Promise<data>-
params用于 request 查询的额外参数,变化不会触发重新加载object-
request-abort终止网络请求,初始化有效booleanfalse
submitter提交按钮相关配置boolean | SubmitterPropstrue
items表单的定义,一般是 json 数组,如果是分步表单,需要使用数组嵌套 json 数组来生成多个表单ProFormItemType[] | ProFormItemType[][][]
layout-type使用的表单布局模式Form | DrawerForm | ModalForm | QueryFilter | StepForm'Form'

Events

事件名说明类型
init表单初始化后回调事件(values, action)=> void
finish提交表单且数据验证成功后回调事件(values)=>Promise<void> | void
reset点击重置按钮的回调(e)=>void
finish-failed提交表单且数据验证失败后回调事件({ values, errorFields })=>void
values-change提交表单且数据验证失败后回调事件(values)=>void
loading-change加载动画变化后的回调事件(loading)=>void

Slots

插槽名说明类型
submitter提交按钮{props, action, defaultDoms}

Exposes

名称说明类型
getFieldValue获取对应字段名的值(name: NamePath) => any
getFieldsValue获取一组字段名对应的值,会按照对应结构返回。默认返回现存字段值,当调用 getFieldsValue(true) 时返回所有值(nameList?: true | NamePath[]) => object
resetInitialValues重新设置表单默认值(values: object) => void
getFieldFormatValue获取格式化之后的单个数据(name: NamePath) => any
getFieldFormatValueObject获取格式化之后的单个数据(name: NamePath) => object
validateFieldsReturnFormatValue验字段后返回格式化之后的所有数据(nameList?: NamePath[]) => Promise<object>
setFieldValue设置单个表单项的值(name: NamePath, value: any) => void
setFieldsValue设置表单的值 (values: object, isMerge?: boolean = true) => void
resetField重置字段到 initialValue,并清除验证信息(name: NamePath) => void
clearValidate移除表单项的校验结果。传入待移除的表单项的 name 属性或者 name 组成的数组,如不传则移除整个表单的校验结果(nameList?: NamePath[]) => void
validateFields触发表单验证(nameList?: NamePath[]) => Promise
reset对整个表单进行重置,将所有字段值重置为初始值并移除校验结果() => void
submit提交表单,与点击 submit 按钮效果相同() => void

ProFormItemType

字段名称说明类型默认值
key确定这个列的唯一值,一般用于 name 重复的情况string | number-
name与实体映射的 key,数组会被转化 [a,b] => Entity.a.bstring | string[]-
fieldType表单项渲染方式,我们自带了一部分,你也可以自定义 fieldTypeProFieldTypeProFieldType.TEXT
title标题的内容,是 form 中的 labelVNode | ((formData: T) => VNode)-
tooltip会在 title 旁边展示一个 icon,鼠标浮动之后展示string-
initialValue表单项默认值,类型根据表单项而定any-
order排序number-
placeholder占位符string | string[]-
widthField 的长度,我们归纳了常用的 Field 长度以及适合的场景,支持了一些枚举 "xs" , "sm" , "md" ,"lg" , "xl"number | "xs" | "sm" | "md" | "lg" | "xl"-
grid开启 grid 模式,仅在group中有效,默认继承表单配置boolean-
colProps在开启 grid 模式时传递给 Col,支持Antv Col 的所有属性ColProps{ xs: 24 }
rowProps在开启 grid 模式时传递给 Row,仅在group、formList、formSet中有效,支持Antv Row 的所有属性RowProps{ gutter: 16 }
spaceProps没有开启grid 模式时传递给 Space,仅在group、formSet中有效,支持Antv Space 的所有属性SpaceProps-
allowClear可以点击清除图标删除内容booleanfalse
rules表单验证规则 ,和FormItemrules 相同FormItemProps['rules'] | (formData: T, action: ProFormActionType) => FormItemProps['rules']-
hidden是否隐藏booleanfalse
disabled是否禁用状态((formData: T, rowData?: Entity) => boolean) | booleanfalse
readonly是否只读状态((formData: T, rowData?: Entity) => boolean) | booleanfalse
readonlyProps只读状态配置{ tooltip?: boolean | string; copy?: boolean; emptyText?: string; ellipsis?: boolean; autoLine?: boolean}{ emptyText: '-', ellipsis: true }
formItemPropsAntv FormItem 的部分属性FormItemProps | ((formData: T, config: ProFormItemType<T, FieldType>) => FormItemProps-
fieldPropsField 的额外属性,不同 Field 值不同((formData: T) => Record<string, any>) | Record<string, any>-
extra额外扩展,支持标题及表单控件后方展示{ label?: VNode | ((formData: T) => VNode); item?: VNode | ((formData: T) => VNode)}-
valueEnum部分表单项支持,支持 object 和 Map,Map 是支持其他基础类型作为 key((formData: T) => ProValueEnumObj | ProValueEnumMap) | ProValueEnumObj | ProValueEnumMap-
colSize一个表单项占用的格子数量,占比= colSize*spancolSize 默认为 1,仅在查询表单中生效number1
convertValue前置转化,发生在组件获得数据之前,一般是后端直接给前端的数据,有时需要精加工一下(value: any,namePath: NamePath) => string | boolean | Record<string, any>-
transform提交时转化,发生在提交的时候,一般来说都是吐给后端的存在数据库里的数据(value: any,namePath: NamePath,allValues: any,) => string | Record<string, any>-
options部分表单项支持,select、checkboxGroud、radioGroup、treeSelect等(string | number | RequestOptionsType)[] | ((formData: T, rowData?: Entity) => (string | number | RequestOptionsType)[])-
request部分表单项支持,从网络获取options数据(params: U,index?: number) => Promise<string | number | RequestOptionsType[]-
dependencies所依赖的 values 变化后,触发 request 重新执行,并把 values 注入到 params 里NamePath[]-
linkage无比强大的联动功能,可以清空、禁用、隐藏其他表单项{ disabled?: Key[] | ((value: any, formData: any) => Key[]); hidden?: Key[] | ((value: any, formData: any) => Key[]); clear?: NamePath[] | ((value: any, formData: any) => NamePath[])}-
renderFormItem自定义渲染表单项的Field,支持jsx、插槽等多种渲染方式string | (({value: any; onChange: <T = any>(value: T, ...args: any[]) => void;defaultDom: VNode; formData: T;action: ProFormActionType }) => VNode | string)-
render自定义渲染表单项,包含labelVNode | ((formData: T) => VNode)-
onChange表单项value变化时的回调,在强大linkage下,基本使用不到(...args: any[]) => void-
onInit表单项初始化时的回调,此处可以回去到Field的ref(ref: any) => void-
children表单控件,group、formList、formSet 下有效ProFormItemType<T, FieldType>[] | ((formData: T) => ProFormItemType<T, FieldType>[])-

fieldType 列表

fieldType 是 ProForm 的灵魂, ProForm 会根据 fieldType 来映射成不同的表单项。以下是支持的常见表单项:

fieldType枚举说明
textProFieldType.TEXT文本框
passwordProFieldType.PASSWORD密码输入框
digitProFieldType.DIGIT数字输入框
digitRangeProFieldType.DIGIT_RANGE数字区间输入框
selectProFieldType.SELECT下拉框
treeSelectProFieldType.TREE_SELECT树形下拉框
cascaderProFieldType.CASCADER级联选择器
textareaProFieldType.TEXTAREA文本域
checkboxProFieldType.CHECKBOX多选框
checkboxGroupProFieldType.CHECKBOX_GROUP多选框组
radioGroupProFieldType.RADIO_GROUP单选框组
switchProFieldType.SWITCH开关
sliderProFieldType.SLIDER滑动输入条
rateProFieldType.RATE星级组件
dateProFieldType.DATE日期
dateRangeProFieldType.DATE_RANGE日期区间
timeProFieldType.TIME时间
timeRangeProFieldType.TIME_RANGE时间区间
uploadButtonProFieldType.UPLOAD_BUTTON上传(按钮)
uploadDraggerProFieldType.UPLOAD_DRAGGER上传(拖拽)
uploadPictureProFieldType.UPLOAD_PICTURE上传(图片)
uploadPictureListProFieldType.UPLOAD_PICTURE_LIST上传(图片列表)
groupProFieldType.GROUP分组
formSetProFieldType.FORM_SET表单集合
formListProFieldType.FORM_LIST表单列表

源代码

组件样式文档

贡献者