---
title: "布局"
description: "通过 layout 配置自定义字段布局，支持栅格、标签页、手风琴等容器与可折叠嵌套字段。"
seo_title: "Layout"
seo_description: "Customize AutoForm field layout via layout config — grid, tabs, accordion containers and collapsible nested fields."
canonical_url: "https://nuxt.mhaibaraai.cn/docs/auto-form/layout"
---
# 布局

> 通过 layout 配置自定义字段布局，支持栅格、标签页、手风琴等容器与可折叠嵌套字段。

## 示例

### 简单容器布局

使用 `afz.layout` 创建一个基础的容器布局，通过 `class` 属性控制样式：

```vue [AutoFormLayoutSimpleExample.vue]
<script setup lang="ts">
import type { FormSubmitEvent } from '@nuxt/ui'
import type { z } from 'zod'

const { afz } = useAutoForm()
const toast = useToast()

const schema = afz.object({
  $layout: afz.layout({
    class: 'space-y-4',
    fields: {
      firstName: afz.string().meta({ label: '名字' }),
      lastName: afz.string().meta({ label: '姓氏' }),
      email: afz.email().meta({ label: '邮箱地址' })
    }
  })
})

type Schema = z.output<typeof schema>

const form = ref<Partial<Schema>>({})

async function onSubmit(event: FormSubmitEvent<Schema>) {
  toast.add({
    title: '提交成功',
    color: 'success',
    description: JSON.stringify(event.data, null, 2)
  })
}
</script>

<template>
  <MAutoForm :schema="schema" :state="form" @submit="onSubmit" />
</template>
```

> \[\!NOTE\]
> 
> 布局字段的 key（如 
> 
> $layout
> 
> ）以 
> 
> $
> 
>  开头是惯例写法，但不是必需的。你可以使用任何字段名，只要其值为 
> 
> afz.layout()
> 
>  即可。

### 网格布局

使用 CSS Grid 创建多列布局，通过字段级别的 `class` 控制跨列：

```vue [AutoFormLayoutGridExample.vue]
<script setup lang="ts">
import type { FormSubmitEvent } from '@nuxt/ui'
import type { z } from 'zod'

const { afz } = useAutoForm()
const toast = useToast()

const schema = afz.object({
  $grid: afz.layout({
    class: 'grid grid-cols-2 gap-4',
    fields: {
      firstName: afz.string().meta({ label: '名字' }),
      lastName: afz.string().meta({ label: '姓氏' }),
      email: afz.email().meta({
        class: 'col-span-2',
        label: '邮箱地址',
        description: '此字段占据两列宽度'
      }),
      phone: afz.string().meta({ label: '电话' }),
      age: afz.number().int().min(0).meta({ label: '年龄' })
    }
  })
})

type Schema = z.output<typeof schema>

const form = ref<Partial<Schema>>({})

async function onSubmit(event: FormSubmitEvent<Schema>) {
  toast.add({
    title: '提交成功',
    color: 'success',
    description: JSON.stringify(event.data, null, 2)
  })
}
</script>

<template>
  <MAutoForm :schema="schema" :state="form" @submit="onSubmit" />
</template>
```

### 响应式多列布局

利用 Tailwind 的响应式前缀，创建自适应不同屏幕尺寸的布局：

```vue [AutoFormLayoutResponsiveExample.vue]
<script setup lang="ts">
import type { FormSubmitEvent } from '@nuxt/ui'
import type { z } from 'zod'

const { afz } = useAutoForm()
const toast = useToast()

const schema = afz.object({
  $responsive: afz.layout({
    // 移动端 1 列，平板 2 列，桌面端 3 列
    class: 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4',
    fields: {
      title: afz.string().meta({
        class: 'col-span-full', // 所有屏幕尺寸都占满整行
        label: '标题'
      }),
      firstName: afz.string().meta({ label: '名字' }),
      lastName: afz.string().meta({ label: '姓氏' }),
      email: afz.email().meta({ label: '邮箱' }),
      phone: afz.string().meta({ label: '电话' }),
      age: afz.number().int().min(0).meta({ label: '年龄' }),
      description: afz.string({ type: 'textarea' }).meta({
        class: 'col-span-full',
        label: '描述'
      })
    }
  })
})

type Schema = z.output<typeof schema>

const form = ref<Partial<Schema>>({})

async function onSubmit(event: FormSubmitEvent<Schema>) {
  toast.add({
    title: '提交成功',
    color: 'success',
    description: JSON.stringify(event.data, null, 2)
  })
}
</script>

<template>
  <MAutoForm :schema="schema" :state="form" @submit="onSubmit" />
</template>
```

