Nuxt.js高级路由配置

学习目标

通过本章节的学习,你将能够:

  • 了解Nuxt.js路由优先级的规则
  • 掌握路由中间件的高级使用方法
  • 学会实现路由动画和过渡效果
  • 掌握路由守卫的高级应用
  • 了解路由配置的最佳实践

核心知识点

路由优先级

路由匹配规则

Nuxt.js的路由系统基于文件系统,路由优先级按照以下规则确定:

  1. 静态路由:具有固定路径的路由(如 /pages/about.vue
  2. 动态路由:包含参数的路由(如 /pages/users/_id.vue
  3. ** catch-all 路由**:匹配任意路径的路由(如 /pages/[...slug].vue
  4. 404 页面:匹配所有未找到的路由(如 /pages/404.vue

优先级示例

/pages/
├── about.vue           # 优先级 1
├── users/              # 优先级 2
│   ├── _id.vue         # 匹配 /users/1, /users/2 等
│   └── index.vue       # 匹配 /users
├── [slug].vue          # 优先级 3
├── [...slug].vue       # 优先级 4
└── 404.vue             # 优先级 5

路由中间件

全局中间件

全局中间件会应用于所有路由:

// middleware/auth.js
export default defineNuxtRouteMiddleware((to, from) => {
  const user = useUserStore().user
  
  if (!user && to.path !== '/login') {
    return navigateTo('/login')
  }
})

nuxt.config.ts 中配置全局中间件:

export default defineNuxtConfig({
  routeRules: {
    '/**': {
      middleware: 'auth'
    }
  }
})

页面级中间件

页面级中间件只应用于特定页面:

<!-- pages/dashboard.vue -->
<template>
  <div class="dashboard">
    <!-- 内容 -->
  </div>
</template>

<script setup>
definePageMeta({
  middleware: 'auth'
})
</script>

命名中间件

命名中间件可以根据条件应用:

// middleware/admin.js
export default defineNuxtRouteMiddleware((to, from) => {
  const user = useUserStore().user
  
  if (!user || !user.isAdmin) {
    return navigateTo('/')
  }
})

在页面中使用:

<!-- pages/admin/index.vue -->
<template>
  <div class="admin">
    <!-- 内容 -->
  </div>
</template>

<script setup>
definePageMeta({
  middleware: 'admin'
})
</script>

路由动画和过渡效果

基本过渡效果

使用 Vue 的 &lt;transition&gt; 组件实现路由过渡:

<!-- app.vue -->
<template>
  <div>
    <NuxtLayout>
      <NuxtPage />
    </NuxtLayout>
  </div>
</template>

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

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

自定义过渡效果

为不同路由定义不同的过渡效果:

<!-- app.vue -->
<template>
  <div>
    <NuxtLayout>
      <NuxtPage :transition="routeTransition" />
    </NuxtLayout>
  </div>
</template>

<script setup>
import { computed } from 'vue'
import { useRoute } from 'nuxt/app'

const route = useRoute()

const routeTransition = computed(() => {
  // 根据路由路径返回不同的过渡效果
  if (route.path.startsWith('/products')) {
    return {
      name: 'product-transition',
      mode: 'out-in'
    }
  }
  
  return {
    name: 'page',
    mode: 'out-in'
  }
})
</script>

<style>
/* 基本过渡效果 */
.page-enter-active,
.page-leave-active {
  transition: opacity 0.3s ease;
}

.page-enter-from,
.page-leave-to {
  opacity: 0;
}

/* 商品页面过渡效果 */
.product-transition-enter-active,
.product-transition-leave-active {
  transition: all 0.5s ease;
}

.product-transition-enter-from {
  opacity: 0;
  transform: translateX(30px);
}

.product-transition-leave-to {
  opacity: 0;
  transform: translateX(-30px);
}
</style>

路由动画

使用 CSS 动画实现更复杂的路由效果:

<!-- app.vue -->
<template>
  <div>
    <NuxtLayout>
      <NuxtPage :transition="{
        name: 'fade',
        mode: 'out-in'
      }" />
    </NuxtLayout>
  </div>
</template>

<style>
.fade-enter-active {
  animation: fade-in 0.5s ease-out;
}

.fade-leave-active {
  animation: fade-out 0.5s ease-in;
}

@keyframes fade-in {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes fade-out {
  from {
    opacity: 1;
    transform: translateY(0);
  }
  to {
    opacity: 0;
    transform: translateY(-20px);
  }
}
</style>

路由守卫高级应用

全局路由守卫

全局路由守卫可以在 plugins/router.js 中配置:

// plugins/router.js
export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.hook('page:start', () => {
    // 页面加载开始
    console.log('Page loading started')
  })
  
  nuxtApp.hook('page:finish', () => {
    // 页面加载完成
    console.log('Page loading finished')
  })
  
  nuxtApp.hook('page:error', (error) => {
    // 页面加载错误
    console.error('Page loading error:', error)
  })
})

路由守卫中间件

使用中间件实现更复杂的路由守卫逻辑:

// middleware/guest.js
export default defineNuxtRouteMiddleware((to, from) => {
  const user = useUserStore().user
  
  if (user && to.path === '/login') {
    return navigateTo('/')
  }
})
// middleware/auth.js
export default defineNuxtRouteMiddleware((to, from) => {
  const user = useUserStore().user
  
  // 检查用户是否登录
  if (!user) {
    return navigateTo('/login')
  }
  
  // 检查用户权限
  if (to.meta.requiresAdmin && !user.isAdmin) {
    return navigateTo('/')
  }
})

在页面中使用:

<!-- pages/admin.vue -->
<template>
  <div class="admin">
    <!-- 内容 -->
  </div>
</template>

<script setup>
definePageMeta({
  middleware: 'auth',
  meta: {
    requiresAdmin: true
  }
})
</script>

路由配置的最佳实践

  1. 合理组织路由结构:根据业务逻辑组织路由文件
  2. 使用命名路由:为路由设置有意义的名称
  3. 添加路由元信息:使用 meta 字段添加路由相关信息
  4. 实现路由过渡:为路由添加适当的过渡效果
  5. 使用路由中间件:集中处理路由相关的逻辑
  6. 优化路由性能:避免嵌套过深的路由结构
  7. 添加路由守卫:保护敏感路由
  8. 实现404页面:为未找到的路由提供友好的错误页面

实用案例分析

案例一:实现路由权限系统

功能需求

实现一个基于角色的路由权限系统,包括:

  • 普通用户只能访问公开页面
  • 登录用户可以访问个人中心
  • 管理员可以访问所有页面

实现步骤

  1. 创建用户状态管理
// stores/user.js
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () => ({
    user: null,
    token: localStorage.getItem('token')
  }),
  getters: {
    isAuthenticated: (state) => !!state.token,
    isAdmin: (state) => state.user?.role === 'admin'
  },
  actions: {
    login(userData) {
      this.user = userData
      this.token = userData.token
      localStorage.setItem('token', userData.token)
    },
    logout() {
      this.user = null
      this.token = null
      localStorage.removeItem('token')
    },
    async fetchUser() {
      if (this.token) {
        // 从API获取用户信息
        try {
          const response = await $fetch('/api/user', {
            headers: {
              Authorization: `Bearer ${this.token}`
            }
          })
          this.user = response
        } catch (error) {
          this.logout()
        }
      }
    }
  }
})
  1. 创建权限中间件
