AutoForm 的插槽系统分为三个层级:
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>
对于数组类型的字段,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 函数支持多种重载形式:
| Signature | Return Type | Description |
|---|---|---|
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'
结合多种插槽类型,构建一个完整的自定义表单:
<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>