- Form 表单
- 表单
- 表单域
- 注意:
- 代码演示
- 表单联动
- 动态校验规则
- 水平登录栏
- 表单布局
- 自定义校验
- 自行处理表单数据
- 高级搜索
- 自定义表单控件
- 动态增减表单项
- 弹出层中的新建表单
- 表单数据存储于上层组件
- 表单数据存储于 Vuex Store 中
- 登录框
- 注册新用户
- 时间类控件
- 校验其他组件
- API
- Form
- 事件
- Form.create(options) | this.$form.createForm(this, options)
- jsx使用方式,使用方式和React版antd一致
- 单文件template使用方式
- validateFields/validateFieldsAndScroll
- validateFields 的 callback 参数示例
- Form.createFormField
- this.form.getFieldDecorator(id, options) 和 v-decorator="[id, options]"
- 特别注意
- getFieldDecorator(id, options) 和 v-decorator="[id, options]" 参数
- Form.Item
- 校验规则
Form 表单
具有数据收集、校验和提交功能的表单,包含复选框、单选框、输入框、下拉选择框等元素。
表单
我们为 form 提供了以下三种排列方式:
- 水平排列:标签和表单控件水平排列;(默认)
- 垂直排列:标签和表单控件上下垂直排列;
- 行内排列:表单项水平行内排列。
表单域
表单一定会包含表单域,表单域可以是输入控件,标准表单域,标签,下拉菜单,文本域等。
这里我们封装了表单域 <Form.Item /> 。
注意:
1、如果使用 Form.create 处理表单使其具有自动收集数据并校验的功能,建议使用jsx。2、如果不是使用Vue.use(Form)形式注册的Form组件,你需要自行将$form挂载到Vue原型上。Vue.prototype.$form = Form
代码演示

表单联动
使用 setFieldsValue 来动态设置其他控件的值。
<template><a-form:form="form"@submit="handleSubmit"><a-form-itemlabel="Note":label-col="{ span: 5 }":wrapper-col="{ span: 12 }"><a-inputv-decorator="['note',{rules: [{ required: true, message: 'Please input your note!' }]}]"/></a-form-item><a-form-itemlabel="Gender":label-col="{ span: 5 }":wrapper-col="{ span: 12 }"><a-selectv-decorator="['gender',{rules: [{ required: true, message: 'Please select your gender!' }]}]"placeholder="Select a option and change input text above"@change="handleSelectChange"><a-select-option value="male">male</a-select-option><a-select-option value="female">female</a-select-option></a-select></a-form-item><a-form-item:wrapper-col="{ span: 12, offset: 5 }"><a-buttontype="primary"html-type="submit">Submit</a-button></a-form-item></a-form></template><script>export default {data () {return {formLayout: 'horizontal',form: this.$form.createForm(this),};},methods: {handleSubmit (e) {e.preventDefault();this.form.validateFields((err, values) => {if (!err) {console.log('Received values of form: ', values);}});},handleSelectChange (value) {console.log(value);this.form.setFieldsValue({note: `Hi, ${value === 'male' ? 'man' : 'lady'}!`,});},},};</script>

动态校验规则
根据不同情况执行不同的校验规则。
<template><a-form :form="form"><a-form-item:label-col="formItemLayout.labelCol":wrapper-col="formItemLayout.wrapperCol"label="Name"><a-inputv-decorator="['username',{rules: [{ required: true, message: 'Please input your name' }]}]"placeholder="Please input your name"/></a-form-item><a-form-item:label-col="formItemLayout.labelCol":wrapper-col="formItemLayout.wrapperCol"label="Nickname"><a-inputv-decorator="['nickname',{rules: [{ required: checkNick, message: 'Please input your nickname' }]}]"placeholder="Please input your nickname"/></a-form-item><a-form-item:label-col="formTailLayout.labelCol":wrapper-col="formTailLayout.wrapperCol"><a-checkbox:checked="checkNick"@change="handleChange">Nickname is required</a-checkbox></a-form-item><a-form-item:label-col="formTailLayout.labelCol":wrapper-col="formTailLayout.wrapperCol"><a-buttontype="primary"@click="check">Check</a-button></a-form-item></a-form></template><script>const formItemLayout = {labelCol: { span: 4 },wrapperCol: { span: 8 },};const formTailLayout = {labelCol: { span: 4 },wrapperCol: { span: 8, offset: 4 },};export default {data () {return {checkNick: false,formItemLayout,formTailLayout,form: this.$form.createForm(this),};},methods: {check () {this.form.validateFields((err) => {if (!err) {console.info('success');}},);},handleChange (e) {this.checkNick = e.target.checked;this.$nextTick(() => {this.form.validateFields(['nickname'], { force: true });});},},};</script>

