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