插槽概览

View source
AutoForm 提供强大的插槽系统,允许你自定义表单的各个部分,从整体布局到单个字段的细节。

概述

AutoForm 的插槽系统分为三个层级:

  • 表单插槽 - 自定义表单整体结构(header、footer、submit)
  • 字段插槽 - 微调每个字段的各个部分(label、hint、error 等)
  • 内容插槽 - 完全接管对象和数组字段的渲染(仅限对象/数组)
插槽支持通用模式特定字段模式,让你既能统一样式,又能精确控制特定字段。

setValue 函数详解

setValue 是插槽中最重要的函数,用于更新字段值。它支持多种调用方式,能智能处理不同的数据结构。

基本用法

// 直接设置当前字段的值
setValue(newValue)

对象字段

对于对象类型的字段,setValue 支持相对路径:

<template #field-content:profile="{ value, setValue }">
  <!-- 方式 1: 设置整个对象 -->
  <UButton @click="setValue({ name: '张三', email: 'test@example.com' })">
    填充默认值
  </UButton>

  <!-- 方式 2: 使用相对路径设置子字段 -->
  <UInput
    :model-value="value?.name"
    @update:model-value="setValue('name', $event)"
  />

  <UInput
    :model-value="value?.email"
    @update:model-value="setValue('email', $event)"
  />
</template>
使用相对路径时,TypeScript 会自动推断可用的键名,提供完整的类型提示。

数组字段

对于数组类型的字段,setValue 支持索引路径:

<template #field-content:tasks="{ value, setValue }">
  <div v-for="(task, index) in value" :key="index">
    <!-- 设置数组元素的属性 -->
    <UInput
      :model-value="task?.title"
      @update:model-value="setValue(`[${index}].title`, $event)"
    />

    <!-- 删除数组元素 -->
    <UButton
      @click="setValue(value.filter((_, i) => i !== index))"
      icon="i-lucide-trash-2"
    />
  </div>

  <!-- 添加新元素 -->
  <UButton
    @click="setValue([...value, { title: '', completed: false }])"
    icon="i-lucide-plus"
  />
</template>

类型签名

setValue 函数支持多种重载形式:

SignatureReturn TypeDescription
setValue(value: T)void设置整个字段值
setValue(relativePath: K, value: any)void设置子字段(对象)或元素属性(数组),K 会根据字段类型自动推导
setValue(relativePath: string, value: any)void字符串路径回退 - 支持任意路径字符串

命名规则总结

插槽命名模式

// 表单级插槽
'header' | 'footer' | 'submit'

// 字段级插槽 - 通用
'field-label' | 'field-hint' | 'field-description' |
'field-help' | 'field-error' | 'field-default'

// 字段级插槽 - 特定字段
'field-label:username' | 'field-hint:email' |
'field-error:password' | 'field-default:bio'

// 内容插槽 - 嵌套对象/数组
'field-content:profile' | 'field-before:tasks' | 'field-after:settings'

综合示例

结合多种插槽类型,构建一个完整的自定义表单:

创建您的账户
完成以下步骤开始使用我们的服务
完成进度0%
密码强度建议
  • 至少 8 个字符
  • 包含大小写字母
  • 包含数字
您已添加 0 个标签
提交表单即表示您同意我们的服务条款和隐私政策
<script lang="ts" setup>
import type { FormSubmitEvent } from '@nuxt/ui'
import type { z } from 'zod/v4'

const toast = useToast()
const { afz } = useAutoForm()

const schema = afz.object({
  username: afz.string({ type: 'withClear' }).min(3).meta({ label: '用户名' }),
  password: afz.string({ type: 'withPasswordToggle' }).min(8).meta({ label: '密码' }),
  age: afz.number().min(18).meta({ label: '年龄' }),
  subscribe: afz.boolean({ controlProps: { label: '订阅我们的新闻通讯' } }).meta({ label: '订阅' }),
  plan: afz.enum(['free', 'pro', 'enterprise']).meta({ label: '套餐' }),
  profile: afz.object({
    bio: afz.string({ type: 'textarea' }).meta({ label: '简介' }),
    website: afz.url().meta({ label: '网站' })
  }).meta({ label: '个人资料', collapsible: { defaultOpen: true } }),
  tags: afz.array(afz.string(), { type: 'inputTags' }).default(['标签一']).meta({ label: '标签' })
})

