4.1 Vue Router在SSR中的应用

4.1.1 路由器配置差异

在SSR环境中,路由器的配置需要考虑服务端和客户端的不同需求:

// src/router/index.js
import { createRouter, createWebHistory, createMemoryHistory } from 'vue-router'
import { isServer } from '@/utils/env'

// 路由配置
const routes = [
  {
    path: '/',
    name: 'Home',
    component: () => import('@/views/Home.vue'),
    meta: {
      title: '首页',
      description: '欢迎访问我们的网站',
      keywords: 'vue, ssr, 首页'
    }
  },
  {
    path: '/about',
    name: 'About',
    component: () => import('@/views/About.vue'),
    meta: {
      title: '关于我们',
      description: '了解更多关于我们的信息'
    }
  },
  {
    path: '/blog',
    name: 'Blog',
    component: () => import('@/views/Blog.vue'),
    children: [
      {
        path: '',
        name: 'BlogList',
        component: () => import('@/views/BlogList.vue')
      },
      {
        path: ':id',
        name: 'BlogPost',
        component: () => import('@/views/BlogPost.vue'),
        meta: {
          requiresData: true
        }
      }
    ]
  },
  {
    path: '/user',
    name: 'User',
    component: () => import('@/views/User.vue'),
    meta: {
      requiresAuth: true
    },
    children: [
      {
        path: 'profile',
        name: 'UserProfile',
        component: () => import('@/views/UserProfile.vue')
      },
      {
        path: 'settings',
        name: 'UserSettings',
        component: () => import('@/views/UserSettings.vue')
      }
    ]
  },
  {
    path: '/:pathMatch(.*)*',
    name: 'NotFound',
    component: () => import('@/views/NotFound.vue'),
    meta: {
      title: '页面未找到'
    }
  }
]

// 创建路由器工厂函数
export function createRouter() {
  const router = createRouter({
    // 服务端使用内存历史,客户端使用Web历史
    history: isServer ? createMemoryHistory() : createWebHistory(),
    routes,
    scrollBehavior(to, from, savedPosition) {
      // 只在客户端执行滚动行为
      if (!isServer) {
        if (savedPosition) {
          return savedPosition
        } else {
          return { top: 0 }
        }
      }
    }
  })
  
  return router
}

4.1.2 路由元信息管理

// src/utils/meta.js
export function updateMetaInfo(route) {
  if (typeof document === 'undefined') return
  
  const { meta } = route
  
  // 更新页面标题
  if (meta.title) {
    document.title = `${meta.title} - Vue SSR App`
  }
  
  // 更新meta标签
  updateMetaTag('description', meta.description)
  updateMetaTag('keywords', meta.keywords)
  
  // 更新Open Graph标签
  updateMetaTag('og:title', meta.title, 'property')
  updateMetaTag('og:description', meta.description, 'property')
  updateMetaTag('og:image', meta.image, 'property')
  
  // 更新Twitter Card标签
  updateMetaTag('twitter:title', meta.title, 'name')
  updateMetaTag('twitter:description', meta.description, 'name')
  updateMetaTag('twitter:image', meta.image, 'name')
}

function updateMetaTag(name, content, attribute = 'name') {
  if (!content) return
  
  let element = document.querySelector(`meta[${attribute}="${name}"]`)
  
  if (element) {
    element.setAttribute('content', content)
  } else {
    element = document.createElement('meta')
    element.setAttribute(attribute, name)
    element.setAttribute('content', content)
    document.head.appendChild(element)
  }
}

// 服务端meta信息生成
export function generateMetaTags(route) {
  const { meta } = route
  const tags = []
  
  if (meta.description) {
    tags.push(`<meta name="description" content="${meta.description}">`)
  }
  
  if (meta.keywords) {
    tags.push(`<meta name="keywords" content="${meta.keywords}">`)
  }
  
  if (meta.title) {
    tags.push(`<meta property="og:title" content="${meta.title}">`)
  }
  
  if (meta.description) {
    tags.push(`<meta property="og:description" content="${meta.description}">`)
  }
  
  if (meta.image) {
    tags.push(`<meta property="og:image" content="${meta.image}">`)
  }
  
  return tags.join('\n')
}

