通用对象类型通常与 selectMenu 结合使用,用于选择复杂数据结构:
<script lang="ts" setup>
import type { FormSubmitEvent } from '@nuxt/ui'
import type { z } from 'zod/v4'
const { afz } = useAutoForm()
const toast = useToast()
const { data: users } = await useFetch('https://jsonplaceholder.typicode.com/users', {
key: 'typicode-users-basic',
transform: (data: { id: number, name: string }[]) => {
return data?.map(user => ({
label: user.name,
value: String(user.id),
avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` }
}))
}
})
const schema = afz.object({
name: afz.string().min(1, '请输入姓名').default('张三'),
age: afz.number().min(1).max(150).default(30),
email: afz.email('请输入有效的邮箱地址').default('zhangsan@example.com'),
user: afz.object({
label: afz.string(),
value: afz.string(),
avatar: afz.object({ src: afz.url() })
}, {
type: 'selectMenu',
controlProps: ({ value }: { value: { avatar: { src: string } } }) => ({
placeholder: '请选择用户',
items: users.value,
avatar: value?.avatar
})
})
})
async function onSubmit(event: FormSubmitEvent<z.output<typeof schema>>) {
toast.add({
title: '提交成功',
color: 'success',
description: JSON.stringify(event.data, null, 2)
})
}
</script>
<template>
<UCard>
<MAutoForm :schema="schema" @submit="onSubmit" />
</UCard>
</template>
嵌套对象结构,支持折叠展开:
<script lang="ts" setup>
import type { FormSubmitEvent } from '@nuxt/ui'
import type { z } from 'zod/v4'
const { afz } = useAutoForm()
const toast = useToast()
const schema = afz.object({
user: afz.object({
name: afz.string(),
email: afz.email()
}).meta({ label: '用户信息', collapsible: { defaultOpen: true } }),
address: afz.object({
street: afz.string(),
city: afz.string(),
zipCode: afz.string()
}).optional().meta({ label: '地址信息' })
})
async function onSubmit(event: FormSubmitEvent<z.output<typeof schema>>) {
toast.add({
title: 'Success',
color: 'success',
description: JSON.stringify(event.data, null, 2)
})
}
</script>
<template>
<UCard>
<MAutoForm :schema="schema" @submit="onSubmit" />
</UCard>
</template>
<script lang="ts" setup>
const { afz } = useAutoForm()
const toast = useToast()
interface UserInfo {
name: string
age: number
email: string
}
const userSchema = {
name: afz.string().min(1, '请输入姓名').default('张三'),
age: afz.number().min(1).max(150).default(18),
email: afz.email('请输入有效的邮箱地址').default('example@example.com')
}
const testUser = {
name: '张三',
age: 28,
email: 'test@example.com',
extraField: '我是额外字段'
}
const normalSchema = afz.object<UserInfo>()(userSchema)
const looseSchema = afz.looseObject<UserInfo>()(userSchema)
const strictSchema = afz.strictObject<UserInfo>()(userSchema)
const currentType = ref<'normal' | 'loose' | 'strict'>('normal')
const items = [
{ label: 'Normal', value: 'normal' },
{ label: 'Loose', value: 'loose' },
{ label: 'Strict', value: 'strict' }
]
const schema = computed(() => {
switch (currentType.value) {
case 'loose': return looseSchema
case 'strict': return strictSchema
default: return normalSchema
}
})
async function click() {
const parsed = schema.value.safeParse(testUser)
if (!parsed.success) {
toast.add({
title: '验证失败',
color: 'error',
description: JSON.stringify(parsed.error.issues, null, 2)
})
} else {
toast.add({
title: '验证成功',
color: 'success',
description: JSON.stringify(parsed.data, null, 2)
})
}
}
</script>
<template>
<UCard class="space-y-4">
<template #header>
<div class="flex items-center gap-2">
<span class="text-sm font-medium">选择对象类型:</span>
<USelect v-model="currentType" :items="items" />
</div>
</template>
<MAutoForm :schema="schema" :submit-button="false">
<template #footer>
<UButton color="primary" @click="click">
验证类型
</UButton>
</template>
</MAutoForm>
</UCard>
</template>
// 直接调用
afz.object(shape, meta?)
// 柯里化调用(类型约束)
afz.object<TypeConstraint>()(shape, meta?)
两种调用方式的区别:
最常用的方式,直接传入字段定义:
const schema = afz.object({
name: afz.string(),
age: afz.number()
})
先传入类型参数,在定义 schema 时获得字段提示:
interface UserInfo {
name: string
age: number
email: string
}
const schema = afz.object<UserInfo>()({
name: afz.string(), // IDE 会提示 name | age | email
email: afz.email()
})
<T> 仅用于 IDE 提示,不影响运行时验证。定义对象的字段结构,决定输出类型:
const schema = afz.object({
name: afz.string(),
age: afz.number(),
email: afz.email()
})
// 输出类型:{ name: string, age: number, email: string }
为嵌套对象添加元数据(label、hint、折叠等):
afz.object({
user: afz.object({
name: afz.string(),
email: afz.email()
}).meta({
label: '用户信息',
collapsible: { defaultOpen: true }
})
})
覆盖默认渲染方式,用于选择复杂对象:
afz.object({
user: afz.object({
label: afz.string(),
value: afz.string(),
avatar: afz.object({ src: afz.url() })
}, {
type: 'selectMenu', // 覆盖默认的嵌套表单
controlProps: {
items: users.value,
placeholder: '请选择用户'
}
})
})
// 输出类型:{ label: string, value: string, avatar: { src: string } }
// 渲染方式:使用 selectMenu 选择器
直接传入自定义组件实例:
import MyObjectControl from './MyObjectControl.vue'
afz.object({}, {
component: MyObjectControl,
controlProps: { /* ... */ }
})
三种对象工厂对应不同的额外字段处理策略:
移除未定义的额外字段:
const schema = afz.object({ name: afz.string() })
schema.parse({ name: '张三', extra: 'removed' })
// 结果:{ name: '张三' }
允许并保留额外字段:
const schema = afz.looseObject({ name: afz.string() })
schema.parse({ name: '张三', extra: 'kept' })
// 结果:{ name: '张三', extra: 'kept' }
额外字段会抛出验证错误:
const schema = afz.strictObject({ name: afz.string() })
schema.parse({ name: '张三', extra: 'error' })
// 错误:Unrecognized key(s) in object: 'extra'