# Form 表单

由输入框、选择器、单选框、多选框等控件组成,用以收集、校验、提交数据

简介

aile-ui/form 是一款 Form 组件,基于 Vue2ElementUI 进行的二次封装,使用组件时,在原 ElForm 属性的基础上新增 config 属性,增强 Form 的功能。 强烈建议配合AileUI其他组件一起使用,解决诸如 Select 组件远程请求回来的数据需要自行实现双向数据绑定的问题。

# 典型表单

包括各种表单项,比如输入框、选择器、开关、单选框、多选框等。

在 AileForm 组件中,表单域由 columns 中配置生成,表单域中可以放置各种类型的表单控件,包括 Input、Select、Checkbox、Radio、Switch、DatePicker、TimePicker

<div class="demo-form">
  <aile-form 
    ref="form" 
    :model="form" 
    :columns="columns"
    label-width="80px"
  />
</div>

<script>
  export default {
    data() {
      return {
        form: {
          name: '',
          region: '',
          date1: '',
          date2: '',
          delivery: false,
          type: [],
          resource: '',
          desc: ''
        }
      }
    },
    computed: {
      columns() {
        return [
          {
            prop: 'name',
            label: '活动名称',
            render: (h, form) => (
              <aile-input v-model={form.name} />
            )
          },
          {
            prop: 'region',
            label: '活动区域',
            render: (h, form) => (
              <aile-select 
                v-model={form.region} 
                config={{
                  data: [
                    {
                      label: '区域一',
                      value: 'shanghai'
                    },
                    {
                      label: '区域二',
                      value: 'beijing'
                    }
                  ],
                  label: 'label',
                  value: 'value'
                }}
              />
            )
          },
          {
            label: '活动时间',
            render: (h, form) => (
              <el-row>
                <el-col span={11}>
                  <el-date-picker type="date" placeholder="选择日期" v-model={form.date1} style="width: 100%;"></el-date-picker>
                </el-col>
                <el-col class="line" span={2}>-</el-col>
                <el-col span={11}>
                  <el-time-picker placeholder="选择时间" v-model={form.date2} style="width: 100%;"></el-time-picker>
                </el-col>
              </el-row>
            )
          },
          {
            prop: 'delivery',
            label: '即时配送',
            render: (h, form) => (
              <el-switch v-model={form.delivery}></el-switch>
            )
          },
          {
            prop: 'type',
            label: '活动性质',
            render: (h, form) => (
              <el-checkbox-group v-model={form.type}>
                <el-checkbox label="美食/餐厅线上活动" name="type"></el-checkbox>
                <el-checkbox label="地推活动" name="type"></el-checkbox>
                <el-checkbox label="线下主题活动" name="type"></el-checkbox>
                <el-checkbox label="单纯品牌曝光" name="type"></el-checkbox>
              </el-checkbox-group>
            )
          },
          {
            prop: 'resource',
            label: '特殊资源',
            render: (h, form) => (
              <el-radio-group v-model={form.resource}>
                <el-radio label="线上品牌商赞助"></el-radio>
                <el-radio label="线下场地免费"></el-radio>
              </el-radio-group>
            )
          },
          {
            prop: 'desc',
            label: '活动形式',
            render: (h, form) => (
              <aile-input type="textarea" v-model={form.desc}></aile-input>
            )
          },
          {
            label: '',
            render: h => (
              <div>
                <el-button type="primary" onClick={this.onSubmit}>立即创建</el-button>
                <el-button>取消</el-button>
              </div>
            )
          }
        ]
      }
    },
    methods: {
      onSubmit() {
        console.log('submit: ', this.form);
      }
    }
  }
</script>
显示代码 复制代码

TIP

W3C 标准中有如下规定 (opens new window)

When there is only one single-line text input field in a form, the user agent should accept Enter in that field as a request to submit the form.

即:当一个 form 元素中只有一个输入框时,在该输入框中按下回车应提交该表单。如果希望阻止这一默认行为,可以在 <el-form> 标签上添加 @submit.native.prevent

# 行内表单

当垂直方向空间受限且表单较简单时,可以在一行内放置表单。

