编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • Android提升进阶

  • iOS开发和进阶

  • Web开发和进阶

    • README
    • HTML工具手册

    • TypeScript入门

    • Vue高级进阶

      • 基础入门
      • 组件开发
      • 响应式系统
      • 路由管理
        • 4.1 路由基础
          • 4.1.1 什么是前端路由
          • 4.1.2 安装Vue Router
          • 4.1.3 配置路由表
          • 4.1.4 RouterView和RouterLink
          • 4.1.5 综合案例与思考
        • 4.2 路由模式
          • 4.2.1 Hash模式
          • 4.2.2 History模式
          • 4.2.3 两种模式对比
          • 4.2.4 综合案例与思考
        • 4.3 动态路由
          • 4.3.1 路由参数
          • 4.3.2 查询参数
          • 4.3.3 嵌套路由
          • 4.3.4 命名路由和命名视图
          • 4.3.5 综合案例与思考
        • 4.4 编程式导航
          • 4.4.1 useRouter和useRoute
          • 4.4.2 push和replace
          • 4.4.3 前进后退
          • 4.4.4 综合案例与思考
        • 4.5 导航守卫
          • 4.5.1 全局前置守卫
          • 4.5.2 路由独享守卫
          • 4.5.3 组件内守卫
          • 4.5.4 完整导航流程
          • 4.5.5 综合案例与思考
        • 4.6 路由进阶
          • 4.6.1 路由懒加载
          • 4.6.2 路由元信息meta
          • 4.6.3 滚动行为
          • 4.6.4 404和重定向
          • 4.6.5 综合案例与思考
      • 状态管理
      • 组合式API
      • 工程化实战
  • Linux应用开发

  • Apps
  • Web开发和进阶
  • Vue高级进阶
杨充
2026-04-23
目录

路由管理

# 04.路由管理

# 目录介绍

  • 4.1 路由基础
    • 4.1.1 什么是前端路由
    • 4.1.2 安装Vue Router
    • 4.1.3 配置路由表
    • 4.1.4 RouterView和RouterLink
    • 4.1.5 综合案例与思考
  • 4.2 路由模式
    • 4.2.1 Hash模式
    • 4.2.2 History模式
    • 4.2.3 两种模式对比
    • 4.2.4 综合案例与思考
  • 4.3 动态路由
    • 4.3.1 路由参数
    • 4.3.2 查询参数
    • 4.3.3 嵌套路由
    • 4.3.4 命名路由和命名视图
    • 4.3.5 综合案例与思考
  • 4.4 编程式导航
    • 4.4.1 useRouter和useRoute
    • 4.4.2 push和replace
    • 4.4.3 前进后退
    • 4.4.4 综合案例与思考
  • 4.5 导航守卫
    • 4.5.1 全局前置守卫
    • 4.5.2 路由独享守卫
    • 4.5.3 组件内守卫
    • 4.5.4 完整导航流程
    • 4.5.5 综合案例与思考
  • 4.6 路由进阶
    • 4.6.1 路由懒加载
    • 4.6.2 路由元信息meta
    • 4.6.3 滚动行为
    • 4.6.4 404和重定向
    • 4.6.5 综合案例与思考

# 4.1 路由基础

# 4.1.1 什么是前端路由

前端路由是单页应用(SPA)的核心技术,实现URL变化时不刷新页面,而是切换展示不同的组件。

传统多页应用(MPA):
  /home   → 请求服务器 → 返回 home.html → 整页刷新
  /about  → 请求服务器 → 返回 about.html → 整页刷新

单页应用(SPA):
  /home   → 前端路由匹配 → 渲染 Home 组件 → 不刷新页面
  /about  → 前端路由匹配 → 渲染 About 组件 → 不刷新页面
1
2
3
4
5
6
7

# 4.1.2 安装Vue Router

# 安装 Vue Router 4(适配 Vue 3)
npm install vue-router@4
1
2

# 4.1.3 配置路由表

// src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '@/views/HomeView.vue'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: HomeView
    },
    {
      path: '/about',
      name: 'about',
      // 路由懒加载:只有访问时才加载组件
      component: () => import('@/views/AboutView.vue')
    },
    {
      path: '/user/:id',
      name: 'user',
      component: () => import('@/views/UserView.vue')
    }
  ]
})

