实战示例
登录、注册分组校验、多文件上传与横向搜索表单等 AutoForm 组合场景。
登录表单
组合邮箱、密码与记住我字段,loadingAuto 接管异步提交态:
<script setup lang="ts">
import type { z } from 'zod'
const { afz } = useAutoForm()
const toast = useToast()
const schema = afz.object({
email: afz.email({ controlProps: { placeholder: 'demo@movk.dev' } }).meta({ label: '邮箱' }),
password: afz.string({ type: 'withPasswordToggle' }).min(6).meta({ label: '密码' }),
remember: afz.boolean({ type: 'switch' }).default(false).meta({ label: '记住我' })
})
const state = reactive<Partial<z.input<typeof schema>>>({})
async function onSubmit() {
await new Promise(r => setTimeout(r, 1500))
toast.add({ title: '登录成功', description: `欢迎 ${state.email}`, color: 'success' })
}
</script>
<template>
<MAutoForm
:schema="schema"
:state="state"
:submit-button-props="{ block: true, label: '登 录' }"
@submit="onSubmit"
/>
</template>
注册分组校验
嵌套 object 分组字段,refine 校验确认密码一致:
<script setup lang="ts">
import type { z } from 'zod'
const { afz } = useAutoForm()
const toast = useToast()
const schema = afz.object({
account: afz.object({
username: afz.string().min(3).meta({ label: '用户名' }),
password: afz.string({ type: 'withPasswordToggle' }).min(8).meta({ label: '密码' }),
confirm: afz.string({ type: 'withPasswordToggle' }).min(8).meta({ label: '确认密码' })
}).meta({ label: '账号信息' }),
profile: afz.object({
name: afz.string().meta({ label: '昵称' }),
phone: afz.string({ type: 'asPhoneNumberInput' }).meta({ label: '手机' }),
interests: afz.array(afz.string(), { type: 'inputTags' }).default([]).meta({ label: '兴趣标签' })
}).meta({ label: '个人资料' }),
agreement: afz.boolean().refine(v => v === true, { message: '必须同意条款' }).meta({ label: '同意条款' })
}).refine(d => d.account?.password === d.account?.confirm, {
message: '两次密码不一致',
path: ['account', 'confirm']
})
const state = reactive<Partial<z.input<typeof schema>>>({})
async function onSubmit() {
await new Promise(r => setTimeout(r, 1200))
toast.add({ title: '注册成功', color: 'success' })
}
</script>
<template>
<MAutoForm :schema="schema" :state="state" :submit-button-props="{ label: '注 册' }" @submit="onSubmit" />
</template>
多文件上传
afz.array(afz.file()) 渲染多文件字段,提交后汇总附件数量:
<script setup lang="ts">
import type { z } from 'zod'
const { afz } = useAutoForm()
const toast = useToast()
const schema = afz.object({
title: afz.string({ controlProps: { placeholder: '附件描述' } }).meta({ label: '标题' }),
attachments: afz.array(afz.file()).default([]).meta({ label: '附件(多选)' })
})
const state = reactive<Partial<z.input<typeof schema>>>({})
async function onSubmit() {
await new Promise(r => setTimeout(r, 600))
const count = state.attachments?.length ?? 0
toast.add({ title: `已上传 ${count} 个文件`, color: 'success' })
}
</script>
<template>
<MAutoForm :schema="schema" :state="state" :submit-button-props="{ label: '上 传' }" @submit="onSubmit" />
</template>
横向搜索表单
globalMeta 横向排布筛选项,footer 插槽承载操作按钮,submit-button 关闭内置按钮:
<script setup lang="ts">
import type { z } from 'zod'
const { afz } = useAutoForm()
const toast = useToast()
const schema = afz.object({
keyword: afz.string({ controlProps: { placeholder: '输入关键字' } }).optional().meta({ label: '关键字' }),
category: afz.enum(['全部', '前端', '后端', '运维'], { type: 'pillGroup' }).default('全部').meta({ label: '分类' }),
status: afz.enum(['active', 'archived'], { type: 'radioGroup' }).default('active').meta({ label: '状态' })
})
const state = reactive<Partial<z.input<typeof schema>>>({})
const searchRef = useTemplateRef('searchForm')
async function onSubmit() {
await new Promise(r => setTimeout(r, 300))
toast.add({ title: '查询完成', description: JSON.stringify(state), color: 'info' })
}
function onReset() {
searchRef.value?.reset()
toast.add({ title: '已重置', color: 'neutral' })
}
</script>
<template>
<MAutoForm
ref="searchForm"
:schema="schema"
:state="state"
:global-meta="{ orientation: 'horizontal', size: 'sm' }"
:submit="false"
@submit="onSubmit"
>
<template #footer="{ loading }">
<div class="flex justify-end gap-2">
<UButton color="neutral" variant="soft" size="sm" label="重置" @click="onReset" />
<UButton type="submit" size="sm" label="搜索" :loading="loading" />
</div>
</template>
</MAutoForm>
</template>