4.2 嵌套路由与布局

4.2.1 嵌套路由配置

// src/router/nested.js
export const nestedRoutes = [
  {
    path: '/admin',
    component: () => import('@/layouts/AdminLayout.vue'),
    meta: {
      requiresAuth: true,
      requiresAdmin: true
    },
    children: [
      {
        path: '',
        name: 'AdminDashboard',
        component: () => import('@/views/admin/Dashboard.vue')
      },
      {
        path: 'users',
        name: 'AdminUsers',
        component: () => import('@/views/admin/Users.vue')
      },
      {
        path: 'posts',
        name: 'AdminPosts',
        component: () => import('@/views/admin/Posts.vue'),
        children: [
          {
            path: '',
            name: 'AdminPostsList',
            component: () => import('@/views/admin/PostsList.vue')
          },
          {
            path: 'create',
            name: 'AdminPostsCreate',
            component: () => import('@/views/admin/PostsCreate.vue')
          },
          {
            path: ':id/edit',
            name: 'AdminPostsEdit',
            component: () => import('@/views/admin/PostsEdit.vue')
          }
        ]
      }
    ]
  },
  {
    path: '/shop',
    component: () => import('@/layouts/ShopLayout.vue'),
    children: [
      {
        path: '',
        name: 'ShopHome',
        component: () => import('@/views/shop/Home.vue')
      },
      {
        path: 'category/:categoryId',
        name: 'ShopCategory',
        component: () => import('@/views/shop/Category.vue'),
        children: [
          {
            path: '',
            name: 'ShopCategoryList',
            component: () => import('@/views/shop/CategoryList.vue')
          },
          {
            path: 'product/:productId',
            name: 'ShopProduct',
            component: () => import('@/views/shop/Product.vue')
          }
        ]
      }
    ]
  }
]

4.2.2 布局组件设计

<!-- src/layouts/AdminLayout.vue -->
<template>
  <div class="admin-layout">
    <AdminHeader />
    <div class="admin-content">
      <AdminSidebar />
      <main class="admin-main">
        <router-view />
      </main>
    </div>
    <AdminFooter />
  </div>
</template>

<script>
import AdminHeader from '@/components/admin/AdminHeader.vue'
import AdminSidebar from '@/components/admin/AdminSidebar.vue'
import AdminFooter from '@/components/admin/AdminFooter.vue'

export default {
  name: 'AdminLayout',
  components: {
    AdminHeader,
    AdminSidebar,
    AdminFooter
  },
  
  async asyncData({ store, route }) {
    // 预取管理员权限信息
    await store.dispatch('auth/checkAdminPermissions')
  }
}
</script>

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

.admin-content {
  flex: 1;
  display: flex;
}

.admin-main {
  flex: 1;
  padding: 20px;
  background-color: #f5f5f5;
}
</style>
<!-- src/layouts/ShopLayout.vue -->
<template>
  <div class="shop-layout">
    <ShopHeader />
    <ShopNavigation />
    <div class="shop-content">
      <aside class="shop-sidebar" v-if="showSidebar">
        <ShopFilters />
      </aside>
      <main class="shop-main">
        <router-view />
      </main>
    </div>
    <ShopFooter />
  </div>
</template>

<script>
import { computed } from 'vue'
import { useRoute } from 'vue-router'
import ShopHeader from '@/components/shop/ShopHeader.vue'
import ShopNavigation from '@/components/shop/ShopNavigation.vue'
import ShopFilters from '@/components/shop/ShopFilters.vue'
import ShopFooter from '@/components/shop/ShopFooter.vue'