设置 inline 属性可以让表单域变为行内的表单域

<div class="demo-form">
  <aile-form 
    class="demo-form-inline" 
    :model="formInline"
    :columns="columns" 
    :inline="true" 
  />
</div>

<script>
  export default {
    data() {
      return {
        formInline: {
          user: '',
          region: ''
        }
      }
    },
    computed: {
      columns() {
        return [
          {
            prop: 'user',
            label: '审批人',
            render: (h, form) => (
              <aile-input v-model={form.user} placeholder="审批人" />
            )
          },
          {
            prop: 'region',
            label: '活动区域',
            render: (h, form) => (
              <aile-select 
                v-model={form.region} 
                config={{
                  data: [
                    {
                      label: '区域一',
                      value: 'shanghai'
                    },
                    {
                      label: '区域二',
                      value: 'beijing'
                    }
                  ],
                  label: 'label',
                  value: 'value'
                }}
              />
            )
          },
          {
            label: '',
            render: h => (
              <el-button type="primary" onClick={this.onSubmit}>查询</el-button>
            )
          }
        ]
      }
    },
    methods: {
      onSubmit() {
        console.log('submit!');
      }
    }
  }
</script>
显示代码 复制代码

# 对齐方式

根据具体目标和制约因素,选择最佳的标签对齐方式。

通过设置 label-position 属性可以改变表单域标签的位置,可选值为 topleft,当设为 top 时标签会置于表单域的顶部

<el-radio-group v-model="labelPosition" size="small">
  <el-radio-button label="left">左对齐</el-radio-button>
  <el-radio-button label="right">右对齐</el-radio-button>
  <el-radio-button label="top">顶部对齐</el-radio-button>
</el-radio-group>
<div style="margin: 20px;"></div>
<aile-form 
  :label-position="labelPosition" 
  label-width="80px" 
  :model="formLabelAlign" 
  :columns="columns"
/>

<script>
  export default {
    data() {
      return {
        labelPosition: 'right',
        formLabelAlign: {
          name: '',
          region: '',
          type: ''
        }
      };
    },
    computed: {
      columns() {
        return [
          {
            prop: 'name',
            label: '名称',
            render: (h, form) => (
              <el-input v-model={form.name} />
            )
          },
          {
            prop: 'region',
            label: '活动区域',
            render: (h, form) => (
              <el-input v-model={form.region} />
            )
          },
          {
            prop: 'type',
            label: '活动形式',
            render: (h, form) => (
              <el-input v-model={form.type} />
            )
          }
        ]
      }
    }
  }
</script>
显示代码 复制代码

# 表单验证

在防止用户犯错的前提下,尽可能让用户更早地发现并纠正错误。

Form 组件提供了表单验证的功能,只需要通过 rules 属性传入约定的验证规则,并将 Form-Item 的 prop 属性设置为需校验的字段名即可。校验规则参见 async-validator (opens new window)

<div class="demo-form">
  <aile-form 
    :model="ruleForm" 
    :columns="columns"
    :rules="rules" 
    ref="ruleForm" 
    label-width="100px" 
    class="demo-ruleForm" 
  />
</div>

