Form 表单
由输入框、选择器、开关等组件构成,用于提交、校验用户输入的数据。
基础用法
基础的登录表单,包含邮箱和密码两个字段。使用 Form 包裹 FormItem,FormItem 的 prop 属性对应 model 中的字段名,rules 中定义验证规则。调用 formRef.value.validate() 可触发表单全局校验,resetFields() 可重置所有字段。
<template>
<div class="form-wrapper">
<Form :model="form" :rules="rules" ref="formRef">
<FormItem label="邮箱" prop="email">
<Input v-model="form.email" placeholder="请输入邮箱" />
</FormItem>
<FormItem label="密码" prop="password">
<Input v-model="form.password" type="password" placeholder="请输入密码" />
</FormItem>
<FormItem>
<Button type="primary" @click.prevent="handleSubmit">登 录</Button>
<Button @click.prevent="handleReset">重 置</Button>
</FormItem>
</Form>
</div>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue'
import Form from '@/components/Form/Form.vue'
import FormItem from '@/components/Form/FormItem.vue'
import Input from '@/components/Input/Input.vue'
import Button from '@/components/Button/Button.vue'
const formRef = ref()
const form = reactive({
email: '',
password: ''
})
const rules = {
email: [
{ type: 'email', required: true, message: '请输入正确的邮箱地址', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, message: '密码不能少于 6 位', trigger: 'blur' }
]
}
const handleSubmit = async () => {
try {
await formRef.value.validate()
console.log('登录成功', form)
} catch (e) {
console.log('校验失败', e)
}
}
const handleReset = () => {
formRef.value.resetFields()
}
</script>
<style scoped>
.form-wrapper {
width: 400px;
}
</style>表单校验
Form 组件基于 async-validator 实现表单校验。校验规则通过 rules 属性传入,规则对象中每个 key 对应一个 FormItem 的 prop 值。
常见的校验规则类型:
| 规则类型 | 说明 | 示例 |
|---|---|---|
required: true | 必填项 | 必须输入内容 |
type: 'email' | 邮箱格式 | 必须符合邮箱格式 |
type: 'number' | 数字类型 | 必须为纯数字 |
type: 'url' | URL 格式 | 必须符合网址格式 |
min / max | 最小/最大长度或数值 | 限制输入范围 |
pattern | 正则表达式 | 自定义格式校验 |
validator | 自定义校验函数 | 自定义复杂逻辑 |
trigger 指定触发校验的事件类型,默认为 'blur',对于下拉选择等场景应设为 'change'。
<template>
<div class="form-wrapper">
<Form :model="form" :rules="rules" ref="formRef">
<FormItem label="用户名" prop="username">
<Input v-model="form.username" placeholder="请输入用户名" />
</FormItem>
<FormItem label="邮箱" prop="email">
<Input v-model="form.email" placeholder="请输入邮箱" />
</FormItem>
<FormItem label="年龄" prop="age">
<Input v-model="form.age" placeholder="请输入年龄" />
</FormItem>
<FormItem label="手机号" prop="phone">
<Input v-model="form.phone" placeholder="请输入手机号" />
</FormItem>
<FormItem label="个人网站" prop="website">
<Input v-model="form.website" placeholder="请输入网址" />
</FormItem>
<FormItem>
<Button type="primary" @click.prevent="handleSubmit">提 交</Button>
<Button @click.prevent="handleReset">重 置</Button>
</FormItem>
</Form>
</div>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue'
import Form from '@/components/Form/Form.vue'
import FormItem from '@/components/Form/FormItem.vue'
import Input from '@/components/Input/Input.vue'
import Button from '@/components/Button/Button.vue'
const formRef = ref()
const form = reactive({
username: '',
email: '',
age: '',
phone: '',
website: ''
})
const rules = {
username: [
{ required: true, message: '用户名不能为空', trigger: 'blur' },
{ min: 2, max: 20, message: '用户名长度为 2-20 个字符', trigger: 'blur' }
],
email: [
{ required: true, message: '邮箱不能为空', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' }
],
age: [
{ required: true, message: '年龄不能为空', trigger: 'blur' },
{ type: 'number', message: '年龄必须为数字', trigger: 'blur' }
],
phone: [
{ required: true, message: '手机号不能为空', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
],
website: [
{ type: 'url', message: '请输入正确的网址格式', trigger: 'blur' }
]
}
const handleSubmit = async () => {
try {
await formRef.value.validate()
console.log('校验通过', form)
} catch (e) {
console.log('校验失败', e)
}
}
const handleReset = () => {
formRef.value.resetFields()
}
</script>
<style scoped>
.form-wrapper {
width: 440px;
}
</style>自定义校验规则
通过 validator 属性可以自定义校验逻辑,函数接收 (rule, value, callback) 三个参数。示例中实现了两次密码必须一致的校验,以及昵称不能包含特定字符的校验。调用 callback() 通过校验,调用 callback(new Error('错误信息')) 返回错误。
<template>
<div class="form-wrapper">
<Form :model="form" :rules="rules" ref="formRef">
<FormItem label="密码" prop="password">
<Input v-model="form.password" type="password" placeholder="请输入密码" />
</FormItem>
<FormItem label="确认密码" prop="confirmPassword">
<Input v-model="form.confirmPassword" type="password" placeholder="请再次输入密码" />
</FormItem>
<FormItem label="昵称" prop="nickname">
<Input v-model="form.nickname" placeholder="请输入昵称" />
</FormItem>
<FormItem>
<Button type="primary" @click.prevent="handleSubmit">注 册</Button>
<Button @click.prevent="handleReset">重 置</Button>
</FormItem>
</Form>
</div>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue'
import Form from '@/components/Form/Form.vue'
import FormItem from '@/components/Form/FormItem.vue'
import Input from '@/components/Input/Input.vue'
import Button from '@/components/Button/Button.vue'
const formRef = ref()
const form = reactive({
password: '',
confirmPassword: '',
nickname: ''
})
// 自定义校验器:密码一致性
const validateConfirm = (_rule: any, value: string, callback: (error?: Error) => void) => {
if (value === '') {
callback(new Error('请再次输入密码'))
} else if (value !== form.password) {
callback(new Error('两次输入的密码不一致'))
} else {
callback()
}
}
// 自定义校验器:昵称不能包含 admin
const validateNickname = (_rule: any, value: string, callback: (error?: Error) => void) => {
if (value && value.includes('admin')) {
callback(new Error('昵称不能包含 admin'))
} else {
callback()
}
}
const rules = {
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, message: '密码不能少于 6 位', trigger: 'blur' }
],
confirmPassword: [
{ required: true, message: '请再次输入密码', trigger: 'blur' },
{ validator: validateConfirm, trigger: 'blur' }
],
nickname: [
{ required: true, message: '请输入昵称', trigger: 'blur' },
{ min: 2, max: 12, message: '昵称长度为 2-12 个字符', trigger: 'blur' },
{ validator: validateNickname, trigger: 'blur' }
]
}
const handleSubmit = async () => {
try {
await formRef.value.validate()
console.log('注册成功', form)
} catch (e) {
console.log('校验失败', e)
}
}
const handleReset = () => {
formRef.value.resetFields()
}
</script>
<style scoped>
.form-wrapper {
width: 400px;
}
</style>Select 与 Switch 组合
Form 可以自由组合各种输入组件。Select 组件在值变更时触发校验,应将 trigger 设置为 'change'。Switch 组件同样推荐使用 'change' 作为触发时机。协议勾选使用 type: 'enum' 规则,枚举值中必须为 true 才能通过校验。
<template>
<div class="form-wrapper">
<Form :model="form" :rules="rules" ref="formRef">
<FormItem label="用户名" prop="username">
<Input v-model="form.username" placeholder="请输入用户名" />
</FormItem>
<FormItem label="部门" prop="dept">
<Select v-model="form.dept" :options="deptOptions" placeholder="请选择部门" />
</FormItem>
<FormItem label="角色" prop="role">
<Select v-model="form.role" :options="roleOptions" placeholder="请选择角色" />
</FormItem>
<FormItem label="手机号" prop="phone">
<Input v-model="form.phone" placeholder="请输入手机号" />
</FormItem>
<FormItem label="开启通知" prop="notify">
<Switch v-model="form.notify" />
</FormItem>
<FormItem label=" " prop="agreement">
<label class="agreement-label">
<input type="checkbox" v-model="form.agreement" />
<span>我已阅读并同意相关协议</span>
</label>
</FormItem>
<FormItem>
<Button type="primary" @click.prevent="handleSubmit">提 交</Button>
<Button @click.prevent="handleReset">重 置</Button>
</FormItem>
</Form>
</div>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue'
import Form from '@/components/Form/Form.vue'
import FormItem from '@/components/Form/FormItem.vue'
import Input from '@/components/Input/Input.vue'
import Select from '@/components/Select/Select.vue'
import Switch from '@/components/Switch/Switch.vue'
import Button from '@/components/Button/Button.vue'
const formRef = ref()
const form = reactive({
username: '',
dept: '',
role: '',
phone: '',
notify: false,
agreement: false
})
const deptOptions = [
{ label: '技术部', value: 'tech' },
{ label: '产品部', value: 'product' },
{ label: '设计部', value: 'design' },
{ label: '市场部', value: 'marketing' },
{ label: '运营部', value: 'operation' }
]
const roleOptions = [
{ label: '管理员', value: 'admin' },
{ label: '普通成员', value: 'member' },
{ label: '访客', value: 'guest' }
]
const rules = {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' }
],
dept: [
{ required: true, message: '请选择部门', trigger: 'change' }
],
role: [
{ required: true, message: '请选择角色', trigger: 'change' }
],
phone: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
],
agreement: [
{ type: 'enum', enum: [true], message: '请勾选同意协议', trigger: 'change' }
]
}
const handleSubmit = async () => {
try {
await formRef.value.validate()
console.log('提交成功', form)
} catch (e) {
console.log('校验失败', e)
}
}
const handleReset = () => {
formRef.value.resetFields()
}
</script>
<style scoped>
.form-wrapper {
width: 420px;
}
.agreement-label {
display: flex;
align-items: center;
gap: 6px;
cursor: pointer;
font-size: 14px;
color: #606266;
}
.agreement-label input {
cursor: pointer;
}
</style>复杂表单
综合示例,包含文本输入、下拉选择、文本域、开关等组件,同时演示了 resetFields(重置)和 clearValidate(清除校验状态)两个方法的效果对比。resetFields 会将表单值恢复为初始状态,clearValidate 仅清除校验提示而不改变值。
<template>
<div class="form-wrapper">
<Form :model="form" :rules="rules" ref="formRef">
<FormItem label="用户名" prop="username">
<Input v-model="form.username" placeholder="请输入用户名" />
</FormItem>
<FormItem label="邮箱" prop="email">
<Input v-model="form.email" placeholder="请输入邮箱" />
</FormItem>
<FormItem label="部门" prop="dept">
<Select v-model="form.dept" :options="deptOptions" placeholder="请选择部门" />
</FormItem>
<FormItem label="职位" prop="role">
<Input v-model="form.role" placeholder="请输入职位" />
</FormItem>
<FormItem label="手机号" prop="phone">
<Input v-model="form.phone" placeholder="请输入手机号" />
</FormItem>
<FormItem label="个人网站" prop="website">
<Input v-model="form.website" placeholder="请输入网址(选填)" />
</FormItem>
<FormItem label="简介" prop="bio">
<Input v-model="form.bio" type="textarea" placeholder="请输入个人简介" />
</FormItem>
<FormItem label="开启通知" prop="notify">
<Switch v-model="form.notify" />
</FormItem>
<FormItem>
<Button type="primary" @click.prevent="handleSubmit">提 交</Button>
<Button @click.prevent="handleReset">重 置</Button>
<Button type="info" @click.prevent="handleClear">清除校验</Button>
</FormItem>
</Form>
</div>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue'
import Form from '@/components/Form/Form.vue'
import FormItem from '@/components/Form/FormItem.vue'
import Input from '@/components/Input/Input.vue'
import Select from '@/components/Select/Select.vue'
import Switch from '@/components/Switch/Switch.vue'
import Button from '@/components/Button/Button.vue'
const formRef = ref()
const form = reactive({
username: '',
email: '',
dept: '',
role: '',
phone: '',
website: '',
bio: '',
notify: false
})
const deptOptions = [
{ label: '技术部', value: 'tech' },
{ label: '产品部', value: 'product' },
{ label: '设计部', value: 'design' },
{ label: '市场部', value: 'marketing' },
{ label: '运营部', value: 'operation' }
]
const rules = {
username: [
{ required: true, message: '用户名不能为空', trigger: 'blur' },
{ min: 2, max: 20, message: '用户名长度为 2-20 个字符', trigger: 'blur' }
],
email: [
{ required: true, message: '邮箱不能为空', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' }
],
dept: [
{ required: true, message: '请选择部门', trigger: 'change' }
],
role: [
{ required: true, message: '职位不能为空', trigger: 'blur' }
],
phone: [
{ required: true, message: '手机号不能为空', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
],
website: [
{ type: 'url', message: '请输入正确的网址格式', trigger: 'blur' }
],
bio: [
{ max: 200, message: '简介不能超过 200 个字符', trigger: 'blur' }
]
}
const handleSubmit = async () => {
try {
await formRef.value.validate()
console.log('提交成功', form)
} catch (e) {
console.log('校验失败', e)
}
}
const handleReset = () => {
formRef.value.resetFields()
}
const handleClear = () => {
formRef.value.clearValidate()
}
</script>
<style scoped>
.form-wrapper {
width: 440px;
}
</style>API
Form Attributes
| Name | Description | Type | Default |
|---|---|---|---|
| model | 表单数据对象,必填,由外部传入,Form 本身不管理数据状态 | object | — |
| rules | 校验规则对象,必填,每个 key 对应 FormItem 的 prop 值 | object | — |
FormItem Attributes
| Name | Description | Type | Default |
|---|---|---|---|
| label | 表单项的标签文字,显示在输入框左侧 | string | — |
| prop | 对应 model 中的字段名,用于取值的关联和校验的匹配,必填 | string | — |
Form Events
| Name | Description | Type |
|---|---|---|
| — | Form 组件当前版本暂未暴露事件 | — |
Form Methods
通过 ref 获取 Form 实例后可以调用以下方法:
| Name | Description | Type |
|---|---|---|
| validate | 对整个表单进行校验,返回 Promise,校验成功 resolve,失败则 reject | function |
| resetFields | 重置所有字段为初始值(调用各 FormItem 的初始值) | function |
| clearValidate | 清除所有字段的校验状态(不改变字段值) | function |
FormItem Slots
| Name | Description | Scope |
|---|---|---|
| label | 自定义标签内容 | |
| default | 自定义输入控件内容,作用域参数提供 validate 函数,可手动调用触发校验 |
校验规则 FormItemRule
基础规则继承自 async-validator,常用属性如下:
| Name | Description | Type |
|---|---|---|
| required | 是否为必填项 | boolean |
| type | 数据类型,可选 string、number、email、boolean、enum、url 等 | string |
| message | 校验失败时的错误提示文字 | string |
| trigger | 触发校验的事件类型,可选 'blur' 或 'change' | string |
| min | 字符串最小长度或数值最小值 | number |
| max | 字符串最大长度或数值最大值 | number |
| pattern | 正则表达式 | RegExp |
| enum | 枚举值数组,配合 type: 'enum' 使用 | any[] |
| validator | 自定义校验函数,签名为 (rule, value, callback) => void | function |
校验规则 trigger 说明
trigger 用于指定在什么事件下触发当前规则的校验:
| 值 | 说明 |
|---|---|
| blur | 输入框失去焦点时触发,适用于大多数文本输入字段 |
| change | 值发生变化时触发,适用于 Select、Switch 等组件 |
| '' | 空字符串时,所有事件均触发校验(不推荐) |
同一条字段可以配置多条规则,每条规则可设置不同的 trigger,如同时设置 { required: true, trigger: 'blur' } 和 { type: 'email', trigger: 'change' } 可实现失去焦点检查必填、输入时检查格式的效果。