Form

一个内置验证和提交处理功能的表单组件。

用法

使用 Form 组件,可以通过验证库来验证表单数据,例如Valibot, Zod, Yup, Joi, Superstruct或使用你自己的验证逻辑。

它与 FormField 组件配合使用,可以自动在表单元素周围显示错误消息。

Schema 验证

它需要两个 props

默认不包含任何验证库,请确保你安装了所需的库。
<script setup lang="ts">
import * as v from 'valibot'
import type { FormSubmitEvent } from '@nuxt/ui'

const schema = v.object({
  email: v.pipe(v.string(), v.email('Invalid email')),
  password: v.pipe(v.string(), v.minLength(8, 'Must be at least 8 characters'))
})

type Schema = v.InferOutput<typeof schema>

const state = reactive({
  email: '',
  password: ''
})

const toast = useToast()
async function onSubmit(event: FormSubmitEvent<Schema>) {
  toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
  console.log(event.data)
}
</script>

<template>
  <UForm :schema="schema" :state="state" class="space-y-4" @submit="onSubmit">
    <UFormField label="Email" name="email">
      <UInput v-model="state.email" />
    </UFormField>

    <UFormField label="Password" name="password">
      <UInput v-model="state.password" type="password" />
    </UFormField>

    <UButton type="submit">
      Submit
    </UButton>
  </UForm>
</template>

错误会根据 nameerror-pattern prop 直接报告给 FormField 组件。这意味着你在 schema 中为 email 属性定义的验证规则将应用于 <FormField name="email">

嵌套的验证规则使用点符号处理。例如,像 { user: z.object({ email: z.string() }) } 这样的规则将应用于 <FormField name="user.email">

自定义验证

使用 validate prop 来应用你自己的验证逻辑。

验证函数必须返回一个错误列表,包含以下属性

  • message - 要显示的错误消息。
  • name - 要将错误发送到的 FormFieldname
它可以与 schema prop 一起使用,以处理复杂的用例。
<script setup lang="ts">
import type { FormError, FormSubmitEvent } from '@nuxt/ui'

const state = reactive({
  email: undefined,
  password: undefined
})

const validate = (state: any): FormError[] => {
  const errors = []
  if (!state.email) errors.push({ name: 'email', message: 'Required' })
  if (!state.password) errors.push({ name: 'password', message: 'Required' })
  return errors
}

const toast = useToast()
async function onSubmit(event: FormSubmitEvent<typeof state>) {
  toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
  console.log(event.data)
}
</script>

<template>
  <UForm :validate="validate" :state="state" class="space-y-4" @submit="onSubmit">
    <UFormField label="Email" name="email">
      <UInput v-model="state.email" />
    </UFormField>

    <UFormField label="Password" name="password">
      <UInput v-model="state.password" type="password" />
    </UFormField>

    <UButton type="submit">
      Submit
    </UButton>
  </UForm>
</template>

输入事件

当输入框触发 inputchangeblur 事件时,Form 组件会自动触发验证。

  • input 事件上触发的验证在你输入时发生。
  • change 事件上触发的验证在你确认值时发生。
  • blur 事件上触发的验证在输入框失去焦点时发生。

你可以使用 validate-on prop 来控制何时进行验证。

单选组
复选组
你可以使用 useFormField 可组合函数在你自己的组件中实现此功能。

错误事件

你可以监听 @error 事件来处理错误。此事件在表单提交时触发,并包含一个 FormError 对象数组,包含以下字段

  • id - 输入框的 id
  • name - FormFieldname
  • message - 要显示的错误消息。

这是一个在表单提交后将焦点设置到第一个包含错误的输入元素上的示例

<script setup lang="ts">
import type { FormError, FormErrorEvent, FormSubmitEvent } from '@nuxt/ui'

const state = reactive({
  email: undefined,
  password: undefined
})

