Vue Router 导航守卫

使用return {...}替代next({...})

虽然 Vue Router 仍然支持 next 方法,但确实不再推荐使用它,并且官方文档中也建议避免使用。在 Vue 3 中,你可以通过返回一个 Promise 或直接返回一个路由路径来处理导航守卫逻辑,而不必使用 next 方法。

以下是如何在全局导航守卫中实现 token 检查和重定向,而不使用 next 方法:

配置 Vue Router

src/router/index.js 中配置路由,并添加全局前置守卫:

 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
import { createRouter, createWebHistory } from 'vue-router';
import Home from '@/views/Home.vue';
import Login from '@/views/Login.vue';

const routes = [
    { path: '/', component: Home, meta: { requiresAuth: true } },
    { path: '/login', component: Login },
];

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

router.beforeEach((to) => {
    const token = localStorage.getItem('token');
    
    if (to.meta.requiresAuth && !token) {
        // 如果没有 token 且目标路由需要认证,则重定向到登录页
        return { path: '/login' };
    }
    // 否则继续导航
    return true;
});

export default router;

解释

  • to:目标路由对象,表示即将要进入的目标。
  • from:当前导航正要离开的路由。
  • next 已被移除,替换为直接返回路由对象或 true

改进的组件示例

src/views 目录中创建 Home.vueLogin.vue 文件,定义组件内容:

Home.vue:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<template>
    <div>
        <h1>Home</h1>
        <p>Welcome to the home page.</p>
    </div>
</template>

<script>
export default {
    name: 'Home',
};
</script>

Login.vue:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<template>
    <div>
        <h1>Login</h1>
        <p>Please log in to continue.</p>
        <!-- Login form and logic here -->
    </div>
</template>

<script>
export default {
    name: 'Login',
};
</script>

在 Vue 应用中使用路由器和 Pinia

src/main.js 中导入并使用路由器和 Pinia:

1
2
3
4
5
6
7
8
9
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import { createPinia } from 'pinia';

const app = createApp(App);
app.use(router);
app.use(createPinia());
app.mount('#app');

通过这种方式,你可以避免使用 next 方法,而是通过在导航守卫中返回路由路径或 true 来处理导航逻辑。这种方式更简洁,也避免了可能的错误来源。

添加动态路由

几个关键点

  1. 后端返回的菜单要转化为标准的路由记录数据类型
  2. 后端返回的嵌套菜单数据结构是否能直接通过router.addRoute方法添加:可以,但是后端返回的component字段(前端组件路径)需要转换为实际的Vue组件

数据需要是标准的路由记录类型,才能被添加

官方参考:添加路由

标准的路由

to.matchedto.meta方法

在 Vue Router 中,route.meta 是一个有用的属性,它可以帮助你获取当前路由的合并的 meta 数据。这里的“非递归合并”指的是 Vue Router 在合并 meta 数据时并不会递归地处理所有父级路由的 meta 数据,而是直接将所有父级和子级路由的 meta 数据合并在一起。

route.meta 的功能和工作原理

  1. 路由记录的合并

    • 在 Vue Router 中,route.matched 数组包含了当前路由匹配到的所有路由记录。这些记录包括了当前路由以及它的所有父级路由。
    • 你可以遍历这个数组来手动提取和合并 meta 数据。每个路由记录的 meta 数据可能会有不同的属性,这通常需要递归合并。
  2. 非递归合并

    • Vue Router 提供了一个 route.meta 方法,它会将所有匹配的路由记录的 meta 数据合并为一个单一的对象。合并是“非递归”的,意味着它不会深入到子路由中进一步合并,而是直接将所有父级和子级的 meta 数据一并合成。
    • 如果 meta 数据中存在相同的字段,后面定义的字段会覆盖前面的字段(类似于普通的对象合并)。

示例

假设你有如下的路由配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
const routes = [
  {
    path: '/parent',
    meta: { requiresAuth: true, role: 'admin' },
    children: [
      {
        path: 'child',
        meta: { requiresAuth: true, permissions: ['read'] },
        children: [
          {
            path: 'grandchild',
            meta: { requiresAuth: false, permissions: ['write'] }
          }
        ]
      }
    ]
  }
];

对于匹配到的 /parent/child/grandchild 路由,route.matched 数组将包含:

  1. /parent
  2. /parent/child
  3. /parent/child/grandchild

手动合并 meta 数据:

1
2
3
4
5
const routeMeta = {
  ...route.matched[0].meta,
  ...route.matched[1].meta,
  ...route.matched[2].meta
};

使用 Vue Router 的 route.meta 方法

1
const routeMeta = route.meta;

route.meta 合并后的结果

1
2
3
4
{
  requiresAuth: false, // 最终结果由最后一个子路由的 `meta` 决定
  permissions: ['write'] // 最终结果由最后一个子路由的 `meta` 决定
}

总结

  • route.matched 数组包含了当前路由和所有父级路由的信息。
  • 非递归合并 指的是 Vue Router 将所有这些 meta 数据直接合并为一个对象,而不是递归地处理每个子路由的 meta
  • route.meta 方法会合并所有匹配的路由记录的 meta 数据,确保你可以方便地获取当前路由和所有父级路由的合并 meta 数据。

全局前置守卫

怎么判断路由匹配成功?

to.matched 属性来判断是否有路由匹配成功。to.matched 是一个数组,包含所有与目标路由匹配的路由记录。你可以检查这个数组是否为空来判断是否有路由匹配成功。

设置动态路由

在全局前置路由守卫中添加逻辑,动态地添加或修改路由。

无论是否登录,对访问登录页的判断要放在前面

例如当已登录,尚未初始化动态路由时,初始化动态路由完成后,重新导航会一直访问登录页

已登录,但是未初始化动态路由时,访问登录页

在动态路由初始化完成之前,用户不能访问登录页或其他受限页面。

重新登录后,清空权限缓存

  • hasInitDynamicRoute设为false
  • 重置动态路由
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计