Form 表单

具有数据收集、校验和提交功能的表单,包含复选框、单选框、输入框、下拉选择框等元素。

表单

我们为 form 提供了以下三种排列方式:

  • 水平排列:标签和表单控件水平排列;(默认)
  • 垂直排列:标签和表单控件上下垂直排列;
  • 行内排列:表单项水平行内排列。

表单域

表单一定会包含表单域,表单域可以是输入控件,标准表单域,标签,下拉菜单,文本域等。

这里我们封装了表单域 <Form.Item />

注意:

1、如果使用 Form.create 处理表单使其具有自动收集数据并校验的功能,建议使用jsx。2、如果不是使用Vue.use(Form)形式注册的Form组件,你需要自行将$form挂载到Vue原型上。Vue.prototype.$form = Form

代码演示

Form 表单 - 图1

表单联动

使用 setFieldsValue 来动态设置其他控件的值。

  1. <template>
  2. <a-form :form="form" @submit="handleSubmit">
  3. <a-form-item label="Note" :label-col="{ span: 5 }" :wrapper-col="{ span: 12 }">
  4. <a-input
  5. v-decorator="['note', { rules: [{ required: true, message: 'Please input your note!' }] }]"
  6. />
  7. </a-form-item>
  8. <a-form-item label="Gender" :label-col="{ span: 5 }" :wrapper-col="{ span: 12 }">
  9. <a-select
  10. v-decorator="[
  11. 'gender',
  12. { rules: [{ required: true, message: 'Please select your gender!' }] },
  13. ]"
  14. placeholder="Select a option and change input text above"
  15. @change="handleSelectChange"
  16. >
  17. <a-select-option value="male">
  18. male
  19. </a-select-option>
  20. <a-select-option value="female">
  21. female
  22. </a-select-option>
  23. </a-select>
  24. </a-form-item>
  25. <a-form-item :wrapper-col="{ span: 12, offset: 5 }">
  26. <a-button type="primary" html-type="submit">
  27. Submit
  28. </a-button>
  29. </a-form-item>
  30. </a-form>
  31. </template>
  32. <script>
  33. export default {
  34. data() {
  35. return {
  36. formLayout: 'horizontal',
  37. form: this.$form.createForm(this, { name: 'coordinated' }),
  38. };
  39. },
  40. methods: {
  41. handleSubmit(e) {
  42. e.preventDefault();
  43. this.form.validateFields((err, values) => {
  44. if (!err) {
  45. console.log('Received values of form: ', values);
  46. }
  47. });
  48. },
  49. handleSelectChange(value) {
  50. console.log(value);
  51. this.form.setFieldsValue({
  52. note: `Hi, ${value === 'male' ? 'man' : 'lady'}!`,
  53. });
  54. },
  55. },
  56. };
  57. </script>

Form 表单 - 图2

动态校验规则

根据不同情况执行不同的校验规则。

  1. <template>
  2. <a-form :form="form">
  3. <a-form-item
  4. :label-col="formItemLayout.labelCol"
  5. :wrapper-col="formItemLayout.wrapperCol"
  6. label="Name"
  7. >
  8. <a-input
  9. v-decorator="[
  10. 'username',
  11. { rules: [{ required: true, message: 'Please input your name' }] },
  12. ]"
  13. placeholder="Please input your name"
  14. />
  15. </a-form-item>
  16. <a-form-item
  17. :label-col="formItemLayout.labelCol"
  18. :wrapper-col="formItemLayout.wrapperCol"
  19. label="Nickname"
  20. >
  21. <a-input
  22. v-decorator="[
  23. 'nickname',
  24. { rules: [{ required: checkNick, message: 'Please input your nickname' }] },
  25. ]"
  26. placeholder="Please input your nickname"
  27. />
  28. </a-form-item>
  29. <a-form-item :label-col="formTailLayout.labelCol" :wrapper-col="formTailLayout.wrapperCol">
  30. <a-checkbox :checked="checkNick" @change="handleChange">
  31. Nickname is required
  32. </a-checkbox>
  33. </a-form-item>
  34. <a-form-item :label-col="formTailLayout.labelCol" :wrapper-col="formTailLayout.wrapperCol">
  35. <a-button type="primary" @click="check">
  36. Check
  37. </a-button>
  38. </a-form-item>
  39. </a-form>
  40. </template>
  41. <script>
  42. const formItemLayout = {
  43. labelCol: { span: 4 },
  44. wrapperCol: { span: 8 },
  45. };
  46. const formTailLayout = {
  47. labelCol: { span: 4 },
  48. wrapperCol: { span: 8, offset: 4 },
  49. };
  50. export default {
  51. data() {
  52. return {
  53. checkNick: false,
  54. formItemLayout,
  55. formTailLayout,
  56. form: this.$form.createForm(this, { name: 'dynamic_rule' }),
  57. };
  58. },
  59. methods: {
  60. check() {
  61. this.form.validateFields(err => {
  62. if (!err) {
  63. console.info('success');
  64. }
  65. });
  66. },
  67. handleChange(e) {
  68. this.checkNick = e.target.checked;
  69. this.$nextTick(() => {
  70. this.form.validateFields(['nickname'], { force: true });
  71. });
  72. },
  73. },
  74. };
  75. </script>