export default {
  name: 'ShopLayout',
  components: {
    ShopHeader,
    ShopNavigation,
    ShopFilters,
    ShopFooter
  },
  
  setup() {
    const route = useRoute()
    
    const showSidebar = computed(() => {
      return ['ShopCategory', 'ShopCategoryList'].includes(route.name)
    })
    
    return {
      showSidebar
    }
  }
}
</script>

4.3 动态路由与参数

4.3.1 动态路由匹配

// src/router/dynamic.js
export const dynamicRoutes = [
  {
    // 用户资料页面
    path: '/user/:userId(\\d+)',
    name: 'UserProfile',
    component: () => import('@/views/UserProfile.vue'),
    props: true,
    meta: {
      requiresData: true
    }
  },
  {
    // 博客文章页面
    path: '/blog/:year(\\d{4})/:month(\\d{2})/:slug',
    name: 'BlogPost',
    component: () => import('@/views/BlogPost.vue'),
    props: route => ({
      year: parseInt(route.params.year),
      month: parseInt(route.params.month),
      slug: route.params.slug
    })
  },
  {
    // 产品页面,支持可选的变体参数
    path: '/product/:productId/:variant?',
    name: 'Product',
    component: () => import('@/views/Product.vue'),
    props: route => ({
      productId: route.params.productId,
      variant: route.params.variant || 'default'
    })
  },
  {
    // 多级分类页面
    path: '/category/:categories+',
    name: 'Category',
    component: () => import('@/views/Category.vue'),
    props: route => ({
      categories: route.params.categories.split('/')
    })
  }
]

4.3.2 路由参数验证

// src/utils/routeValidation.js
export const routeValidators = {
  userId: (value) => {
    const id = parseInt(value)
    return !isNaN(id) && id > 0
  },
  
  year: (value) => {
    const year = parseInt(value)
    return year >= 2000 && year <= new Date().getFullYear()
  },
  
  month: (value) => {
    const month = parseInt(value)
    return month >= 1 && month <= 12
  },
  
  slug: (value) => {
    return /^[a-z0-9-]+$/.test(value)
  }
}

// 路由守卫中使用验证
export function validateRouteParams(to, from, next) {
  const { params } = to
  
  for (const [key, value] of Object.entries(params)) {
    const validator = routeValidators[key]
    if (validator && !validator(value)) {
      next({ name: 'NotFound' })
      return
    }
  }
  
  next()
}

4.3.3 查询参数处理

// src/utils/queryParams.js
export function parseQueryParams(query) {
  const parsed = {}
  
  for (const [key, value] of Object.entries(query)) {
    if (Array.isArray(value)) {
      parsed[key] = value
    } else if (value === 'true') {
      parsed[key] = true
    } else if (value === 'false') {
      parsed[key] = false
    } else if (!isNaN(value) && value !== '') {
      parsed[key] = Number(value)
    } else {
      parsed[key] = value
    }
  }
  
  return parsed
}

export function buildQueryString(params) {
  const searchParams = new URLSearchParams()
  
  for (const [key, value] of Object.entries(params)) {
    if (value !== null && value !== undefined && value !== '') {
      if (Array.isArray(value)) {
        value.forEach(v => searchParams.append(key, v))
      } else {
        searchParams.append(key, value)
      }
    }
  }
  
  return searchParams.toString()
}

// 在组件中使用
export function useQueryParams() {
  const route = useRoute()
  const router = useRouter()
  
  const queryParams = computed(() => parseQueryParams(route.query))
  
  const updateQuery = (newParams) => {
    const query = { ...route.query, ...newParams }
    router.push({ query })
  }
  
  return {
    queryParams,
    updateQuery
  }
}

4.4 导航守卫详解

4.4.1 全局守卫

// src/router/guards.js
import { useAuthStore } from '@/stores/auth'
import { updateMetaInfo } from '@/utils/meta'

