Vue - 路由

Vue 3 中的路由系统主要依赖官方的 vue-router 库(当前最新稳定版是 4.x,专为 Vue 3 设计),用于实现单页应用(SPA)的页面跳转和状态管理。以下是 Vue 3 路由的详细解析:

一、核心概念

  1. 路由(Route):对应单页应用中的一个 “页面”,由路径(path)和组件(component)组成。
  2. 路由器(Router):管理所有路由的实例,通过 createRouter 创建。
  3. 路由出口(Router View):组件 <RouterView />,用于渲染当前路由对应的组件。
  4. 路由链接(Router Link):组件 <RouterLink />,用于生成跳转链接(替代 <a> 标签,避免页面刷新)。

二、基础用法

安装

npm install vue-router@4  # Vue 3 需使用 4.x 版本

image

快速入门

1.创建文件router文件夹下面的index.ts

// src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '@/components/Home.vue'
import NewsView from '@/components/News.vue'
import AboutView from '@/components/About.vue'

const router = createRouter({
  history: createWebHistory(), //路由器工作模式
  routes: [
    {
      path: '/',
      name: 'home',
      component: HomeView,
    },
    {
      path: '/news',
      name: 'news',
      component: NewsView,
    },
    {
      path: '/about',
      name: 'about',
      component: AboutView,
    }
  ],
})

// 导出路由
export default router

2.创建 Home.vue、About.vue、News.vue,地址 src\components\Home.vue

<template>
    <div>
        <h1>Home</h1>
    </div>
</template>

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

export default defineComponent({
    setup() {
        return {
            msg: 'Hello Vue 3!',
        }
    },
})
</script>

3.src\main.ts 配置使用路由

import { createApp } from 'vue'
// import { createPinia } from 'pinia'

import App from './App.vue'
import router from './router'

const app = createApp(App)

// app.use(createPinia())
app.use(router)

app.mount('#app')

4.app.vue 配置RouterView

RouterView:当前路由对应的组件会渲染在这里

src\App.vue 展示区显示

<template>
    <div class="app">
        <h2 class="title">Vue路由测试</h2>
        <!-- 导航区 -->
        <div class="navigate">
            <a href="#">首页</a>
            <a href="#">新闻</a>
            <a href="#">关于</a>
        </div>
        <!-- 展示区 -->
        <div class="main-content">
            <RouterView />
        </div>
    </div>
</template>

<script lang="ts" setup name="App">
import { RouterView } from 'vue-router';
</script>

5.最后我们把跳转再加上RouterLink跳转,active-class 添加点击效果

<template>
    <div class="app">
        <h2 class="title">Vue路由测试</h2>
        <!-- 导航区 -->
        <div class="navigate">
            <RouterLink to="/" active-class="router-active">首页</RouterLink>
            <RouterLink to="/news" active-class="router-active">新闻</RouterLink>
            <RouterLink to="/about" active-class="router-active">关于</RouterLink>
        </div>
        <!-- 展示区 -->
        <div class="main-content">
            <RouterView />
        </div>
    </div>
</template>

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

点击添加效果:

.router-active {
    color: red !important;
    background-color: aquamarine !important;
    font-weight: bold;
}

三、两点注意

1.路由组件通常存放在pagesviews文件来一般组件通常存放在components文件夹。

2.通过点击导航,视觉效果上“消失”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。

四、路由工作模式

  1. history 模式

    优点: URL 更加美观,不带有 #,更接近传统的网站 URL
    缺点:后期项目上线,需要服务端配合处理路径问题,否则刷新会有 404 错误。

    const router = createRouter ({
    history:createWebHistory (), //history 模式
    /******/
    })
    

    history: createWebHistory(import.meta.env.BASE_URL),

    import.meta.env.BASE_URL

    import.meta 是 ECMAScript 标准中引入的一个元数据对象,用于获取与模块相关的信息。在 Vite 等构建工具中,import.meta.env 用于访问环境变量。BASE_URL 是 Vite 预设的一个环境变量,它表示项目的基础公共路径。

    作用

    • 处理项目部署路径:当项目部署在服务器的非根路径下时,比如部署在 https://example.com/myapp/ ,而不是 https://example.com/BASE_URL 就会被设置为 /myapp/ ,将其传递给 createWebHistory ,Vue Router 就能够正确处理所有路由路径,使得在访问不同路由时,路径都能基于这个基础路径来生成。例如,访问 /about 时,实际请求的 URL 就是 https://example.com/myapp/about
    • 确保静态资源路径正确:除了路由相关,BASE_URL 还能帮助项目中正确引用静态资源(如图片、CSS 文件、JavaScript 文件等)。如果没有正确设置,在非根路径部署的情况下,可能会导致静态资源无法正确加载。
  2. hash 模式

    优点:兼容性更好,因为不需要服务器端处理路径。
    缺点: URL 带有 # 不太美观,且在 SEO 优化方面相对较差。

    const router = createRouter ({
    history:createWebHashHistory (), //hash 模式
    /******/
    })
    

