Nuxt UI v3-alpha 已发布!

试用一下
组件

表单

收集和验证表单数据。

使用

使用 Form 组件通过 schema 库(如 YupZodJoiValibotSuperstruct 或你自己的验证逻辑)来验证表单数据。

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

表单组件需要两个 props

  • state - 一个包含表单状态的响应式对象。
  • schema - 来自验证库(如 YupZodJoiValibotSuperstruct)的 schema 对象。
请注意,默认情况下 不包含任何验证库,因此请确保 安装所需的库
<script setup lang="ts">
import { object, string, type InferType } from 'yup'
import type { FormSubmitEvent } from '#ui/types'

const schema = object({
  email: string().email('Invalid email').required('Required'),
  password: string()
    .min(8, 'Must be at least 8 characters')
    .required('Required')
})

type Schema = InferType<typeof schema>

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

async function onSubmit(event: FormSubmitEvent<Schema>) {
  // Do something with event.data
  console.log(event.data)
}
</script>

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

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

    <UButton type="submit">
      Submit
    </UButton>
  </UForm>
</template>
<script setup lang="ts">
import { z } from 'zod'
import type { FormSubmitEvent } from '#ui/types'

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

type Schema = z.output<typeof schema>

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

async function onSubmit(event: FormSubmitEvent<Schema>) {
  // Do something with data
  console.log(event.data)
}
</script>

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

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

    <UButton type="submit">
      Submit
    </UButton>
  </UForm>
</template>
<script setup lang="ts">
import Joi from 'joi'
import type { FormSubmitEvent } from '#ui/types'

const schema = Joi.object({
  email: Joi.string().required(),
  password: Joi.string()
    .min(8)
    .required()
})

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

async function onSubmit(event: FormSubmitEvent<any>) {
  // Do something with event.data
  console.log(event.data)
}
</script>

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

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

    <UButton type="submit">
      Submit
    </UButton>
  </UForm>
</template>
<script setup lang="ts">
import * as v from 'valibot'
import type { FormSubmitEvent } from '#ui/types'

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

async function onSubmit(event: FormSubmitEvent<Schema>) {
  // Do something with event.data
  console.log(event.data)
}
</script>

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

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

    <UButton type="submit">
      Submit
    </UButton>
  </UForm>
</template>
<script setup lang="ts">
import { object, string, nonempty, type Infer } from 'superstruct'
import type { FormSubmitEvent } from '#ui/types'

const schema = object({
  email: nonempty(string()),
  password: nonempty(string())
})

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

type Schema = Infer<typeof schema>

async function onSubmit(event: FormSubmitEvent<Schema>) {
  console.log(event.data)
}
</script>

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

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

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

自定义验证

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

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

  • message - 要显示的错误消息。
  • path - 与 name 属性相对应的表单元素的路径。
请注意,它可以与 schema prop 一起使用,以处理复杂的用例。
<script setup lang="ts">
import type { FormError, FormSubmitEvent } from '#ui/types'

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

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

async function onSubmit(event: FormSubmitEvent<any>) {
  // Do something with data
  console.log(event.data)
}
</script>

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

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

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

这也可以用于与其他验证库集成。以下是用 Vuelidate 的示例

<script setup lang="ts">
import useVuelidate from '@vuelidate/core'

const props = defineProps({
  rules: { type: Object, required: true },
  model: { type: Object, required: true }
})

const form = ref();
const v = useVuelidate(props.rules, props.model)

async function validateWithVuelidate() {
  v.value.$touch()
  await v.value.$validate()
  return v.value.$errors.map((error) => ({
    message: error.$message,
    path: error.$propertyPath,
  }))
}

defineExpose({
  validate: async () => {
    await form.value.validate()
  }
})
</script>

<template>
  <UForm ref="form" :state="model" :validate="validateWithVuelidate">
    <slot />
  </UForm>
</template>

后端验证

如果需要,你可以在表单提交后手动设置错误。为此,只需使用 form.setErrors 函数设置所需的错误即可。

<script setup lang="ts">
import type { Form, FormSubmitEvent } from '#ui/types'

interface Schema {
  email?: string
  password?: string
}

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

const form = ref<Form<Schema>>()

async function onSubmit (event: FormSubmitEvent<Schema>) {
  form.value!.clear()
  try {
    const response = await $fetch('...')
    // ...
  } catch (err) {
    if (err.statusCode === 422) {
      form.value!.setErrors(err.data.errors.map((err) => ({
        // Map validation errors to { path: string, message: string }
        message: err.message,
        path: err.path,
      })))
    }
  }
}
</script>

<template>
  <UForm ref="form" :state="state" @submit="onSubmit">
    <UFormGroup label="Email" name="email">
      <UInput v-model="state.email" />
    </UFormGroup>

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

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

输入事件

Form 组件在 submitinputblurchange 事件发生时自动触发验证。

这确保了只要用户与表单元素交互,就会显示任何错误。你可以使用 validate-on prop 控制验证发生的时机。

请注意,input 事件直到初始 blur 事件发生后才会触发。这是为了防止在用户键入时表单被验证。可以通过在 eager-validation prop 上将 FormGroup 设置为 true 来覆盖此行为。
看看这个组件!

错误事件

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

  • id - 表单元素的标识符。
  • path - 与 name 相匹配的表单元素的路径。
  • message - 要显示的错误消息。

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

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

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

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

async function onSubmit(event: FormSubmitEvent<any>) {
  // Do something with data
  console.log(event.data)
}

async function onError(event: FormErrorEvent) {
  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">
    <UFormGroup label="Email" name="email">
      <UInput v-model="state.email" />
    </UFormGroup>

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

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

Props

state必需
Record<string, any>
schema
ZodType<any, ZodTypeDef, any> | ObjectSchema<any, AnyObject, any, ""> | AnySchema<any> | BaseSchema<any, any> | ... 7 more ... | Struct<...>
undefined
validate
((state: any) => Promise<FormError<string>[]>) | ((state: any) => FormError<string>[])
[]
validateOn
FormEventType[]
["blur", "input", "change", "submit"]

API

通过模板 ref 访问组件时,可以使用以下方法

submit ()
Promise<void>

触发表单提交。

validate (path?: string | string[], opts: { silent?: boolean })
Promise<T>

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

clear (path?: string)

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

getErrors (path?: string)
FormError[]

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

setErrors (errors: FormError[], path?: string)

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

errors
Ref<FormError[]>

对包含验证错误的数组的引用。使用它来访问或操作错误信息。