水平登录栏
水平登录栏,常用在顶部导航栏中。
<template><a-formlayout="inline":form="form"@submit="handleSubmit"><a-form-item:validate-status="userNameError() ? 'error' : ''":help="userNameError() || ''"><a-inputv-decorator="['userName',{rules: [{ required: true, message: 'Please input your username!' }]}]"placeholder="Username"><a-iconslot="prefix"type="user"style="color:rgba(0,0,0,.25)"/></a-input></a-form-item><a-form-item:validate-status="passwordError() ? 'error' : ''":help="passwordError() || ''"><a-inputv-decorator="['password',{rules: [{ required: true, message: 'Please input your Password!' }]}]"type="password"placeholder="Password"><a-iconslot="prefix"type="lock"style="color:rgba(0,0,0,.25)"/></a-input></a-form-item><a-form-item><a-buttontype="primary"html-type="submit":disabled="hasErrors(form.getFieldsError())">Log in</a-button></a-form-item></a-form></template><script>function hasErrors (fieldsError) {return Object.keys(fieldsError).some(field => fieldsError[field]);}export default {data () {return {hasErrors,form: this.$form.createForm(this),};},mounted () {this.$nextTick(() => {// To disabled submit button at the beginning.this.form.validateFields();});},methods: {// Only show error after a field is touched.userNameError () {const { getFieldError, isFieldTouched } = this.form;return isFieldTouched('userName') && getFieldError('userName');},// Only show error after a field is touched.passwordError () {const { getFieldError, isFieldTouched } = this.form;return isFieldTouched('password') && getFieldError('password');},handleSubmit (e) {e.preventDefault();this.form.validateFields((err, values) => {if (!err) {console.log('Received values of form: ', values);}});},},};</script>

表单布局
表单有三种布局。
<template><div><a-form :layout="formLayout"><a-form-itemlabel="Form Layout":label-col="formItemLayout.labelCol":wrapper-col="formItemLayout.wrapperCol"><a-radio-groupdefault-value="horizontal"@change="handleFormLayoutChange"><a-radio-button value="horizontal">Horizontal</a-radio-button><a-radio-button value="vertical">Vertical</a-radio-button><a-radio-button value="inline">Inline</a-radio-button></a-radio-group></a-form-item><a-form-itemlabel="Field A":label-col="formItemLayout.labelCol":wrapper-col="formItemLayout.wrapperCol"><a-input placeholder="input placeholder" /></a-form-item><a-form-itemlabel="Field B":label-col="formItemLayout.labelCol":wrapper-col="formItemLayout.wrapperCol"><a-input placeholder="input placeholder" /></a-form-item><a-form-item:wrapper-col="buttonItemLayout.wrapperCol"><a-button type="primary">Submit</a-button></a-form-item></a-form></div></template><script>export default {data () {return {formLayout: 'horizontal',};},computed: {formItemLayout () {const { formLayout } = this;return formLayout === 'horizontal' ? {labelCol: { span: 4 },wrapperCol: { span: 14 },} : {};},buttonItemLayout () {const { formLayout } = this;return formLayout === 'horizontal' ? {wrapperCol: { span: 14, offset: 4 },} : {};},},methods: {handleFormLayoutChange (e) {this.formLayout = e.target.value;},},};</script>

自定义校验
我们提供了 validateStatus help hasFeedback 等属性,你可以不需要使用 Form.create 和 getFieldDecorator,自己定义校验的时机和内容。
validateStatus: 校验状态,可选 ‘success’, ‘warning’, ‘error’, ‘validating’。hasFeedback:用于给输入框添加反馈图标。help:设置校验文案。
<template><a-form><a-form-item:label-col="labelCol":wrapper-col="wrapperCol"label="Fail"validate-status="error"help="Should be combination of numbers & alphabets"><a-inputid="error"placeholder="unavailable choice"/></a-form-item><a-form-item:label-col="labelCol":wrapper-col="wrapperCol"label="Warning"validate-status="warning"><a-inputid="warning"placeholder="Warning"/></a-form-item><a-form-item:label-col="labelCol":wrapper-col="wrapperCol"label="Validating"has-feedbackvalidate-status="validating"help="The information is being validated..."><a-inputid="validating"placeholder="I'm the content is being validated"/></a-form-item><a-form-item:label-col="labelCol":wrapper-col="wrapperCol"label="Success"has-feedbackvalidate-status="success"><a-inputid="success"placeholder="I'm the content"/></a-form-item><a-form-item:label-col="labelCol":wrapper-col="wrapperCol"label="Warning"has-feedbackvalidate-status="warning"><a-inputid="warning"placeholder="Warning"/></a-form-item><a-form-item:label-col="labelCol":wrapper-col="wrapperCol"label="Fail"has-feedbackvalidate-status="error"help="Should be combination of numbers & alphabets"><a-inputid="error"placeholder="unavailable choice"/></a-form-item><a-form-item:label-col="labelCol":wrapper-col="wrapperCol"label="Success"has-feedbackvalidate-status="success"><a-date-picker style="width: 100%" /></a-form-item><a-form-item:label-col="labelCol":wrapper-col="wrapperCol"label="Warning"has-feedbackvalidate-status="warning"><a-time-picker style="width: 100%" /></a-form-item><a-form-item:label-col="labelCol":wrapper-col="wrapperCol"label="Error"has-feedbackvalidate-status="error"><a-select default-value="1"><a-select-option value="1">Option 1</a-select-option><a-select-option value="2">Option 2</a-select-option><a-select-option value="3">Option 3</a-select-option></a-select></a-form-item><a-form-item:label-col="labelCol":wrapper-col="wrapperCol"label="Validating"has-feedbackvalidate-status="validating"help="The information is being validated..."><a-cascader:default-value="['1']":options="[]"/></a-form-item><a-form-itemlabel="inline":label-col="labelCol":wrapper-col="wrapperCol"style="margin-bottom:0;"><a-form-itemvalidate-status="error"help="Please select the correct date":style="{ display: 'inline-block', width: 'calc(50% - 12px)' }"><a-date-picker style="width: 100%" /></a-form-item><span :style="{ display: 'inline-block', width: '24px', textAlign: 'center' }">-</span><a-form-item :style="{ display: 'inline-block', width: 'calc(50% - 12px)' }"><a-date-picker style="width: 100%" /></a-form-item></a-form-item><a-form-item:label-col="labelCol":wrapper-col="wrapperCol"label="Success"has-feedbackvalidate-status="success"><a-input-number style="width: 100%" /></a-form-item></a-form></template><script>export default {data () {return {labelCol: {xs: { span: 24 },sm: { span: 5 },},wrapperCol: {xs: { span: 24 },sm: { span: 12 },},};},};</script>

自行处理表单数据
使用 Form.create 处理后的表单具有自动收集数据并校验的功能,但如果您不需要这个功能,或者默认的行为无法满足业务需求,可以选择不使用 Form.create 并自行处理数据。
<template><a-form><a-form-item:label-col="labelCol":wrapper-col="wrapperCol"label="Prime between 8 & 12":validate-status="number.validateStatus":help="number.errorMsg || tips"><a-input-number:min="8":max="12":value="number.value"@change="handleNumberChange"/></a-form-item></a-form></template><script>function validatePrimeNumber (number) {if (number === 11) {return {validateStatus: 'success',errorMsg: null,};}return {validateStatus: 'error',errorMsg: 'The prime between 8 and 12 is 11!',};}export default {data () {return {labelCol: { span: 7 },wrapperCol: { span: 12 },number: {value: 11,},tips: 'A prime is a natural number greater than 1 that has no positive divisors other than 1 and itself.',};},methods: {handleNumberChange (value) {this.number = {...validatePrimeNumber(value),value,};},},};</script>

高级搜索
三列栅格式的表单排列方式,常用于数据表格的高级搜索。有部分定制的样式代码,由于输入标签长度不确定,需要根据具体情况自行调整。
<template><div id="components-form-demo-advanced-search"><a-formclass="ant-advanced-search-form":form="form"@submit="handleSearch"><a-row :gutter="24"><a-colv-for="i in 10":key="i":span="8":style="{ display: i < count ? 'block' : 'none' }"><a-form-item :label="`Field ${i}`"><a-inputv-decorator="[`field-${i}`,{rules: [{required: true,message: 'Input something!',}],}]"placeholder="placeholder"/></a-form-item></a-col></a-row><a-row><a-col:span="24":style="{ textAlign: 'right' }"><a-buttontype="primary"html-type="submit">Search</a-button><a-button:style="{ marginLeft: '8px' }"@click="handleReset">Clear</a-button><a:style="{ marginLeft: '8px', fontSize: '12px' }"@click="toggle">Collapse <a-icon :type="expand ? 'up' : 'down'" /></a></a-col></a-row></a-form><div class="search-result-list">Search Result List</div></div></template><script>export default {data () {return {expand: false,form: this.$form.createForm(this),};},computed: {count () {return this.expand ? 11 : 7;},},methods: {handleSearch (e) {e.preventDefault();this.form.validateFields((error, values) => {console.log('error', error);console.log('Received values of form: ', values);});},handleReset () {this.form.resetFields();},toggle () {this.expand = !this.expand;},},};</script><style>.ant-advanced-search-form {padding: 24px;background: #fbfbfb;border: 1px solid #d9d9d9;border-radius: 6px;}.ant-advanced-search-form .ant-form-item {display: flex;}.ant-advanced-search-form .ant-form-item-control-wrapper {flex: 1;}#components-form-demo-advanced-search .ant-form {max-width: none;}#components-form-demo-advanced-search .search-result-list {margin-top: 16px;border: 1px dashed #e9e9e9;border-radius: 6px;background-color: #fafafa;min-height: 200px;text-align: center;padding-top: 80px;}</style>

自定义表单控件
自定义或第三方的表单控件,也可以与 Form 组件一起使用。只要该组件遵循以下的约定:
- 提供受控属性
value或其它与valuePropName-参数) 的值同名的属性。- 提供
onChange事件或trigger-参数) 的值同名的事件。- 不能是函数式组件。
<template><a-formlayout="inline":form="form"@submit="handleSubmit"><a-form-item label="Price"><price-inputv-decorator="['price',{initialValue: { number: 0, currency: 'rmb' },rules: [{ validator: checkPrice }],}]"/></a-form-item><a-form-item><a-buttontype="primary"html-type="submit">Submit</a-button></a-form-item></a-form></template><script>const hasProp = (instance, prop) => {const $options = instance.$options || {};const propsData = $options.propsData || {};return prop in propsData;};const PriceInput = {props: ['value'],template: `<span><a-inputtype='text':value="number"@change="handleNumberChange"style="width: 63%; margin-right: 2%;"/><a-select:value="currency"style="width: 32%"@change="handleCurrencyChange"><a-select-option value='rmb'>RMB</a-select-option><a-select-option value='dollar'>Dollar</a-select-option></a-select></span>`,data () {const value = this.value || {};return {number: value.number || 0,currency: value.currency || 'rmb',};},watch: {value (val = {}) {this.number = val.number || 0;this.currency = val.currency || 'rmb';},},methods: {handleNumberChange (e) {const number = parseInt(e.target.value || 0, 10);if (isNaN(number)) {return;}if (!hasProp(this, 'value')) {this.number = number;}this.triggerChange({ number });},handleCurrencyChange (currency) {if (!hasProp(this, 'value')) {this.currency = currency;}this.triggerChange({ currency });},triggerChange (changedValue) {// Should provide an event to pass value to Form.this.$emit('change', Object.assign({}, this.$data, changedValue));},},};export default {components: {PriceInput,},beforeCreate () {this.form = this.$form.createForm(this);},methods: {handleSubmit (e) {e.preventDefault();this.form.validateFields((err, values) => {if (!err) {console.log('Received values of form: ', values);}});},checkPrice (rule, value, callback) {if (value.number > 0) {callback();return;}callback('Price must greater than zero!');},},};</script>

动态增减表单项
动态增加、减少表单项。
<template><a-form:form="form"@submit="handleSubmit"><a-form-itemv-for="(k, index) in form.getFieldValue('keys')":key="k"v-bind="index === 0 ? formItemLayout : formItemLayoutWithOutLabel":label="index === 0 ? 'Passengers' : ''":required="false"><a-inputv-decorator="[`names[${k}]`,{validateTrigger: ['change', 'blur'],preserve: true,rules: [{required: true,whitespace: true,message: 'Please input passenger\'s name or delete this field.',}],}]"placeholder="passenger name"style="width: 60%; margin-right: 8px"/><a-iconv-if="form.getFieldValue('keys').length > 1"class="dynamic-delete-button"type="minus-circle-o":disabled="form.getFieldValue('keys').length === 1"@click="() => remove(k)"/></a-form-item><a-form-item v-bind="formItemLayoutWithOutLabel"><a-buttontype="dashed"style="width: 60%"@click="add"><a-icon type="plus" /> Add field</a-button></a-form-item><a-form-item v-bind="formItemLayoutWithOutLabel"><a-buttontype="primary"html-type="submit">Submit</a-button></a-form-item></a-form></template><script>let id = 0;export default {data () {return {formItemLayout: {labelCol: {xs: { span: 24 },sm: { span: 4 },},wrapperCol: {xs: { span: 24 },sm: { span: 20 },},},formItemLayoutWithOutLabel: {wrapperCol: {xs: { span: 24, offset: 0 },sm: { span: 20, offset: 4 },},},};},beforeCreate () {this.form = this.$form.createForm(this);this.form.getFieldDecorator('keys', { initialValue: [], preserve: true });},methods: {remove (k) {const { form } = this;// can use data-binding to getconst keys = form.getFieldValue('keys');// We need at least one passengerif (keys.length === 1) {return;}// can use data-binding to setform.setFieldsValue({keys: keys.filter(key => key !== k),});},add () {const { form } = this;// can use data-binding to getconst keys = form.getFieldValue('keys');const nextKeys = keys.concat(++id);// can use data-binding to set// important! notify form to detect changesform.setFieldsValue({keys: nextKeys,});},handleSubmit (e) {e.preventDefault();this.form.validateFields((err, values) => {if (!err) {console.log('Received values of form: ', values);}});},},};</script><style>.dynamic-delete-button {cursor: pointer;position: relative;top: 4px;font-size: 24px;color: #999;transition: all .3s;}.dynamic-delete-button:hover {color: #777;}.dynamic-delete-button[disabled] {cursor: not-allowed;opacity: 0.5;}</style>

弹出层中的新建表单
当用户访问一个展示了某个列表的页面,想新建一项但又不想跳转页面时,可以用 Modal 弹出一个表单,用户填写必要信息后创建新的项。
<template><div><a-buttontype="primary"@click="showModal">New Collection</a-button><collection-create-formref="collectionForm":visible="visible"@cancel="handleCancel"@create="handleCreate"/></div></template><script>const CollectionCreateForm = {props: ['visible'],beforeCreate () {this.form = this.$form.createForm(this);},template: `<a-modal:visible="visible"title='Create a new collection'okText='Create'@cancel="() => { $emit('cancel') }"@ok="() => { $emit('create') }"><a-form layout='vertical' :form="form"><a-form-item label='Title'><a-inputv-decorator="['title',{rules: [{ required: true, message: 'Please input the title of collection!' }],}]"/></a-form-item><a-form-item label='Description'><a-inputtype='textarea'v-decorator="['description']"/></a-form-item><a-form-item class='collection-create-form_last-form-item'><a-radio-groupv-decorator="['modifier',{initialValue: 'private',}]"><a-radio value='public'>Public</a-radio><a-radio value='private'>Private</a-radio></a-radio-group></a-form-item></a-form></a-modal>`,};export default {components: { CollectionCreateForm },data () {return {visible: false,};},methods: {showModal () {this.visible = true;},handleCancel () {this.visible = false;},handleCreate () {const form = this.$refs.collectionForm.form;form.validateFields((err, values) => {if (err) {return;}console.log('Received values of form: ', values);form.resetFields();this.visible = false;});},},};</script>

表单数据存储于上层组件
通过使用 onFieldsChange 与 mapPropsToFields,可以把表单的数据存储到上层组件。注意:mapPropsToFields 里面返回的表单域数据必须使用 Form.createFormField 包装。如果你使用Form.create,上层组件传递的属性,必须在Form.create({ props: …})的props中声明。如果使用this.$form.createForm,你可以使用任何数据,不仅仅局限于上层组件的属性。
<template><div id="components-form-demo-global-state"><customized-form:username="fields.username"@change="handleFormChange"/><pre class="language-bash">{{ JSON.stringify(fields, null, 2) }}</pre></div></template><script>const CustomizedForm = {props: ['username'],template: `<a-form layout='inline' :form="form"><a-form-item label='Username'><a-inputv-decorator="['username',{rules: [{ required: true, message: 'Username is required!' }],}]"/></a-form-item></a-form>`,created () {this.form = this.$form.createForm(this, {onFieldsChange: (_, changedFields) => {this.$emit('change', changedFields);},mapPropsToFields: () => {return {username: this.$form.createFormField({...this.username,value: this.username.value,}),};},onValuesChange (_, values) {console.log(values);},});},watch: {username () {this.form.updateFields({username: this.$form.createFormField({...this.username,value: this.username.value,}),});},},};export default {components: {CustomizedForm,},data () {return {fields: {username: {value: 'benjycui',},},};},methods: {handleFormChange (changedFields) {console.log('changedFields', changedFields);this.fields = { ...this.fields, ...changedFields };},},};</script><style>#components-form-demo-global-state .language-bash {max-width: 400px;border-radius: 6px;margin-top: 24px;}</style>

表单数据存储于 Vuex Store 中
通过使用 onFieldsChange 与 mapPropsToFields,可以把表单的数据存储到 Vuex 中。注意:mapPropsToFields 里面返回的表单域数据必须使用 Form.createFormField 包装。
<template><div id="components-form-demo-vuex"><a-form:form="form"@submit="handleSubmit"><a-form-item label="Username"><a-inputv-decorator="['username',{rules: [{ required: true, message: 'Username is required!' }],}]"/></a-form-item><a-buttontype="primary"html-type="submit">Submit</a-button></a-form></div></template><script>export default {computed: {username () {return this.$store.state.username;},},watch: {username (val) {console.log('this.$store.state.username: ', val);this.form.setFieldsValue({username: val});},},created () {this.form = this.$form.createForm(this, {onFieldsChange: (_, changedFields) => {this.$emit('change', changedFields);},mapPropsToFields: () => {return {username: this.$form.createFormField({value: this.username,}),};},onValuesChange: (_, values) =>{console.log(values);// Synchronize to vuex store in real time// this.$store.commit('update', values)},});},methods: {handleSubmit (e) {e.preventDefault();this.form.validateFields((err, values) => {if (!err) {console.log('Received values of form: ', values);this.$store.commit('update', values);}});},},};</script><style>#components-form-demo-vuex .language-bash {max-width: 400px;border-radius: 6px;margin-top: 24px;}</style>

登录框
普通的登录框,可以容纳更多的元素。
<template><a-formid="components-form-demo-normal-login":form="form"class="login-form"@submit="handleSubmit"><a-form-item><a-inputv-decorator="['userName',{ rules: [{ required: true, message: 'Please input your username!' }] }]"placeholder="Username"><a-iconslot="prefix"type="user"style="color: rgba(0,0,0,.25)"/></a-input></a-form-item><a-form-item><a-inputv-decorator="['password',{ rules: [{ required: true, message: 'Please input your Password!' }] }]"type="password"placeholder="Password"><a-iconslot="prefix"type="lock"style="color: rgba(0,0,0,.25)"/></a-input></a-form-item><a-form-item><a-checkboxv-decorator="['remember',{valuePropName: 'checked',initialValue: true,}]">Remember me</a-checkbox><aclass="login-form-forgot"href="">Forgot password</a><a-buttontype="primary"html-type="submit"class="login-form-button">Log in</a-button>Or <a href="">register now!</a></a-form-item></a-form></template><script>export default {beforeCreate () {this.form = this.$form.createForm(this);},methods: {handleSubmit (e) {e.preventDefault();this.form.validateFields((err, values) => {if (!err) {console.log('Received values of form: ', values);}});},},};</script><style>#components-form-demo-normal-login .login-form {max-width: 300px;}#components-form-demo-normal-login .login-form-forgot {float: right;}#components-form-demo-normal-login .login-form-button {width: 100%;}</style>

注册新用户
用户填写必须的信息以注册新用户。
<template><a-form:form="form"@submit="handleSubmit"><a-form-itemv-bind="formItemLayout"label="E-mail"><a-inputv-decorator="['email',{rules: [{type: 'email', message: 'The input is not valid E-mail!',}, {required: true, message: 'Please input your E-mail!',}]}]"/></a-form-item><a-form-itemv-bind="formItemLayout"label="Password"><a-inputv-decorator="['password',{rules: [{required: true, message: 'Please input your password!',}, {validator: validateToNextPassword,}],}]"type="password"/></a-form-item><a-form-itemv-bind="formItemLayout"label="Confirm Password"><a-inputv-decorator="['confirm',{rules: [{required: true, message: 'Please confirm your password!',}, {validator: compareToFirstPassword,}],}]"type="password"@blur="handleConfirmBlur"/></a-form-item><a-form-itemv-bind="formItemLayout"><span slot="label">Nickname <a-tooltip title="What do you want others to call you?"><a-icon type="question-circle-o" /></a-tooltip></span><a-inputv-decorator="['nickname',{rules: [{ required: true, message: 'Please input your nickname!', whitespace: true }]}]"/></a-form-item><a-form-itemv-bind="formItemLayout"label="Habitual Residence"><a-cascaderv-decorator="['residence',{initialValue: ['zhejiang', 'hangzhou', 'xihu'],rules: [{ type: 'array', required: true, message: 'Please select your habitual residence!' }],}]":options="residences"/></a-form-item><a-form-itemv-bind="formItemLayout"label="Phone Number"><a-inputv-decorator="['phone',{rules: [{ required: true, message: 'Please input your phone number!' }],}]"style="width: 100%"><a-selectslot="addonBefore"v-decorator="['prefix',{ initialValue: '86' }]"style="width: 70px"><a-select-option value="86">+86</a-select-option><a-select-option value="87">+87</a-select-option></a-select></a-input></a-form-item><a-form-itemv-bind="formItemLayout"label="Website"><a-auto-completev-decorator="['website',{rules: [{ required: true, message: 'Please input website!' }]}]"placeholder="website"@change="handleWebsiteChange"><template slot="dataSource"><a-select-optionv-for="website in autoCompleteResult":key="website">{{ website }}</a-select-option></template><a-input /></a-auto-complete></a-form-item><a-form-itemv-bind="formItemLayout"label="Captcha"extra="We must make sure that your are a human."><a-row :gutter="8"><a-col :span="12"><a-inputv-decorator="['captcha',{rules: [{ required: true, message: 'Please input the captcha you got!' }]}]"/></a-col><a-col :span="12"><a-button>Get captcha</a-button></a-col></a-row></a-form-item><a-form-item v-bind="tailFormItemLayout"><a-checkboxv-decorator="['agreement', {valuePropName: 'checked'}]">I have read the <a href="">agreement</a></a-checkbox></a-form-item><a-form-item v-bind="tailFormItemLayout"><a-buttontype="primary"html-type="submit">Register</a-button></a-form-item></a-form></template><script>const residences = [{value: 'zhejiang',label: 'Zhejiang',children: [{value: 'hangzhou',label: 'Hangzhou',children: [{value: 'xihu',label: 'West Lake',}],}],}, {value: 'jiangsu',label: 'Jiangsu',children: [{value: 'nanjing',label: 'Nanjing',children: [{value: 'zhonghuamen',label: 'Zhong Hua Men',}],}],}];export default {data () {return {confirmDirty: false,residences,autoCompleteResult: [],formItemLayout: {labelCol: {xs: { span: 24 },sm: { span: 8 },},wrapperCol: {xs: { span: 24 },sm: { span: 16 },},},tailFormItemLayout: {wrapperCol: {xs: {span: 24,offset: 0,},sm: {span: 16,offset: 8,},},},};},beforeCreate () {this.form = this.$form.createForm(this);},methods: {handleSubmit (e) {e.preventDefault();this.form.validateFieldsAndScroll((err, values) => {if (!err) {console.log('Received values of form: ', values);}});},handleConfirmBlur (e) {const value = e.target.value;this.confirmDirty = this.confirmDirty || !!value;},compareToFirstPassword (rule, value, callback) {const form = this.form;if (value && value !== form.getFieldValue('password')) {callback('Two passwords that you enter is inconsistent!');} else {callback();}},validateToNextPassword (rule, value, callback) {const form = this.form;if (value && this.confirmDirty) {form.validateFields(['confirm'], { force: true });}callback();},handleWebsiteChange (value) {let autoCompleteResult;if (!value) {autoCompleteResult = [];} else {autoCompleteResult = ['.com', '.org', '.net'].map(domain => `${value}${domain}`);}this.autoCompleteResult = autoCompleteResult;},},};</script>

时间类控件
时间类组件的 value 类型为 moment 对象,所以在提交服务器前需要预处理。
<template><a-form:form="form"@submit="handleSubmit"><a-form-itemv-bind="formItemLayout"label="DatePicker"><a-date-picker v-decorator="['date-picker', config]" /></a-form-item><a-form-itemv-bind="formItemLayout"label="DatePicker[showTime]"><a-date-pickerv-decorator="['date-time-picker', config]"show-timeformat="YYYY-MM-DD HH:mm:ss"/></a-form-item><a-form-itemv-bind="formItemLayout"label="MonthPicker"><a-monthPicker v-decorator="['month-picker', config]" /></a-form-item><a-form-itemv-bind="formItemLayout"label="RangePicker"><a-range-picker v-decorator="['range-picker', rangeConfig]" /></a-form-item><a-form-itemv-bind="formItemLayout"label="RangePicker[showTime]"><a-range-pickerv-decorator="['range-time-picker', rangeConfig]"show-timeformat="YYYY-MM-DD HH:mm:ss"/></a-form-item><a-form-itemv-bind="formItemLayout"label="TimePicker"><a-time-picker v-decorator="['time-picker', config]" /></a-form-item><a-form-item:wrapper-col="{xs: { span: 24, offset: 0 },sm: { span: 16, offset: 8 },}"><a-buttontype="primary"html-type="submit">Submit</a-button></a-form-item></a-form></template><script>export default {data () {return {formItemLayout: {labelCol: {xs: { span: 24 },sm: { span: 8 },},wrapperCol: {xs: { span: 24 },sm: { span: 16 },},},config: {rules: [{ type: 'object', required: true, message: 'Please select time!' }],},rangeConfig: {rules: [{ type: 'array', required: true, message: 'Please select time!' }],},};},beforeCreate () {this.form = this.$form.createForm(this);},methods: {handleSubmit (e) {e.preventDefault();this.form.validateFields((err, fieldsValue) => {if (err) {return;}// Should format date value before submit.const rangeValue = fieldsValue['range-picker'];const rangeTimeValue = fieldsValue['range-time-picker'];const values = {...fieldsValue,'date-picker': fieldsValue['date-picker'].format('YYYY-MM-DD'),'date-time-picker': fieldsValue['date-time-picker'].format('YYYY-MM-DD HH:mm:ss'),'month-picker': fieldsValue['month-picker'].format('YYYY-MM'),'range-picker': [rangeValue[0].format('YYYY-MM-DD'), rangeValue[1].format('YYYY-MM-DD')],'range-time-picker': [rangeTimeValue[0].format('YYYY-MM-DD HH:mm:ss'),rangeTimeValue[1].format('YYYY-MM-DD HH:mm:ss'),],'time-picker': fieldsValue['time-picker'].format('HH:mm:ss'),};console.log('Received values of form: ', values);});},},};</script>

校验其他组件
以上演示没有出现的表单控件对应的校验演示。
<template><a-formid="components-form-demo-validate-other":form="form"@submit="handleSubmit"><a-form-itemv-bind="formItemLayout"label="Plain Text"><span class="ant-form-text">China</span></a-form-item><a-form-itemv-bind="formItemLayout"label="Select"has-feedback><a-selectv-decorator="['select',{rules: [{ required: true, message: 'Please select your country!' }]}]"placeholder="Please select a country"><a-select-option value="china">China</a-select-option><a-select-option value="usa">U.S.A</a-select-option></a-select></a-form-item><a-form-itemv-bind="formItemLayout"label="Select[multiple]"><a-selectv-decorator="['select-multiple', {rules: [{ required: true, message: 'Please select your favourite colors!', type: 'array' }],}]"mode="multiple"placeholder="Please select favourite colors"><a-select-option value="red">Red</a-select-option><a-select-option value="green">Green</a-select-option><a-select-option value="blue">Blue</a-select-option></a-select></a-form-item><a-form-itemv-bind="formItemLayout"label="InputNumber"><a-input-numberv-decorator="['input-number', { initialValue: 3 }]":min="1":max="10"/><span class="ant-form-text">machines</span></a-form-item><a-form-itemv-bind="formItemLayout"label="Switch"><a-switch v-decorator="['switch', { valuePropName: 'checked' }]" /></a-form-item><a-form-itemv-bind="formItemLayout"label="Slider"><a-sliderv-decorator="['slider']":marks="{ 0: 'A', 20: 'B', 40: 'C', 60: 'D', 80: 'E', 100: 'F' }"/></a-form-item><a-form-itemv-bind="formItemLayout"label="Radio.Group"><a-radio-group v-decorator="['radio-group']"><a-radio value="a">item 1</a-radio><a-radio value="b">item 2</a-radio><a-radio value="c">item 3</a-radio></a-radio-group></a-form-item><a-form-itemv-bind="formItemLayout"label="Radio.Button"><a-radio-group v-decorator="['radio-button']"><a-radio-button value="a">item 1</a-radio-button><a-radio-button value="b">item 2</a-radio-button><a-radio-button value="c">item 3</a-radio-button></a-radio-group></a-form-item><a-form-itemv-bind="formItemLayout"label="Checkbox.Group"><a-checkbox-groupv-decorator="['checkbox-group', {initialValue: ['A', 'B']}]"style="width: 100%;"><a-row><a-col :span="8"><a-checkbox value="A">A</a-checkbox></a-col><a-col :span="8"><a-checkboxdisabledvalue="B">B</a-checkbox></a-col><a-col :span="8"><a-checkbox value="C">C</a-checkbox></a-col><a-col :span="8"><a-checkbox value="D">D</a-checkbox></a-col><a-col :span="8"><a-checkbox value="E">E</a-checkbox></a-col></a-row></a-checkbox-group></a-form-item><a-form-itemv-bind="formItemLayout"label="Rate"><a-ratev-decorator="['rate', {initialValue: 3.5}]"allow-half/></a-form-item><a-form-itemv-bind="formItemLayout"label="Upload"extra="longgggggggggggggggggggggggggggggggggg"><a-uploadv-decorator="['upload', {valuePropName: 'fileList',getValueFromEvent: normFile,}]"name="logo"action="/upload.do"list-type="picture"><a-button><a-icon type="upload" /> Click to upload</a-button></a-upload></a-form-item><a-form-itemv-bind="formItemLayout"label="Dragger"><div class="dropbox"><a-upload-draggerv-decorator="['dragger', {valuePropName: 'fileList',getValueFromEvent: normFile,}]"name="files"action="/upload.do"><p class="ant-upload-drag-icon"><a-icon type="inbox" /></p><p class="ant-upload-text">Click or drag file to this area to upload</p><p class="ant-upload-hint">Support for a single or bulk upload.</p></a-upload-dragger></div></a-form-item><a-form-item:wrapper-col="{ span: 12, offset: 6 }"><a-buttontype="primary"html-type="submit">Submit</a-button></a-form-item></a-form></template><script>export default {data: () => ({formItemLayout: {labelCol: { span: 6 },wrapperCol: { span: 14 },},}),beforeCreate () {this.form = this.$form.createForm(this);},methods: {handleSubmit (e) {e.preventDefault();this.form.validateFields((err, values) => {if (!err) {console.log('Received values of form: ', values);}});},normFile (e) {console.log('Upload event:', e);if (Array.isArray(e)) {return e;}return e && e.fileList;},},};</script><style>#components-form-demo-validate-other .dropbox {height: 180px;line-height: 1.5;}</style>
API
Form
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| form | 经 Form.create() 包装过的组件会自带 this.form 属性,如果使用template语法,可以使用this.$form.createForm(this, options) | object | 无 |
| hideRequiredMark | 隐藏所有表单项的必选标记 | Boolean | false |
| layout | 表单布局 | 'horizontal'|'vertical'|'inline' | 'horizontal' |
事件
| 事件名称 | 说明 | 回调参数 |
|---|---|---|
| submit | 数据验证成功后回调事件 | Function(e:Event) |
Form.create(options) | this.$form.createForm(this, options)
使用方式如下:
jsx使用方式,使用方式和React版antd一致
const CustomizedForm = {}CustomizedForm = Form.create({})(CustomizedForm);
如果需要为包装组件实例维护一个ref,可以使用wrappedComponentRef。
单文件template使用方式
<template><a-form :form="form" /></template><script>export default {beforeCreate () {this.form = this.$form.createForm(this, options)},}</script>
options 的配置项如下。
| 参数 | 说明 | 类型 |
|---|---|---|
| props | 仅仅支持Form.create({})(CustomizedForm)的使用方式,父组件需要映射到表单项上的属性声明(和vue组件props一致) | {} |
| mapPropsToFields | 把父组件的属性映射到表单项上(如:把 Redux store 中的值读出),需要对返回值中的表单域数据用 Form.createFormField 标记,如果使用$form.createForm创建收集器,你可以将任何数据映射到Field中,不受父组件约束 | (props) => ({ [fieldName]: FormField { value } }) |
| validateMessages | 默认校验信息,可用于把默认错误信息改为中文等,格式与 newMessages 返回值一致 | Object { [nested.path]: String } |
| onFieldsChange | 当 Form.Item 子节点的值发生改变时触发,可以把对应的值转存到 Redux store | Function(props, fields) |
| onValuesChange | 任一表单域的值发生改变时的回调 | (props, values) => void |
经过 Form.create 包装的组件将会自带 this.form 属性,this.form 提供的 API 如下:
注意:使用
getFieldsValuegetFieldValuesetFieldsValue等时,应确保对应的 field 已经用getFieldDecorator或v-decorator注册过了。
| 方法 | 说明 | 类型 |
|---|---|---|
| getFieldDecorator | 用于和表单进行双向绑定,单文件template可以使用指令v-decorator进行绑定,详见下方描述 | |
| getFieldError | 获取某个输入控件的 Error | Function(name) |
| getFieldsError | 获取一组输入控件的 Error ,如不传入参数,则获取全部组件的 Error | Function([names: string[]]) |
| getFieldsValue | 获取一组输入控件的值,如不传入参数,则获取全部组件的值 | Function([fieldNames: string[]]) |
| getFieldValue | 获取一个输入控件的值 | Function(fieldName: string) |
| isFieldsTouched | 判断是否任一输入控件经历过 getFieldDecorator 或 v-decorator 的值收集时机 options.trigger | (names?: string[]) => boolean |
| isFieldTouched | 判断一个输入控件是否经历过 getFieldDecorator 或 v-decorator 的值收集时机 options.trigger | (name: string) => boolean |
| isFieldValidating | 判断一个输入控件是否在校验状态 | Function(name) |
| resetFields | 重置一组输入控件的值(为 initialValue)与状态,如不传入参数,则重置所有组件 | Function([names: string[]]) |
| setFields | 设置一组输入控件的值与错误状态。 | Function({ [fieldName]: { value: any, errors: [Error] } }) |
| setFieldsValue | 设置一组输入控件的值 | Function({ [fieldName]: value } |
| validateFields | 校验并获取一组输入域的值与 Error,若 fieldNames 参数为空,则校验全部组件 | Function([fieldNames: string[]], [options: object], callback: Function(errors, values)) |
| validateFieldsAndScroll | 与 validateFields 相似,但校验完后,如果校验不通过的菜单域不在可见范围内,则自动滚动进可见范围 | 参考 validateFields |
validateFields/validateFieldsAndScroll
const { form: { validateFields } } = this;validateFields((errors, values) => {// ...});validateFields(['field1', 'field2'], (errors, values) => {// ...});validateFields(['field1', 'field2'], options, (errors, values) => {// ...});
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| options.first | 若为 true,则每一表单域的都会在碰到第一个失败了的校验规则后停止校验 | boolean | false |
| options.firstFields | 指定表单域会在碰到第一个失败了的校验规则后停止校验 | String[] | [] |
| options.force | 对已经校验过的表单域,在 validateTrigger 再次被触发时是否再次校验 | boolean | false |
| options.scroll | 定义 validateFieldsAndScroll 的滚动行为,详细配置见 dom-scroll-into-view config | Object | {} |
validateFields 的 callback 参数示例
errors:
{"userName": {"errors": [{"message": "Please input your username!","field": "userName"}]},"password": {"errors": [{"message": "Please input your Password!","field": "password"}]}}
values:
{"userName": "username","password": "password",}
Form.createFormField
用于标记 mapPropsToFields 返回的表单域数据,例子。
this.form.getFieldDecorator(id, options) 和 v-decorator="[id, options]"
经过 getFieldDecorator或v-decorator 包装的控件,表单控件会自动添加 value(或 valuePropName 指定的其他属性) onChange(或 trigger 指定的其他属性),数据同步将被 Form 接管,这会导致以下结果:
- 你不再需要也不应该用
onChange来做同步,但还是可以继续监听onChange等事件。 - 你不能用控件的
valuedefaultValue等属性来设置表单域的值,默认值可以用getFieldDecorator或v-decorator里的initialValue。 - 你不应该用
v-model,可以使用this.form.setFieldsValue来动态改变表单值。
特别注意
getFieldDecorator和v-decorator不能用于装饰纯函数组件。getFieldDecorator和v-decorator调用不能位于纯函数组件中 https://cn.vuejs.org/v2/api/#functional。
getFieldDecorator(id, options) 和 v-decorator="[id, options]" 参数
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| id | 必填输入控件唯一标志。支持嵌套式的写法。 | string | |
| options.getValueFromEvent | 可以把 onChange 的参数(如 event)转化为控件的值 | function(..args) | reference |
| options.initialValue | 子节点的初始值,类型、可选值均由子节点决定(注意:由于内部校验时使用 === 判断是否变化,建议使用变量缓存所需设置的值而非直接使用字面量)) | ||
| options.normalize | 转换默认的 value 给控件,一个选择全部的例子 | function(value, prevValue, allValues): any | - |
| options.preserve | 即便字段不再使用,也保留该字段的值 | boolean | false |
| options.rules | 校验规则,参考下方文档 | object[] | |
| options.trigger | 收集子节点的值的时机 | string | 'change' |
| options.validateFirst | 当某一规则校验不通过时,是否停止剩下的规则的校验 | boolean | false |
| options.validateTrigger | 校验子节点值的时机 | string|string[] | 'change' |
| options.valuePropName | 子节点的值的属性,如 Switch 的是 'checked' | string | 'value' |
Form.Item
注意:一个 Form.Item 建议只放一个被 getFieldDecorator或v-decorator 装饰过的 child,当有多个被装饰过的 child 时,help required validateStatus 无法自动生成。
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| colon | 配合 label 属性使用,表示是否显示 label 后面的冒号 | boolean | true |
| extra | 额外的提示信息,和 help 类似,当需要错误信息和提示文案同时出现时,可以使用这个。 | string|slot | |
| hasFeedback | 配合 validateStatus 属性使用,展示校验状态图标,建议只配合 Input 组件使用 | boolean | false |
| help | 提示信息,如不设置,则会根据校验规则自动生成 | string|slot | |
| label | label 标签的文本 | string|slot | |
| labelCol | label 标签布局,同 <Col> 组件,设置 span offset 值,如 {span: 3, offset: 12} 或 sm: {span: 3, offset: 12} | object | |
| required | 是否必填,如不设置,则会根据校验规则自动生成 | boolean | false |
| validateStatus | 校验状态,如不设置,则会根据校验规则自动生成,可选:'success' 'warning' 'error' 'validating' | string | |
| wrapperCol | 需要为输入控件设置布局样式时,使用该属性,用法同 labelCol | object |
校验规则
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| enum | 枚举类型 | string | - |
| len | 字段长度 | number | - |
| max | 最大长度 | number | - |
| message | 校验文案 | string | - |
| min | 最小长度 | number | - |
| pattern | 正则表达式校验 | RegExp | - |
| required | 是否必选 | boolean | false |
| transform | 校验前转换字段值 | function(value) => transformedValue:any | - |
| type | 内建校验类型,可选项 | string | 'string' |
| validator | 自定义校验(注意,callback 必须被调用) | function(rule, value, callback) | - |
| whitespace | 必选时,空格是否会被视为错误 | boolean | false |
更多高级用法可研究 async-validator。