Form 表单 - 图3

水平登录栏

水平登录栏,常用在顶部导航栏中。

  1. <template>
  2. <a-form layout="inline" :form="form" @submit="handleSubmit">
  3. <a-form-item :validate-status="userNameError() ? 'error' : ''" :help="userNameError() || ''">
  4. <a-input
  5. v-decorator="[
  6. 'userName',
  7. { rules: [{ required: true, message: 'Please input your username!' }] },
  8. ]"
  9. placeholder="Username"
  10. >
  11. <a-icon slot="prefix" type="user" style="color:rgba(0,0,0,.25)" />
  12. </a-input>
  13. </a-form-item>
  14. <a-form-item :validate-status="passwordError() ? 'error' : ''" :help="passwordError() || ''">
  15. <a-input
  16. v-decorator="[
  17. 'password',
  18. { rules: [{ required: true, message: 'Please input your Password!' }] },
  19. ]"
  20. type="password"
  21. placeholder="Password"
  22. >
  23. <a-icon slot="prefix" type="lock" style="color:rgba(0,0,0,.25)" />
  24. </a-input>
  25. </a-form-item>
  26. <a-form-item>
  27. <a-button type="primary" html-type="submit" :disabled="hasErrors(form.getFieldsError())">
  28. Log in
  29. </a-button>
  30. </a-form-item>
  31. </a-form>
  32. </template>
  33. <script>
  34. function hasErrors(fieldsError) {
  35. return Object.keys(fieldsError).some(field => fieldsError[field]);
  36. }
  37. export default {
  38. data() {
  39. return {
  40. hasErrors,
  41. form: this.$form.createForm(this, { name: 'horizontal_login' }),
  42. };
  43. },
  44. mounted() {
  45. this.$nextTick(() => {
  46. // To disabled submit button at the beginning.
  47. this.form.validateFields();
  48. });
  49. },
  50. methods: {
  51. // Only show error after a field is touched.
  52. userNameError() {
  53. const { getFieldError, isFieldTouched } = this.form;
  54. return isFieldTouched('userName') && getFieldError('userName');
  55. },
  56. // Only show error after a field is touched.
  57. passwordError() {
  58. const { getFieldError, isFieldTouched } = this.form;
  59. return isFieldTouched('password') && getFieldError('password');
  60. },
  61. handleSubmit(e) {
  62. e.preventDefault();
  63. this.form.validateFields((err, values) => {
  64. if (!err) {
  65. console.log('Received values of form: ', values);
  66. }
  67. });
  68. },
  69. },
  70. };
  71. </script>

Form 表单 - 图4

表单布局

表单有三种布局。

  1. <template>
  2. <div>
  3. <a-form :layout="formLayout">
  4. <a-form-item
  5. label="Form Layout"
  6. :label-col="formItemLayout.labelCol"
  7. :wrapper-col="formItemLayout.wrapperCol"
  8. >
  9. <a-radio-group default-value="horizontal" @change="handleFormLayoutChange">
  10. <a-radio-button value="horizontal">
  11. Horizontal
  12. </a-radio-button>
  13. <a-radio-button value="vertical">
  14. Vertical
  15. </a-radio-button>
  16. <a-radio-button value="inline">
  17. Inline
  18. </a-radio-button>
  19. </a-radio-group>
  20. </a-form-item>
  21. <a-form-item
  22. label="Field A"
  23. :label-col="formItemLayout.labelCol"
  24. :wrapper-col="formItemLayout.wrapperCol"
  25. >
  26. <a-input placeholder="input placeholder" />
  27. </a-form-item>
  28. <a-form-item
  29. label="Field B"
  30. :label-col="formItemLayout.labelCol"
  31. :wrapper-col="formItemLayout.wrapperCol"
  32. >
  33. <a-input placeholder="input placeholder" />
  34. </a-form-item>
  35. <a-form-item :wrapper-col="buttonItemLayout.wrapperCol">
  36. <a-button type="primary">
  37. Submit
  38. </a-button>
  39. </a-form-item>
  40. </a-form>
  41. </div>
  42. </template>
  43. <script>
  44. export default {
  45. data() {
  46. return {
  47. formLayout: 'horizontal',
  48. };
  49. },
  50. computed: {
  51. formItemLayout() {
  52. const { formLayout } = this;
  53. return formLayout === 'horizontal'
  54. ? {
  55. labelCol: { span: 4 },
  56. wrapperCol: { span: 14 },
  57. }
  58. : {};
  59. },
  60. buttonItemLayout() {
  61. const { formLayout } = this;
  62. return formLayout === 'horizontal'
  63. ? {
  64. wrapperCol: { span: 14, offset: 4 },
  65. }
  66. : {};
  67. },
  68. },
  69. methods: {
  70. handleFormLayoutChange(e) {
  71. this.formLayout = e.target.value;
  72. },
  73. },
  74. };
  75. </script>