// middleware/auth.js
export default defineNuxtRouteMiddleware(async (to, from) => {
  const userStore = useUserStore()
  
  // 检查是否有token
  if (userStore.token) {
    // 获取用户信息
    await userStore.fetchUser()
  }
  
  // 检查路由是否需要认证
  if (to.meta.requiresAuth) {
    if (!userStore.isAuthenticated) {
      return navigateTo('/login')
    }
    
    // 检查是否需要管理员权限
    if (to.meta.requiresAdmin && !userStore.isAdmin) {
      return navigateTo('/')
    }
  }
  
  // 检查是否是登录页面,已登录用户不需要访问
  if (to.path === '/login' && userStore.isAuthenticated) {
    return navigateTo('/')
  }
})
  1. 配置路由权限
<!-- pages/index.vue -->
<template>
  <div class="home">
    <!-- 首页内容 -->
  </div>
</template>

<script setup>
definePageMeta({
  middleware: 'auth'
})
</script>
<!-- pages/dashboard.vue -->
<template>
  <div class="dashboard">
    <!-- 个人中心内容 -->
  </div>
</template>

<script setup>
definePageMeta({
  middleware: 'auth',
  meta: {
    requiresAuth: true
  }
})
</script>
<!-- pages/admin/index.vue -->
<template>
  <div class="admin">
    <!-- 管理员内容 -->
  </div>