const validate = (state: any): FormError[] => {
  const errors = []
  if (!state.email) errors.push({ name: 'email', message: 'Required' })
  if (!state.password) errors.push({ name: 'password', message: 'Required' })
  return errors
}

const toast = useToast()
async function onSubmit(event: FormSubmitEvent<typeof state>) {
  toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
  console.log(event.data)
}

async function onError(event: FormErrorEvent) {
  if (event?.errors?.[0]?.id) {
    const element = document.getElementById(event.errors[0].id)
    element?.focus()
    element?.scrollIntoView({ behavior: 'smooth', block: 'center' })
  }
}
</script>

<template>
  <UForm :validate="validate" :state="state" class="space-y-4" @submit="onSubmit" @error="onError">
    <UFormField label="Email" name="email">
      <UInput v-model="state.email" />
    </UFormField>

    <UFormField label="Password" name="password">
      <UInput v-model="state.password" type="password" />
    </UFormField>

    <UButton type="submit">
      Submit
    </UButton>
  </UForm>
</template>

嵌套表单

嵌套表单组件可以让你更有效地管理复杂的数据结构,例如列表或条件字段。

例如,它可以用于根据用户的输入动态添加字段

<script setup lang="ts">
import * as z from 'zod'
import type { FormSubmitEvent } from '@nuxt/ui'

const schema = z.object({
  name: z.string().min(2),
  news: z.boolean().default(false)
})

type Schema = z.output<typeof schema>

const nestedSchema = z.object({
  email: z.string().email()
})

type NestedSchema = z.output<typeof nestedSchema>

const state = reactive<Partial<Schema & NestedSchema>>({ })

const toast = useToast()
async function onSubmit(event: FormSubmitEvent<Schema>) {
  toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
  console.log(event.data)
}
</script>

<template>
  <UForm
    :state="state"
    :schema="schema"
    class="gap-4 flex flex-col w-60"
    @submit="onSubmit"
  >
    <UFormField label="Name" name="name">
      <UInput v-model="state.name" placeholder="John Lennon" />
    </UFormField>

    <div>
      <UCheckbox v-model="state.news" name="news" label="Register to our newsletter" @update:model-value="state.email = undefined" />
    </div>

    <UForm v-if="state.news" :state="state" :schema="nestedSchema" attach>
      <UFormField label="Email" name="email">
        <UInput v-model="state.email" placeholder="[email protected]" />
      </UFormField>
    </UForm>

    <div>
      <UButton type="submit">
        Submit
      </UButton>
    </div>
  </UForm>
</template>

或验证列表输入

<script setup lang="ts">
import * as z from 'zod'
import type { FormSubmitEvent } from '@nuxt/ui'

const schema = z.object({
  customer: z.string().min(2)
})

type Schema = z.output<typeof schema>

const itemSchema = z.object({
  description: z.string().min(1),
  price: z.number().min(0)
})

type ItemSchema = z.output<typeof itemSchema>

const state = reactive<Partial<Schema & { items: Partial<ItemSchema>[] }>>({
  items: [{}]
})

function addItem() {
  if (!state.items) {
    state.items = []
  }
  state.items.push({})
}

function removeItem() {
  if (state.items) {
    state.items.pop()
  }
}

const toast = useToast()

async function onSubmit(event: FormSubmitEvent<Schema>) {
  toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
  console.log(event.data)
}
</script>

<template>
  <UForm
    :state="state"
    :schema="schema"
    class="gap-4 flex flex-col w-60"
    @submit="onSubmit"
  >
    <UFormField label="Customer" name="customer">
      <UInput v-model="state.customer" placeholder="Wonka Industries" />
    </UFormField>

    <UForm
      v-for="item, count in state.items"
      :key="count"
      :state="item"
      :schema="itemSchema"
      attach
      class="flex gap-2"
    >
      <UFormField :label="!count ? 'Description' : undefined" name="description">
        <UInput v-model="item.description" />
      </UFormField>
      <UFormField :label="!count ? 'Price' : undefined" name="price" class="w-20">
        <UInput v-model="item.price" type="number" />
      </UFormField>
    </UForm>

    <div class="flex gap-2">
      <UButton color="neutral" variant="subtle" size="sm" @click="addItem()">
        Add Item
      </UButton>

      <UButton color="neutral" variant="ghost" size="sm" @click="removeItem()">
        Remove Item
      </UButton>
    </div>
    <div>
      <UButton type="submit">
        Submit
      </UButton>
    </div>
  </UForm>