五、RouterLink 路由跳转方式 to

1. 路由跳转方式

  • 声明式跳转:使用 <RouterLink to="路径">(推荐,自动处理历史记录)。

    <!-- 基础用法 -->
    <RouterLink to="/news">新闻</RouterLink>
    
    <!-- 带参数的对象形式 -->
    <RouterLink :to="{ path: '/news', query: { id: 1 } }">新闻详情</RouterLink>
    
    <!-- 带参数的对象形式:name -->
    <RouterLink :to="{ name: 'aboutPage' }" active-class="router-active">关于</RouterLink>
    
  • 编程式跳转:通过 useRouter 钩子获取路由实例,调用 push/replace 方法。

    <script setup>
    import { useRouter } from 'vue-router'
    const router = useRouter()
    
    // 跳转
    const goToNews = () => {
      router.push('/news')  // 字符串路径
      // 或对象形式
      router.push({ path: '/news', query: { id: 1 } })
    }
    
    // 替换当前历史记录(无法回退)
    const replaceToAbout = () => {
      router.replace('/about')
    }
    </script>
    

2. 路由参数

  • Query 参数:类似 URL 中的查询字符串(?id=1&name=test),通过 useRoute 钩子的 query 属性获取。

    <!-- 跳转时携带参数 -->
    <RouterLink :to="{ path: '/news', query: { id: 1, name: '新闻1' } }">新闻1</RouterLink>
    
    <!-- 目标组件中获取参数 -->
    <script setup>
    import { useRoute } from 'vue-router'
    const route = useRoute()
    console.log(route.query.id)  // 1
    console.log(route.query.name)  // '新闻1'
    </script>
    
  • Params 参数:路径中的动态片段(如 /news/:id),需在路由规则中定义。

    // 路由规则中定义 params
    {
      path: '/news/:id',  // 动态参数 id
      name: 'NewsDetail',
      component: NewsDetail
    }
    
    <!-- 跳转时携带 params -->
    <RouterLink :to="{ name: 'NewsDetail', params: { id: 1 } }">新闻1</RouterLink>
    <!-- 注意:params 必须配合 name 使用,不能用 path -->
    
    <!-- 目标组件中获取 params -->
    <script setup>
    import { useRoute } from 'vue-router'
    const route = useRoute()
    console.log(route.params.id)  // 1
    </script>
    

六、嵌套路由

先把news.vue挪动一下位置

image

子路由添加 children

{
      path: '/news',
      name: 'newsPage',
      component: NewsView,
      children: [
        {
          path: 'detail',
          name: 'newsDetail',
          component: NewsDetail,
        },
      ],
    },

在需要展示子路由的地方也使用 RouterView

这里用 query 方式传参

news.vue

<template>
    <div class="news">
        <div class="news-list">
            <ul v-for="theNew in newsList" :key="theNew.id">
                <li>
                    <RouterLink :to="{ path: '/news/detail', query: { id: theNew.id } }">{{ theNew.title }}</RouterLink>
                </li>
            </ul>
        </div>
        <!-- 展示区 -->
        <div class="news-content">
            <RouterView />
        </div>
    </div>
</template>
<script lang="ts" setup>
import { reactive } from 'vue';

const newsList = reactive([
    { id: 'asfdtrfay01', title: '经典名著新译本推出', content: '更贴合现代读者阅读习惯' },
    { id: 'asfdtrfay02', title: '传统戏剧展演活动开启', content: '多剧种轮番登台献艺' },
    { id: 'asfdtrfay03', title: '博物馆推出新展览', content: '展示珍贵历史文物' },
    { id: 'asfdtrfay04', title: '文化创意市集热闹举行', content: '各类文创产品琳琅满目' }
]);