export default router
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// src/main.ts 中注册路由
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

const app = createApp(App)
app.use(router)  // 注册路由插件
app.mount('#app')
1
2
3
4
5
6
7
8

# 4.1.4 RouterView和RouterLink

<!-- src/App.vue -->
<template>
  <nav>
    <!-- RouterLink:声明式导航(生成 <a> 标签) -->
    <RouterLink to="/">首页</RouterLink>
    <RouterLink to="/about">关于</RouterLink>
    <RouterLink :to="{ name: 'user', params: { id: 1 } }">用户</RouterLink>
  </nav>

  <!-- RouterView:路由出口,匹配的组件渲染在这里 -->
  <RouterView />
</template>

<script setup lang="ts">
import { RouterLink, RouterView } from 'vue-router'
</script>

<style>
/* 激活状态的链接样式 */
.router-link-active {
  color: #42b883;
  font-weight: bold;
}
/* 精确匹配的激活样式 */
.router-link-exact-active {
  border-bottom: 2px solid #42b883;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 4.1.5 综合案例与思考

综合案例:带导航的基础路由应用

// router/index.ts
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: '/', name: 'home', component: () => import('@/views/Home.vue') },
    { path: '/products', name: 'products', component: () => import('@/views/Products.vue') },
    { path: '/contact', name: 'contact', component: () => import('@/views/Contact.vue') }
  ]
})

export default router
1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- App.vue -->
<template>
  <div class="app">
    <nav class="navbar">
      <div class="brand">MyApp</div>
      <div class="nav-links">
        <RouterLink
          v-for="link in navLinks"
          :key="link.path"
          :to="link.path"
          class="nav-link"
        >
          {{ link.label }}
        </RouterLink>
      </div>
    </nav>
    <main class="content">
      <RouterView />
    </main>
  </div>
</template>

<script setup lang="ts">
const navLinks = [
  { path: '/', label: '首页' },
  { path: '/products', label: '产品' },
  { path: '/contact', label: '联系' }
]
</script>

<style>
.navbar { display: flex; justify-content: space-between; padding: 16px 24px; background: #1a1a2e; }
.brand { color: white; font-size: 20px; font-weight: bold; }
.nav-link { color: #ccc; margin-left: 24px; text-decoration: none; }
.nav-link.router-link-active { color: #42b883; }
.content { padding: 24px; }
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

思考题:

  1. 前端路由和后端路由有什么本质区别?SPA的路由不请求服务器,页面内容从哪里来?
  2. RouterLink 和普通 <a> 标签有什么区别?为什么要用 RouterLink?
  3. 路由懒加载 () => import(...) 的原理是什么?它能带来什么性能提升?

# 4.2 路由模式

# 4.2.1 Hash模式

URL 中带 # 号,通过 hashchange 事件监听变化:

import { createRouter, createWebHashHistory } from 'vue-router'

const router = createRouter({
  history: createWebHashHistory(),  // Hash 模式
  routes: [...]
})

// URL 形式:http://example.com/#/about
// # 后面的内容不会发送到服务器
1
2
3
4
5
6
7
8
9

# 4.2.2 History模式

使用 HTML5 History API,URL 更美观:

import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),  // History 模式
  routes: [...]
})

// URL 形式:http://example.com/about
// 需要服务器配置兜底(所有路径返回 index.html)
1
2
3
4
5
6
7
8
9

Nginx 配置示例:

location / {
  try_files $uri $uri/ /index.html;
}
1
2
3

# 4.2.3 两种模式对比