</template>

API

Props

Prop默认值类型
state

Partial<any>

一个表示表单当前状态的对象。

id

string | number

schema

Struct<any, any> | ObjectSchema<object, AnyObject, any, ""> | AnySchema<object> | ArraySchema<object> | AlternativesSchema<object> | BinarySchema<object> | BooleanSchema<object> | DateSchema<object> | FunctionSchema<object> | NumberSchema<object> | ObjectSchema<object> | StringSchema<object> | LinkSchema<object> | SymbolSchema<object> | StandardSchemaV1<object, object>

用于验证表单状态的 Schema。支持 Standard Schema 对象、Yup、Joi 和 Superstructs。

validate

(state: Partial<any>): FormError<string>[] | Promise<FormError<string>[]>

用于验证表单状态的自定义验证函数。

validateOn

['blur', 'change', 'input']

FormInputEvents[]

触发表单验证的输入事件列表。

disabled

boolean

禁用表单内的所有输入。

validateOnInputDelay

300

number

在输入事件上验证表单前的延迟(毫秒)。

transform

true

boolean

如果为 true,schema 转换将在提交时应用于 state。

attach

true

boolean

如果为 true,此表单将附加到其父级 Form(如果存在),并在同时进行验证。

loadingAuto

true

boolean

true 时,所有表单元素将在 @submit 事件时被禁用。这将导致任何获得焦点的输入元素失去焦点状态。

Slots

Slot类型
default

{ errors: FormError<string>[]; }

Emits

Event类型
submit

FormSubmitEvent<any>

error

FormErrorEvent

Expose

你可以使用此组件的实例,通过useTemplateRef.

<script setup lang="ts">
const form = useTemplateRef('form')
</script>

<template>
  <UForm ref="form" />
</template>

这将使你能够访问以下内容

姓名类型
submit()Promise<void>

触发表单提交。

validate(opts: { name?: keyof T | (keyof T)[], silent?: boolean, nested?: boolean, transform?: boolean })Promise<T>

触发表单验证。除非 opts.silent 设置为 true,否则会抛出任何错误。

clear(path?: keyof T)void

清除与特定 path 相关的表单错误。如果未提供 path,则清除所有表单错误。

getErrors(path?: keyof T)FormError[]

检索与特定 path 相关的表单错误。如果未提供 path,则返回所有表单错误。

setErrors(errors: FormError[], name?: keyof T)void

为给定 path 设置表单错误。如果未提供 path,则覆盖所有错误。

errorsRef<FormError[]>

一个包含验证错误数组的引用。使用它可以访问或操作错误信息。

disabledRef<boolean>
dirtyRef<boolean> 如果至少有一个表单字段被用户更新过,则为 true
dirtyFieldsDeepReadonly<Set<keyof T>> 跟踪已被用户修改过的字段。
touchedFieldsDeepReadonly<Set<keyof T>> 跟踪用户与之交互过的字段。
blurredFieldsDeepReadonly<Set<keyof T>> 跟踪被用户模糊的字段。

主题

app.config.ts
export default defineAppConfig({
  ui: {
    form: {
      base: ''
    }
  }
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'

export default defineConfig({
  plugins: [
    vue(),
    ui({
      ui: {
        form: {
          base: ''
        }
      }
    })
  ]
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import uiPro from '@nuxt/ui-pro/vite'

export default defineConfig({
  plugins: [
    vue(),
    uiPro({
      ui: {
        form: {
          base: ''
        }
      }
    })
  ]
})