</script>

使用 useRoute,接收参数传值 route

detail.vue

<template>
    <ul class="news-list">
        <li>{{ route.query.id }}</li>
        <li>标题:xxx</li>
        <li>内容:xxx</li>
    </ul>
</template>

<script setup lang="ts" name="detail">
import { useRoute } from 'vue-router'
const route = useRoute()
console.log(route.query.id)
</script>

image

如果用 params 方式

src/router/index.ts

 {
      path: '/news',
      name: 'newsPage',
      component: NewsView,
      children: [
        {
          path: 'detail/:id/:title/:content',
          name: 'newsDetail',
          component: NewsDetail,
        },
      ],
    },

src\views\news\News.vue

                    <RouterLink
                        :to="{ name: 'newsDetail', params: { id: theNew.id, title: theNew.title, content: theNew.content } }">
                        {{ theNew.title }}</RouterLink>

src\views\news\Detail.vue

<template>
    <ul class="news-list">
        <li>{{ route.params.id }}</li>
        <li>{{ route.params.title }}</li>
        <li>{{ route.params.content }}</li>
    </ul>
</template>

<script setup lang="ts" name="detail">
import { useRoute } from 'vue-router'
const route = useRoute()
</script>

image

七、props 配置

在 Vue 中,props 是组件之间传递数据的主要方式,用于从父组件向子组件传递数据。它是实现组件复用和通信的核心机制之一,以下是关于 Vue 中 props 的详细解析:

1. 子组件声明接收 props

子组件需要显式声明它期望接收的 props,可以指定名称、类型、默认值等规则:

  • defineProps:Vue 3 <script setup> 中专门用于声明 props 的宏,无需导入即可使用。

  • TypeScript 类型声明:也可以用 TypeScript 类型直接声明:

const props = defineProps<{
  message: string
  count?: number // 可选参数
}>()

2. 父组件传递 props

父组件在使用子组件时,通过属性的形式传递数据:

<!-- 父组件 Parent.vue -->
<template>
  <div>
    <!-- 传递静态值 -->
    <Child message="Hello" count="10" /> <!-- 注意:这里的 "10" 是字符串,会被自动转换为数字(根据子组件类型) -->
    
    <!-- 传递动态值(使用 v-bind 或 : 简写) -->
    <Child 
      :message="dynamicMsg" 
      :count="dynamicCount" 
    />
  </div>
</template>

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

const dynamicMsg = ref('动态消息')
const dynamicCount = ref(20)
</script>

例子:

{
      path: '/news',
      name: 'newsPage',
      component: NewsView,
      children: [
        {
          path: 'detail/:id/:title/:content',
          name: 'newsDetail',
          component: NewsDetail,
          //第1种:将params的参数作为props传递给子组件(只能传递params参数,方便)
           //props: true,

          //第2种:函数式(可以传递params参数,也可以传递query参数)
          props: (route) => ({
            id: route.params.id,
            title: route.params.title,
            content: route.params.content,
            RouteMessage: '动态传的属性',
          }),
		  
          //第3种:对象式(可以传递params参数,也可以传递query参数)
          //props: { RouteMessage: '动态传的属性' },
        },
      ],
    },

src\views\news\News.vue 父组件

<!-- 展示区 -->
        <div class="news-content">
            <RouterView StaticMessage="静态传的属性" />
        </div>

src\views\news\Detail.vue 子组件

<template>
    <ul class="news-list">
        <li>{{ id }}</li>
        <li>{{ title }}</li>
        <li>{{ content }}</li>
        <li>{{ StaticMessage }}</li>
        <li>{{ RouteMessage }}</li>
    </ul>
</template>

<script setup lang="ts" name="detail">
//第一种写法
// defineProps<{
//     id: string,
//     title: string,
//     content: string
// }>();

//第二种写法
defineProps(['id', 'title', 'content', 'StaticMessage', 'RouteMessage'])

</script>

image

八、编程式导航

在 Vue 中,编程式导航指的是通过JavaScript 代码实现路由跳转,而不是通过 <RouterLink> 组件(声明式导航)。这在需要根据逻辑条件动态跳转的场景中非常有用(如登录成功后跳转首页)。

Vue Router 提供了一系列 API 实现编程式导航,核心方法基于路由实例(通过 useRouter 获取)。

一、核心方法

1. router.push()