export function setupGlobalGuards(router) {
  // 全局前置守卫
  router.beforeEach(async (to, from, next) => {
    console.log('Navigation from', from.path, 'to', to.path)
    
    // 1. 验证路由参数
    if (!validateRouteParams(to)) {
      next({ name: 'NotFound' })
      return
    }
    
    // 2. 检查认证状态
    const authStore = useAuthStore()
    if (to.meta.requiresAuth && !authStore.isAuthenticated) {
      next({
        name: 'Login',
        query: { redirect: to.fullPath }
      })
      return
    }
    
    // 3. 检查管理员权限
    if (to.meta.requiresAdmin && !authStore.isAdmin) {
      next({ name: 'Forbidden' })
      return
    }
    
    // 4. 检查用户权限
    if (to.meta.permissions) {
      const hasPermission = to.meta.permissions.some(permission => 
        authStore.hasPermission(permission)
      )
      if (!hasPermission) {
        next({ name: 'Forbidden' })
        return
      }
    }
    
    next()
  })
  
  // 全局解析守卫
  router.beforeResolve(async (to, from, next) => {
    // 预取数据
    if (to.meta.requiresData) {
      try {
        await prefetchRouteData(to)
      } catch (error) {
        console.error('Failed to prefetch data:', error)
        next({ name: 'Error', params: { error: error.message } })
        return
      }
    }
    
    next()
  })
  
  // 全局后置钩子
  router.afterEach((to, from, failure) => {
    if (failure) {
      console.error('Navigation failed:', failure)
      return
    }
    
    // 更新页面标题和meta信息
    updateMetaInfo(to)
    
    // 发送页面访问统计
    if (typeof gtag !== 'undefined') {
      gtag('config', 'GA_MEASUREMENT_ID', {
        page_path: to.fullPath,
        page_title: to.meta.title
      })
    }
    
    // 滚动到顶部(客户端)
    if (typeof window !== 'undefined') {
      window.scrollTo(0, 0)
    }
  })
}

function validateRouteParams(route) {
  // 实现路由参数验证逻辑
  return true
}

async function prefetchRouteData(route) {
  // 实现数据预取逻辑
  const matchedComponents = route.matched
    .flatMap(record => Object.values(record.components || {}))
  
  const promises = matchedComponents
    .filter(component => component.asyncData)
    .map(component => component.asyncData({ route }))
  
  await Promise.all(promises)
}

4.4.2 路由独享守卫

// src/router/routeGuards.js
export const adminGuard = async (to, from, next) => {
  const authStore = useAuthStore()
  
  if (!authStore.isAuthenticated) {
    next({ name: 'Login' })
    return
  }
  
  if (!authStore.isAdmin) {
    next({ name: 'Forbidden' })
    return
  }
  
  // 检查管理员会话是否过期
  try {
    await authStore.validateAdminSession()
    next()
  } catch (error) {
    next({ name: 'Login' })
  }
}

export const userProfileGuard = async (to, from, next) => {
  const authStore = useAuthStore()
  const userId = to.params.userId
  
  // 检查用户是否有权限访问该资料页
  if (authStore.currentUser.id !== userId && !authStore.isAdmin) {
    next({ name: 'Forbidden' })
    return
  }
  
  next()
}

// 在路由配置中使用
const routes = [
  {
    path: '/admin',
    component: AdminLayout,
    beforeEnter: adminGuard,
    children: [...]
  },
  {
    path: '/user/:userId',
    component: UserProfile,
    beforeEnter: userProfileGuard
  }
]

4.4.3 组件内守卫

<!-- src/views/BlogPost.vue -->
<template>
  <article class="blog-post">
    <header>
      <h1>{{ post.title }}</h1>
      <div class="meta">
        <span>作者: {{ post.author }}</span>
        <span>发布时间: {{ formatDate(post.publishedAt) }}</span>
      </div>
    </header>
    <div class="content" v-html="post.content"></div>
  </article>
</template>

<script>
import { ref, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useBlogStore } from '@/stores/blog'