<script>
  export default {
    data() {
      return {
        ruleForm: {
          name: '',
          region: '',
          date1: '',
          date2: '',
          delivery: false,
          type: [],
          resource: '',
          desc: ''
        },
        rules: {
          name: [
            { required: true, message: '请输入活动名称', trigger: 'blur' },
            { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }
          ],
          region: [
            { required: true, message: '请选择活动区域', trigger: 'change' }
          ],
          date1: [
            { type: 'date', required: true, message: '请选择日期', trigger: 'change' }
          ],
          date2: [
            { type: 'date', required: true, message: '请选择时间', trigger: 'change' }
          ],
          type: [
            { type: 'array', required: true, message: '请至少选择一个活动性质', trigger: 'change' }
          ],
          resource: [
            { required: true, message: '请选择活动资源', trigger: 'change' }
          ],
          desc: [
            { required: true, message: '请填写活动形式', trigger: 'blur' }
          ]
        }
      };
    },
    computed: {
      columns() {
        return [
          {
            prop: 'name',
            label: '活动名称',
            render: (h, form) => (
              <aile-input v-model={form.name} />
            )
          },
          {
            prop: 'region',
            label: '活动区域',
            render: (h, form) => (
              <aile-select 
                v-model={form.region} 
                config={{
                  data: [
                    {
                      label: '区域一',
                      value: 'shanghai'
                    },
                    {
                      label: '区域二',
                      value: 'beijing'
                    }
                  ],
                  label: 'label',
                  value: 'value'
                }}
              />
            )
          },
          {
            label: '活动时间',
            render: (h, form) => (
              <el-row>
                <el-col span={11}>
                  <el-date-picker type="date" placeholder="选择日期" v-model={form.date1} style="width: 100%;"></el-date-picker>
                </el-col>
                <el-col class="line" span={2}>-</el-col>
                <el-col span={11}>
                  <el-time-picker placeholder="选择时间" v-model={form.date2} style="width: 100%;"></el-time-picker>
                </el-col>
              </el-row>
            )
          },
          {
            prop: 'delivery',
            label: '即时配送',
            render: (h, form) => (
              <el-switch v-model={form.delivery}></el-switch>
            )
          },
          {
            prop: 'type',
            label: '活动性质',
            render: (h, form) => (
              <el-checkbox-group v-model={form.type}>
                <el-checkbox label="美食/餐厅线上活动" name="type"></el-checkbox>
                <el-checkbox label="地推活动" name="type"></el-checkbox>
                <el-checkbox label="线下主题活动" name="type"></el-checkbox>
                <el-checkbox label="单纯品牌曝光" name="type"></el-checkbox>
              </el-checkbox-group>
            )
          },
          {
            prop: 'resource',
            label: '特殊资源',
            render: (h, form) => (
              <el-radio-group v-model={form.resource}>
                <el-radio label="线上品牌商赞助"></el-radio>
                <el-radio label="线下场地免费"></el-radio>
              </el-radio-group>
            )
          },
          {
            prop: 'desc',
            label: '活动形式',
            render: (h, form) => (
              <aile-input type="textarea" v-model={form.desc}></aile-input>
            )
          },
          {
            label: '',
            render: h => (
              <div>
                <el-button type="primary" onClick={() => this.onSubmit('ruleForm')}>立即创建</el-button>
                <el-button onClick={() => this.resetForm('ruleForm')}>重置</el-button>
              </div>
            )
          }
        ]
      }
    },
    methods: {
      onSubmit(formName) {
        this.$refs[formName].validate((valid) => {
          if (valid) {
            alert('submit!');
          } else {
            console.log('error submit!!');
            return false;
          }
        });
      },
      resetForm(formName) {
        this.$refs[formName].resetFields();
      }
    }
  }
</script>
显示代码 复制代码

# 自定义校验规则

这个例子中展示了如何使用自定义验证规则来完成密码的二次验证。

本例还使用status-icon属性为输入框添加了表示校验结果的反馈图标。

<div class="demo-form">
  <aile-form 
    :model="ruleForm" 
    :columns="columns"
    status-icon 
    :rules="rules" 
    ref="ruleForm" 
    label-width="100px" 
    class="demo-ruleForm" 
  />
</div>