> \[\!TIP\]
> 
> 使用 Tailwind 的响应式前缀（
> 
> md:
> 
> 、
> 
> lg:
> 
> ）可以轻松实现跨设备的自适应布局。字段可以通过 
> 
> col-span-full
> 
>  占据整行。

### 嵌套布局

布局字段可以无限嵌套，构建复杂的层级结构：

```vue [AutoFormLayoutNestedExample.vue]
<script setup lang="ts">
import type { FormSubmitEvent } from '@nuxt/ui'
import type { z } from 'zod'

const { afz } = useAutoForm()
const toast = useToast()

const schema = afz.object({
  $personalInfo: afz.layout({
    class: 'grid grid-cols-2 gap-4',
    fields: {
      firstName: afz.string().meta({ label: '名字' }),
      lastName: afz.string().meta({ label: '姓氏' })
    }
  }),

  $contactInfo: afz.layout({
    class: 'space-y-4 mt-10',
    fields: {
      email: afz.email().meta({ label: '邮箱地址' }),
      $phoneLayout: afz.layout({
        class: 'grid grid-cols-3 gap-2',
        fields: {
          countryCode: afz.string().default('+86').meta({ label: '国家代码' }),
          phoneNumber: afz.string().meta({
            class: 'col-span-2',
            label: '电话号码',
            hint: '此字段占据两列宽度'
          })
        }
      })
    }
  })
})

type Schema = z.output<typeof schema>

const form = ref<Partial<Schema>>({})

async function onSubmit(event: FormSubmitEvent<Schema>) {
  toast.add({
    title: '提交成功',
    color: 'success',
    description: JSON.stringify(event.data, null, 2)
  })
}
</script>

<template>
  <MAutoForm :schema="schema" :state="form" @submit="onSubmit" />
</template>
```

> \[\!WARNING\]
> 
> 嵌套布局的深度没有限制，但过深的嵌套可能影响可读性。建议保持在 2-3 层。

### 手风琴布局

使用 Nuxt UI 的 `UAccordion` 组件，将字段组织在可折叠的面板中：

```vue [AutoFormLayoutAccordionExample.vue]
<script setup lang="ts">
import type { FormSubmitEvent } from '@nuxt/ui'
import type { z } from 'zod'
import { UAccordion } from '#components'

const { afz } = useAutoForm()
const toast = useToast()

const schema = afz.object({
  $accordion: afz.layout({
    component: UAccordion,
    props: {
      type: 'multiple',
      ui: {
        content: 'space-y-4'
      },
      items: [
        { label: '基本信息', icon: 'i-lucide-user', slot: 'item-0' },
        { label: '详细信息', icon: 'i-lucide-square-pen', slot: 'item-1' }
      ]
    },
    fieldSlots: {
      name: 'item-0',
      email: 'item-0',
      bio: 'item-1'
    },
    fields: {
      name: afz.string().meta({ label: '姓名' }),
      email: afz.email().meta({ label: '邮箱' }),
      bio: afz.string({ type: 'textarea' }).meta({ label: '个人简介' }).optional()
    }
  })
})

type Schema = z.output<typeof schema>

const form = ref<Partial<Schema>>({})

async function onSubmit(event: FormSubmitEvent<Schema>) {
  toast.add({
    title: '提交成功',
    color: 'success',
    description: JSON.stringify(event.data, null, 2)
  })
}
</script>

<template>
  <MAutoForm :schema="schema" :state="form" @submit="onSubmit" />
</template>
```

> \[\!TIP\]
> 
> fieldSlots
> 
>  用于将不同字段分配到布局组件的不同插槽中。插槽名称需要与 
> 
> items
> 
>  中定义的 
> 
> slot
> 
>  对应。

### 标签页布局

使用 `UTabs` 组件创建分页式表单，支持动态响应式配置：

