Nuxt.js页面与布局 #

一、页面系统概述 #

Nuxt.js 的页面系统基于文件系统路由,每个 .vue 文件在 pages/ 目录下都会自动注册为一个路由。页面可以配合布局系统实现统一的页面结构。

二、页面基础 #

2.1 创建页面 #

pages/index.vue

vue
<template>
  <div>
    <h1>首页</h1>
  </div>
</template>

<script setup lang="ts">
</script>

2.2 页面配置 #

使用 definePageMeta 配置页面:

vue
<script setup lang="ts">
definePageMeta({
  layout: 'default',
  middleware: 'auth',
  keepalive: true,
  pageTransition: {
    name: 'slide',
    mode: 'out-in'
  }
})
</script>

2.3 页面配置选项 #

选项 类型 说明
layout string 指定布局名称
middleware string | string[] 路由中间件
keepalive boolean | object 保持组件状态
name string 路由名称
key string | function 组件 key
pageTransition object 页面过渡动画
redirect string 重定向路径
validate function 路由验证函数
alias string | string[] 路由别名
path string 自定义路径

三、布局系统 #

3.1 创建布局 #

layouts/default.vue

vue
<template>
  <div class="layout">
    <AppHeader />
    <main class="main">
      <slot />
    </main>
    <AppFooter />
  </div>
</template>

<script setup lang="ts">
</script>