<script>
  export default {
    data() {
      var checkAge = (rule, value, callback) => {
        if (!value) {
          return callback(new Error('年龄不能为空'));
        }
        setTimeout(() => {
          if (!Number.isInteger(value)) {
            callback(new Error('请输入数字值'));
          } else {
            if (value < 18) {
              callback(new Error('必须年满18岁'));
            } else {
              callback();
            }
          }
        }, 1000);
      };
      var validatePass = (rule, value, callback) => {
        if (value === '') {
          callback(new Error('请输入密码'));
        } else {
          if (this.ruleForm.checkPass !== '') {
            this.$refs.ruleForm.validateField('checkPass');
          }
          callback();
        }
      };
      var validatePass2 = (rule, value, callback) => {
        if (value === '') {
          callback(new Error('请再次输入密码'));
        } else if (value !== this.ruleForm.pass) {
          callback(new Error('两次输入密码不一致!'));
        } else {
          callback();
        }
      };
      return {
        ruleForm: {
          pass: '',
          checkPass: '',
          age: ''
        },
        rules: {
          pass: [
            { validator: validatePass, trigger: 'blur' }
          ],
          checkPass: [
            { validator: validatePass2, trigger: 'blur' }
          ],
          age: [
            { validator: checkAge, trigger: 'blur' }
          ]
        }
      };
    },
    computed: {
      columns() {
        return [
          {
            prop: 'pass',
            label: '密码',
            render: (h, form) => (
              <aile-input 
                type='password'
                v-model={form.pass}
                autocomplete='off'
              />
            )
          },
          {
            prop: 'checkPass',
            label: '确认密码',
            render: (h, form) => (
              <aile-input 
                type='password'
                v-model={form.checkPass}
                autocomplete='off'
              />
            )
          },
          {
            prop: 'age',
            label: '年龄',
            render: (h, form) => (
              <aile-input v-model_number={form.age} />
            )
          },
          {
            label: '',
            render: h => (
              <div>
                <el-button type="primary" onClick={() => this.submitForm('ruleForm')}>提交</el-button>
                <el-button onClick={() => this.resetForm('ruleForm')}>重置</el-button>
              </div>
            )
          }
        ]
      }
    },
    methods: {
      submitForm(formName) {
        this.$refs[formName].validate((valid) => {
          if (valid) {
            alert('submit!');
          } else {
            console.log('error submit!!');
            return false;
          }
        });
      },
      resetForm(formName) {
        this.$refs[formName].resetFields();
      }
    }
  }
</script>
显示代码 复制代码

TIP

  1. 自定义校验 callback 必须被调用。 更多高级用法可参考 async-validator (opens new window)
  2. 除了在 Form 组件上一次性传递所有的验证规则外还可以在单个的表单域上传递属性的验证规则

# 动态增减表单项

AileForm 可以通过配置 config.columns.items 实现快速创建数组表单项。

<div class="demo-form">
  <aile-form 
    :model="dynamicValidateForm" 
    :columns="columns"
    ref="dynamicValidateForm" 
    label-width="100px" 
    label-position="right"
    class="demo-dynamic"
  />
</div>

<script>
  export default {
    data() {
      return {
        dynamicValidateForm: {
          domains: [{
            value: ''
          }],
          email: ''
        }
      };
    },
    computed: {
      columns() {
        return [
          {
            prop: 'email',
            label: '邮箱',
            rules: [
              { required: true, message: '请输入邮箱地址', trigger: 'blur' },
              { type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }
            ],
            render: (h, form) => (
              <aile-input v-model={form.email} />
            )
          },
          {
            prop: 'domains',
            label: '域名',
            items: () => [
              {
                prop: 'value',
                renderLabel: (h, form, root, scope) => <span>域名{scope.itemIndex}</span>,
                rules: { required: true, message: '域名不能为空', trigger: 'blur' },
                render: (h, form) => (
                  <aile-input v-model={form.value} />
                )
              }
            ]
          },
          {
            label: '',
            render: h => (
              <div>
                <el-button type='primary' onClick={() => this.submitForm('dynamicValidateForm')}>提交</el-button>
                <el-button onClick={() => this.resetForm('dynamicValidateForm')}>重置</el-button>
              </div>
            )
          }
        ]
      }
    },
    methods: {
      submitForm(formName) {
        this.$refs[formName].validate((valid) => {
          if (valid) {
            alert('submit!');
          } else {
            console.log('error submit!!');
            return false;
          }
        });
      },
      resetForm(formName) {
        this.$refs[formName].resetFields();
      },
      removeDomain(item) {
        var index = this.dynamicValidateForm.domains.indexOf(item)
        if (index !== -1) {
          this.dynamicValidateForm.domains.splice(index, 1)
        }
      },
      addDomain() {
        this.dynamicValidateForm.domains.push({
          value: '',
          key: Date.now()
        });
      }
    }
  }
</script>
显示代码 复制代码

# 数字类型验证

