实战示例

View source
登录、注册分组校验、多文件上传与横向搜索表单等 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>
Copyright © 2025 - 2026 YiXuan - MIT License