export default {
  name: 'BlogPost',
  
  setup() {
    const route = useRoute()
    const router = useRouter()
    const blogStore = useBlogStore()
    const post = ref(null)
    
    return {
      post,
      formatDate: (date) => new Date(date).toLocaleDateString()
    }
  },
  
  // 组件内前置守卫
  async beforeRouteEnter(to, from, next) {
    try {
      const blogStore = useBlogStore()
      const post = await blogStore.fetchPost(to.params.id)
      
      if (!post) {
        next({ name: 'NotFound' })
        return
      }
      
      // 检查文章是否已发布
      if (!post.published && !blogStore.canViewDraft) {
        next({ name: 'Forbidden' })
        return
      }
      
      next(vm => {
        vm.post = post
      })
    } catch (error) {
      next({ name: 'Error' })
    }
  },
  
  // 组件内更新守卫
  async beforeRouteUpdate(to, from, next) {
    if (to.params.id !== from.params.id) {
      try {
        const blogStore = useBlogStore()
        this.post = await blogStore.fetchPost(to.params.id)
        next()
      } catch (error) {
        next({ name: 'Error' })
      }
    } else {
      next()
    }
  },
  
  // 组件内离开守卫
  beforeRouteLeave(to, from, next) {
    if (this.hasUnsavedChanges) {
      const answer = window.confirm('您有未保存的更改,确定要离开吗?')
      if (answer) {
        next()
      } else {
        next(false)
      }
    } else {
      next()
    }
  }
}
</script>

4.5 路由懒加载与代码分割

4.5.1 基础懒加载

// src/router/lazy.js
// 基础懒加载
const Home = () => import('@/views/Home.vue')
const About = () => import('@/views/About.vue')

// 带有加载状态的懒加载
const BlogPost = () => import(
  /* webpackChunkName: "blog" */
  '@/views/BlogPost.vue'
)

// 预加载(在空闲时间加载)
const UserProfile = () => import(
  /* webpackChunkName: "user" */
  /* webpackPreload: true */
  '@/views/UserProfile.vue'
)

// 条件懒加载
const AdminDashboard = () => {
  if (process.env.NODE_ENV === 'development') {
    return import('@/views/admin/Dashboard.vue')
  } else {
    return import(
      /* webpackChunkName: "admin" */
      '@/views/admin/Dashboard.vue'
    )
  }
}

4.5.2 智能代码分割

// src/router/chunks.js
// 按功能模块分割
const blogRoutes = [
  {
    path: '/blog',
    component: () => import(
      /* webpackChunkName: "blog" */
      '@/views/Blog.vue'
    )
  },
  {
    path: '/blog/:id',
    component: () => import(
      /* webpackChunkName: "blog" */
      '@/views/BlogPost.vue'
    )
  }
]

// 按用户角色分割
const adminRoutes = [
  {
    path: '/admin',
    component: () => import(
      /* webpackChunkName: "admin" */
      '@/layouts/AdminLayout.vue'
    ),
    children: [
      {
        path: 'dashboard',
        component: () => import(
          /* webpackChunkName: "admin" */
          '@/views/admin/Dashboard.vue'
        )
      },
      {
        path: 'users',
        component: () => import(
          /* webpackChunkName: "admin-users" */
          '@/views/admin/Users.vue'
        )
      }
    ]
  }
]

// 按访问频率分割
const commonRoutes = [
  {
    path: '/',
    component: () => import(
      /* webpackChunkName: "common" */
      '@/views/Home.vue'
    )
  },
  {
    path: '/about',
    component: () => import(
      /* webpackChunkName: "common" */
      '@/views/About.vue'
    )
  }
]

const rareRoutes = [
  {
    path: '/privacy',
    component: () => import(
      /* webpackChunkName: "legal" */
      '@/views/Privacy.vue'
    )
  },
  {
    path: '/terms',
    component: () => import(
      /* webpackChunkName: "legal" */
      '@/views/Terms.vue'
    )
  }
]