</template>

<script setup>
definePageMeta({
  middleware: 'auth',
  meta: {
    requiresAuth: true,
    requiresAdmin: true
  }
})
</script>

案例二:实现路由动画系统

功能需求

实现一个具有丰富动画效果的路由系统,包括:

  • 首页进入/离开动画
  • 商品页面进入/离开动画
  • 详情页面进入/离开动画

实现步骤

  1. 配置全局过渡效果
<!-- app.vue -->
<template>
  <div>
    <NuxtLayout>
      <NuxtPage :transition="getPageTransition" />
    </NuxtLayout>
  </div>
</template>

<script setup>
import { useRoute } from 'nuxt/app'

const route = useRoute()

const getPageTransition = () => {
  // 根据路由路径返回不同的过渡效果
  if (route.path === '/') {
    return {
      name: 'home-transition'
    }
  } else if (route.path.startsWith('/products')) {
    return {
      name: 'product-transition'
    }
  } else if (route.path.match(/\/products\/\d+/)) {
    return {
      name: 'detail-transition'
    }
  }
  
  // 默认过渡效果
  return {
    name: 'default-transition'
  }
}
</script>

<style>
/* 默认过渡效果 */
.default-transition-enter-active,
.default-transition-leave-active {
  transition: opacity 0.3s ease;
}

.default-transition-enter-from,
.default-transition-leave-to {
  opacity: 0;
}

/* 首页过渡效果 */
.home-transition-enter-active {
  animation: home-in 1s ease-out;
}

.home-transition-leave-active {
  animation: home-out 1s ease-in;
}

@keyframes home-in {
  from {
    opacity: 0;
    transform: scale(0.8);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

@keyframes home-out {
  from {
    opacity: 1;
    transform: scale(1);
  }
  to {
    opacity: 0;
    transform: scale(1.2);
  }
}

/* 商品列表过渡效果 */
.product-transition-enter-active,
.product-transition-leave-active {
  transition: all 0.5s ease;
}

.product-transition-enter-from {
  opacity: 0;
  transform: translateX(30px);
}

.product-transition-leave-to {
  opacity: 0;
  transform: translateX(-30px);
}

/* 商品详情过渡效果 */
.detail-transition-enter-active,
.detail-transition-leave-active {
  transition: all 0.6s ease;
}

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

.detail-transition-leave-to {
  opacity: 0;
  transform: translateY(-20px);
}
</style>
  1. 创建页面组件
<!-- pages/index.vue -->
<template>
  <div class="home">
    <h1>首页</h1>
    <NuxtLink to="/products">查看商品</NuxtLink>
  </div>
</template>
<!-- pages/products/index.vue -->
<template>
  <div class="products">
    <h1>商品列表</h1>
    <div v-for="product in products" :key="product.id" class="product-card">
      <h2>{{ product.name }}</h2>
      <NuxtLink :to="`/products/${product.id}`">查看详情</NuxtLink>
    </div>
  </div>
</template>

<script setup>
const { data: products } = useAsyncData('products', () => {
  return $fetch('/api/products')
})
</script>
<!-- pages/products/_id.vue -->
<template>
  <div class="product-detail">
    <h1>{{ product.name }}</h1>
    <p>{{ product.description }}</p>
    <NuxtLink to="/products">返回列表</NuxtLink>
  </div>
</template>

<script setup>
const route = useRoute()

const { data: product } = useAsyncData('product', () => {
  return $fetch(`/api/products/${route.params.id}`)
})
</script>

总结

本章节介绍了Nuxt.js的高级路由配置,包括:

  1. 路由优先级:了解了路由匹配的规则和优先级顺序
  2. 路由中间件:掌握了全局中间件和页面级中间件的使用
  3. 路由动画和过渡效果:学会了如何实现基本和自定义的路由过渡效果
  4. 路由守卫的高级应用:掌握了基于角色的权限系统实现
  5. 路由配置的最佳实践:了解了路由组织和管理的最佳实践

通过本章节的学习,你应该能够实现复杂的路由系统,包括权限控制、动画效果和高级导航逻辑。这些技术将帮助你创建更加用户友好和功能完整的Nuxt.js应用。

« 上一篇 Nuxt.js模块系统和扩展 下一篇 » Nuxt.js缓存策略