最常用的跳转方法,新增一条历史记录(类似浏览器的 history.pushState),用户点击后退按钮可以回到上一页。

基本用法

<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()

// 1. 字符串路径
const goToHome = () => {
  router.push('/home')
}

// 2. 对象形式(推荐,支持更多参数)
const goToNews = () => {
  router.push({
    path: '/news', // 路径
    query: { id: 1, name: '新闻1' } // 查询参数(?id=1&name=新闻1)
  })
}

// 3. 命名路由(配合路由配置中的 name 属性)
const goToAbout = () => {
  router.push({
    name: 'About', // 路由名称(在路由规则中定义)
    params: { userId: 123 } // 动态参数(需在路由规则中声明 /about/:userId)
  })
}
</script>

2. router.replace()

替换当前历史记录(类似 history.replaceState),不会新增记录,用户点击后退会跳过当前页。

用法

<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()

// 登录成功后跳转首页,不保留登录页历史
const loginSuccess = () => {
  router.replace('/home')
  
  // 也支持对象形式
  router.replace({
    path: '/home',
    query: { from: 'login' }
  })
}
</script>

3. router.go(n)

基于当前历史记录进行前进 / 后退(类似浏览器的 history.go),n 为整数:

  • n = 1:前进一页
  • n = -1:后退一页
  • n = 2:前进两页

用法

<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()

const goBack = () => {
  router.go(-1) // 后退一页
}

const goForward = () => {
  router.go(1) // 前进一页
}
</script>

便捷别名:

  • router.back():等价于 router.go(-1)(后退)
  • router.forward():等价于 router.go(1)(前进)

二、参数传递

编程式导航支持两种参数传递方式:queryparams

