Vue-router-3.0.1 使用router.addRoutes()设置动态路由,页面刷新后无效
发布于 2 个月前 作者 feer 226 次浏览 来自 问答
粉丝福利 : 关注VUE中文社区公众号,回复视频领取粉丝福利

vue-router-3.0.1 使用router.addRoutes()设置动态,页面刷新后无效

问题总述

vue-router-3.0.1 使用router.addRoutes()设置动态路由,从非动态路由(router初始化时就存在的路由)页面跳转到动态添加的路由的页面,一切正常。但是,在动态添加的路由的页面刷新后(即动态路由对应的页面自己刷新再路由到自己),路由匹配失败,页面无法渲染。

详细介绍

最近做一个小项目,vue相关依赖版本如图:

    "vue": "^2.5.2",
    "vue-router": "^3.0.1",
    "vuex": "^3.0.1"

项目中,用户登录系统前,初始默认路由如下:

在vux中,state中设置“用户信息”userInfo字段,其中‘uid’为用户登录唯一ID

state: {
    userInfo: {
        uid: '',
        role: ''
    }
},

现在要实现如下功能:

用户登录前,系统菜单栏只有“首页”、“讨论区”两个功能以及对应的初始路由。当用户登录系统后,动态添加“个人中心”模块和其对应路由。