<style scoped>
.layout {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

.main {
  flex: 1;
}
</style>

3.2 使用布局 #

app.vue 中使用:

vue
<template>
  <NuxtLayout>
    <NuxtPage />
  </NuxtLayout>
</template>

3.3 指定布局 #

页面中指定布局:

vue
<script setup lang="ts">
definePageMeta({
  layout: 'admin'
})
</script>

3.4 多种布局 #

创建多个布局文件:

text
layouts/
├── default.vue      → 默认布局
├── admin.vue        → 管理后台布局
├── blog.vue         → 博客布局
└── auth.vue         → 认证页面布局

layouts/admin.vue

vue
<template>
  <div class="admin-layout">
    <AdminSidebar />
    <div class="admin-content">
      <AdminHeader />
      <main class="admin-main">
        <slot />
      </main>
    </div>
  </div>
</template>

layouts/auth.vue

vue
<template>
  <div class="auth-layout">
    <div class="auth-container">
      <slot />
    </div>
  </div>
</template>

<style scoped>
.auth-layout {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}

.auth-container {
  background: white;
  padding: 2rem;
  border-radius: 8px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
</style>

四、布局进阶 #

4.1 布局传参 #

向布局传递数据:

vue
<script setup lang="ts">
definePageMeta({
  layout: 'custom',
  layoutProps: {
    title: '用户管理',
    showBack: true
  }
})
</script>

布局中接收参数:

vue
<script setup lang="ts">
const props = defineProps<{
  title?: string
  showBack?: boolean
}>()
</script>

<template>
  <div>
    <header>
      <button v-if="showBack" @click="$router.back()">返回</button>
      <h1>{{ title }}</h1>
    </header>
    <slot />
  </div>
</template>

4.2 动态布局 #

根据条件动态切换布局:

vue
<script setup lang="ts">
const { isAuthenticated } = useAuth()

const layoutName = computed(() => {
  return isAuthenticated.value ? 'default' : 'auth'
})
</script>

<template>
  <NuxtLayout :name="layoutName">
    <NuxtPage />
  </NuxtLayout>
</template>

4.3 嵌套布局 #

使用多个 NuxtLayout 实现嵌套:

vue
<template>
  <NuxtLayout name="outer">
    <NuxtLayout name="inner">
      <NuxtPage />
    </NuxtLayout>
  </NuxtLayout>
</template>

4.4 布局插槽 #

布局可以定义多个插槽:

layouts/default.vue

vue
<template>
  <div>
    <header>
      <slot name="header">
        <AppHeader />
      </slot>
    </header>
    <main>
      <slot />
    </main>
    <footer>
      <slot name="footer">
        <AppFooter />
      </slot>
    </footer>
  </div>
</template>

页面中使用:

vue
<template>
  <NuxtLayout>
    <template #header>
      <CustomHeader />
    </template>
    
    <div class="content">
      页面内容
    </div>
    
    <template #footer>
      <CustomFooter />
    </template>
  </NuxtLayout>
</template>

五、页面过渡动画 #

5.1 全局过渡 #

app.vue

vue
<template>
  <NuxtLayout>
    <NuxtPage :transition="{
      name: 'page',
      mode: 'out-in'
    }" />
  </NuxtLayout>
</template>

<style>
.page-enter-active,
.page-leave-active {
  transition: opacity 0.3s ease;
}

.page-enter-from,
.page-leave-to {
  opacity: 0;
}
</style>

5.2 页面级过渡 #

vue
<script setup lang="ts">
definePageMeta({
  pageTransition: {
    name: 'slide-up',
    mode: 'out-in'
  }
})
</script>

<style>
.slide-up-enter-active,
.slide-up-leave-active {
  transition: all 0.3s ease;
}

.slide-up-enter-from {
  opacity: 0;
  transform: translateY(20px);
}

.slide-up-leave-to {
  opacity: 0;
  transform: translateY(-20px);
}
</style>

5.3 条件过渡 #

vue
<script setup lang="ts">
const route = useRoute()

definePageMeta({
  pageTransition: {
    name: 'slide',
    mode: 'out-in',
    onBeforeEnter: () => {
      console.log('过渡开始前')
    },
    onAfterEnter: () => {
      console.log('过渡结束后')
    }
  }
})
</script>

六、KeepAlive #

6.1 启用KeepAlive #

vue
<script setup lang="ts">
definePageMeta({
  keepalive: true
})
</script>

6.2 KeepAlive配置 #

vue
<script setup lang="ts">
definePageMeta({
  keepalive: {
    include: ['ComponentA', 'ComponentB'],
    exclude: ['ComponentC'],
    max: 10
  }
})
</script>

6.3 全局KeepAlive #

app.vue

vue
<template>
  <NuxtLayout>
    <NuxtPage :keepalive="{ include: ['HomePage', 'ListPage'] }" />
  </NuxtLayout>
</template>

七、页面元信息 #

7.1 useHead #

vue
<script setup lang="ts">
useHead({
  title: '页面标题',
  meta: [
    { name: 'description', content: '页面描述' },
    { property: 'og:title', content: '分享标题' },
    { property: 'og:description', content: '分享描述' },
    { property: 'og:image', content: '/og-image.png' }
  ],
  link: [
    { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
  ],
  script: [
    { src: '/script.js', defer: true }
  ],
  style: [
    { children: 'body { margin: 0; }' }
  ]
})
</script>

7.2 响应式元信息 #

vue
<script setup lang="ts">
const { data: article } = await useFetch('/api/article/1')

useHead({
  title: computed(() => article.value?.title || '加载中...'),
  meta: [
    {
      name: 'description',
      content: computed(() => article.value?.excerpt || '')
    }
  ]
})
</script>

7.3 全局元信息 #

nuxt.config.ts

typescript
export default defineNuxtConfig({
  app: {
    head: {
      title: '我的应用',
      meta: [
        { name: 'description', content: '应用描述' },
        { name: 'viewport', content: 'width=device-width, initial-scale=1' }
      ],
      link: [
        { rel: 'icon', href: '/favicon.ico' }
      ]
    }
  }
})

八、错误页面 #

8.1 自定义错误页面 #

error.vue

vue
<template>
  <div class="error-page">
    <h1>{{ error.statusCode }}</h1>
    <p>{{ error.message }}</p>
    <button @click="handleError">返回首页</button>
  </div>
</template>

<script setup lang="ts">
interface ErrorProps {
  statusCode: number
  message: string
}

const props = defineProps<{
  error: ErrorProps
}>()

const handleError = () => clearError({ redirect: '/' })
</script>

<style scoped>
.error-page {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
}

.error-page h1 {
  font-size: 6rem;
  color: #e74c3c;
}
</style>

8.2 抛出错误 #

vue
<script setup lang="ts">
throw createError({
  statusCode: 404,
  message: '页面不存在'
})
</script>

九、完整示例 #

9.1 完整布局系统 #

layouts/default.vue

vue
<template>
  <div class="app">
    <AppHeader />
    <div class="app-body">
      <AppSidebar v-if="showSidebar" />
      <main class="app-main" :class="{ 'with-sidebar': showSidebar }">
        <slot />
      </main>
    </div>
    <AppFooter />
  </div>
</template>

<script setup lang="ts">
const route = useRoute()

const showSidebar = computed(() => {
  return !['/', '/login', '/register'].includes(route.path)
})
</script>

<style scoped>
.app {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

.app-body {
  flex: 1;
  display: flex;
}

.app-main {
  flex: 1;
  padding: 1rem;
}

.app-main.with-sidebar {
  margin-left: 250px;
}
</style>

pages/index.vue

vue
<template>
  <div class="home">
    <h1>欢迎来到我的网站</h1>
    <p>这是一个使用 Nuxt.js 构建的网站</p>
  </div>
</template>

<script setup lang="ts">
definePageMeta({
  layout: 'default'
})

useHead({
  title: '首页 - 我的网站',
  meta: [
    { name: 'description', content: '网站首页' }
  ]
})
</script>

十、总结 #

本章介绍了 Nuxt.js 页面与布局系统:

  • 使用 definePageMeta 配置页面
  • 创建和使用多种布局
  • 布局传参和动态布局
  • 页面过渡动画
  • KeepAlive 保持组件状态
  • 使用 useHead 管理元信息
  • 自定义错误页面

页面和布局是构建应用结构的基础,下一章我们将学习组件开发。

最后更新:2026-03-28