数字类型的验证需要在 v-model 处加上 .number 的修饰符,这是 Vue 自身提供的用于将绑定值转化为 number 类型的修饰符。在Vue中使用JSX语法时,修饰符采用下 _number 的方式

<div class="demo-form">
  <aile-form 
    :model="numberValidateForm"
    :columns="columns"
    ref="numberValidateForm"
    label-width="100px"
    class="demo-ruleForm"
  />
</div>

<script>
  export default {
    data() {
      return {
        numberValidateForm: {
          age: ''
        }
      };
    },
    computed: {
      columns() {
        return [
          {
            prop: 'age',
            label: '年龄',
            rules: [
              { required: true, message: '年龄不能为空'},
              { type: 'number', message: '年龄必须为数字值'}
            ],
            render: (h, form) => (
              <aile-input 
                type="age" 
                v-model_number={form.age}
                autocomplete="off" 
              />
            )
          },
          {
            label: '',
            render: h => (
              <div>
                <el-button type='primary' onClick={() => this.submitForm('numberValidateForm')}>提交</el-button>
                <el-button onClick={() => this.resetForm('numberValidateForm')}>重置</el-button>
              </div>
            )
          }
        ]
      }
    },
    methods: {
      submitForm(formName) {
        this.$refs[formName].validate((valid) => {
          if (valid) {
            alert('submit!');
          } else {
            console.log('error submit!!');
            return false;
          }
        });
      },
      resetForm(formName) {
        this.$refs[formName].resetFields();
      }
    }
  }
</script>
显示代码 复制代码

TIP

嵌套在 el-form-item 中的 el-form-item 标签宽度默认为零,不会继承 el-formlabel-width。如果需要可以为其单独设置 label-width 属性。

# 嵌套表单

AileForm 可以通过配置 config.columns.children 实现表单的嵌套。

<div class="demo-form">
  <aile-form 
    ref="form" 
    :model="nestForm"
    :columns="columns" 
    label-width="80px" 
    size="mini"
  />
</div>

<script>
  export default {
    data() {
      return {
        nestForm: {
          time: {
            label: '',
            value: {
              year: '',
              month: ''
            }
          }
        }
      };
    },
    computed: {
      columns() {
        return [
          {
            prop: 'time',
            label: '时间',
            children: [
              {
                prop: 'label',
                label: '标签',
                render: (h, form) => (
                  <aile-input v-model={form.label} />
                )
              },
              {
                prop: 'value',
                label: '信息',
                children: [
                  {
                    prop: 'year',
                    label: '年',
                    render: (h, form) => (
                      <aile-input v-model={form.year} />
                    )
                  },
                  {
                    prop: 'month',
                    label: '月',
                    render: (h, form) => (
                      <aile-input v-model={form.month} />
                    )
                  }
                ]
              }
            ]
          }
        ]
      }
    }
  };
</script>
显示代码 复制代码

# 表单内组件尺寸控制

通过设置 Form 上的 size 属性可以使该表单内所有可调节大小的组件继承该尺寸。Form-Item 也具有该属性。

如果希望某个表单项或某个表单组件的尺寸不同于 Form 上的size属性,直接为这个表单项或表单组件设置自己的size即可。

<div class="demo-form">
  <aile-form 
    ref="form" 
    :model="sizeForm"
    :columns="columns" 
    label-width="80px" 
    size="mini"
  />
</div>

