Object 对象
afz.object() 的调用方式、嵌套结构与额外字段处理策略。
两种调用方式的区别
// 直接调用
afz.object(shape, meta?)
// 柯里化调用(类型参数 `<T>` 仅用于 IDE 提示,不影响运行时验证。)
afz.object<T>()(shape, meta?)
const schema = afz.object({
name: afz.string(),
age: afz.number()
})
interface UserInfo {
name: string
age: number
email: string
}
const schema = afz.object<UserInfo>()({
name: afz.string(), // IDE 会提示 name | age | email
email: afz.email()
})
通用对象
通用对象类型通常与 selectMenu 结合使用,用于选择复杂数据结构:
<script lang="ts" setup>
import type { FormSubmitEvent } from '@nuxt/ui'
import type { z } from 'zod'
const { afz } = useAutoForm()
const toast = useToast()
const { data: users, execute, pending } = await useLazyFetch('https://jsonplaceholder.typicode.com/users', {
key: 'typicode-users-basic',
immediate: false,
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}`, loading: 'lazy' as const }
}))
}
})
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,
'loading': pending.value,
'onUpdate:open': () => {
if (!users.value && !pending.value) {
execute()
}
}
})
})
})
async function onSubmit(event: FormSubmitEvent<z.output<typeof schema>>) {
toast.add({
title: '提交成功',
color: 'success',
description: JSON.stringify(event.data, null, 2)
})
}
</script>
<template>
<MAutoForm :schema="schema" @submit="onSubmit" />
</template>
嵌套对象
嵌套对象结构,支持折叠展开:
<script lang="ts" setup>
import type { FormSubmitEvent } from '@nuxt/ui'
import type { z } from 'zod'
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>
<MAutoForm :schema="schema" @submit="onSubmit" />
</template>
类型化对象
使用 TypeScript 接口约束对象结构,在定义 schema 时获得字段名的 IDE 提示:
选择对象类型:
<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="false">
<template #footer>
<UButton color="primary" @click="click">
验证类型
</UButton>
</template>
</MAutoForm>
</UCard>
</template>
三种对象工厂对应不同的额外字段处理策略:
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'