第 4 篇:布局骨架------页面壳、栅格、卡片
本篇目标
把「统一布局」做成几个固定的布局组件。之后每个页面 = 布局骨架 + 一份 schema,
AI 不碰布局,布局由这几个组件锁定。
1. 布局拆成四个固定件
| 组件 | 职责 | 放哪 |
|---|---|---|
PageShell |
整个页面的外层:内边距、最大宽度、纵向节奏 | src/components/layout/PageShell.vue |
PageHeader |
页头:标题 + 右侧操作区 | src/components/layout/PageHeader.vue |
SectionCard |
内容卡片:白底、圆角、可带标题 | src/components/layout/SectionCard.vue |
FormGrid |
表单单栅格:字段按列排布、自动换行 | src/components/layout/FormGrid.vue |
这四个组件是本系列自研的,代码很短、可自由改。布局规则全部内聚在这里,
后续所有页面共用,等于「布局写死」的落地实现。
2. 逐个实现
PageShell.vue
vue
<script setup lang="ts">
defineProps<{ maxWidth?: string }>()
</script>
<template>
<div class="min-h-screen bg-background">
<main
class="mx-auto w-full px-[var(--spacing-page)] py-[var(--spacing-page)]"
:style="{ maxWidth: maxWidth ?? '960px' }"
>
<slot />
</main>
</div>
</template>
PageHeader.vue
vue
<script setup lang="ts">
defineProps<{ title: string; description?: string }>()
</script>
<template>
<div class="mb-[var(--spacing-page)] flex flex-wrap items-center justify-between gap-4">
<div>
<h1 class="text-[var(--text-title)] font-semibold text-foreground">{{ title }}</h1>
<p v-if="description" class="mt-1 text-[var(--text-caption)] text-muted-foreground">
{{ description }}
</p>
</div>
<div class="flex items-center gap-2"><slot name="actions" /></div>
</div>
</template>
SectionCard.vue
vue
<script setup lang="ts">
defineProps<{ title?: string }>()
</script>
<template>
<section class="rounded-[var(--radius-lg)] border border-border bg-card p-[var(--spacing-card)]">
<h2 v-if="title" class="mb-4 text-[var(--text-body)] font-semibold text-foreground">
{{ title }}
</h2>
<slot />
</section>
</template>
FormGrid.vue
vue
<script setup lang="ts">
import { computed } from 'vue'
const props = defineProps<{ columns?: number }>()
// 每格最小宽度,窄屏自动换行成单列
const style = computed(() => ({
gridTemplateColumns: `repeat(${props.columns ?? 2}, minmax(0, 1fr))`,
}))
</script>
<template>
<div class="grid gap-[var(--spacing-field)]" :style="style">
<slot />
</div>
</template>
说明:
columns默认 2 列。移动端如果想强制单列,可在 FormGrid 内加一条媒体查询,或用 Tailwind 的响应式类自行调整(本系列保持简单,字段多时第 6 篇的 span 属性可跨列)。
3. 组合成一个「固定壳」示例
vue
<script setup lang="ts">
import PageShell from '@/components/layout/PageShell.vue'
import PageHeader from '@/components/layout/PageHeader.vue'
import SectionCard from '@/components/layout/SectionCard.vue'
import FormGrid from '@/components/layout/FormGrid.vue'
import { Button } from '@/components/ui/button'
</script>
<template>
<PageShell>
<PageHeader title="客户资料" description="统一样式的表单页示例">
<template #actions><Button>保存</Button></template>
</PageHeader>
<SectionCard title="基本信息">
<FormGrid :columns="2">
<div>(这里放第 6 篇的 SchemaForm 渲染结果)</div>
</FormGrid>
</SectionCard>
</PageShell>
</template>
4. 为什么这能锁死布局
- 页边距、最大宽度、卡片圆角、字段间距,全部在四个组件里写死
- 新页面只需「用这四个组件拼壳 + 塞 schema」,布局不可能歪
- AI 无论生成多少页,都进同一个壳
5. 验证
- 复制上面代码,跑
pnpm dev,确认标题 / 卡片 / 操作区位置正常。 - 把
FormGrid的columns改成 3,看是否自动变三列;缩窄浏览器窗口到手机宽度,确认能换行。 - 修改
--spacing-page(第 3 篇变量),整页留白跟着变 → 布局与 Tokens 联动成功。
本篇小结
- 布局被收敛成 PageShell / PageHeader / SectionCard / FormGrid 四个组件
- 所有页面共用,布局固定
- 下一步定义「每次变」的那部分------第 5 篇:Schema 设计