<script>
  export default {
    data() {
      return {
        sizeForm: {
          name: '',
          region: '',
          date1: '',
          date2: '',
          type: [],
          resource: ''
        }
      };
    },
    computed: {
      columns() {
        return [
          {
            prop: 'name',
            label: '活动名称',
            render: (h, form) => (
              <aile-input v-model={form.name} />
            )
          },
          {
            prop: 'region',
            label: '活动区域',
            render: (h, form) => (
              <aile-select 
                v-model={form.region} 
                config={{
                  data: [
                    {
                      label: '区域一',
                      value: 'shanghai'
                    },
                    {
                      label: '区域二',
                      value: 'beijing'
                    }
                  ],
                  label: 'label',
                  value: 'value'
                }}
              />
            )
          },
          {
            label: '活动时间',
            render: (h, form) => (
              <el-row>
                <el-col span={11}>
                  <el-date-picker type="date" placeholder="选择日期" v-model={form.date1} style="width: 100%;"></el-date-picker>
                </el-col>
                <el-col class="line" span={2}>-</el-col>
                <el-col span={11}>
                  <el-time-picker placeholder="选择时间" v-model={form.date2} style="width: 100%;"></el-time-picker>
                </el-col>
              </el-row>
            )
          },
          {
            prop: 'type',
            label: '活动性质',
            render: (h, form) => (
              <el-checkbox-group v-model={form.type}>
                <el-checkbox label="美食/餐厅线上活动" name="type"></el-checkbox>
                <el-checkbox label="地推活动" name="type"></el-checkbox>
                <el-checkbox label="线下主题活动" name="type"></el-checkbox>
                <el-checkbox label="单纯品牌曝光" name="type"></el-checkbox>
              </el-checkbox-group>
            )
          },
          {
            prop: 'resource',
            label: '特殊资源',
            render: (h, form) => (
              <el-radio-group v-model={form.resource} size='medium'>
                <el-radio border label="线上品牌商赞助"></el-radio>
                <el-radio border label="线下场地免费"></el-radio>
              </el-radio-group>
            )
          },
          {
            label: '',
            size: 'large',
            render: h => (
              <div>
                <el-button type="primary" onClick={this.onSubmit}>立即创建</el-button>
                <el-button>取消</el-button>
              </div>
            )
          }
        ]
      }
    },
    methods: {
      onSubmit() {
        console.log('submit!');
      }
    }
  };
</script>
显示代码 复制代码

# 配置项

# 插件配置

main.js 中通过插件方式引入 AileUI 时(使用方式:插件安装),可对全局的 AileForm 配置如下属性:

参数 数据类型 默认值 说明
config Object {} Config 配置项
form Object {} ElForm Props (opens new window)
formItem Object {} ElFormItem Props (opens new window)

# config 配置项

参数 数据类型 默认值 说明
emptyText String '-' 空白占位符
layout Object null ElLayout Row Props

# Form Attributes 表单属性

仅展示必填项和新增项,其余参数见 Element Doc Form #Form Attributes (opens new window)

参数 数据类型 是否必须 默认值 可选值 说明
model Object - - 表单数据(使用方式同ElForm)
columns Array - - 表单列配置项
config Array - - Config 配置项
form Array - - ElForm Props (opens new window)
formItem Array - - ElFormItem Props (opens new window)

# Form Methods 表单方法

支持全部 el-form 方法,详见 Element Doc Form #Form Methods (opens new window)

# Form Events 表单事件

支持全部 el-form 方法,详见 Element Doc Form #Form Events (opens new window)

# Form-Item Methods 表单列方法

暂不支持

# Column 列配置项

仅展示必填项和新增项,其余参数见 Element Doc Form #Form-column Attributes (opens new window)

参数 数据类型 是否必须 说明
prop String 设置表单列的别名(非必须,但是建议设置)
label String 设置表单列的显示标签(非必须,但是建议设置)
render Function(h, form, root) => VNode 自定义渲染内容,可选返回VNode
formatter Function(form, root) => string 自定义渲染内容,可选返回字符串
children Array 当数据项类型为[object]时使用,返回column数组,与render/formatter/items互斥
items Function(form, root) => [column, ...] 当数据项类型为[array]时使用,可动态增删子节点,返回column数组,与render/formatter/children互斥
buttonText String 设置数组表单-添加按钮的文字内容
buttonClass String 设置数组表单-添加按钮的图标类名
defaultValue - 当上一级数据项类型为[array],且传递了[items]属性时使用,可设置数据项初始值
show Function(form, root) => boolean 是否渲染该列,默认渲染
layout Object 设置布局模式,可传入[el-row]和[el-col]支持的所有属性
renderLabel Function(h, form, root)/VNode 自定义标签内容
renderError Function(h, form, root, {error})/VNode 自定义表单校验信息的显示方式
labelWidth String 设置当前表单域标签的宽度,例如 '50px',支持 auto。
labelPosition String 设置当前表单域标签的位置,可选值:right/center/left/top