Form 表单 - 图5

自定义校验

我们提供了 validateStatus help hasFeedback 等属性,你可以不需要使用 Form.creategetFieldDecorator,自己定义校验的时机和内容。

  • validateStatus: 校验状态,可选 ‘success’, ‘warning’, ‘error’, ‘validating’。
  • hasFeedback:用于给输入框添加反馈图标。
  • help:设置校验文案。
  1. <template>
  2. <a-form>
  3. <a-form-item
  4. :label-col="labelCol"
  5. :wrapper-col="wrapperCol"
  6. label="Fail"
  7. validate-status="error"
  8. help="Should be combination of numbers & alphabets"
  9. >
  10. <a-input id="error" placeholder="unavailable choice" />
  11. </a-form-item>
  12. <a-form-item
  13. :label-col="labelCol"
  14. :wrapper-col="wrapperCol"
  15. label="Warning"
  16. validate-status="warning"
  17. >
  18. <a-input id="warning" placeholder="Warning" />
  19. </a-form-item>
  20. <a-form-item
  21. :label-col="labelCol"
  22. :wrapper-col="wrapperCol"
  23. label="Validating"
  24. has-feedback
  25. validate-status="validating"
  26. help="The information is being validated..."
  27. >
  28. <a-input id="validating" placeholder="I'm the content is being validated" />
  29. </a-form-item>
  30. <a-form-item
  31. :label-col="labelCol"
  32. :wrapper-col="wrapperCol"
  33. label="Success"
  34. has-feedback
  35. validate-status="success"
  36. >
  37. <a-input id="success" placeholder="I'm the content" />
  38. </a-form-item>
  39. <a-form-item
  40. :label-col="labelCol"
  41. :wrapper-col="wrapperCol"
  42. label="Warning"
  43. has-feedback
  44. validate-status="warning"
  45. >
  46. <a-input id="warning2" placeholder="Warning" />
  47. </a-form-item>
  48. <a-form-item
  49. :label-col="labelCol"
  50. :wrapper-col="wrapperCol"
  51. label="Fail"
  52. has-feedback
  53. validate-status="error"
  54. help="Should be combination of numbers & alphabets"
  55. >
  56. <a-input id="error2" placeholder="unavailable choice" />
  57. </a-form-item>
  58. <a-form-item
  59. :label-col="labelCol"
  60. :wrapper-col="wrapperCol"
  61. label="Success"
  62. has-feedback
  63. validate-status="success"
  64. >
  65. <a-date-picker style="width: 100%" />
  66. </a-form-item>
  67. <a-form-item
  68. :label-col="labelCol"
  69. :wrapper-col="wrapperCol"
  70. label="Warning"
  71. has-feedback
  72. validate-status="warning"
  73. >
  74. <a-time-picker style="width: 100%" />
  75. </a-form-item>
  76. <a-form-item
  77. :label-col="labelCol"
  78. :wrapper-col="wrapperCol"
  79. label="Error"
  80. has-feedback
  81. validate-status="error"
  82. >
  83. <a-select default-value="1">
  84. <a-select-option value="1">
  85. Option 1
  86. </a-select-option>
  87. <a-select-option value="2">
  88. Option 2
  89. </a-select-option>
  90. <a-select-option value="3">
  91. Option 3
  92. </a-select-option>
  93. </a-select>
  94. </a-form-item>
  95. <a-form-item
  96. :label-col="labelCol"
  97. :wrapper-col="wrapperCol"
  98. label="Validating"
  99. has-feedback
  100. validate-status="validating"
  101. help="The information is being validated..."
  102. >
  103. <a-cascader :default-value="['1']" :options="[]" />
  104. </a-form-item>
  105. <a-form-item
  106. label="inline"
  107. :label-col="labelCol"
  108. :wrapper-col="wrapperCol"
  109. style="margin-bottom:0;"
  110. >
  111. <a-form-item
  112. validate-status="error"
  113. help="Please select the correct date"
  114. :style="{ display: 'inline-block', width: 'calc(50% - 12px)' }"
  115. >
  116. <a-date-picker style="width: 100%" />
  117. </a-form-item>
  118. <span :style="{ display: 'inline-block', width: '24px', textAlign: 'center' }">
  119. -
  120. </span>
  121. <a-form-item :style="{ display: 'inline-block', width: 'calc(50% - 12px)' }">
  122. <a-date-picker style="width: 100%" />
  123. </a-form-item>
  124. </a-form-item>
  125. <a-form-item
  126. :label-col="labelCol"
  127. :wrapper-col="wrapperCol"
  128. label="Success"
  129. has-feedback
  130. validate-status="success"
  131. >
  132. <a-input-number style="width: 100%" />
  133. </a-form-item>
  134. </a-form>
  135. </template>
  136. <script>
  137. export default {
  138. data() {
  139. return {
  140. labelCol: {
  141. xs: { span: 24 },
  142. sm: { span: 5 },
  143. },
  144. wrapperCol: {
  145. xs: { span: 24 },
  146. sm: { span: 12 },
  147. },
  148. };
  149. },
  150. };
  151. </script>

