Form 表单
示例
操作按钮
使用 actions
插槽来提供表单操作按钮。
<template>
<article>
<veui-form
:data="formData"
@submit="handleSubmit"
>
<veui-field label="型号">
<veui-select
v-model="formData.model"
:options="models"
/>
</veui-field>
<veui-field label="日期">
<veui-date-picker
v-model="formData.date"
/>
</veui-field>
<template #actions>
<veui-button
ui="primary"
type="submit"
>
提交
</veui-button>
<veui-button>取消</veui-button>
</template>
</veui-form>
</article>
</template>
<script>
import { Form, Field, Button, Select, DatePicker } from 'veui'
export default {
components: {
'veui-form': Form,
'veui-field': Field,
'veui-button': Button,
'veui-select': Select,
'veui-date-picker': DatePicker
},
data () {
return {
models: [
{
label: 'APTX-4867',
value: 'aptx-4867'
},
{
label: 'APTX-4868',
value: 'aptx-4868'
},
{
label: 'APTX-4869',
value: 'aptx-4869'
},
{
label: 'APTX-4870',
value: 'aptx-4870'
}
],
formData: {
model: null,
date: null
}
}
},
methods: {
handleSubmit (data) {
this.$toast(JSON.stringify(data, null, 2))
}
}
}
</script>
只读
设置 readonly
来使内部表单项处于只读状态。
<template>
<article>
<section>
<veui-checkbox v-model="readonly">
只读
</veui-checkbox>
</section>
<veui-form
:data="formData"
:readonly="readonly"
>
<veui-field label="姓名">
<veui-input v-model="formData.name"/>
</veui-field>
</veui-form>
</article>
</template>
<script>
import { Form, Field, Input, Checkbox } from 'veui'
export default {
components: {
'veui-checkbox': Checkbox,
'veui-form': Form,
'veui-field': Field,
'veui-input': Input
},
data () {
return {
readonly: true,
formData: {
name: ''
}
}
}
}
</script>
<style lang="less" scoped>
section {
margin-bottom: 20px;
}
</style>
禁用
设置 disabled
来使内部表单项处于禁用状态。
<template>
<article>
<section>
<veui-checkbox v-model="disabled">
禁用
</veui-checkbox>
</section>
<veui-form
:data="formData"
:disabled="disabled"
>
<veui-field label="姓名">
<veui-input v-model="formData.name"/>
</veui-field>
</veui-form>
</article>
</template>
<script>
import { Form, Field, Input, Checkbox } from 'veui'
export default {
components: {
'veui-checkbox': Checkbox,
'veui-form': Form,
'veui-field': Field,
'veui-input': Input
},
data () {
return {
disabled: true,
formData: {
name: ''
}
}
}
}
</script>
<style lang="less" scoped>
section {
margin-bottom: 20px;
}
</style>
提示与辅助文本
<template>
<article>
<section>
<span class="label-text">辅助信息位置:</span>
<veui-radio-button-group
v-model="helpPosition"
ui="s"
:items="helpPositions"
/>
<veui-checkbox
v-model="labelPosition"
true-value="top"
false-value="side"
ui="s"
style="margin-left: 8px"
>
上下布局
</veui-checkbox>
</section>
<veui-form
:data="formData"
:label-position="labelPosition"
>
<veui-field
name="name"
label="姓名"
tip="你的全名"
help="至少 2 个字符"
:help-position="helpPosition"
>
<veui-input v-model="formData.name"/>
</veui-field>
<veui-field
name="address"
label="地址"
tip="居住地的详细地址"
help="精确到门牌号"
:help-position="helpPosition"
>
<veui-input v-model="formData.address"/>
</veui-field>
</veui-form>
</article>
</template>
<script>
import { Form, Field, Input, Checkbox, RadioButtonGroup } from 'veui'
export default {
components: {
'veui-form': Form,
'veui-field': Field,
'veui-input': Input,
'veui-radio-button-group': RadioButtonGroup,
'veui-checkbox': Checkbox
},
data () {
return {
formData: {
name: '',
address: ''
},
helpPositions: [
{ label: 'top', value: 'top' },
{ label: 'side', value: 'side' },
{ label: 'bottom', value: 'bottom' }
],
helpPosition: 'side',
labelPosition: 'side'
}
}
}
</script>
<style lang="less" scoped>
section {
margin-bottom: 20px;
}
.label-text {
font-size: 12px;
margin-right: 8px;
}
</style>
规则校验
<template>
<article class="veui-form-demo">
<veui-form
ref="form"
:data="data"
>
<veui-field
field="phone"
name="phone"
:rules="phoneRule"
label="手机"
>
<veui-input
v-model="data.phone"
name="phone"
autocomplete="off"
/>
</veui-field>
<template #actions>
<veui-button
ui="primary"
type="submit"
>提交</veui-button>
</template>
</veui-form>
</article>
</template>
<script>
import {
Form,
Field,
Input,
Button
} from 'veui'
export default {
name: 'demo-form',
components: {
'veui-input': Input,
'veui-button': Button,
'veui-form': Form,
'veui-field': Field
},
data () {
return {
data: {
phone: '1888888888a'
},
phoneRule: [
{
name: 'pattern',
value: /^1\d{10}$/,
message: '{value} 不是正确的手机号',
triggers: 'blur'
}
]
}
}
}
</script>
内联规则校验
<template>
<article class="veui-form-demo">
<veui-form
ref="form"
:data="data"
>
<veui-field
label="密码"
name="password"
:rules="[
{ name: 'required', triggers: 'input,blur' },
{ name: 'minLength', value: '6', triggers: 'blur' }
]"
>
<veui-input
v-model="data.password"
type="password"
/>
</veui-field>
<veui-field
label="确认密码"
name="password2"
:rules="p2Rules"
>
<veui-input
v-model="data.password2"
type="password"
/>
</veui-field>
</veui-form>
</article>
</template>
<script>
import {
Form,
Field,
Input
} from 'veui'
export default {
name: 'demo-form',
components: {
'veui-input': Input,
'veui-form': Form,
'veui-field': Field
},
data () {
return {
data: {
password: '',
password2: ''
}
}
},
computed: {
p2Rules () {
return [
{ name: 'required', triggers: 'input,blur' },
{
name: 'prefix',
value: this.data.password,
triggers: 'input',
message: '两次输入的密码不一致',
validate (val, ruleValue) {
return (ruleValue || '').indexOf(val || '') === 0
}
},
{
name: 'same',
value: this.data.password,
triggers: 'change,password:input',
message: '两次输入的密码不一致',
validate (val, ruleValue) {
return !val || (ruleValue || '') === val
}
}
]
}
}
}
</script>
异步联合校验
<template>
<article class="form-demo">
<veui-form
ref="form"
:data="data"
:validators="validators"
>
<veui-fieldset
label="预期收入"
class="salary"
tip="下限必须小于上限"
:required="true"
>
<veui-field
field="start"
name="start"
:rules="numRequiredRule"
class="start-field"
>
<veui-input
v-model="data.start"
class="input"
/>
</veui-field>
<veui-span style="margin: 0 4px">
-
</veui-span>
<veui-field
field="end"
name="end"
:rules="numRequiredRule"
>
<veui-input
v-model="data.end"
class="input"
/>
</veui-field>
<veui-span>万</veui-span>
</veui-fieldset>
<template #actions="{ validating }">
<veui-button
ui="primary"
:loading="validating"
type="submit"
>
提交
</veui-button>
</template>
</veui-form>
</article>
</template>
<script>
import {
Form,
Fieldset,
Field,
Input,
Button,
Span
} from 'veui'
export default {
name: 'demo-form',
components: {
'veui-span': Span,
'veui-input': Input,
'veui-button': Button,
'veui-form': Form,
'veui-field': Field,
'veui-fieldset': Fieldset
},
data () {
return {
data: {
start: 20000,
end: 10000
},
numRequiredRule: [
{
name: 'numeric',
value: true,
triggers: 'blur,input'
},
{
name: 'required',
value: true,
triggers: 'blur,input'
}
],
validators: [
{
fields: ['start', 'end'],
handler (start, end) {
if (start == null || end == null) {
return true
}
return new Promise(function (resolve) {
setTimeout(function () {
let res = {}
if (parseInt(start, 10) < 4000) {
res.start = {
status: 'warning',
message: '请提高下限'
}
}
if (parseInt(start, 10) >= parseInt(end, 10)) {
res.end = '上限必须大于下限'
}
resolve(Object.keys(res).length ? res : true)
}, 2000)
})
},
triggers: ['change', 'submit,input']
}
]
}
}
}
</script>
<style lang="less" scoped>
.form-demo {
.salary {
.input {
width: 80px;
}
}
}
</style>
前置、后置校验
<template>
<article class="veui-form-demo">
<veui-form
ref="form"
:data="data"
:before-validate="beforeValidate"
:after-validate="afterValidate"
>
<veui-field
field="name"
name="name"
label="姓名"
>
<veui-input v-model="data.name"/>
</veui-field>
<veui-field
field="phone"
name="phone"
label="手机"
>
<veui-input
v-model="data.phone"
name="phone"
autocomplete="off"
/>
</veui-field>
<template #actions>
<veui-button
ui="primary"
type="submit"
>提交</veui-button>
</template>
</veui-form>
</article>
</template>
<script>
import {
Form,
Field,
Input,
Button
} from 'veui'
import confirmManager from 'veui/managers/confirm'
export default {
name: 'demo-form',
components: {
'veui-input': Input,
'veui-button': Button,
'veui-form': Form,
'veui-field': Field
},
data () {
return {
data: {
name: '曹达华',
phone: '18888888888'
}
}
},
methods: {
beforeValidate () {
return new Promise((resolve) => {
confirmManager
.warn('前置校验通过吗?', '确认', {
ok: () => {}
})
.then((ok) => {
resolve(ok)
})
})
},
afterValidate () {
return new Promise((resolve) => {
confirmManager
.warn('后置校验通过吗?', '确认', {
ok: () => {}
})
.then((ok) => {
resolve(ok)
})
})
}
}
}
</script>
抽象表单项
<template>
<article>
<veui-form
:data="formData"
>
<veui-field
label="门店"
name="store"
:rules="[{
name: 'required', message: '请选择门店', triggers: 'select'
}]"
>
<veui-transfer
v-model="formData.store"
:datasource="storeList"
>
<template #selected-item-label="{ label, value }">
<div class="selected-store">
<span class="store-label">{{ label }}</span>
<veui-field
:key="`storeCounts.${value}`"
:name="`storeCounts.${value}`"
:rules="[
{ name: 'required', message: `请填写${label}的数量`, triggers: 'change,blur' }
]"
abstract
>
<veui-number-input
v-model="formData.storeCounts[value]"
class="store-number"
ui="s"
:min="1"
/>
</veui-field>
</div>
</template>
</veui-transfer>
</veui-field>
</veui-form>
</article>
</template>
<script>
import { Form, Field, NumberInput, Transfer } from 'veui'
export default {
components: {
'veui-form': Form,
'veui-field': Field,
'veui-number-input': NumberInput,
'veui-transfer': Transfer
},
data () {
return {
disabled: true,
formData: {
store: [],
storeCounts: {}
},
storeList: [
{ label: '门店1', value: '1' },
{ label: '门店2', value: '2' },
{ label: '门店3', value: '3' },
{ label: '门店4', value: '4' }
]
}
}
}
</script>
<style lang="less" scoped>
.selected-store {
display: flex;
align-items: center;
.store-label {
min-width: 60px;
}
}
</style>
整合原生输入
<template>
<article>
<veui-form
:data="formData"
>
<veui-field
label="名称"
name="name"
withhold-validity
:rules="[{ name: 'required', triggers: 'input,blur' }]"
>
<template #default="{ invalid, listeners }">
<input
v-model="formData.name"
:class="{
'demo-invalid': invalid
}"
v-on="listeners"
>
</template>
</veui-field>
</veui-form>
</article>
</template>
<script>
import { Form, Field } from 'veui'
export default {
components: {
'veui-form': Form,
'veui-field': Field
},
data () {
return {
formData: {
name: ''
}
}
}
}
</script>
<style lang="less" scoped>
.demo-invalid {
border: 1px solid #cc1800;
}
</style>
API
属性
名称 | 类型 | 默认值 | 描述 | ||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
readonly | boolean | false | 内部输入组件是否为只读状态。 | ||||||||||||||||||||||||||||||
disabled | boolean | false | 内部输入组件是否为禁用状态。 | ||||||||||||||||||||||||||||||
data | Object | - | 表单绑定的数据,和表单中的输入组件通过 | ||||||||||||||||||||||||||||||
validators | Array<Object> | - | 表单联合校验、异步校验器。项目类型为
| ||||||||||||||||||||||||||||||
before-validate | function | - | 表单进入提交流程后,进行校验之前的 hook,传入参数为 (data) ,data 为表单 data 属性值的副本。支持返回 Promise ,返回值或 Promise.resolve 的值为 true 或 undefined 表示流程继续,其它返回值表示中断流程并触发 invalid 事件。 | ||||||||||||||||||||||||||||||
after-validate | function | - | 表单校验成功后,触发 submit 事件之前的 hook,传入参数为 (data) ,与 beforeValidate 的入参是同一个引用。支持返回 Promise ,返回值或 Promise.resolve 的值为 true 或 undefined 表示流程继续,其它返回值表示中断流程并触发 invalid 事件。 |
插槽
名称 | 描述 | |||||||||
---|---|---|---|---|---|---|---|---|---|---|
default | 可直接内联
| |||||||||
actions | 表单操作内容,如“提交”、“取消”按钮等。无默认内容。插槽参数与 default 插槽相同。 |
事件
名称 | 描述 | |||||||||
---|---|---|---|---|---|---|---|---|---|---|
submit | 在原生
| |||||||||
invalid |
|
方法
名称 | 描述 |
---|---|
submit | 手动提交表单。
|
validate | 手动校验表单。
可选参数 返回值是 |
clearValidities | 手动清除表单校验信息。
可选参数 |
setValidities | 手动设置表单校验信息。
|
表单提交流程
表单校验逻辑
表单校验内部分为 Field
的 rule
校验和 validators
的校验。
Field
的rule
是单值、同步校验。详见表单项。validators
可以是多值、异步的校验。
validators: [
{
fields: ['start', 'end'],
validate (start, end) {
if (start == null || end == null) {
return true
}
if (parseInt(start, 10) >= parseInt(end, 10)) {
return {
start: '下限必须小于上限'
}
}
return true
},
triggers: ['change', 'submit,input']
},
{
fields: ['phone'],
validate (phone) {
return new Promise(function (resolve) {
setTimeout(function () {
let res
if (phone === '18888888888') {
res = {
phone: '该手机已被注册'
}
}
return resolve(res)
}, 3000)
})
},
triggers: ['input']
}
]
交互过程的校验
提交过程的校验
提交时,其中一个过程的校验失败不会导致整个校验终止,校验信息将在两个过程执行完毕后进行整合,并传递到 invalid
事件中去。