---
title: "实战示例"
description: "登录、注册分组校验、多文件上传与横向搜索表单等 AutoForm 组合场景。"
seo_title: "Recipes"
seo_description: "Real-world AutoForm recipes — login form, grouped registration with cross-field validation, multi-file upload and a horizontal search form."
canonical_url: "https://nuxt.mhaibaraai.cn/docs/auto-form/recipes"
---
# 实战示例

> 登录、注册分组校验、多文件上传与横向搜索表单等 AutoForm 组合场景。

## 登录表单

组合邮箱、密码与记住我字段，`loadingAuto` 接管异步提交态：

```vue [AutoFormRecipeLoginExample.vue]
<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` 校验确认密码一致：

```vue [AutoFormRecipeRegisterExample.vue]
<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())` 渲染多文件字段，提交后汇总附件数量：

```vue [AutoFormRecipeUploadExample.vue]
<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` 关闭内置按钮：

```vue [AutoFormRecipeSearchExample.vue]
<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>
```


## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