4.5.3 加载状态处理

<!-- src/components/AsyncRouteWrapper.vue -->
<template>
  <div class="async-route-wrapper">
    <Suspense>
      <template #default>
        <router-view />
      </template>
      <template #fallback>
        <div class="loading-container">
          <div class="loading-spinner"></div>
          <p>正在加载页面...</p>
        </div>
      </template>
    </Suspense>
  </div>
</template>

<script>
export default {
  name: 'AsyncRouteWrapper'
}
</script>

<style scoped>
.loading-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 200px;
}

.loading-spinner {
  width: 40px;
  height: 40px;
  border: 4px solid #f3f3f3;
  border-top: 4px solid #3498db;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}
</style>

4.6 路由过渡动画

4.6.1 基础过渡效果

<!-- src/App.vue -->
<template>
  <div id="app">
    <Header />
    <router-view v-slot="{ Component, route }">
      <transition
        :name="getTransitionName(route)"
        mode="out-in"
        @before-enter="onBeforeEnter"
        @enter="onEnter"
        @leave="onLeave"
      >
        <component :is="Component" :key="route.path" />
      </transition>
    </router-view>
    <Footer />
  </div>
</template>

<script>
import { useRoute } from 'vue-router'

export default {
  name: 'App',
  
  setup() {
    const route = useRoute()
    
    const getTransitionName = (route) => {
      // 根据路由元信息决定过渡效果
      if (route.meta.transition) {
        return route.meta.transition
      }
      
      // 根据路由深度决定过渡方向
      const depth = route.path.split('/').length
      return depth > 2 ? 'slide-left' : 'fade'
    }
    
    const onBeforeEnter = (el) => {
      console.log('Before enter transition')
    }
    
    const onEnter = (el, done) => {
      console.log('Enter transition')
      done()
    }
    
    const onLeave = (el, done) => {
      console.log('Leave transition')
      done()
    }
    
    return {
      getTransitionName,
      onBeforeEnter,
      onEnter,
      onLeave
    }
  }
}
</script>

<style>
/* 淡入淡出效果 */
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.3s ease;
}

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

/* 滑动效果 */
.slide-left-enter-active,
.slide-left-leave-active {
  transition: transform 0.3s ease;
}

.slide-left-enter-from {
  transform: translateX(100%);
}

.slide-left-leave-to {
  transform: translateX(-100%);
}

/* 缩放效果 */
.scale-enter-active,
.scale-leave-active {
  transition: transform 0.3s ease;
}

.scale-enter-from,
.scale-leave-to {
  transform: scale(0.8);
}
</style>

4.6.2 高级过渡效果

// src/utils/transitions.js
export const transitionMixin = {
  data() {
    return {
      transitionName: 'fade'
    }
  },
  
  watch: {
    '$route'(to, from) {
      const toDepth = to.path.split('/').length
      const fromDepth = from.path.split('/').length
      
      if (toDepth < fromDepth) {
        this.transitionName = 'slide-right'
      } else if (toDepth > fromDepth) {
        this.transitionName = 'slide-left'
      } else {
        this.transitionName = 'fade'
      }
    }
  }
}

// 页面级过渡效果
export const pageTransitions = {
  // 首页特殊效果
  home: {
    enterClass: 'animate__animated animate__fadeInUp',
    leaveClass: 'animate__animated animate__fadeOutDown'
  },
  
  // 管理页面滑动效果
  admin: {
    enterClass: 'slide-in-right',
    leaveClass: 'slide-out-left'
  },
  
  // 模态框效果
  modal: {
    enterClass: 'zoom-in',
    leaveClass: 'zoom-out'
  }
}

4.7 路由缓存策略

4.7.1 组件缓存