对比项 Hash模式 History模式
URL形式 /#/about /about
服务器配置 不需要 需要配置兜底
SEO 差(搜索引擎不识别#后内容) 好
兼容性 所有浏览器 IE10+
美观度 差 好
推荐场景 后台管理系统、不需要SEO 面向用户的网站

# 4.2.4 综合案例与思考

思考题:

  1. 为什么 Hash 模式不需要服务器配置?# 在 URL 中有什么特殊含义?
  2. History 模式下刷新页面返回404,是因为什么?如何解决?
  3. 如果你的项目需要 SSR(服务端渲染),应该选择哪种路由模式?

# 4.3 动态路由

# 4.3.1 路由参数

使用 :param 定义动态路由参数:

// 路由配置
const routes = [
  { path: '/user/:id', name: 'user', component: UserView },
  { path: '/post/:category/:id', name: 'post', component: PostView }
]
1
2
3
4
5
<!-- UserView.vue -->
<template>
  <h2>用户详情:{{ userId }}</h2>
</template>

<script setup lang="ts">
import { useRoute, watch } from 'vue-router'

const route = useRoute()
const userId = route.params.id  // 获取路由参数

// 注意:同一组件的路由参数变化时,组件不会重新创建
// 需要用 watch 监听参数变化
watch(
  () => route.params.id,
  (newId) => {
    console.log('用户ID变化:', newId)
    // 重新请求数据
  }
)
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 4.3.2 查询参数

使用 ?key=value 传递查询参数:

<template>
  <!-- 传递查询参数 -->
  <RouterLink :to="{ path: '/search', query: { keyword: 'vue', page: 1 } }">
    搜索
  </RouterLink>
  <!-- 生成: /search?keyword=vue&page=1 -->
</template>
1
2
3
4
5
6
7
<!-- SearchView.vue -->
<script setup lang="ts">
import { useRoute } from 'vue-router'

const route = useRoute()
const keyword = route.query.keyword   // 'vue'
const page = route.query.page         // '1'(注意:query值都是字符串)
</script>
1
2
3
4
5
6
7
8

# 4.3.3 嵌套路由

通过 children 配置嵌套路由:

const routes = [
  {
    path: '/dashboard',
    component: DashboardLayout,
    children: [
      { path: '', name: 'dashboard', component: Overview },       // /dashboard
      { path: 'analytics', component: Analytics },  // /dashboard/analytics
      { path: 'settings', component: Settings }     // /dashboard/settings
    ]
  }
]
1
2
3
4
5
6
7
8
9
10
11
<!-- DashboardLayout.vue -->
<template>
  <div class="dashboard">
    <aside class="sidebar">
      <RouterLink to="/dashboard">概览</RouterLink>
      <RouterLink to="/dashboard/analytics">分析</RouterLink>
      <RouterLink to="/dashboard/settings">设置</RouterLink>
    </aside>
    <main class="main">
      <!-- 嵌套路由的出口 -->
      <RouterView />
    </main>
  </div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 4.3.4 命名路由和命名视图

// 命名路由:通过 name 导航
const routes = [
  { path: '/user/:id', name: 'user-detail', component: UserDetail }
]

// 使用命名路由
// <RouterLink :to="{ name: 'user-detail', params: { id: 123 } }">用户</RouterLink>
1
2
3
4
5
6
7
// 命名视图:一个页面多个 RouterView
const routes = [
  {
    path: '/admin',
    components: {           // 注意是 components(复数)
      default: AdminMain,
      sidebar: AdminSidebar,
      header: AdminHeader
    }
  }
]
1
2
3
4
5
6
7
8
9
10
11
<template>
  <RouterView name="header" />
  <RouterView name="sidebar" />
  <RouterView />  <!-- default -->
</template>
1
2
3
4
5

# 4.3.5 综合案例与思考

综合案例:博客文章系统路由

// router/index.ts
const routes = [
  { path: '/', name: 'home', component: () => import('@/views/Home.vue') },
  {
    path: '/blog',
    component: () => import('@/views/BlogLayout.vue'),
    children: [
      { path: '', name: 'blog-list', component: () => import('@/views/BlogList.vue') },
      { path: ':id', name: 'blog-detail', component: () => import('@/views/BlogDetail.vue') },
      { path: 'category/:category', name: 'blog-category', component: () => import('@/views/BlogCategory.vue') }
    ]
  }
]
1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- BlogDetail.vue -->
<template>
  <article v-if="post">
    <h1>{{ post.title }}</h1>
    <p class="meta">分类: {{ post.category }} | ID: {{ postId }}</p>
    <div v-html="post.content"></div>
    <div class="nav">
      <RouterLink v-if="prevId" :to="{ name: 'blog-detail', params: { id: prevId } }">
        ← 上一篇
      </RouterLink>
      <RouterLink v-if="nextId" :to="{ name: 'blog-detail', params: { id: nextId } }">
        下一篇 →
      </RouterLink>
    </div>
  </article>
</template>

<script setup lang="ts">
import { ref, watch } from 'vue'
import { useRoute } from 'vue-router'

const route = useRoute()
const post = ref<any>(null)
const postId = ref('')
const prevId = ref('')
const nextId = ref('')

const fetchPost = async (id: string) => {
  postId.value = id
  // 模拟API请求
  post.value = {
    title: `文章 ${id}`,
    category: '前端',
    content: `<p>这是文章 ${id} 的内容...</p>`
  }
  prevId.value = Number(id) > 1 ? String(Number(id) - 1) : ''
  nextId.value = String(Number(id) + 1)
}

// 监听路由参数变化
watch(() => route.params.id, (newId) => {
  if (newId) fetchPost(newId as string)
}, { immediate: true })
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

思考题:

  1. 路由参数(params)和查询参数(query)有什么区别?各适用于什么场景?
  2. 嵌套路由中子路由的 path 不加 / 开头和加 / 开头有什么区别?
  3. 当路由参数变化但组件相同时(如从 /user/1 到 /user/2),Vue为什么不重新创建组件?

# 4.4 编程式导航

# 4.4.1 useRouter和useRoute

<script setup lang="ts">
import { useRouter, useRoute } from 'vue-router'

// useRouter:获取路由器实例,用于导航
const router = useRouter()

// useRoute:获取当前路由信息(响应式)
const route = useRoute()

console.log(route.path)       // 当前路径
console.log(route.params)     // 路由参数
console.log(route.query)      // 查询参数
console.log(route.name)       // 路由名称
console.log(route.meta)       // 路由元信息
console.log(route.fullPath)   // 完整路径(含query和hash)
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 4.4.2 push和replace

<script setup lang="ts">
import { useRouter } from 'vue-router'

const router = useRouter()

// push:跳转并添加历史记录
const goToAbout = () => {
  router.push('/about')
}

// 使用对象形式(推荐)
const goToUser = (id: number) => {
  router.push({ name: 'user', params: { id } })
}

// 带查询参数
const goToSearch = (keyword: string) => {
  router.push({ path: '/search', query: { keyword, page: 1 } })
}

// replace:跳转但不添加历史记录(用户点击后退不会回到当前页)
const redirectToLogin = () => {
  router.replace('/login')
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 4.4.3 前进后退

<script setup lang="ts">
import { useRouter } from 'vue-router'

const router = useRouter()

// 后退一步
const goBack = () => router.go(-1)

// 前进一步
const goForward = () => router.go(1)

// 后退两步
const goBack2 = () => router.go(-2)

// 简写
const back = () => router.back()     // 等价于 router.go(-1)
const forward = () => router.forward() // 等价于 router.go(1)
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 4.4.4 综合案例与思考

综合案例:带面包屑的页面导航

<template>
  <div>
    <nav class="breadcrumb">
      <span v-for="(crumb, i) in breadcrumbs" :key="i">
        <a v-if="i < breadcrumbs.length - 1" @click="goTo(crumb.path)" href="#">
          {{ crumb.label }}
        </a>
        <span v-else>{{ crumb.label }}</span>
        <span v-if="i < breadcrumbs.length - 1"> / </span>
      </span>
    </nav>

    <div class="actions">
      <button @click="router.back()">← 返回</button>
      <button @click="goHome">首页</button>
      <button @click="goToUser(42)">用户42</button>
      <button @click="search('Vue3')">搜索Vue3</button>
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import { useRouter, useRoute } from 'vue-router'

const router = useRouter()
const route = useRoute()

const breadcrumbs = computed(() => {
  const crumbs = [{ label: '首页', path: '/' }]
  const segments = route.path.split('/').filter(Boolean)
  let path = ''
  segments.forEach(segment => {
    path += '/' + segment
    crumbs.push({ label: segment, path })
  })
  return crumbs
})

const goTo = (path: string) => router.push(path)
const goHome = () => router.push('/')
const goToUser = (id: number) => router.push({ name: 'user', params: { id } })
const search = (keyword: string) => router.push({ path: '/search', query: { keyword } })
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

思考题:

  1. router.push 和 router.replace 的区别是什么?登录成功后应该用哪个跳转?
  2. useRoute() 返回的是响应式对象,这意味着什么?为什么需要用 watch 监听它的变化?
  3. 编程式导航和声明式导航(RouterLink)各适用于什么场景?

# 4.5 导航守卫

# 4.5.1 全局前置守卫

在路由跳转前执行逻辑(如权限验证):

// router/index.ts
router.beforeEach((to, from) => {
  const isAuthenticated = !!localStorage.getItem('token')

  // 如果访问需要登录的页面,且未登录,重定向到登录页
  if (to.meta.requiresAuth && !isAuthenticated) {
    return { name: 'login', query: { redirect: to.fullPath } }
  }

  // 如果已登录,不允许再访问登录页
  if (to.name === 'login' && isAuthenticated) {
    return { name: 'home' }
  }

  // 返回 true 或 undefined 表示放行
})

// 全局后置守卫(不能改变导航,用于分析、修改标题等)
router.afterEach((to) => {
  document.title = (to.meta.title as string) || '默认标题'
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 4.5.2 路由独享守卫

在路由配置中直接定义守卫:

const routes = [
  {
    path: '/admin',
    component: AdminView,
    beforeEnter: (to, from) => {
      const userRole = getUserRole()
      if (userRole !== 'admin') {
        alert('权限不足')
        return false  // 取消导航
      }
    }
  }
]
1
2
3
4
5
6
7
8
9
10
11
12
13

# 4.5.3 组件内守卫

在组件中使用导航守卫:

<script setup lang="ts">
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'

// 离开当前路由前(如:表单未保存时提示)
onBeforeRouteLeave((to, from) => {
  if (hasUnsavedChanges.value) {
    const answer = confirm('有未保存的修改,确认离开吗?')
    if (!answer) return false  // 取消导航
  }
})

// 当前路由参数变化时(如:/user/1 → /user/2)
onBeforeRouteUpdate((to, from) => {
  console.log('路由参数变化:', from.params.id, '→', to.params.id)
})
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 4.5.4 完整导航流程

完整的导航解析流程:

1. 触发导航
2. ✓ 失活组件调用 onBeforeRouteLeave
3. ✓ 调用全局 beforeEach
4. ✓ 重用组件调用 onBeforeRouteUpdate
5. ✓ 路由配置中 beforeEnter
6. ✓ 解析异步路由组件
7. ✓ 激活组件中 beforeRouteEnter(Options API)
8. ✓ 调用全局 beforeResolve
9. ✓ 导航确认
10. ✓ 调用全局 afterEach
11. ✓ DOM 更新
1
2
3
4
5
6
7
8
9
10
11
12
13

# 4.5.5 综合案例与思考

综合案例:完整的路由权限系统

// router/index.ts
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/login',
      name: 'login',
      component: () => import('@/views/Login.vue'),
      meta: { title: '登录', guest: true }
    },
    {
      path: '/',
      name: 'home',
      component: () => import('@/views/Home.vue'),
      meta: { title: '首页' }
    },
    {
      path: '/dashboard',
      name: 'dashboard',
      component: () => import('@/views/Dashboard.vue'),
      meta: { title: '控制台', requiresAuth: true }
    },
    {
      path: '/admin',
      name: 'admin',
      component: () => import('@/views/Admin.vue'),
      meta: { title: '管理', requiresAuth: true, roles: ['admin'] }
    }
  ]
})

