Skip to content

布局

布局是包裹页面的外壳组件,用于共享导航栏、侧边栏、页脚等公共 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类型说明
namestring布局名称
fallbackstring后备布局
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/

注意事项

  1. 必须包含 <slot />:布局组件必须包含 <slot />,否则页面内容无法渲染
  2. 布局中不要有 <NuxtPage /><NuxtPage /> 只在 app.vue 或布局中使用一次
  3. 布局中的数据获取:布局中使用 useFetch 需要注意,它不会在布局切换时重新执行
  4. 布局和页面的生命周期:布局切换时页面会重新渲染

INFO

常见错误:在布局中用 useFetch 获取用户信息 然后在多个页面间导航时发现数据不刷新。这是因为布局组件不会随页面切换而重新创建。解决方案:在 app.vue 或页面中获取数据,或使用 watch 监听路由变化

知识脉络

text
页面 → 你在这里:布局

         ├─→ 下一步:组件

         └─→ 相关:页面元信息(layout 选项)

基于 Nuxt 4 官方文档整理编写