<!-- src/components/CachedRouterView.vue -->
<template>
  <router-view v-slot="{ Component, route }">
    <keep-alive :include="cachedComponents" :max="maxCacheSize">
      <component :is="Component" :key="getCacheKey(route)" />
    </keep-alive>
  </router-view>
</template>

<script>
import { ref, computed } from 'vue'
import { useRoute } from 'vue-router'

export default {
  name: 'CachedRouterView',
  
  setup() {
    const route = useRoute()
    const maxCacheSize = 10
    
    // 需要缓存的组件列表
    const cachedComponents = ref([
      'BlogList',
      'UserProfile',
      'ProductList'
    ])
    
    // 生成缓存键
    const getCacheKey = (route) => {
      if (route.meta.cacheKey) {
        return typeof route.meta.cacheKey === 'function'
          ? route.meta.cacheKey(route)
          : route.meta.cacheKey
      }
      return route.fullPath
    }
    
    return {
      cachedComponents,
      maxCacheSize,
      getCacheKey
    }
  }
}
</script>

4.7.2 智能缓存管理

// src/utils/routeCache.js
class RouteCache {
  constructor(maxSize = 20) {
    this.cache = new Map()
    this.maxSize = maxSize
    this.accessOrder = []
  }
  
  get(key) {
    if (this.cache.has(key)) {
      // 更新访问顺序
      this.updateAccessOrder(key)
      return this.cache.get(key)
    }
    return null
  }
  
  set(key, value) {
    if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
      // 删除最少使用的缓存
      const lru = this.accessOrder.shift()
      this.cache.delete(lru)
    }
    
    this.cache.set(key, value)
    this.updateAccessOrder(key)
  }
  
  updateAccessOrder(key) {
    const index = this.accessOrder.indexOf(key)
    if (index > -1) {
      this.accessOrder.splice(index, 1)
    }
    this.accessOrder.push(key)
  }
  
  clear() {
    this.cache.clear()
    this.accessOrder = []
  }
  
  delete(key) {
    this.cache.delete(key)
    const index = this.accessOrder.indexOf(key)
    if (index > -1) {
      this.accessOrder.splice(index, 1)
    }
  }
}

export const routeCache = new RouteCache()

// 缓存中间件
export function setupRouteCache(router) {
  router.beforeEach((to, from, next) => {
    // 检查是否有缓存的数据
    const cacheKey = to.fullPath
    const cachedData = routeCache.get(cacheKey)
    
    if (cachedData && to.meta.useCache) {
      to.meta.cachedData = cachedData
    }
    
    next()
  })
  
  router.afterEach((to) => {
    // 缓存页面数据
    if (to.meta.shouldCache) {
      const cacheKey = to.fullPath
      const dataToCache = {
        timestamp: Date.now(),
        data: to.meta.pageData
      }
      routeCache.set(cacheKey, dataToCache)
    }
  })
}

4.8 本章小结

4.8.1 核心要点

  1. 路由配置:服务端使用内存历史,客户端使用Web历史
  2. 嵌套路由:合理设计布局组件和子路由结构
  3. 动态路由:支持参数验证和查询参数处理
  4. 导航守卫:实现权限控制和数据预取
  5. 懒加载:按需加载组件,优化性能
  6. 过渡动画:提升用户体验
  7. 缓存策略:智能缓存组件和数据

4.8.2 最佳实践

  • 合理使用路由元信息管理页面属性
  • 实现完善的权限控制系统
  • 优化代码分割策略
  • 设计流畅的页面过渡效果
  • 建立智能的缓存机制

4.8.3 下章预告

下一章我们将学习状态管理与数据流: - Vuex/Pinia在SSR中的应用 - 服务端状态同步 - 数据预取策略 - 状态持久化 - 实时数据更新


练习作业:

  1. 创建一个包含嵌套路由的管理后台
  2. 实现动态路由参数验证
  3. 配置路由懒加载和代码分割
  4. 添加页面过渡动画效果
  5. 实现智能的路由缓存策略

下一章: 状态管理与数据流