Form 表单 - 图6

自行处理表单数据

使用 Form.create 处理后的表单具有自动收集数据并校验的功能,但如果您不需要这个功能,或者默认的行为无法满足业务需求,可以选择不使用 Form.create 并自行处理数据。

  1. <template>
  2. <a-form>
  3. <a-form-item
  4. :label-col="labelCol"
  5. :wrapper-col="wrapperCol"
  6. label="Prime between 8 & 12"
  7. :validate-status="number.validateStatus"
  8. :help="number.errorMsg || tips"
  9. >
  10. <a-input-number :min="8" :max="12" :value="number.value" @change="handleNumberChange" />
  11. </a-form-item>
  12. </a-form>
  13. </template>
  14. <script>
  15. function validatePrimeNumber(number) {
  16. if (number === 11) {
  17. return {
  18. validateStatus: 'success',
  19. errorMsg: null,
  20. };
  21. }
  22. return {
  23. validateStatus: 'error',
  24. errorMsg: 'The prime between 8 and 12 is 11!',
  25. };
  26. }
  27. export default {
  28. data() {
  29. return {
  30. labelCol: { span: 7 },
  31. wrapperCol: { span: 12 },
  32. number: {
  33. value: 11,
  34. },
  35. tips:
  36. 'A prime is a natural number greater than 1 that has no positive divisors other than 1 and itself.',
  37. };
  38. },
  39. methods: {
  40. handleNumberChange(value) {
  41. this.number = {
  42. ...validatePrimeNumber(value),
  43. value,
  44. };
  45. },
  46. },
  47. };
  48. </script>

Form 表单 - 图7

高级搜索

三列栅格式的表单排列方式,常用于数据表格的高级搜索。有部分定制的样式代码,由于输入标签长度不确定,需要根据具体情况自行调整。

  1. <template>
  2. <div id="components-form-demo-advanced-search">
  3. <a-form class="ant-advanced-search-form" :form="form" @submit="handleSearch">
  4. <a-row :gutter="24">
  5. <a-col
  6. v-for="i in 10"
  7. :key="i"
  8. :span="8"
  9. :style="{ display: i < count ? 'block' : 'none' }"
  10. >
  11. <a-form-item :label="`Field ${i}`">
  12. <a-input
  13. v-decorator="[
  14. `field-${i}`,
  15. {
  16. rules: [
  17. {
  18. required: true,
  19. message: 'Input something!',
  20. },
  21. ],
  22. },
  23. ]"
  24. placeholder="placeholder"
  25. />
  26. </a-form-item>
  27. </a-col>
  28. </a-row>
  29. <a-row>
  30. <a-col :span="24" :style="{ textAlign: 'right' }">
  31. <a-button type="primary" html-type="submit">
  32. Search
  33. </a-button>
  34. <a-button :style="{ marginLeft: '8px' }" @click="handleReset">
  35. Clear
  36. </a-button>
  37. <a :style="{ marginLeft: '8px', fontSize: '12px' }" @click="toggle">
  38. Collapse <a-icon :type="expand ? 'up' : 'down'" />
  39. </a>
  40. </a-col>
  41. </a-row>
  42. </a-form>
  43. <div class="search-result-list">
  44. Search Result List
  45. </div>
  46. </div>
  47. </template>
  48. <script>
  49. export default {
  50. data() {
  51. return {
  52. expand: false,
  53. form: this.$form.createForm(this, { name: 'advanced_search' }),
  54. };
  55. },
  56. computed: {
  57. count() {
  58. return this.expand ? 11 : 7;
  59. },
  60. },
  61. methods: {
  62. handleSearch(e) {
  63. e.preventDefault();
  64. this.form.validateFields((error, values) => {
  65. console.log('error', error);
  66. console.log('Received values of form: ', values);
  67. });
  68. },
  69. handleReset() {
  70. this.form.resetFields();
  71. },
  72. toggle() {
  73. this.expand = !this.expand;
  74. },
  75. },
  76. };
  77. </script>
  78. <style>
  79. .ant-advanced-search-form {
  80. padding: 24px;
  81. background: #fbfbfb;
  82. border: 1px solid #d9d9d9;
  83. border-radius: 6px;
  84. }
  85. .ant-advanced-search-form .ant-form-item {
  86. display: flex;
  87. }
  88. .ant-advanced-search-form .ant-form-item-control-wrapper {
  89. flex: 1;
  90. }
  91. #components-form-demo-advanced-search .ant-form {
  92. max-width: none;
  93. }
  94. #components-form-demo-advanced-search .search-result-list {
  95. margin-top: 16px;
  96. border: 1px dashed #e9e9e9;
  97. border-radius: 6px;
  98. background-color: #fafafa;
  99. min-height: 200px;
  100. text-align: center;
  101. padding-top: 80px;
  102. }
  103. </style>