type Schema = z.output<typeof schema>

const form = ref<Partial<Schema>>({})

async function onSubmit(event: FormSubmitEvent<Schema>) {
  toast.add({
    title: '提交成功',
    color: 'success',
    description: JSON.stringify(event.data, null, 2)
  })
}

const completedFields = computed(() => {
  return Object.entries(form.value).filter(([, value]) => {
    if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
      return Object.values(value).some(v => v !== undefined && v !== null && v !== '')
    }
    return value !== undefined && value !== null && value !== ''
  }).length
})

const totalFields = Object.keys(schema.shape).length
const progress = computed(() => Math.round((completedFields.value / totalFields) * 100))
</script>

<template>
  <UCard>
    <MAutoForm
      :schema="schema"
      :state="form"
      class="w-lg"
      @submit="onSubmit"
    >
      <template #header="{ loading }">
        <div class="mb-6 space-y-4">
          <UAlert
            color="primary"
            variant="subtle"
            icon="i-lucide-rocket"
            title="创建您的账户"
            description="完成以下步骤开始使用我们的服务"
          >
            <template v-if="loading" #actions>
              <UBadge color="primary" variant="subtle">
                处理中...
              </UBadge>
            </template>
          </UAlert>

          <UCard>
            <div class="flex items-center justify-between text-sm mb-2">
              <span class="text-gray-600 dark:text-gray-400">完成进度</span>
              <UBadge color="primary" variant="subtle">
                {{ progress }}%
              </UBadge>
            </div>
            <UProgress :value="progress" color="primary" />
          </UCard>
        </div>
      </template>

      <template #field-label:username="{ label }">
        <UBadge
          icon="i-lucide-user"
          :label="label"
          color="primary"
          variant="subtle"
          size="xs"
        />
        <UBadge
          color="error"
          variant="subtle"
          size="xs"
          class="ml-2"
        >
          必填
        </UBadge>
      </template>

      <template #field-label:password="{ label }">
        <UBadge
          icon="i-lucide-lock"
          :label="label"
          color="success"
          variant="subtle"
          size="xs"
        />
      </template>

      <template #field-help:password>
        <UAlert
          color="warning"
          variant="subtle"
          icon="i-lucide-shield-check"
          title="密码强度建议"
          class="my-2"
        >
          <template #actions>
            <ul class="space-y-1 text-xs list-disc list-inside">
              <li>至少 8 个字符</li>
              <li>包含大小写字母</li>
              <li>包含数字</li>
            </ul>
          </template>
        </UAlert>
      </template>

      <template #field-before:profile>
        <USeparator icon="i-lucide-circle-user" />
      </template>

      <template #field-hint:tags="{ value }">
        <UBadge icon="i-lucide-tags" color="info" variant="subtle">
          您已添加 {{ value?.length || 0 }} 个标签
        </UBadge>
      </template>

      <template #footer="{ errors }">
        <div class="mt-6 space-y-4">
          <UAlert
            v-if="errors.length > 0"
            color="error"
            variant="subtle"
            icon="i-lucide-circle-alert"
            :title="`发现 ${errors.length} 个错误`"
            description="请修正以下错误后重新提交"
          >
            <template #actions>
              <ul class="space-y-1 text-sm list-disc list-inside">
                <li v-for="(error, index) in errors" :key="index">
                  {{ error.message }}
                </li>
              </ul>
            </template>
          </UAlert>

          <UAlert
            color="neutral"
            variant="subtle"
            icon="i-lucide-info"
            description="提交表单即表示您同意我们的服务条款和隐私政策"
          />
        </div>
      </template>
    </MAutoForm>
  </UCard>
</template>
Copyright © 2025 - 2025 YiXuan - MIT License