```vue [AutoFormLayoutTabsExample.vue]
<script setup lang="ts">
import { UTabs } from '#components'
import type { FormSubmitEvent } from '@nuxt/ui'
import type { z } from 'zod'
import type { AutoFormFieldContext } from '#movk/types'

const { afz } = useAutoForm()
const toast = useToast()

const schema = afz.object({
  userType: afz.enum(['personal', 'company'])
    .default('personal')
    .meta({ label: '用户类型' }),

  $tabs: afz.layout({
    component: UTabs,
    props: ({ state }) => {
      const isCompany = state?.userType === 'company'
      return {
        ui: { content: 'space-y-4' },
        items: isCompany
          ? [
              { label: '公司信息', icon: 'i-lucide-building', slot: 'tab-0' },
              { label: '联系方式', icon: 'i-lucide-phone', slot: 'tab-1' }
            ]
          : [
              { label: '个人信息', icon: 'i-lucide-user', slot: 'tab-0' },
              { label: '联系方式', icon: 'i-lucide-phone', slot: 'tab-1' }
            ]
      }
    },
    fieldSlots: ({ state }) => {
      if (state?.userType === 'company') {
        return {
          companyName: 'tab-0',
          registrationNumber: 'tab-0',
          phone: 'tab-1',
          email: 'tab-1',
          address: 'tab-1'
        }
      }
      return {
        username: 'tab-0',
        age: 'tab-0',
        phone: 'tab-1',
        email: 'tab-1',
        address: 'tab-1'
      }
    },
    fields: {
      username: afz.string().meta({
        label: '姓名',
        if: ({ state }: AutoFormFieldContext) => state?.userType === 'personal'
      }).optional(),
      age: afz.number().int().min(0).meta({
        label: '年龄',
        if: ({ state }: AutoFormFieldContext) => state?.userType === 'personal'
      }).optional(),
      companyName: afz.string().meta({
        label: '公司名称',
        if: ({ state }: AutoFormFieldContext) => state?.userType === 'company'
      }).optional(),
      registrationNumber: afz.string().meta({
        label: '注册号',
        if: ({ state }: AutoFormFieldContext) => state?.userType === 'company'
      }).optional(),
      phone: afz.string().meta({ label: '联系电话' }),
      email: afz.email().meta({ label: '邮箱地址' }),
      address: afz.string().meta({ label: '地址' })
    }
  })
})

type Schema = z.output<typeof schema>

const form = ref<Partial<Schema>>({})

async function onSubmit(event: FormSubmitEvent<Schema>) {
  toast.add({
    title: '提交成功',
    color: 'success',
    description: JSON.stringify(event.data, null, 2)
  })
}
</script>

<template>
  <MAutoForm :schema="schema" :state="form" @submit="onSubmit" />
</template>
```

> \[\!TIP\]
> 
> 标签页布局展示了如何根据表单状态动态调整布局结构。切换用户类型时，标签页标题和字段分布会自动更新。

> \[\!WARNING\]
> 
> 在使用条件渲染（
> 
> if
> 
> ）时，建议将字段标记为 
> 
> .optional()
> 
> ，避免验证错误。

## API

### `AutoFormLayoutConfig`

`afz.layout` 接受一个配置对象，包含以下属性：

| 属性           | 类型                                               | 说明                 |
| ------------ | ------------------------------------------------ | ------------------ |
| `component`  | `Component | string`                             | 布局容器组件（默认：`'div'`） |
| `class`      | `string | (context) => string`                   | CSS 类名，支持响应式函数     |
| `props`      | `object | (context) => object`                   | 组件属性，支持响应式函数       |
| `slots`      | `Record<string, () => VNode> | (context) => ...` | 组件插槽内容             |
| `fields`     | `Record<string, ZodType>`                        | 包含的字段定义            |
| `fieldSlot`  | `string | (context) => string`                   | 将**所有**字段渲染到指定插槽   |
| `fieldSlots` | `Record<string, string> | (context) => ...`      | 将**不同**字段分配到不同插槽   |

### `AutoFormNestedCollapsible`

`collapsible` 属性接受一个配置对象，支持以下选项：

| 属性              | 类型                                                     | 默认值     | 说明              |
| --------------- | ------------------------------------------------------ | ------- | --------------- |
| `enabled`       | `boolean`                                              | `true`  | 是否启用折叠功能        |
| `defaultOpen`   | `boolean`                                              |         | 默认是否展开          |
| `open`          | `boolean`                                              |         | 控制展开/收起状态（受控模式） |
| `disabled`      | `boolean`                                              |         | 禁用折叠功能（始终展开）    |
| `unmountOnHide` | `boolean`                                              | `true`  | 隐藏时卸载内容         |
| `as`            | `string`                                               | `'div'` | 渲染的元素类型         |
| `class`         | `ClassNameValue`                                       |         | CSS 类名          |
| `ui`            | `{ root?: ClassNameValue; content?: ClassNameValue; }` |         | UI 样式配置         |


## Sitemap

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