// 全局前置守卫
router.beforeEach((to, from) => {
  const token = localStorage.getItem('token')
  const userRole = localStorage.getItem('role') || 'user'

  // 需要登录但未登录
  if (to.meta.requiresAuth && !token) {
    return { name: 'login', query: { redirect: to.fullPath } }
  }

  // 已登录访问登录页
  if (to.meta.guest && token) {
    return { name: 'home' }
  }

  // 角色权限检查
  const allowedRoles = to.meta.roles as string[] | undefined
  if (allowedRoles && !allowedRoles.includes(userRole)) {
    alert('权限不足')
    return false
  }
})

// 全局后置守卫:设置页面标题
router.afterEach((to) => {
  document.title = `${to.meta.title || '应用'} - MyApp`
})

export default router
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

思考题:

  1. 全局守卫、路由独享守卫、组件内守卫各适用于什么场景?如何选择?
  2. 导航守卫中返回 false 和返回 { name: 'login' } 有什么区别?
  3. 如何在导航守卫中实现"登录后跳回原页面"的功能?

# 4.6 路由进阶

# 4.6.1 路由懒加载

// 每个路由页面打包为独立的JS文件,按需加载
const routes = [
  {
    path: '/dashboard',
    // 使用动态 import,Vite 自动进行代码分割
    component: () => import('@/views/Dashboard.vue')
  },
  {
    path: '/settings',
    // 使用注释为chunk命名
    component: () => import(/* webpackChunkName: "settings" */ '@/views/Settings.vue')
  }
]
1
2
3
4
5
6
7
8
9
10
11
12
13

