Skip to content

Form 表单

由输入框、选择器、开关等组件构成,用于提交、校验用户输入的数据。

基础用法

基础的登录表单,包含邮箱和密码两个字段。使用 Form 包裹 FormItemFormItemprop 属性对应 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 对应一个 FormItemprop 值。

常见的校验规则类型:

规则类型说明示例
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

NameDescriptionTypeDefault
model表单数据对象,必填,由外部传入,Form 本身不管理数据状态object
rules校验规则对象,必填,每个 key 对应 FormItem 的 prop 值object

FormItem Attributes

NameDescriptionTypeDefault
label表单项的标签文字,显示在输入框左侧string
prop对应 model 中的字段名,用于取值的关联和校验的匹配,必填string

Form Events

NameDescriptionType
Form 组件当前版本暂未暴露事件

Form Methods

通过 ref 获取 Form 实例后可以调用以下方法:

NameDescriptionType
validate对整个表单进行校验,返回 Promise,校验成功 resolve,失败则 rejectfunction
resetFields重置所有字段为初始值(调用各 FormItem 的初始值)function
clearValidate清除所有字段的校验状态(不改变字段值)function

FormItem Slots

NameDescriptionScope
label自定义标签内容
default自定义输入控件内容,作用域参数提供 validate 函数,可手动调用触发校验

校验规则 FormItemRule

基础规则继承自 async-validator,常用属性如下:

NameDescriptionType
required是否为必填项boolean
type数据类型,可选 stringnumberemailbooleanenumurlstring
message校验失败时的错误提示文字string
trigger触发校验的事件类型,可选 'blur''change'string
min字符串最小长度或数值最小值number
max字符串最大长度或数值最大值number
pattern正则表达式RegExp
enum枚举值数组,配合 type: 'enum' 使用any[]
validator自定义校验函数,签名为 (rule, value, callback) => voidfunction

校验规则 trigger 说明

trigger 用于指定在什么事件下触发当前规则的校验:

说明
blur输入框失去焦点时触发,适用于大多数文本输入字段
change值发生变化时触发,适用于 Select、Switch 等组件
''空字符串时,所有事件均触发校验(不推荐)

同一条字段可以配置多条规则,每条规则可设置不同的 trigger,如同时设置 { required: true, trigger: 'blur' }{ type: 'email', trigger: 'change' } 可实现失去焦点检查必填、输入时检查格式的效果。