Form 表单
新粗野主义风格的表单系统,基于 vee-validate 构建,提供可组合的子组件用于结构化表单布局。
预览
安装
pnpm dlx brutx-vue@latest add form需要额外安装依赖:
pnpm add vee-validate @vee-validate/zod zod用法
<script setup>
import { toTypedSchema } from '@vee-validate/zod'
import { z } from 'zod'
import { Form, FormField, FormItem, FormLabel, FormControl, FormDescription, FormMessage } from 'brutx-ui-vue/form'
import { Input, Button } from 'brutx-ui-vue'
const schema = toTypedSchema(z.object({
username: z.string().min(2).max(50),
email: z.string().email(),
}))
function onSubmit(values) {
console.log('Form submitted:', values)
}
</script>
<template>
<Form :validation-schema="schema" @submit="onSubmit">
<FormField name="username" v-slot="{ componentField }">
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input v-bind="componentField" placeholder="Enter username" />
</FormControl>
<FormDescription>This is your public display name.</FormDescription>
<FormMessage />
</FormItem>
</FormField>
<FormField name="email" v-slot="{ componentField }">
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input v-bind="componentField" type="email" placeholder="you@example.com" />
</FormControl>
<FormMessage />
</FormItem>
</FormField>
<Button type="submit" variant="primary">Submit</Button>
</Form>
</template>FormWizard 多步骤表单
基于 Stepper 组件构建的向导式表单,支持步骤验证、线性导航和自定义步骤内容。
<script setup>
import { ref } from 'vue'
import { FormWizard } from 'brutx-ui-vue/form'
import type { FormStep } from 'brutx-ui-vue/form'
import { Input } from 'brutx-ui-vue'
const values = ref({})
const steps = [
{ id: 'personal', title: '个人信息' },
{ id: 'address', title: '地址' },
{ id: 'review', title: '确认' },
]
function onComplete(finalValues) {
console.log('提交:', finalValues)
}
</script>
<template>
<FormWizard
v-model="values"
:steps="steps"
@complete="onComplete"
>
<template #step-personal>
<Input v-model="values.name" placeholder="姓名" />
</template>
<template #step-address>
<Input v-model="values.address" placeholder="地址" />
</template>
<template #step-review>
<p>确认信息:{{ values }}</p>
</template>
</FormWizard>
</template>FormConditional 条件字段
使用判断函数控制字段组是否挂载。此例通过闭包读取调用方管理的响应式值,并为 Form 提供初始字段值:
<script setup>
import { ref } from 'vue'
import { Form, FormConditional, FormField } from 'brutx-ui-vue/form'
const values = ref({ type: 'company' })
const showCompany = () => values.value.type === 'company'
const showPersonal = () => values.value.type === 'personal'
</script>
<template>
<Form :initial-values="values">
<FormField name="type" />
<FormConditional :when="showCompany">
<FormField name="companyName" />
<FormField name="taxId" />
</FormConditional>
<FormConditional :when="showPersonal">
<FormField name="idNumber" />
</FormConditional>
</Form>
</template>绑定 FormControl 与输入控件
FormControl 要求默认插槽只有一个根控件。作用域插槽提供生成的 ID、描述关联、无效状态和表单统一尺寸:
<FormControl v-slot="{ id, ariaDescribedby, ariaInvalid, size }">
<Input :id="id" :size="size" :aria-describedby="ariaDescribedby" :aria-invalid="ariaInvalid" />
</FormControl>子组件
| 组件 | 说明 |
|---|---|
Form | 根表单组件,集成 vee-validate |
FormField | 字段包装器,连接表单状态,提供字段上下文 |
FormItem | 标签、控件和消息的布局容器,生成唯一 ID,并按表单布局配置排列内容 |
FormLabel | 支持错误状态的标签,注入字段上下文 |
FormControl | 输入控件的包装器,按表单布局放入控件列,并通过 slot 提供无障碍属性和尺寸 |
FormDescription | 输入框下方的辅助文本 |
FormMessage | 验证错误消息,注入字段上下文 |
FormWizard | 多步骤向导式表单 |
FormConditional | 根据表单值动态显示/隐藏字段组 |
数据类型
FormFieldContext
FormField 通过 provide/inject 向子组件提供 FormFieldContext:
interface FormFieldContext {
name: ComputedRef<string>
error: Ref<string | undefined>
value: Ref<unknown>
setValue: (value: unknown) => void
setError: (message: string | undefined) => void
}FormItemContext
FormItem 通过 provide/inject 向子组件提供 FormItemContext:
interface FormItemContext {
id: string
formItemId: string
formDescriptionId: string
formMessageId: string
}FormControl、FormLabel、FormMessage 均注入 FormFieldContext 和 FormItemContext,可直接访问字段值和错误状态。
FormStep
interface FormStep {
id: string
title: string
description?: string
icon?: Component
validator?: (values: Record<string, unknown>) => ValidationResult
optional?: boolean
}ValidationResult
interface ValidationResult {
valid: boolean
errors: Record<string, string>
}组合式函数
useFormWizard
在 FormWizard 子组件中获取向导上下文:
const {
currentStep, // Ref<number> - 当前步骤
steps, // ComputedRef<FormStep[]> - 步骤配置
values, // ComputedRef<Record<string, unknown>> - 表单数据
updateValues, // (values: Record<string, unknown>) => void - 更新表单数据
nextStep, // () => void - 下一步
previousStep, // () => void - 上一步
goToStep, // (step: number) => void - 跳转步骤
complete, // () => void - 完成表单
getStepErrors, // (step: number) => Record<string, string> | undefined - 获取指定步骤的错误
isFirstStep, // ComputedRef<boolean>
isLastStep, // ComputedRef<boolean>
canGoNext, // ComputedRef<boolean>
} = useFormWizard()程序化控制
通过 ref 访问 Form 组件实例后可调用以下方法:
<script setup>
import { ref } from 'vue'
import { Form } from 'brutx-ui-vue/form'
const formRef = ref(null)
async function handleSubmit() {
const isValid = await formRef.value?.validate()
if (isValid) {
// 提交表单
}
}
function handleReset() {
formRef.value?.resetFields()
}
</script>
<template>
<Form ref="formRef" :scroll-to-error="true">
<!-- 表单字段 -->
</Form>
</template>API 参考
Form
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
inline | boolean | undefined | false | 让表单项在可换行的横向布局中排列。 |
详情声明类型boolean | undefined 字面量
默认值详情
来源packages/ui/src/components/form/Form.vue | |||
labelPosition | "right" | "left" | "top" | undefined | "right" | left 与 right 将标签放在控件左侧并分别左对齐或右对齐;top 将标签放在控件上方。 |
详情声明类型"right" | "left" | "top" | undefined 字面量
默认值详情
来源packages/ui/src/components/form/Form.vue | |||
labelWidth | string | number | undefined | undefined | 横向标签布局的标签列宽度;数值按像素处理,字符串按 CSS 宽度处理,top 布局不使用此值。 |
详情声明类型string | number | undefined 默认值详情
来源packages/ui/src/components/form/Form.vue | |||
scrollToError | boolean | undefined | false | 调用实例 validate() 且验证失败时,平滑滚动到首个错误字段;原生提交处理不使用此选项。 |
详情声明类型boolean | undefined 字面量
默认值详情
来源packages/ui/src/components/form/Form.vue | |||
size | "default" | "lg" | "sm" | undefined | "default" | 为表单项间距、标签、说明和错误消息提供统一尺寸,并通过 FormControl 插槽向控件提供 size。 |
详情声明类型"default" | "lg" | "sm" | undefined 字面量
默认值详情
来源packages/ui/src/components/form/Form.vue | |||
class | string | undefined | undefined | 合并到原生 form 根元素的自定义 CSS 类。 |
详情声明类型string | undefined 默认值详情
来源packages/ui/src/components/form/Form.vue | |||
initialValues | Record<string, unknown> | undefined | undefined | 传给 vee-validate 的表单初始字段值;属性引用改变且表单尚未被用户修改时,使用新值重置表单。 |
详情声明类型Record<string, unknown> | undefined 默认值详情
来源packages/ui/src/components/form/Form.vue | |||
validationSchema | unknown | undefined | 传给 vee-validate 的响应式验证模式,可使用其支持的规则或转换后的 Zod schema。 |
详情声明类型unknown 默认值详情
来源packages/ui/src/components/form/Form.vue | |||
| 名称 | 签名或类型 | 说明 |
|---|---|---|
@submit | [values: Record<string, unknown>] | 表单提交且 vee-validate 验证通过后发出字段值。 |
详情声明类型[values: Record<string, unknown>] 来源packages/ui/src/components/form/Form.vue | ||
| 名称 | 签名或类型 | 说明 |
|---|---|---|
#default | {} | 表单字段、说明与提交操作,子组件可读取 Form 提供的验证和布局上下文。 |
详情声明类型{}来源packages/ui/src/components/form/Form.vue | ||
| 名称 | 签名或类型 | 说明 |
|---|---|---|
validate | () => Promise<boolean> | 验证全部字段并异步返回是否有效;失败且 scrollToError 开启时滚动到第一个错误字段。 |
详情声明类型() => Promise<boolean> 来源packages/ui/src/components/form/Form.vue | ||
validateField | (field: string) => Promise<boolean> | 按字段名称验证单个字段,并异步返回该字段是否有效。 |
详情声明类型(field: string) => Promise<boolean> 来源packages/ui/src/components/form/Form.vue | ||
resetFields | () => void | 调用 vee-validate 的 resetForm,将字段和表单状态恢复到初始状态。 |
详情声明类型() => void 来源packages/ui/src/components/form/Form.vue | ||
clearValidate | (fields?: string[] | undefined) => void | 清除指定字段的验证错误;未传参数或传空数组时清除全部错误,保留当前字段值。 |
详情声明类型(fields?: string[] | undefined) => void 来源packages/ui/src/components/form/Form.vue | ||
scrollToField | (field: string) => void | 通过表单控件的 name 查找字段并平滑滚动到其顶部;同名单选组取第一个控件,找不到时不执行滚动。 |
详情声明类型(field: string) => void 来源packages/ui/src/components/form/Form.vue | ||
FormConditional
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
when必填 | (values: Record<string, unknown>) => boolean | — | 决定条件内容是否挂载的判断函数,需要在 Form 内使用;建议使用稳定的具名函数引用。
|
详情声明类型(values: Record<string, unknown>) => boolean 默认值详情
来源packages/ui/src/components/form/FormConditional.vue | |||
class | string | undefined | undefined | 条件为真时合并到包裹内容的 div 元素上的 CSS 类。 |
详情声明类型string | undefined 默认值详情
来源packages/ui/src/components/form/FormConditional.vue | |||
| 名称 | 签名或类型 | 说明 |
|---|---|---|
#default | {} | 条件为真时挂载的字段或其他内容;条件为假时卸载整个内容区域。 |
详情声明类型{}来源packages/ui/src/components/form/FormConditional.vue | ||
FormControl
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
class | string | undefined | — | 与表单控件布局样式合并,并通过 as-child 应用到默认插槽唯一根控件的 CSS 类。 |
详情声明类型string | undefined 默认值详情
来源packages/ui/src/components/form/FormControl.vue | |||
| 名称 | 签名或类型 | 说明 |
|---|---|---|
#default | { ariaDescribedby: string | undefined; ariaInvalid: true | undefined; size: FormLayoutSize | undefined; class: string; id: string | undefined; } | 放置唯一根控件。提供与 FormItem 关联的 id、class、ariaDescribedby、ariaInvalid 和 Form 的 size,可显式绑定到控件;有效错误文本存在时描述关系包含错误消息。 |
详情声明类型{ ariaDescribedby: string | undefined; ariaInvalid: true | undefined; size: FormLayoutSize | undefined; class: string; id: string | undefined; }关联类型 FormLayoutSize 类型定义 — FormLayoutSizeexport type FormLayoutSize = 'sm' | 'default' | 'lg' 来源packages/ui/src/components/form/FormControl.vue | ||
FormDescription
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
class | string | undefined | — | 合并到字段辅助说明段落的 CSS 类;段落使用 FormItem 提供的描述 ID。 |
详情声明类型string | undefined 默认值详情
来源packages/ui/src/components/form/FormDescription.vue | |||
| 名称 | 签名或类型 | 说明 |
|---|---|---|
#default | {} | 字段的辅助说明;未提供此插槽时不渲染说明段落。 |
详情声明类型{}来源packages/ui/src/components/form/FormDescription.vue | ||
FormField
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
name必填 | string | — | 向 vee-validate 注册的字段名称,在组件生命周期内应保持稳定;更换名称需要处理旧字段值和错误残留。 |
详情 | |||
| 名称 | 签名或类型 | 说明 |
|---|---|---|
#default | { componentField: { name: string; modelValue: unknown; 'onUpdate:modelValue': (e: unknown, shouldValidate?: boolean | undefined) => void; onFocusout: (event: FocusEvent) => void; }; } | 字段内容,通常放置 FormItem。提供 componentField,可通过 v-bind 绑定到控件,传递字段名称、当前值、值更新和失焦校验事件。 |
详情声明类型{ componentField: { name: string; modelValue: unknown; 'onUpdate:modelValue': (e: unknown, shouldValidate?: boolean | undefined) => void; onFocusout: (event: FocusEvent) => void; }; }来源packages/ui/src/components/form/FormField.vue | ||
FormItem
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
class | string | undefined | — | 合并到字段布局容器的 CSS 类,容器按 Form 的标签位置、宽度和尺寸布局。 |
详情 | |||
| 名称 | 签名或类型 | 说明 |
|---|---|---|
#default | {} | 字段标签、控件、说明及错误消息,共享此 FormItem 自动生成的关联 ID。 |
详情声明类型{}来源packages/ui/src/components/form/FormItem.vue | ||
FormLabel
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
class | string | undefined | — | 合并到字段标签的 CSS 类;存在非空错误消息时自动使用错误颜色。 |
详情 | |||
| 名称 | 签名或类型 | 说明 |
|---|---|---|
#default | {} | 字段标签内容,通过 for 关联 FormItem 的控件 ID。 |
详情声明类型{}来源packages/ui/src/components/form/FormLabel.vue | ||
FormMessage
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
class | string | undefined | — | 合并到验证错误段落的 CSS 类;仅当字段错误去除空白后非空时渲染,并使用 role="alert"。 |
详情声明类型string | undefined 默认值详情
来源packages/ui/src/components/form/FormMessage.vue | |||
FormWizard
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
steps必填 | FormStep[] | — | 步骤配置列表,包含稳定 id、标题及可选 validator;id 决定 step-{id} 插槽名称。 |
详情声明类型FormStep[] 关联类型 FormStep 类型定义 — FormStepexport interface FormStep {
id: string
title: string
description?: string
icon?: Component
/** 入参为全量 values(跨步骤共享的扁平字段集合),约定只返回当前步骤相关字段的错误 */
validator?: (values: Record<string, unknown>) => ValidationResult
/** 线性导航下可跳过(不要求完成即可前进),但不豁免 validator */
optional?: boolean
}默认值详情
来源packages/ui/src/components/form/form-wizard-types.ts | |||
modelValue | Record<string, unknown> | undefined | { }按实例调用工厂函数 | 跨步骤共享的扁平字段记录,通过 v-model 控制;useFormWizard().updateValues() 会浅合并字段补丁后发出更新。 |
详情声明类型Record<string, unknown> | undefined 默认值详情
来源packages/ui/src/components/form/form-wizard-types.ts | |||
initialStep | number | undefined | 0 | 初始步骤索引,从 0 开始,创建时约束到 steps 的有效范围。 |
详情声明类型number | undefined 默认值详情
来源packages/ui/src/components/form/form-wizard-types.ts | |||
validateOnNext | boolean | undefined | true | 前进时验证当前步骤;完成时重新验证全部带 validator 的步骤,包括 optional 步骤。 |
详情声明类型boolean | undefined 字面量
默认值详情
来源packages/ui/src/components/form/form-wizard-types.ts | |||
showIndicator | boolean | undefined | true | 显示顶部步骤指示器,用户可通过指示器请求导航。 |
详情声明类型boolean | undefined 字面量
默认值详情
来源packages/ui/src/components/form/form-wizard-types.ts | |||
linear | boolean | undefined | true | 向前导航前要求目标之前的必选步骤已完成;optional 步骤可跳过,向后导航不受此限制。 |
详情声明类型boolean | undefined 字面量
默认值详情
来源packages/ui/src/components/form/form-wizard-types.ts | |||
class | string | undefined | undefined | 合并到带 role="form" 的向导根容器的 CSS 类。 |
详情声明类型string | undefined 默认值详情
来源packages/ui/src/components/form/form-wizard-types.ts | |||
| 名称 | 签名或类型 | 说明 |
|---|---|---|
@update:modelValue | [values: Record<string, unknown>] | 通过向导上下文更新字段时,发出与现有数据浅合并后的完整字段记录。 |
详情声明类型[values: Record<string, unknown>] 来源packages/ui/src/components/form/FormWizard.vue | ||
@complete | [values: Record<string, unknown>] | 完成操作通过已启用的步骤验证后发出全部表单数据。 |
详情声明类型[values: Record<string, unknown>] 来源packages/ui/src/components/form/FormWizard.vue | ||
@step-change | [step: number, previousStep: number] | 导航切换或完成校验失败后回到错误步骤时,发出目标与先前的步骤索引,均从 0 开始。 |
详情声明类型[step: number, previousStep: number] 来源packages/ui/src/components/form/FormWizard.vue | ||
@validation-error | [step: number, errors: Record<string, string>] | 步骤 validator 返回无效结果时发出步骤索引及其字段错误记录。 |
详情声明类型[step: number, errors: Record<string, string>] 来源packages/ui/src/components/form/FormWizard.vue | ||
@navigation-blocked | [targetStep: number, blockedStep: number] | 线性导航遇到未完成的必选前置步骤时,发出目标索引和阻塞步骤索引。 |
| 名称 | 签名或类型 | 说明 |
|---|---|---|
#step-{id} | { step: FormStep; index: number } | 按步骤 id 命名的动态插槽,接收步骤配置与从 0 开始的索引。全部步骤内容保持挂载,通过 v-show 切换当前面板。 |
详情声明类型{ step: FormStep; index: number }来源packages/ui/src/components/form/FormWizard.vue | ||
可访问性
表单结构
FormItem自动生成唯一 ID,确保FormLabel、FormControl、FormDescription、FormMessage之间的关联关系正确。FormLabel通过for属性关联到对应的输入控件。FormControl通过作用域 slot 提供id、aria-describedby、aria-invalid属性,需绑定到内部输入控件以保证无障碍性。
验证错误
- 验证失败时,
FormLabel自动添加text-brutal-destructive样式。 FormMessage使用role="alert"确保屏幕阅读器能及时播报错误信息。- 输入控件的
aria-invalid属性会自动设置为true。
FormWizard 导航
- 步骤指示器使用
role="tablist"和role="tab"语义。 - 支持键盘导航(Tab、Enter、Space)。
- 当
linear模式启用时,被阻止的步骤会通过aria-disabled标识。
常见问题
Q: 提交时没有触发表单验证是怎么回事?
A: 请确认已正确安装并配置了 vee-validate 和 @vee-validate/zod 依赖,并且将 validationSchema 传递给了 Form 组件。同时,每个需要验证的字段必须用 FormField 包裹,并确保 FormField 的 name 属性与 schema 中的字段名一致。
Q: FormWizard 中如何跳过某些步骤的验证?
A: 在 steps 配置中设置 optional: true 可将步骤标记为可选,可选步骤在前进时不会触发验证。如果需要更精细的控制,可以在步骤的 validator 函数中自定义验证逻辑,根据条件返回 { valid: true, errors: {} } 来跳过验证。
Q: FormConditional 的条件函数为什么不生效?
A: FormConditional 的 when 函数接收的参数是当前表单的完整值对象。请确保字段名拼写正确,且表单值已通过 v-model 或 initialValues 正确初始化。如果条件依赖的字段尚未渲染或值为 undefined,条件判断可能会返回意外结果。