FormModel 表单 (支持 v-model 检验)(版本:1.5.0+)
具有数据收集、校验和提交功能的表单,包含复选框、单选框、输入框、下拉选择框等元素。
何时使用
- 需要对输入的数据类型进行校验时。
表单
我们为 form
提供了以下三种排列方式:
- 水平排列:标签和表单控件水平排列;(默认)
- 垂直排列:标签和表单控件上下垂直排列;
- 行内排列:表单项水平行内排列。
表单域
表单一定会包含表单域,表单域可以是输入控件,标准表单域,标签,下拉菜单,文本域等。
这里我们封装了表单域 <FormModel.Item />
。
组件注册
import { FormModel } from 'ant-design-vue';
Vue.use(FormModel);
代码演示
Activity name
Activity zone
please select your zone
Activity time
Instant delivery
Activity type
Online Promotion Offline
Resources
Sponsor Venue
Activity form
CreateCancel
典型表单
在 Form
组件中,每一个表单域由一个 FormItem
组件构成,表单域中可以放置各种类型的表单控件,比如输入框、选择器、开关、单选框、多选框等。
<template>
<a-form-model :model="form" :label-col="labelCol" :wrapper-col="wrapperCol">
<a-form-model-item label="Activity name">
<a-input v-model="form.name" />
</a-form-model-item>
<a-form-model-item label="Activity zone">
<a-select v-model="form.region" placeholder="please select your zone">
<a-select-option value="shanghai">
Zone one
</a-select-option>
<a-select-option value="beijing">
Zone two
</a-select-option>
</a-select>
</a-form-model-item>
<a-form-model-item label="Activity time">
<a-date-picker
v-model="form.date1"
show-time
type="date"
placeholder="Pick a date"
style="width: 100%;"
/>
</a-form-model-item>
<a-form-model-item label="Instant delivery">
<a-switch v-model="form.delivery" />
</a-form-model-item>
<a-form-model-item label="Activity type">
<a-checkbox-group v-model="form.type">
<a-checkbox value="1" name="type">
Online
</a-checkbox>
<a-checkbox value="2" name="type">
Promotion
</a-checkbox>
<a-checkbox value="3" name="type">
Offline
</a-checkbox>
</a-checkbox-group>
</a-form-model-item>
<a-form-model-item label="Resources">
<a-radio-group v-model="form.resource">
<a-radio value="1">
Sponsor
</a-radio>
<a-radio value="2">
Venue
</a-radio>
</a-radio-group>
</a-form-model-item>
<a-form-model-item label="Activity form">
<a-input v-model="form.desc" type="textarea" />
</a-form-model-item>
<a-form-model-item :wrapper-col="{ span: 14, offset: 4 }">
<a-button type="primary" @click="onSubmit">
Create
</a-button>
<a-button style="margin-left: 10px;">
Cancel
</a-button>
</a-form-model-item>
</a-form-model>
</template>
<script>
export default {
data() {
return {
labelCol: { span: 4 },
wrapperCol: { span: 14 },
form: {
name: '',
region: undefined,
date1: undefined,
delivery: false,
type: [],
resource: '',
desc: '',
},
};
},
methods: {
onSubmit() {
console.log('submit!', this.form);
},
},
};
</script>
Log in
内联登录栏
水平登录栏,常用在顶部导航栏中。
<template>
<a-form-model layout="inline" :model="formInline" @submit="handleSubmit" @submit.native.prevent>
<a-form-model-item>
<a-input v-model="formInline.user" placeholder="Username">
<a-icon slot="prefix" type="user" style="color:rgba(0,0,0,.25)" />
</a-input>
</a-form-model-item>
<a-form-model-item>
<a-input v-model="formInline.password" type="password" placeholder="Password">
<a-icon slot="prefix" type="lock" style="color:rgba(0,0,0,.25)" />
</a-input>
</a-form-model-item>
<a-form-model-item>
<a-button
type="primary"
html-type="submit"
:disabled="formInline.user === '' || formInline.password === ''"
>
Log in
</a-button>
</a-form-model-item>
</a-form-model>
</template>
<script>
export default {
data() {
return {
formInline: {
user: '',
password: '',
},
};
},
methods: {
handleSubmit(e) {
console.log(this.formInline);
},
},
};
</script>
Form Layout
Horizontal Vertical Inline
Field A
Field B
Submit
表单布局
表单有三种布局。
<template>
<a-form-model :layout="form.layout" :model="form" v-bind="formItemLayout">
<a-form-model-item label="Form Layout">
<a-radio-group v-model="form.layout">
<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-model-item>
<a-form-model-item label="Field A">
<a-input v-model="form.fieldA" placeholder="input placeholder" />
</a-form-model-item>
<a-form-model-item label="Field B">
<a-input v-model="form.fieldB" placeholder="input placeholder" />
</a-form-model-item>
<a-form-model-item :wrapper-col="buttonItemLayout.wrapperCol">
<a-button type="primary">
Submit
</a-button>
</a-form-model-item>
</a-form-model>
</template>
<script>
export default {
data() {
return {
form: {
layout: 'horizontal',
fieldA: '',
fieldB: '',
},
};
},
computed: {
formItemLayout() {
const { layout } = this.form;
return layout === 'horizontal'
? {
labelCol: { span: 4 },
wrapperCol: { span: 14 },
}
: {};
},
buttonItemLayout() {
const { layout } = this.form;
return layout === 'horizontal'
? {
wrapperCol: { span: 14, offset: 4 },
}
: {};
},
},
};
</script>
Activity name
Activity zone
please select your zone
Activity time
Instant delivery
Activity type
Online Promotion Offline
Resources
Sponsor Venue
Activity form
CreateReset
表单验证
Form 组件提供了表单验证的功能,只需要通过 rules
属性传入约定的验证规则,并将 FormItem
的 prop
属性设置为需校验的字段名即可。校验规则参见 async-validator
<template>
<a-form-model
ref="ruleForm"
:model="form"
:rules="rules"
:label-col="labelCol"
:wrapper-col="wrapperCol"
>
<a-form-model-item ref="name" label="Activity name" prop="name">
<a-input
v-model="form.name"
@blur="
() => {
$refs.name.onFieldBlur();
}
"
/>
</a-form-model-item>
<a-form-model-item label="Activity zone" prop="region">
<a-select v-model="form.region" placeholder="please select your zone">
<a-select-option value="shanghai">
Zone one
</a-select-option>
<a-select-option value="beijing">
Zone two
</a-select-option>
</a-select>
</a-form-model-item>
<a-form-model-item label="Activity time" required prop="date1">
<a-date-picker
v-model="form.date1"
show-time
type="date"
placeholder="Pick a date"
style="width: 100%;"
/>
</a-form-model-item>
<a-form-model-item label="Instant delivery" prop="delivery">
<a-switch v-model="form.delivery" />
</a-form-model-item>
<a-form-model-item label="Activity type" prop="type">
<a-checkbox-group v-model="form.type">
<a-checkbox value="1" name="type">
Online
</a-checkbox>
<a-checkbox value="2" name="type">
Promotion
</a-checkbox>
<a-checkbox value="3" name="type">
Offline
</a-checkbox>
</a-checkbox-group>
</a-form-model-item>
<a-form-model-item label="Resources" prop="resource">
<a-radio-group v-model="form.resource">
<a-radio value="1">
Sponsor
</a-radio>
<a-radio value="2">
Venue
</a-radio>
</a-radio-group>
</a-form-model-item>
<a-form-model-item label="Activity form" prop="desc">
<a-input v-model="form.desc" type="textarea" />
</a-form-model-item>
<a-form-model-item :wrapper-col="{ span: 14, offset: 4 }">
<a-button type="primary" @click="onSubmit">
Create
</a-button>
<a-button style="margin-left: 10px;" @click="resetForm">
Reset
</a-button>
</a-form-model-item>
</a-form-model>
</template>
<script>
export default {
data() {
return {
labelCol: { span: 4 },
wrapperCol: { span: 14 },
other: '',
form: {
name: '',
region: undefined,
date1: undefined,
delivery: false,
type: [],
resource: '',
desc: '',
},
rules: {
name: [
{ required: true, message: 'Please input Activity name', trigger: 'blur' },
{ min: 3, max: 5, message: 'Length should be 3 to 5', trigger: 'blur' },
],
region: [{ required: true, message: 'Please select Activity zone', trigger: 'change' }],
date1: [{ required: true, message: 'Please pick a date', trigger: 'change' }],
type: [
{
type: 'array',
required: true,
message: 'Please select at least one activity type',
trigger: 'change',
},
],
resource: [
{ required: true, message: 'Please select activity resource', trigger: 'change' },
],
desc: [{ required: true, message: 'Please input activity form', trigger: 'blur' }],
},
};
},
methods: {
onSubmit() {
this.$refs.ruleForm.validate(valid => {
if (valid) {
alert('submit!');
} else {
console.log('error submit!!');
return false;
}
});
},
resetForm() {
this.$refs.ruleForm.resetFields();
},
},
};
</script>
Password
Confirm
Age
SubmitReset
自定义校验规则
这个例子中展示了如何使用自定义验证规则来完成密码的二次验证。本例还使用 has-feedback
属性为输入框添加了表示校验结果的反馈图标。
自定义校验 callback 必须被调用。 更多高级用法可参考 async-validator
<template>
<a-form-model ref="ruleForm" :model="ruleForm" :rules="rules" v-bind="layout">
<a-form-model-item has-feedback label="Password" prop="pass">
<a-input v-model="ruleForm.pass" type="password" autocomplete="off" />
</a-form-model-item>
<a-form-model-item has-feedback label="Confirm" prop="checkPass">
<a-input v-model="ruleForm.checkPass" type="password" autocomplete="off" />
</a-form-model-item>
<a-form-model-item has-feedback label="Age" prop="age">
<a-input v-model.number="ruleForm.age" />
</a-form-model-item>
<a-form-model-item :wrapper-col="{ span: 14, offset: 4 }">
<a-button type="primary" @click="submitForm('ruleForm')">
Submit
</a-button>
<a-button style="margin-left: 10px" @click="resetForm('ruleForm')">
Reset
</a-button>
</a-form-model-item>
</a-form-model>
</template>
<script>
export default {
data() {
let checkPending;
let checkAge = (rule, value, callback) => {
clearTimeout(checkPending);
if (!value) {
return callback(new Error('Please input the age'));
}
checkPending = setTimeout(() => {
if (!Number.isInteger(value)) {
callback(new Error('Please input digits'));
} else {
if (value < 18) {
callback(new Error('Age must be greater than 18'));
} else {
callback();
}
}
}, 1000);
};
let validatePass = (rule, value, callback) => {
if (value === '') {
callback(new Error('Please input the password'));
} else {
if (this.ruleForm.checkPass !== '') {
this.$refs.ruleForm.validateField('checkPass');
}
callback();
}
};
let validatePass2 = (rule, value, callback) => {
if (value === '') {
callback(new Error('Please input the password again'));
} else if (value !== this.ruleForm.pass) {
callback(new Error("Two inputs don't match!"));
} else {
callback();
}
};
return {
ruleForm: {
pass: '',
checkPass: '',
age: '',
},
rules: {
pass: [{ validator: validatePass, trigger: 'change' }],
checkPass: [{ validator: validatePass2, trigger: 'change' }],
age: [{ validator: checkAge, trigger: 'change' }],
},
layout: {
labelCol: { span: 4 },
wrapperCol: { span: 14 },
},
};
},
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>
Add field
SubmitReset
动态增减表单项
动态增加、减少表单项。
<template>
<a-form-model
ref="dynamicValidateForm"
:model="dynamicValidateForm"
v-bind="formItemLayoutWithOutLabel"
>
<a-form-model-item
v-for="(domain, index) in dynamicValidateForm.domains"
:key="domain.key"
v-bind="index === 0 ? formItemLayout : {}"
:label="index === 0 ? 'Domains' : ''"
:prop="'domains.' + index + '.value'"
:rules="{
required: true,
message: 'domain can not be null',
trigger: 'blur',
}"
>
<a-input
v-model="domain.value"
placeholder="please input domain"
style="width: 60%; margin-right: 8px"
/>
<a-icon
v-if="dynamicValidateForm.domains.length > 1"
class="dynamic-delete-button"
type="minus-circle-o"
:disabled="dynamicValidateForm.domains.length === 1"
@click="removeDomain(domain)"
/>
</a-form-model-item>
<a-form-model-item v-bind="formItemLayoutWithOutLabel">
<a-button type="dashed" style="width: 60%" @click="addDomain">
<a-icon type="plus" /> Add field
</a-button>
</a-form-model-item>
<a-form-model-item v-bind="formItemLayoutWithOutLabel">
<a-button type="primary" html-type="submit" @click="submitForm('dynamicValidateForm')">
Submit
</a-button>
<a-button style="margin-left: 10px" @click="resetForm('dynamicValidateForm')">
Reset
</a-button>
</a-form-model-item>
</a-form-model>
</template>
<script>
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 },
},
},
dynamicValidateForm: {
domains: [],
},
};
},
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) {
let 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>
<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>
API
Form
参数 | 说明 | 类型 | 默认值 | 版本 |
---|---|---|---|---|
model | 表单数据对象 | object | ||
rules | 表单验证规则 | object | ||
hideRequiredMark | 隐藏所有表单项的必选标记 | Boolean | false | |
labelAlign | label 标签的文本对齐方式 | ‘left’ | ‘right’ | ‘right’ | |
layout | 表单布局 | ‘horizontal’|’vertical’|’inline’ | ‘horizontal’ | |
labelCol | label 标签布局,同 <Col> 组件,设置 span offset 值,如 {span: 3, offset: 12} 或 sm: {span: 3, offset: 12} | object | ||
wrapperCol | 需要为输入控件设置布局样式时,使用该属性,用法同 labelCol | object | ||
colon | 配置 Form.Item 的 colon 的默认值 (只有在属性 layout 为 horizontal 时有效) | boolean | true | |
validateOnRuleChange | 是否在 rules 属性改变后立即触发一次验证 | boolean | true |
事件
事件名称 | 说明 | 回调参数 |
---|---|---|
submit | 数据验证成功后回调事件 | Function(e:Event) |
validate | 任一表单项被校验后触发 | 被校验的表单项 prop 值,校验是否通过,错误消息(如果存在) |
方法
方法名 | 说明 | 参数 |
---|---|---|
validate | 对整个表单进行校验的方法,参数为一个回调函数。该回调函数会在校验结束后被调用,并传入两个参数:是否校验成功和未通过校验的字段。若不传入回调函数,则会返回一个 promise | Function(callback: Function(boolean, object)) |
validateField | 对部分表单字段进行校验的方法 | Function(props: array | string, callback: Function(errorMessage: string)) |
resetFields | 对整个表单进行重置,将所有字段值重置为初始值并移除校验结果 | — |
clearValidate | 移除表单项的校验结果。传入待移除的表单项的 prop 属性或者 prop 组成的数组,如不传则移除整个表单的校验结果 | Function(props: array | string) |
Form.Item
参数 | 说明 | 类型 | 默认值 | 版本 |
---|---|---|---|---|
prop | 表单域 model 字段,在使用 validate、resetFields 方法的情况下,该属性是必填的 | string | ||
rules | 表单验证规则 | object | array | ||
autoLink | 是否自动关联表单域,对于大部分情况都可以使用自动关联,如果不满足自动关联的条件,可以手动关联,参见下方注意事项 | boolean | true | |
colon | 配合 label 属性使用,表示是否显示 label 后面的冒号 | boolean | true | |
extra | 额外的提示信息,和 help 类似,当需要错误信息和提示文案同时出现时,可以使用这个。 | string|slot | ||
hasFeedback | 配合 validateStatus 属性使用,展示校验状态图标,建议只配合 Input 组件使用 | boolean | false | |
help | 提示信息,如不设置,则会根据校验规则自动生成 | string|slot | ||
htmlFor | 设置子元素 label htmlFor 属性 | string | ||
label | label 标签的文本 | string|slot | ||
labelCol | label 标签布局,同 <Col> 组件,设置 span offset 值,如 {span: 3, offset: 12} 或 sm: {span: 3, offset: 12} | object | ||
labelAlign | 标签文本对齐方式 | ‘left’ | ‘right’ | ‘right’ | |
required | 是否必填,如不设置,则会根据校验规则自动生成 | boolean | false | |
validateStatus | 校验状态,如不设置,则会根据校验规则自动生成,可选:’success’ ‘warning’ ‘error’ ‘validating’ | string | ||
wrapperCol | 需要为输入控件设置布局样式时,使用该属性,用法同 labelCol | object |
注意:
Form.Item 会对唯一子元素进行劫持,并监听 blur
和 change
事件,来达到自动校验的目的,所以请确保表单域没有其它元素包裹。如果有多个子元素,将只会监听第一个子元素的变化。
如果要监听的表单域不满足自动监听的条件,可以通过如下方式关联表单域:
<a-form-model-item prop="form.name" ref="name" :autoLink="false">
<a-input v-model="other" />
<span>hahha</span>
<div>
<a-input
v-model="form.name"
@blur="() => {$refs.name.onFieldBlur()}"
@change="() => {$refs.name.onFieldChange()}"
/>
</div>
</a-form-model-item>
校验规则
参数 | 说明 | 类型 | 默认值 |
---|---|---|---|
trigger | 校验触发的时机 | ‘blur’ | ‘change’ | [‘change’, ‘blur’] | - |
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。