1. Query 参数(查询字符串)

  • 显示在 URL 中(如 ?id=1&name=test
  • 刷新页面不会丢失
  • 无需在路由规则中预先定义
// 跳转时携带
router.push({
  path: '/news',
  query: { id: 1, name: '新闻1' }
})

// 目标组件中获取
import { useRoute } from 'vue-router'
const route = useRoute()
console.log(route.query.id) // 1
console.log(route.query.name) // '新闻1'

2. Params 参数(动态路径参数)

  • 嵌入在 URL 路径中(如 /news/1
  • 刷新页面不会丢失
  • 必须在路由规则中预先定义(如 path: '/news/:id'
// 路由规则中定义
const routes = [
  {
    path: '/news/:id', // 声明动态参数 id
    name: 'NewsDetail',
    component: NewsDetail
  }
]

// 跳转时携带(必须用 name 匹配路由,不能用 path)
router.push({
  name: 'NewsDetail',
  params: { id: 1 }
})

// 目标组件中获取
import { useRoute } from 'vue-router'
const route = useRoute()
console.log(route.params.id) // 1

三、常见场景

  1. 登录成功后跳转

    const handleLogin = async () => {
      const success = await api.login(credentials)
      if (success) {
        router.push('/dashboard') // 登录成功跳转到 dashboard
      }
    }
    
  2. 条件判断跳转

    const goToTarget = () => {
      if (hasPermission) {
        router.push('/target')
      } else {
        router.push('/forbidden')
      }
    }
    
  3. 关闭弹窗后返回上一页

    const closeModal = () => {
      router.back() // 关闭弹窗后后退
    }
    

四、注意事项

  1. paramspath 冲突:使用 params 时,必须通过 name 指定路由,不能用 path,否则 params 会被忽略。

    // 错误:用 path 时 params 无效
    router.push({ path: '/news', params: { id: 1 } })
    
    // 正确:用 name 匹配
    router.push({ name: 'NewsDetail', params: { id: 1 } })
    
  2. 路由跳转后的回调pushreplace 支持回调函数(适用于 Vue Router 3.x,4.x 推荐用 await):

    // 4.x 中推荐用 await
    const navigate = async () => {
      await router.push('/home')
      console.log('跳转完成')
    }
    
  3. 避免重复跳转同一路由报错:Vue Router 4.x 中,重复跳转同一路由会报错,可通过捕获错误解决:

    const goToSameRoute = () => {
      try {
        router.push('/current-route')
      } catch (err) {
        // 忽略重复跳转错误
      }
    }
    

九、路由 replace 属性

在 Vue Router 中,replace 是路由跳转的一种方式,与常见的 push 方法不同,它的核心特点是替换当前历史记录,而不是新增一条记录。

一、基本用法

1. 声明式导航(模板中)

使用 <RouterLink> 组件时,添加 replace 属性实现替换式跳转:

<!-- 普通跳转(push,新增历史记录) -->
<RouterLink to="/news">新闻</RouterLink>

<!-- 替换式跳转(replace,替换当前历史记录) -->
<RouterLink to="/news" replace>新闻</RouterLink>

2. 编程式导航(脚本中)

通过 useRouter 获取路由实例,调用 replace 方法:

<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()

// 编程式替换跳转
const goToNews = () => {
  // 字符串路径
  router.replace('/news')
  
  // 或对象形式(支持携带参数)
  router.replace({
    path: '/news',
    query: { id: 1 } // 携带查询参数
  })
}
</script>

二、与 push 的核心区别

跳转方式 历史记录行为 后退按钮效果 适用场景
push 新增一条历史记录 点击后退会回到上一个页面 普通跳转(如列表 → 详情)
replace 替换当前历史记录 点击后退会跳过当前页,回到上上个页面 不需要保留当前页历史的场景

三、典型使用场景

  1. 登录页跳转
    用户登录后跳转到首页,通常不需要保留登录页的历史记录(避免后退时回到登录页):

    // 登录成功后
    router.replace('/home')
    
  2. 页面重定向
    例如从 / 重定向到 /home 时,使用 replace 避免用户后退到空路径:

    // 路由配置中
    const routes = [
      {
        path: '/',
        redirect: { path: '/home', replace: true } // 重定向时替换历史
      }
    ]
    
  3. 临时页面跳转
    如弹窗内的跳转、临时预览页等,不需要用户后退时再次访问的场景。

四、注意事项

  • replace 不会改变历史记录的数量,只是替换了当前位置的记录。
  • 携带参数的方式与 push 完全一致(支持 queryparams 等)。
  • 在 Vue Router 4.x(Vue 3 版本)中,replace 的用法与 3.x(Vue 2 版本)完全一致,没有语法差异。

十、页面重定向

页面重定向
例如从 / 重定向到 /home 时,使用 replace 避免用户后退到空路径:

const routes = [
  {
    path: '/',
    redirect: '/home'
  }
]
// 路由配置中
const routes = [
  {
    path: '/',
    redirect: { path: '/home', replace: true } // 重定向时替换历史
  }
]

十一、路由懒加载 component: () => import('../About.vue')

路由懒加载(Route Lazy Loading)是 Vue、React 等单页应用(SPA)中优化性能的重要技术,核心思想是在需要的时候才加载路由对应的组件代码,而不是在应用初始加载时就加载所有组件。

为什么需要路由懒加载?

单页应用的特点是所有代码最终会打包成一个或少数几个 JS 文件。如果应用规模较大(有很多页面组件),初始打包的文件会非常大,导致:

  • 首屏加载时间过长(用户需要等待所有代码下载完成才能看到页面)
  • 浪费带宽(用户可能永远不会访问某些页面,却要下载其代码)

路由懒加载的原理

通过 ES6 的动态 import () 语法,将不同路由对应的组件分割成多个独立的代码块(chunk)。只有当用户访问该路由时,浏览器才会异步下载对应的代码块并执行。

例如,不使用懒加载时,所有组件会被打包到同一个 app.js 中:

// 不使用懒加载:初始加载时就包含 About 组件代码
import About from '../views/About.vue'
const routes = [
  { path: '/about', component: About }
]

使用懒加载后,About 组件会被单独打包成 About.js

// 使用懒加载:只有访问 /about 时才加载 About 组件代码
const routes = [
  { 
    path: '/about', 
    component: () => import('../views/About.vue') // 动态 import
  }
]

路由懒加载的优势

  1. 减小初始加载体积:只加载首页必需的代码,提升首屏加载速度。
  2. 节省带宽资源:用户未访问的页面代码不会被下载。
  3. 优化用户体验:更快的首屏加载速度,减少用户等待时间。

在 Vue Router 中的实现

Vue Router 官方推荐使用动态 import() 实现路由懒加载,语法如下:

// 路由配置文件(router/index.js)
import { createRouter, createWebHistory } from 'vue-router'

const routes = [
  {
    path: '/',
    name: 'Home',
    component: () => import('../views/HomeView.vue') // 懒加载 Home 组件
  },
  {
    path: '/about',
    name: 'About',
    // 注释说明懒加载的效果
    component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

export default router
  • 其中 /* webpackChunkName: "about" */ 是可选的魔法注释,用于指定打包后的代码块名称(方便调试)。
  • 打包后会生成类似 about.[hash].js 的独立文件,仅在访问 /about 时加载。

点击关于的时候,再加载

image

总结

路由懒加载是通过动态导入语法,将路由组件代码分割成多个小块,按需加载的技术。它能有效优化单页应用的加载性能,是大型 Vue 项目的必备优化手段。核心就是 “不用不加载,用的时候再加载”。

路由守卫

一、核心结论

路由守卫本质是Vue Router 提供的钩子函数,用于监听路由的导航过程,实现对路由跳转的控制(如权限验证、页面埋点、加载动画等)。

  • 前置路由守卫(beforeEach、beforeEnter、beforeRouteEnter):在路由跳转前触发,可取消或修改导航,是实现权限控制的核心;
  • 后置路由守卫(afterEach):在路由跳转完成后触发,无法修改导航,主要用于执行一些收尾操作(如埋点、关闭加载动画)。

简单来说:前置守卫管 “能不能跳”,后置守卫管 “跳完做什么”

二、路由守卫的分类与执行顺序

Vue Router 4.x 的路由守卫分为全局守卫、路由独享守卫、组件内守卫三大类,其中包含前置和后置类型,其执行顺序是掌握守卫的关键:

整体执行顺序(路由跳转时)

1. 全局前置守卫(router.beforeEach)
2. 路由独享前置守卫(route.beforeEnter)
3. 组件内前置守卫(beforeRouteEnter)
4. 导航确认(路由跳转完成)
5. 全局后置守卫(router.afterEach)
6. 组件内守卫(beforeRouteUpdate,路由参数变化时触发)
7. 组件内守卫(beforeRouteLeave,离开组件时触发)

三、前置路由守卫

前置路由守卫是可中断导航的钩子,接收三个参数:to(目标路由)、from(当前路由)、next(导航控制函数,Vue Router 4.x 中可通过返回值替代)。

1. 全局前置守卫(router.beforeEach)

核心作用:全局拦截所有路由跳转,适用于全局权限验证、登录状态检查、路由白名单控制等场景。

参数 含义 示例
to 目标路由对象,包含目标路由的路径、参数、元信息等 to.path → /userto.meta.requiresAuth → true
from 当前路由对象,包含当前路由的信息 from.path → /home
next(Vue Router 3.x) 导航控制函数:next()放行、next('/login')重定向、next(false)取消导航 (Vue Router 4.x 已推荐用返回值替代,兼容但不推荐)

参考:page-404-路由守卫

import { createRouter, createWebHistory } from "vue-router";
import type { RouteRecordRaw } from "vue-router";

// 定义路由配置
const routes: RouteRecordRaw[] = [
  {
    path: "/",
    redirect: "/Home", // 根路径重定向到首页
  },
  {
    path: "/Login",
    name: "Login",
    component: () => import("@/views/Login.vue"),
    meta: {
      title: "登录",
    },
  },
  {
    path: "/Home",
    name: "Home",
    component: () => import("@/views/Home.vue"),
    meta: {
      title: "首页",
    },
  },
  {
    path: "/404",
    name: "404",
    component: () => import("@/views/404.vue"),
    meta: {
      title: "页面未找到",
    },
  },
  {
    path: "/:pathMatch(.*)*", // 捕获所有未匹配的路由
    redirect: "/404",
  },
];

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes,
});

export default router;

main.ts

import router from "./router";


router.beforeEach((to, from, next) => {
try {
  const token = localStorage.getItem("token");
  
  const whitelist = ["/Login", "/404"]; // 白名单路由
  
  // 如果没有token且需要认证,跳转到登录页
  if (!token && whitelist.indexOf(to.path) === -1) {
	next({
	  path: "/Login",
	});
	return;
  }
  
  // 如果有token且访问登录页,跳转到首页
  if (token && to.name === "Login") {
	next({ path: "/Home" });
	return;
  }
  
  next();
} catch (error) {
  console.error("路由守卫错误:", error);
  ElMessage.error("导航失败,请重试");
  next(false); // 取消导航
}
});

2. 路由独享前置守卫(route.beforeEnter)

核心作用:仅拦截指定路由的跳转,适用于单个路由的特殊权限验证(如管理员路由的角色检查)。

const routes = [
  {
    path: '/admin',
    component: () => import('../views/Admin.vue'),
    meta: { requiresAuth: true, requiresAdmin: true },
    // 路由独享前置守卫
    beforeEnter: (to, from) => {
      const userRole = localStorage.getItem('role')
      // 验证是否为管理员角色
      if (to.meta.requiresAdmin && userRole !== 'admin') {
        // 无权限则重定向到首页
        return '/'
      }
    }
  }
]

特点

  • 只作用于当前路由,不会影响其他路由;
  • 执行顺序在全局前置守卫之后,组件内前置守卫之前。

3. 组件内前置守卫(beforeRouteEnter)

核心作用:在进入组件前触发,适用于组件级别的权限验证获取组件初始化数据

<template>
  <div>用户中心</div>
</template>

<script setup>
// Vue3 中组件内守卫的写法(需使用选项式API,或通过 defineOptions 定义)
import { defineOptions } from 'vue'

defineOptions({
  beforeRouteEnter(to, from) {
    // 注意:此时组件实例尚未创建,无法访问 this
    const userId = localStorage.getItem('userId')
    if (!userId) {
      return '/login'
    }

    // 若需要访问组件实例,可通过回调函数(Vue Router 4.x 支持)
    // next(vm => { vm.fetchData() }) // 3.x 写法
  }
})
</script>

四、后置路由守卫(router.afterEach)

后置路由守卫是不可中断导航的钩子,在路由跳转完成后触发,仅接收 to 和 from 两个参数,无法修改导航。

核心作用

适用于页面埋点统计、关闭加载动画、修改页面标题、滚动到页面顶部等场景。

// 全局后置守卫
router.afterEach((to, from) => {
  // 1. 修改页面标题(结合路由元信息)
  document.title = to.meta.title || 'Vue3 项目'

  // 2. 页面埋点:统计页面访问量
  console.log(`访问了${to.path}页面,从${from.path}跳转而来`)

  // 3. 滚动到页面顶部
  window.scrollTo(0, 0)

  // 4. 关闭加载动画(假设存在全局加载状态)
  const loading = document.getElementById('loading')
  if (loading) loading.style.display = 'none'
})

路由元信息配置(配合后置守卫)

const routes = [
  {
    path: '/',
    component: Home,
    meta: { title: '首页' } // 页面标题
  },
  {
    path: '/user',
    component: UserCenter,
    meta: { title: '用户中心', requiresAuth: true }
  }
]

五、其他重要的守卫

除了核心的前置和后置守卫,还有两个常用的组件内守卫需要掌握:

1. beforeRouteLeave(组件内守卫,前置类型)

核心作用:在离开组件时触发,可用于提示用户保存未提交的表单、确认是否离开当前页面

<template>
  <form>
    <input v-model="formData.name" placeholder="姓名">
  </form>
</template>

<script setup>
import { ref, defineOptions } from 'vue'

const formData = ref({ name: '' })
const isFormEdited = ref(false)

// 监听表单变化
formData.value.name = ''
formData.value.name = '张三'
isFormEdited.value = true

defineOptions({
  beforeRouteLeave(to, from) {
    // 若表单已编辑,提示用户确认离开
    if (isFormEdited.value) {
      const confirmLeave = window.confirm('表单内容未保存,确定离开吗?')
      // 取消导航
      if (!confirmLeave) return false
    }
  }
})
</script>

2. beforeRouteUpdate(组件内守卫)

核心作用:在路由参数变化但组件未销毁时触发(如从 /user/1 跳转到 /user/2),适用于更新组件数据

<template>
  <div>用户ID:{{ userId }}</div>
</template>

<script setup>
import { ref, defineOptions, onMounted } from 'vue'

const userId = ref('')

const fetchUserInfo = (id) => {
  // 模拟请求用户数据
  console.log(`获取用户${id}的信息`)
  userId.value = id
}

onMounted(() => {
  // 初始化获取数据
  fetchUserInfo(route.params.id)
})

defineOptions({
  beforeRouteUpdate(to, from) {
    // 路由参数变化时更新数据
    fetchUserInfo(to.params.id)
  }
})
</script>
posted @ 2025-09-10 18:14  【唐】三三  阅读(55)  评论(0)    收藏  举报