Form 表单 - 图8

自定义表单控件

自定义或第三方的表单控件,也可以与 Form 组件一起使用。只要该组件遵循以下的约定:

  • 提供受控属性 value 或其它与 valuePropName-参数) 的值同名的属性。
  • 提供 onChange 事件或 trigger-参数) 的值同名的事件。
  • 不能是函数式组件。
<template>
  <a-form layout="inline" :form="form" @submit="handleSubmit">
    <a-form-item label="Price">
      <price-input
        v-decorator="[
          'price',
          {
            initialValue: { number: 0, currency: 'rmb' },
            rules: [{ validator: checkPrice }],
          },
        ]"
      />
    </a-form-item>
    <a-form-item>
      <a-button type="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-input
        type='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, { name: 'customized_form_controls' });
  },
  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>

Form 表单 - 图9

动态增减表单项

动态增加、减少表单项。

<template>
  <a-form :form="form" @submit="handleSubmit">
    <a-form-item
      v-for="(k, index) in form.getFieldValue('keys')"
      :key="k"
      v-bind="index === 0 ? formItemLayout : formItemLayoutWithOutLabel"
      :label="index === 0 ? 'Passengers' : ''"
      :required="false"
    >
      <a-input
        v-decorator="[
          `names[${k}]`,
          {
            validateTrigger: ['change', 'blur'],
            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-icon
        v-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-button type="dashed" style="width: 60%" @click="add">
        <a-icon type="plus" /> Add field
      </a-button>
    </a-form-item>
    <a-form-item v-bind="formItemLayoutWithOutLabel">
      <a-button type="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, { name: 'dynamic_form_item' });
    this.form.getFieldDecorator('keys', { initialValue: [], preserve: true });
  },
  methods: {
    remove(k) {
      const { form } = this;
      // can use data-binding to get
      const keys = form.getFieldValue('keys');
      // We need at least one passenger
      if (keys.length === 1) {
        return;
      }

      // can use data-binding to set
      form.setFieldsValue({
        keys: keys.filter(key => key !== k),
      });
    },

    add() {
      const { form } = this;
      // can use data-binding to get
      const keys = form.getFieldValue('keys');
      const nextKeys = keys.concat(id++);
      // can use data-binding to set
      // important! notify form to detect changes
      form.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 0.3s;
}
.dynamic-delete-button:hover {
  color: #777;
}
.dynamic-delete-button[disabled] {
  cursor: not-allowed;
  opacity: 0.5;
}
</style>

Form 表单 - 图10

弹出层中的新建表单

当用户访问一个展示了某个列表的页面,想新建一项但又不想跳转页面时,可以用 Modal 弹出一个表单,用户填写必要信息后创建新的项。

<template>
  <div>
    <a-button type="primary" @click="showModal">
      New Collection
    </a-button>
    <collection-create-form
      ref="collectionForm"
      :visible="visible"
      @cancel="handleCancel"
      @create="handleCreate"
    />
  </div>
</template>

<script>
const CollectionCreateForm = {
  props: ['visible'],
  beforeCreate() {
    this.form = this.$form.createForm(this, { name: 'form_in_modal' });
  },
  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-input
            v-decorator="[
              'title',
              {
                rules: [{ required: true, message: 'Please input the title of collection!' }],
              }
            ]"
          />
        </a-form-item>
        <a-form-item label='Description'>
          <a-input
            type='textarea'
            v-decorator="['description']"
          />
        </a-form-item>
        <a-form-item class='collection-create-form_last-form-item'>
          <a-radio-group
            v-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>

Form 表单 - 图11

表单数据存储于上层组件

通过使用 onFieldsChangemapPropsToFields,可以把表单的数据存储到上层组件。注意: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-input
          v-decorator="[
            'username',
            {
              rules: [{ required: true, message: 'Username is required!' }],
            }
          ]"
        />
      </a-form-item>
    </a-form>
  `,
  created() {
    this.form = this.$form.createForm(this, {
      name: 'global_state',
      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>

Form 表单 - 图12

表单数据存储于 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-input
          v-decorator="[
            'username',
            {
              rules: [{ required: true, message: 'Username is required!' }],
            },
          ]"
        />
      </a-form-item>
      <a-button type="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>

Form 表单 - 图13

登录框

普通的登录框,可以容纳更多的元素。

<template>
  <a-form
    id="components-form-demo-normal-login"
    :form="form"
    class="login-form"
    @submit="handleSubmit"
  >
    <a-form-item>
      <a-input
        v-decorator="[
          'userName',
          { rules: [{ required: true, message: 'Please input your username!' }] },
        ]"
        placeholder="Username"
      >
        <a-icon slot="prefix" type="user" style="color: rgba(0,0,0,.25)" />
      </a-input>
    </a-form-item>
    <a-form-item>
      <a-input
        v-decorator="[
          'password',
          { rules: [{ required: true, message: 'Please input your Password!' }] },
        ]"
        type="password"
        placeholder="Password"
      >
        <a-icon slot="prefix" type="lock" style="color: rgba(0,0,0,.25)" />
      </a-input>
    </a-form-item>
    <a-form-item>
      <a-checkbox
        v-decorator="[
          'remember',
          {
            valuePropName: 'checked',
            initialValue: true,
          },
        ]"
      >
        Remember me
      </a-checkbox>
      <a class="login-form-forgot" href="">
        Forgot password
      </a>
      <a-button type="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, { name: 'normal_login' });
  },
  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>

Form 表单 - 图14

注册新用户

用户填写必须的信息以注册新用户。

<template>
  <a-form :form="form" @submit="handleSubmit">
    <a-form-item v-bind="formItemLayout" label="E-mail">
      <a-input
        v-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-item v-bind="formItemLayout" label="Password">
      <a-input
        v-decorator="[
          'password',
          {
            rules: [
              {
                required: true,
                message: 'Please input your password!',
              },
              {
                validator: validateToNextPassword,
              },
            ],
          },
        ]"
        type="password"
      />
    </a-form-item>
    <a-form-item v-bind="formItemLayout" label="Confirm Password">
      <a-input
        v-decorator="[
          'confirm',
          {
            rules: [
              {
                required: true,
                message: 'Please confirm your password!',
              },
              {
                validator: compareToFirstPassword,
              },
            ],
          },
        ]"
        type="password"
        @blur="handleConfirmBlur"
      />
    </a-form-item>
    <a-form-item v-bind="formItemLayout">
      <span slot="label">
        Nickname&nbsp;
        <a-tooltip title="What do you want others to call you?">
          <a-icon type="question-circle-o" />
        </a-tooltip>
      </span>
      <a-input
        v-decorator="[
          'nickname',
          {
            rules: [{ required: true, message: 'Please input your nickname!', whitespace: true }],
          },
        ]"
      />
    </a-form-item>
    <a-form-item v-bind="formItemLayout" label="Habitual Residence">
      <a-cascader
        v-decorator="[
          'residence',
          {
            initialValue: ['zhejiang', 'hangzhou', 'xihu'],
            rules: [
              { type: 'array', required: true, message: 'Please select your habitual residence!' },
            ],
          },
        ]"
        :options="residences"
      />
    </a-form-item>
    <a-form-item v-bind="formItemLayout" label="Phone Number">
      <a-input
        v-decorator="[
          'phone',
          {
            rules: [{ required: true, message: 'Please input your phone number!' }],
          },
        ]"
        style="width: 100%"
      >
        <a-select
          slot="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-item v-bind="formItemLayout" label="Website">
      <a-auto-complete
        v-decorator="['website', { rules: [{ required: true, message: 'Please input website!' }] }]"
        placeholder="website"
        @change="handleWebsiteChange"
      >
        <template slot="dataSource">
          <a-select-option v-for="website in autoCompleteResult" :key="website">
            {{ website }}
          </a-select-option>
        </template>
        <a-input />
      </a-auto-complete>
    </a-form-item>
    <a-form-item
      v-bind="formItemLayout"
      label="Captcha"
      extra="We must make sure that your are a human."
    >
      <a-row :gutter="8">
        <a-col :span="12">
          <a-input
            v-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-checkbox v-decorator="['agreement', { valuePropName: 'checked' }]">
        I have read the
        <a href="">
          agreement
        </a>
      </a-checkbox>
    </a-form-item>
    <a-form-item v-bind="tailFormItemLayout">
      <a-button type="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, { name: 'register' });
  },
  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>

Form 表单 - 图15

时间类控件

时间类组件的 value 类型为 moment 对象,所以在提交服务器前需要预处理。

<template>
  <a-form :form="form" @submit="handleSubmit">
    <a-form-item v-bind="formItemLayout" label="DatePicker">
      <a-date-picker v-decorator="['date-picker', config]" />
    </a-form-item>
    <a-form-item v-bind="formItemLayout" label="DatePicker[showTime]">
      <a-date-picker
        v-decorator="['date-time-picker', config]"
        show-time
        format="YYYY-MM-DD HH:mm:ss"
      />
    </a-form-item>
    <a-form-item v-bind="formItemLayout" label="MonthPicker">
      <a-monthPicker v-decorator="['month-picker', config]" />
    </a-form-item>
    <a-form-item v-bind="formItemLayout" label="RangePicker">
      <a-range-picker v-decorator="['range-picker', rangeConfig]" />
    </a-form-item>
    <a-form-item v-bind="formItemLayout" label="RangePicker[showTime]">
      <a-range-picker
        v-decorator="['range-time-picker', rangeConfig]"
        show-time
        format="YYYY-MM-DD HH:mm:ss"
      />
    </a-form-item>
    <a-form-item v-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-button type="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, { name: 'time_related_controls' });
  },
  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>

Form 表单 - 图16

校验其他组件

以上演示没有出现的表单控件对应的校验演示。

<template>
  <a-form id="components-form-demo-validate-other" :form="form" v-bind="formItemLayout" @submit="handleSubmit">
    <a-form-item label="Plain Text">
      <span class="ant-form-text">
        China
      </span>
    </a-form-item>
    <a-form-item label="Select" has-feedback>
      <a-select
        v-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-item label="Select[multiple]">
      <a-select
        v-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-item label="InputNumber">
      <a-input-number v-decorator="['input-number', { initialValue: 3 }]" :min="1" :max="10" />
      <span class="ant-form-text">
        machines
      </span>
    </a-form-item>

    <a-form-item label="Switch">
      <a-switch v-decorator="['switch', { valuePropName: 'checked' }]" />
    </a-form-item>

    <a-form-item label="Slider">
      <a-slider
        v-decorator="['slider']"
        :marks="{ 0: 'A', 20: 'B', 40: 'C', 60: 'D', 80: 'E', 100: 'F' }"
      />
    </a-form-item>

    <a-form-item 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-item 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-item label="Checkbox.Group">
      <a-checkbox-group
        v-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-checkbox disabled value="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-item label="Rate">
      <a-rate v-decorator="['rate', { initialValue: 3.5 }]" allow-half />
    </a-form-item>

    <a-form-item label="Upload" extra="longgggggggggggggggggggggggggggggggggg">
      <a-upload
        v-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-item label="Dragger">
      <div class="dropbox">
        <a-upload-dragger
          v-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-button type="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, { name: 'validate_other' });
  },
  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

参数说明类型默认值
formForm.create() 包装过的组件会自带 this.form 属性,如果使用 template 语法,可以使用 this.$form.createForm(this, options)object
hideRequiredMark隐藏所有表单项的必选标记Booleanfalse
layout表单布局'horizontal'|'vertical'|'inline''horizontal'
labelCollabel 标签布局,同 <Col> 组件,设置 span offset 值,如 {span: 3, offset: 12}sm: {span: 3, offset: 12}object
wrapperCol需要为输入控件设置布局样式时,使用该属性,用法同 labelColobject
selfUpdate自定义字段更新逻辑,说明见下,需 1.3.17 版本以上booleanfalse

事件

事件名称说明回调参数
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 } })
name设置表单域内字段 id 的前缀-
validateMessages默认校验信息,可用于把默认错误信息改为中文等,格式与 newMessages 返回值一致Object { [nested.path]: String }
onFieldsChangeForm.Item 子节点的值发生改变时触发,可以把对应的值转存到 Redux storeFunction(props, fields)
onValuesChange任一表单域的值发生改变时的回调(props, values) => void

经过 Form.create 包装的组件将会自带 this.form 属性,this.form 提供的 API 如下:

注意:使用 getFieldsValue getFieldValue setFieldsValue 等时,应确保对应的 field 已经用 getFieldDecoratorv-decorator 注册过了。

方法说明类型
getFieldDecorator用于和表单进行双向绑定,单文件 template 可以使用指令v-decorator进行绑定,详见下方描述
getFieldError获取某个输入控件的 ErrorFunction(name)
getFieldsError获取一组输入控件的 Error ,如不传入参数,则获取全部组件的 ErrorFunction([names: string[]])
getFieldsValue获取一组输入控件的值,如不传入参数,则获取全部组件的值Function([fieldNames: string[]])
getFieldValue获取一个输入控件的值Function(fieldName: string)
isFieldsTouched判断是否任一输入控件经历过 getFieldDecoratorv-decorator 的值收集时机 options.trigger(names?: string[]) => boolean
isFieldTouched判断一个输入控件是否经历过 getFieldDecoratorv-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))
validateFieldsAndScrollvalidateFields 相似,但校验完后,如果校验不通过的菜单域不在可见范围内,则自动滚动进可见范围参考 validateFields

validateFields/validateFieldsAndScroll

const {
  form: { validateFields },
} = this;
validateFields((errors, values) => {
  // ...
});
validateFields(['field1', 'field2'], (errors, values) => {
  // ...
});
validateFields(['field1', 'field2'], options, (errors, values) => {
  // ...
});
参数说明类型默认值
options.first若为 true,则每一表单域的都会在碰到第一个失败了的校验规则后停止校验booleanfalse
options.firstFields指定表单域会在碰到第一个失败了的校验规则后停止校验String[][]
options.force对已经校验过的表单域,在 validateTrigger 再次被触发时是否再次校验booleanfalse
options.scroll定义 validateFieldsAndScroll 的滚动行为,详细配置见 dom-scroll-into-view configObject{}

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]"

经过 getFieldDecoratorv-decorator 包装的控件,表单控件会自动添加 value(或 valuePropName 指定的其他属性) onChange(或 trigger 指定的其他属性),数据同步将被 Form 接管,这会导致以下结果:

  • 不再需要也不应该onChange 来做同步,但还是可以继续监听 onChange 等事件。
  • 你不能用控件的 value defaultValue 等属性来设置表单域的值,默认值可以用 getFieldDecoratorv-decorator 里的 initialValue
  • 你不应该用 v-model,可以使用 this.form.setFieldsValue 来动态改变表单值。

特别注意

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即便字段不再使用,也保留该字段的值booleanfalse
options.rules校验规则,参考下方文档object[]
options.trigger收集子节点的值的时机string'change'
options.validateFirst当某一规则校验不通过时,是否停止剩下的规则的校验booleanfalse
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 后面的冒号booleantrue
extra额外的提示信息,和 help 类似,当需要错误信息和提示文案同时出现时,可以使用这个。string|slot
hasFeedback配合 validateStatus 属性使用,展示校验状态图标,建议只配合 Input 组件使用booleanfalse
help提示信息,如不设置,则会根据校验规则自动生成string|slot
labellabel 标签的文本string|slot
labelCollabel 标签布局,同 <Col> 组件,设置 span offset 值,如 {span: 3, offset: 12}sm: {span: 3, offset: 12}object
required是否必填,如不设置,则会根据校验规则自动生成booleanfalse
validateStatus校验状态,如不设置,则会根据校验规则自动生成,可选:'success' 'warning' 'error' 'validating'string
wrapperCol需要为输入控件设置布局样式时,使用该属性,用法同 labelColobject
selfUpdate自定义字段更新逻辑,你可以通过 Form 的 selfUpdate 进行统一设置。当和 Form 同时设置时,以 Item 为准。 说明见下 需 1.3.17 版本以上booleanfalse

校验规则

参数说明类型默认值
enum枚举类型string-
len字段长度number-
max最大长度number-
message校验文案string-
min最小长度number-
pattern正则表达式校验RegExp-
required是否必选booleanfalse
transform校验前转换字段值function(value) => transformedValue:any-
type内建校验类型,可选项string'string'
validator自定义校验(注意,callback 必须被调用function(rule, value, callback)-
whitespace必选时,空格是否会被视为错误booleanfalse

更多高级用法可研究 async-validator

selfUpdate

设置 selfUpdatetrue 后,Form 通过增量方式更新,只更新被修改的字段。大部分场景下,你只需要编写代码即可。而在某些特定场景,例如修改某个字段值后出现新的字段选项、或者纯粹希望表单任意变化都需要进行渲染。你可以通过修改 Form.Item 取消 selfUpdate,或者在 change / onValuesChange 回调中手动调用 this.$forceUpdate() 更新组件。示例

如果你并不精通 Vue,并不建议使用 selfUpdate,如果出现性能问题,可以尝试这把 Form 相关的业务独立到一个单独的组件中,减少组件渲染的消耗。