Form

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

用法

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

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

Schema 验证

它需要两个 prop:

默认情况下**不包含任何验证库**,请确保您**安装所需的库**。
<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

属性

属性默认值类型
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。支持标准 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 as T

boolean

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

attach

true

boolean

如果为 true,此表单将附着到其父表单(如果有)并同时进行验证。

loadingAuto

true

boolean

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

插槽

插槽类型
default

{ errors: FormError<string>[]; loading: boolean; }

事件

事件类型
error

[payload: FormErrorEvent]

submit

[payload: FormSubmitEvent<any>]

可访问属性

您可以使用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

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

getErrors(path?: keyof T)FormError[]

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

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

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

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: ''
        }
      }
    })
  ]
})