# 4.6.2 路由元信息meta

const routes = [
  {
    path: '/admin',
    component: AdminView,
    meta: {
      requiresAuth: true,    // 需要登录
      roles: ['admin'],       // 允许的角色
      title: '管理后台',       // 页面标题
      keepAlive: true,        // 是否缓存组件
      transition: 'fade'      // 转场动画
    }
  }
]

// 在组件或守卫中访问 meta
const route = useRoute()
console.log(route.meta.title)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 4.6.3 滚动行为

const router = createRouter({
  history: createWebHistory(),
  routes: [...],
  scrollBehavior(to, from, savedPosition) {
    // 浏览器后退/前进时恢复位置
    if (savedPosition) {
      return savedPosition
    }
    // 跳转到锚点
    if (to.hash) {
      return { el: to.hash, behavior: 'smooth' }
    }
    // 默认回到顶部
    return { top: 0, behavior: 'smooth' }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 4.6.4 404和重定向

const routes = [
  // 重定向
  { path: '/home', redirect: '/' },
  { path: '/old-path', redirect: { name: 'new-route' } },

  // 别名
  { path: '/user', component: User, alias: '/people' },

  // 404 页面(匹配所有未定义的路由,必须放在最后)
  {
    path: '/:pathMatch(.*)*',
    name: 'not-found',
    component: () => import('@/views/NotFound.vue')
  }
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 4.6.5 综合案例与思考

综合案例:带过渡动画和缓存的路由

<!-- App.vue -->
<template>
  <nav>
    <RouterLink to="/">首页</RouterLink>
    <RouterLink to="/list">列表</RouterLink>
    <RouterLink to="/detail/1">详情</RouterLink>
  </nav>

  <RouterView v-slot="{ Component, route }">
    <!-- 路由过渡动画 -->
    <Transition :name="route.meta.transition as string || 'fade'" mode="out-in">
      <!-- KeepAlive 缓存组件 -->
      <KeepAlive :include="cachedViews">
        <component :is="Component" :key="route.path" />
      </KeepAlive>
    </Transition>
  </RouterView>
</template>

<script setup lang="ts">
import { ref } from 'vue'

// 需要缓存的组件名称列表
const cachedViews = ref(['ListView'])
</script>

<style>
.fade-enter-active, .fade-leave-active {
  transition: opacity 0.3s ease;
}
.fade-enter-from, .fade-leave-to {
  opacity: 0;
}

.slide-enter-active, .slide-leave-active {
  transition: transform 0.3s ease;
}
.slide-enter-from {
  transform: translateX(100%);
}
.slide-leave-to {
  transform: translateX(-100%);
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

思考题:

  1. 路由懒加载的原理是什么?它是如何实现"按需加载"的?
  2. KeepAlive 缓存路由组件时,生命周期会有什么变化?onActivated 和 onDeactivated 是什么?
  3. 如何实现"列表页进入详情页用右滑动画,返回列表页用左滑动画"的效果?
上次更新: 2026/06/10, 11:13:41
响应式系统
状态管理

← 响应式系统 状态管理→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式