🌐 Nodejs.cn

VeeValidate

使用 VeeValidate 和 Zod 在 Vue 中构建表单。

在本指南中,我们将研究如何使用 VeeValidate 构建表单。我们将介绍如何使用 <Field /> 组件构建表单、使用 Zod 添加模式验证、错误处理、可访问性等内容。

🌐 In this guide, we will take a look at building forms with VeeValidate. We'll cover building forms with the <Field /> component, adding schema validation using Zod, error handling, accessibility, and more.

::vue-school-link{class="mt-6" lesson="forms-and-form-validation-with-shadcn-vue" placement="top"} 观看一段关于使用 shadcn-vue 构建表单和验证的 Vue School 视频。::

演示

🌐 Demo

我们将要创建以下表单。它有一个简单的文本输入框和一个多行文本框。提交时,我们将验证表单数据并显示任何错误。

🌐 We are going to build the following form. It has a simple text input and a textarea. On submit, we'll validate the form data and display any errors.

Bug Report

Help us improve by reporting bugs you encounter.

0/100 characters

Include steps to reproduce, expected behavior, and what actually happened.

<script setup lang="ts">
import { toTypedSchema } from '@vee-validate/zod'
import { useForm, Field as VeeField } from 'vee-validate'
import { toast } from 'vue-sonner'
import { z } from 'zod'

import { Button } from '@/components/ui/button'
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from '@/components/ui/card'
import {
  Field,
  FieldDescription,
  FieldError,
  FieldGroup,
  FieldLabel,
} from '@/components/ui/field'
import { Input } from '@/components/ui/input'
import {
  InputGroup,
  InputGroupAddon,
  InputGroupText,
  InputGroupTextarea,
} from '@/components/ui/input-group'

const formSchema = toTypedSchema(
  z.object({
    title: z
      .string()
      .min(5, 'Bug title must be at least 5 characters.')
      .max(32, 'Bug title must be at most 32 characters.'),
    description: z
      .string()
      .min(20, 'Description must be at least 20 characters.')
      .max(100, 'Description must be at most 100 characters.'),
  }),
)

const { handleSubmit, resetForm } = useForm({
  validationSchema: formSchema,
  initialValues: {
    title: '',
    description: '',
  },
})

const onSubmit = handleSubmit((data) => {
  toast('You submitted the following values:', {
    description: h('pre', { class: 'bg-code text-code-foreground mt-2 w-[320px] overflow-x-auto rounded-md p-4' }, h('code', JSON.stringify(data, null, 2))),
    position: 'bottom-right',
    class: 'flex flex-col gap-2',
    style: {
      '--border-radius': 'calc(var(--radius)  + 4px)',
    },
  })
})
</script>

<template>
  <Card class="w-full sm:max-w-md">
    <CardHeader>
      <CardTitle>Bug Report</CardTitle>
      <CardDescription>
        Help us improve by reporting bugs you encounter.
      </CardDescription>
    </CardHeader>
    <CardContent>
      <form id="form-vee-demo" @submit="onSubmit">
        <FieldGroup>
          <VeeField v-slot="{ field, errors }" name="title">
            <Field :data-invalid="!!errors.length">
              <FieldLabel for="form-vee-demo-title">
                Bug Title
              </FieldLabel>
              <Input
                id="form-vee-demo-title"
                v-bind="field"
                placeholder="Login button not working on mobile"
                autocomplete="off"
                :aria-invalid="!!errors.length"
              />
              <FieldError v-if="errors.length" :errors="errors" />
            </Field>
          </VeeField>

          <VeeField v-slot="{ field, errors }" name="description">
            <Field :data-invalid="!!errors.length">
              <FieldLabel for="form-vee-demo-description">
                Description
              </FieldLabel>
              <InputGroup>
                <InputGroupTextarea
                  id="form-vee-demo-description"
                  v-bind="field"
                  placeholder="I'm having an issue with the login button on mobile."
                  :rows="6"
                  class="min-h-24 resize-none"
                  :aria-invalid="!!errors.length"
                />
                <InputGroupAddon align="block-end">
                  <InputGroupText class="tabular-nums">
                    {{ field.value?.length || 0 }}/100 characters
                  </InputGroupText>
                </InputGroupAddon>
              </InputGroup>
              <FieldDescription>
                Include steps to reproduce, expected behavior, and what actually
                happened.
              </FieldDescription>
              <FieldError v-if="errors.length" :errors="errors" />
            </Field>
          </VeeField>
        </FieldGroup>
      </form>
    </CardContent>
    <CardFooter>
      <Field orientation="horizontal">
        <Button type="button" variant="outline" @click="resetForm">
          Reset
        </Button>
        <Button type="submit" form="form-vee-demo">
          Submit
        </Button>
      </Field>
    </CardFooter>
  </Card>
</template>

方法

🌐 Approach

这个表单利用 VeeValidate 来实现高性能、灵活的表单处理。我们将使用 <Field /> 组件来构建表单,这使你可以完全自由地控制标记和样式

🌐 This form leverages VeeValidate for performant, flexible form handling. We'll build our form using the <Field /> component, which gives you complete flexibility over the markup and styling.

  • 使用 VeeValidate 的 useForm 组合式函数进行表单状态管理。
  • VeeValidate 的 <Field /> 组件,带有用于受控输入和验证的作用域插槽。
  • shadcn-vue <Field /> 组件用于构建无障碍表单。
  • 使用 Zod 和 toTypedSchema 的客户端验证。

剖析

🌐 Anatomy

这是一个使用 VeeValidate 的 <Field /> 组件与作用域插槽以及 shadcn-vue <Field /> 组件的表单基本示例。

🌐 Here's a basic example of a form using VeeValidate's <Field /> component with scoped slots and shadcn-vue <Field /> components.

<template>
  <VeeField v-slot="{ field, errors }" name="title">
    <Field :data-invalid="!!errors.length">
      <FieldLabel for="title">
        Bug Title
      </FieldLabel>
      <Input
        id="title"
        v-bind="field"
        placeholder="Login button not working on mobile"
        autocomplete="off"
        :aria-invalid="!!errors.length"
      />
      <FieldDescription>
        Provide a concise title for your bug report.
      </FieldDescription>
      <FieldError v-if="errors.length" :errors="errors" />
    </Field>
  </VeeField>
</template>

表单

🌐 Form

创建表单模式

🌐 Create a form schema

我们将首先使用 Zod 模式来定义我们表单的形状

🌐 We'll start by defining the shape of our form using a Zod schema