布局
布局是包裹页面的外壳组件,用于共享导航栏、侧边栏、页脚等公共 UI。
为什么需要布局?
没有布局时,每个页面都要写导航栏和页脚:
vue
<!-- 每个页面都重复写导航栏和页脚... -->
<template>
<AppHeader />
<main>
<h1>关于我们</h1>
<p>...</p>
</main>
<AppFooter />
</template>有布局后,公共部分只写一次:
vue
<!-- layouts/default.vue -->
<template>
<AppHeader />
<main><slot /></main>
<AppFooter />
</template>默认布局
创建 app/layouts/default.vue:
vue
<!-- app/layouts/default.vue -->
<template>
<div>
<AppHeader />
<main>
<slot /> <!-- 页面内容在这里渲染 -->
</main>
<AppFooter />
</div>
</template>所有没有指定布局的页面默认使用 default.vue。
<slot /> 是什么?
它是 Vue 的插槽——页面的内容会被"插入"到 <slot /> 的位置。就像一个相框,框架(布局)是固定的,照片(页面内容)可以替换。
自定义布局
vue
<!-- app/layouts/admin.vue -->
<template>
<div class="admin-layout">
<AdminSidebar />
<main class="admin-content">
<slot />
</main>
</div>
</template>
<style scoped>
.admin-layout {
display: flex;
}
.admin-content {
flex: 1;
padding: 20px;
}
</style>在页面中使用:
vue
<script setup>
definePageMeta({
layout: 'admin', // 使用 admin 布局
})
</script>禁用布局
vue
<script setup>
definePageMeta({
layout: false, // 不使用任何布局
})
</script>什么时候禁用布局?
登录页面、注册页面、全屏展示页面——这些页面不需要导航栏和页脚。
Nuxt 4 布局 Props(v4.4+)
Nuxt 4.4 支持通过 definePageMeta 向布局传递 Props:
vue
<!-- pages/dashboard.vue -->
<script setup>
definePageMeta({
layout: {
name: 'panel',
props: {
sidebar: true,
title: 'Dashboard',
},
},
})
</script>vue
<!-- layouts/panel.vue -->
<script setup lang="ts">
defineProps<{
sidebar?: boolean
title?: string
}>()
</script>
<template>
<div class="panel-layout">
<aside v-if="sidebar">
<h2>{{ title }}</h2>
</aside>
<main>
<slot />
</main>
</div>
</template>为什么需要向布局传 Props?
以前同一个布局的所有页面外观完全一样。现在你可以让不同页面在使用同一布局时,有不同的行为(如侧边栏是否显示、标题是什么)。
NuxtLayout 组件
<NuxtLayout> 是布局的容器组件,通常在 app.vue 中使用:
vue
<!-- app/app.vue -->
<template>
<NuxtRouteAnnouncer />
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>NuxtLayout Props
| Prop | 类型 | 说明 |
|---|---|---|
name | string | 布局名称 |
fallback | string | 后备布局 |
vue
<!-- 动态切换布局 -->
<template>
<NuxtLayout :name="layoutName">
<NuxtPage />
</NuxtLayout>
</template>
<script setup>
const layoutName = computed(() =>
isMobile.value ? 'mobile' : 'default'
)
</script>动态切换布局
在中间件中动态设置布局:
ts
// app/middleware/set-layout.ts
export default defineNuxtRouteMiddleware((to) => {
setPageLayout(to.meta.layout as string || 'default')
})definePageMeta 中的布局 vs 中间件中设置布局
| 方式 | 时机 | 适用场景 |
|---|---|---|
definePageMeta({ layout: 'admin' }) | 编译时 | 固定布局 |
setPageLayout() | 运行时 | 根据条件动态选择布局 |
运行时布局选择
如果你想根据屏幕尺寸、用户角色等动态选择布局,用 setPageLayout()。
布局过渡
ts
// nuxt.config.ts
export default defineNuxtConfig({
layoutTransition: {
name: 'layout',
mode: 'out-in',
},
})css
/* app/assets/css/transitions.css */
.layout-enter-active,
.layout-leave-active {
transition: opacity 0.3s;
}
.layout-enter-from,
.layout-leave-to {
opacity: 0;
}常见布局模式
博客布局
vue
<!-- app/layouts/blog.vue -->
<template>
<div>
<BlogHeader />
<div class="blog-container">
<main>
<slot />
</main>
<aside>
<BlogSidebar />
</aside>
</div>
<BlogFooter />
</div>
</template>空白布局(登录/注册)
vue
<!-- app/layouts/blank.vue -->
<template>
<div class="blank-layout">
<slot />
</div>
</template>
<style scoped>
.blank-layout {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
</style>后台管理布局
vue
<!-- app/layouts/admin.vue -->
<template>
<div class="admin">
<AdminNavbar />
<div class="admin-body">
<AdminSidebar :menus="menus" />
<main class="admin-content">
<slot />
</main>
</div>
</div>
</template>布局 vs 页面 vs 组件的关系
text
app.vue
└── NuxtLayout (布局:导航栏 + 页脚)
└── NuxtPage (页面:具体内容)
├── 组件A
├── 组件B
└── 组件C| 概念 | 作用 | 典型内容 | 文件位置 |
|---|---|---|---|
| 布局 | 页面的外壳 | 导航栏、侧边栏、页脚 | app/layouts/ |
| 页面 | 路由对应的内容 | 业务内容 | app/pages/ |
| 组件 | 可复用的 UI 单元 | 按钮、卡片、表单 | app/components/ |
注意事项
- 必须包含
<slot />:布局组件必须包含<slot />,否则页面内容无法渲染 - 布局中不要有
<NuxtPage />:<NuxtPage />只在app.vue或布局中使用一次 - 布局中的数据获取:布局中使用
useFetch需要注意,它不会在布局切换时重新执行 - 布局和页面的生命周期:布局切换时页面会重新渲染
INFO
️ 常见错误:在布局中用 useFetch 获取用户信息 然后在多个页面间导航时发现数据不刷新。这是因为布局组件不会随页面切换而重新创建。解决方案:在 app.vue 或页面中获取数据,或使用 watch 监听路由变化
知识脉络
text
页面 → 你在这里:布局
│
├─→ 下一步:组件
│
└─→ 相关:页面元信息(layout 选项)