个人实现机制如下:

  1. 用户登录前,实例化“首页”和“讨论区”的初始路由
    

    // router/index.js /**

    • 静态路由 */ export const staticRouters = [{ path: ‘/’, redirect: ‘/Home’ }, { path: ‘/Home’, component: Wrapper, children: [{ path: ‘’, name: ‘Home’, component: Home }], meta: { name: ‘首页’ } }, { path: ‘/DiscussZone’, component: Wrapper, children: [{ path: ‘’, name: ‘DiscussZone’, component: DiscussZone }], meta: { name: ‘讨论区’ } }, { path: ‘/Login’, name: ‘Login’, component: Login }, { path: ‘/Register’, name: ‘Register’, component: Register }]

    export default new Router({ routes: staticRouters })

  2. 当用户登录系统后,将用户`uid`写入浏览器`localStorage`中(后面用户强制刷新页面时,要使用其判断),同时提交`mutation`,设置`vue`x中`stat`e的`uesrInfo.uid`字段,此时,`uid`字段改变,便会重新计算`getters`中的用户菜单栏`(userStatusMenu)`,在系统菜单栏动态添加“个人中心”模块入口,并计算出该模块的动态新增的路由`userDynamicRouters`。
    

    // router/index.js /**

    • 动态路由 / export const dynamicRouters = [{ path: ‘/MyCenter’, component: Wrapper, children: [{ path: ‘’, name: ‘MyCenter’, component: MyCenter, children: myCenterRouter }], meta: { name: ‘个人中心’ } }, { path: '’, name: ‘404’, component: NotFound }]

    /**

    • “个人中心” 子路由 */ const myCenterRouter = [{ path: ‘myGame’, name: ‘myGame’, component: MyGame, meta: { name: ‘我发布的比赛’ } }, { path: ‘myPosttings’, name: ‘myPosttings’, component: MyPosttings, meta: { name: ‘我的帖子’ } }]

    import { dynamicRouters } from ‘@/router/index’ // 动态路由部分路由配置

  1. vuexgettes计算求得需要动态新增的路由userDynamicRouters后,使用router.addRoutes``(userDynamicRouters)添加动态路由到初始化的路由实例对象router

至此,初步解决了根据用户登录状态动态生成系统菜单栏功能模块入口和添加动态路由功能。登陆后,动态添加的“个人中心”模块也可以正常路由。

但是系统页面F5或者Ctrl+F5刷新后,因为刷新页面vuexrouter实例会重新初始化到初始状态,所以vuexstate.userInfo.uid和新增的动态路由会被刷掉。

所以,在main.js中设置全局路由守卫router.beforeEach(),当页面刷新时重新判断登录时存储的localStorageuid字段,如果存在,说明用户登录,此时如果vuexstate.userInfo.uid如果为空,说明是用户登录后进行了刷新页面的操作,此时会重新向vuex提交mutation,设置state.userInfo.uid,并重新计算动态路由,再addRotes()到路由实例router。如下图:

至此解决了页面刷新时,动态路由失效的问题。

但是,But,However,我遇到了一个的坑,也是这篇帖子最后想问的问题!!前面交代那么多,就是想把自己实现的机制描述清楚,方便大神带带我,指导一下!!

我遇到的坑和问题

**这个坑是:**我在“首页”、“讨论区”这种router初始化时就存在的模块中点击菜单跳转到动态路由模块“个人中心”包括在这些页面刷新页面后再跳转“个人中心”时,一切路由都正常!But,但是,在进入“个人中心”,也就是路由到动态增加的动态路由后,在动态路由页面直接刷新页面,却无法正常路由,页面没有渲染!!
找了半天,google,度娘找了一圈,也没找到处理方法!小弟愚钝,请大神赐教!!帮忙看看什么原因?

19 回复

这个坑我也遇到拉~~ addRouters后 通过sessionStorage保存一下 然后在main.js中 获取该sessionStorage 再去vuex中拉取一下路由,这样解决了刷新新增的路由没了~ 不知道是否有更好的方法

这种问题应该是和渲染有关系,由于取用户信息是在 router 中执行,导致 router 已经开始渲染了,但还没有这个路由。我觉得保存用户信息应该在创建 vue 实例之前对 store 进行 commit;如果一定要在后面进行 commit 的话,在写 router-view 时一定要注意在没有初始化之前使用 v-if 阻止路由进行渲染,加入 loading 等表示在获取权限。
我最近也在做权限和信息相关内容。整个流程是:先挂载vue 实例 -》然后获取权限信息 -》对store 进行 commit -》为 router 添加动态路由
挂载实例的时候先不要进行 router-view 渲染,获取完成权限信息后,设置 store 中初始化信息为true, 然后进行 router-view 的渲染,这样不会影响路由请求问题

十分感谢你的回复。你的描述我理解啦,beforeEach()触发的时候,目标路由已经开始获取匹配,这时动态路由并没有增加进去,所以匹配不到路由记录。我权限控制这里做的还是存在问题的,我结合你的建议修改一下试试,应该可以解决。再次感谢!

感谢你的回复,页面刷新的时候vuex中的数据和router实例对象会被重新初始化,就像你说的,我也是把id等必要信息保存在sessionStorage或者localStorage后,在main.js中设置一个全局路由前置守卫,每次刷新页面后重新获取sessionStorage或者localStorage中的值,再去设置路由。如图:

image

但我遇到了一个问题:

比如初始用户未登录时,有“首页“”和“讨论区”两个模块和他们对应的路由,然后用户登录后,会增加“个人中心”模块和其对应路由。具体实现是登陆后将userId写入vuex中的state,根据vuex中的userId是否为空会动态在getters中计算出登录后的功能模块和对应路由,代码如下图。目前我做到这里一切都好,刷新页面后也可以正常路由。

唯一的问题是: 在动态展示的“个人中心”页面(当然,该模块对应的路由也是登陆后动态增加的)直接刷新页面或者在地址栏按下回车Enter,页面没有任何内容渲染,看了一下是没有匹配到合适的路由记录。

但是个人觉得,如上图中代码,执行前置守卫总的next()方法之后,vue-router才会开始再次匹配router中的路由记录,而此时已经重新添加动态路由,应该是可以匹配到的,但页面却还是无法渲染。请指教!!

next 时候重新指定以下刷新前的路径:例如:next({ …to, replace: true })

您好,实在抱歉,五一假期没能及时回复,请见谅。您描述的意思我大致明白。

我先是在用户点击“登录”按钮时,验证是合法用户后,首先commit一个mutation,将用户id(初始值为空字符串)写入vuex中,此时用户id改变,就会重新计算getters中的动态路由userDynamicRouters,此时,我再使用this.$router.addRoutes(this.userDynamicRouters)向router对象中添加动态路由。

// login
image
// vuex

而页面刷新时,因为登录时设置的vuex中的数据和router中动态添加的动态路由会重新初始化被刷掉,所以我就在main.js中增加全局路由前置守卫beforeEach(to, from. next),用户刷新页面时,包括在动态路由页面刷新页面,此时触发beforeEach(),重新设置vuex中的用户id,这时又会再次计算getters中的动态路由userDynamicRouters,再使用this.$router.addRoutes(this.userDynamicRouters)向router对象中添加动态路由,之后执行前置守卫的next()方法跳转路由。

// main.js全局路由前置守卫

个人觉得执行前置守卫总的next()方法之后,vue-router才会开始再次匹配router中的路由记录,而此时已经重新添加动态路由,应该是可以匹配到的,但页面却还是无法渲染。请指教!!

有没有遇到过刷新一次成功,刷新第二次就报错的情况?调试发现刷新的时候事实上跳了两次路由,进了两次路由导航函数

我目前遇到的场景和楼主完全一致,也卡在这里了,不知道楼主最后是如何解决的呢?

:slight_smile:

研究了半天终于解决了,方案很简单,初始路由不要加重定向到404路由,否则beforeEach时地址已经变成/404了,而不是刷新之前的页面的地址,要在每次动态添加路由的时候加上

router.beforeEach((to, from, next) => {
  NProgress.start()
  if (getToken()) {
    if (to.path === '/login') {
      next({ path: '/' })
      NProgress.done()
    } else {
      if (store.getters.role) { // 判断是否已拉取完user_info信息
        next()
      } else { // 如果store中不存在用户信息,重新拉取并更新路由
        store.dispatch('GetUserInfo').then(res => {
          const menus = res.data.menus
          router.addRoutes(GenerateRoutes(menus)) // 动态添加路由
          // 初始路由不要加重定向到404路由,否则beforeEach时地址已经变成/404了,而不是刷新之前的页面的地址
          // 而是在每次动态添加路由的时候加上
          router.addRoutes([{ path: '*', redirect: '/404', hidden: true }])
          next({ ...to, replace: true })
        })
      }
    }
  } else {
    if (whiteList.indexOf(to.path) !== -1) {
      next()
    } else {
      next(`/login?redirect=${to.path}`) // 否则全部重定向到登录页
      NProgress.done()
    }
  }
})

理论上应该是这样的,但问题是next 方法不是异步的,每当触发router 的勾子时,实际上目标的 route 已经获取完成了,不会再去重新获取,导致即使进行了 commit,也不会去取最新的。可以参考下源码:https://github.com/vuejs/vue-router/blob/d539788df8394efe41c1534e1e9d1555aa2edbe2/src/history/base.js#L163

这个 route 在执行 next 的时候是不会产生变化的,在第一次导航时候应该就已经确定了。
不过如果 next 时候指定下新的路径,可能能够实现,可以试下

除去这个问题,在 router 里面来控制权限貌似没有什么必要吧,如果这个过程是异步的,会导致你的菜单长度会在异步结束后变化,体验可能更差一些。之前我见过这种异步的方案,菜单异步即使加了动画,也让人感觉很奇怪,不如直接前置权限,全部由 vuex 处理

用你的方法解决了这个问题

楼主,最后解决问题了吗?

类似解决方案 解决该问题的核心就是,在刷新页面(初始化),重新注入(addRoutes)缓存在本地的动态路由

github.com

BiYuqi/vue-element-admin/blob/master/src/router/index.js

import Vue from 'vue'
import Router from 'vue-router'
import Cookie from 'js-cookie'
import Util from '@/utils/baseSetting'
import routes from './baseConfig'
import otherRoute from './otherRoutes'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import store from '@/store'
import errPage from '@/utils/404'
import i18n from '../lang'
import { Message } from 'element-ui'
Vue.use(Router)
export const router = new Router({
  // mode: 'history',
  routes: routes.concat(...otherRoute)
})

NProgress.configure({ showSpinner: false })
/* eslint handle-callback-err: "error" */

This file has been truncated. show original

谢谢楼主 用楼主的方法解决了相同的问题

回到顶部