背景
最近项目过程中出现了一个奇怪的问题,当用户登入系统后部分路由首次跳转时会出现空白页面,但是在该页面上刷新又可以正常显示路由对应的页面。
系统内包含用户、角色、菜单等管理模块,前端使用了动态路由
问题复现
- 系统内有两个角色,A1和A2
- A1角色包含P1、P2、P3页面的权限
- A2角色包含P1、P2、P3、P4、P5页面的权限
- 当首次使用A1角色对应的用户登入系统时,侧边栏所有菜单都可以正常访问
- 此时退出A1角色对应的用户并使用A2角色对应的用户登入系统,其中P1、P2、P3菜单对应的页面可以正常访问,但是P4、P5对应的页面首次访问时出现空白的情况,刷新后P4、P5对应的页面可以正常访问
猜想原因
由于项目内使用到了Vue-Router动态路由,猜想可能是因为用户退出登录后再次登入,未重新挂载新的路由导致的。
动态路由代码如下:
// 路由前置导航守卫,处理页面刷新、登录退出
router.beforeEach((to, from, next) => {
// 登录页直接跳转
if (to.path === '/login') {
next();
// 回到登录页面,清除路由、当前登录人等信息
clearDynamicMenus();
clearUser();
// 非登录页面需要校验是否已登录、是否页面刷新
} else {
// 未登录时跳转至登录页面
let loginFlag = Cookie.get("loginFlag");
if (loginFlag === undefined) {
next({
path: '/login'
});
return;
}
// 页面刷新,需要重新挂载路由
if (store.state.pageStatus.refresh) {
// 从localStorage中重新加载用户、菜单等信息到Vuex
reloadUser();
reloadDynamicMenus();
// 加载动态路由
let dynamicRoutes = loadRoutes();
// 没有匹配到路由数据
if (dynamicRoutes === null) {
next({
path: '/login'
})
} else {
// 防止重复配置路由,因为本次系统内一级路由只有登录页和后台首页(以及其子路由),所以这里通过判断是否存在第二个一级路由从而做到防止重复配置路由
if(router.options.routes[1] === undefined){
// 添加动态路由并重置Vuex 页面刷新标记的值
router.addRoutes(dynamicRoutes);
router.options.routes.push(dynamicRoutes);
// 在页面刷新后重新配置路由
store.state.pageStatus.refresh = false;
}
next({
...to,
replace: true
});
}
}
next()
}
});
如上代码,如果需要重新加载路由,此处主要依赖 store.state.pageStatus.refresh 的值
思考:
store.state.pageStatus.refresh 属性的值到底何时会变化?
- 当页面刷新时,store.state.pageStatus.refresh 属性会重置为初始值
- 当首次进入系统页面时store.state.pageStatus.refresh属性为默认值,动态路由加载完毕会被设置为false
/**
* 页面状态类
*/
const pageStatus = {
state: {
/**
* 是否页面刷新
*/
refresh: true
}
}
默认值为true,到这里,问题原因已经浮出水面:
- 当用户A初次访问系统时刷新标记为true,此时登录系统成功,访问了/login以外的其他路由,此时动态加载了动态路由数据,并将刷新标记置为false
- 当用户A退出登陆时,Vuex保存了数据状态,此时系统刷新标记的值为false,此时切换用户B登入不会触发动态路由的重新加载,路由还为A用户对应权限的路由
- 当B用户和A用户对应的权限不同时,B用户访问A用户没有权限的路由时就会出现页面空白,因为此时Vue-Router中并未加载该路由
- 当B用户刷新页面时,Vuex数据状态被重置,刷新标记值还原为true,用户访问/login以外的其他路由时就会触发动态路由的重新加载,此时B用户访问的所有页面都能够正常展示
解决方案
- 当用户登录成功时,重置Vuex刷新标记的值为true,强制触发动态路由的加载
注意: 此方案会导致动态路由重复加载
- 为了避免路由重复加载,需要重置Vuex刷新标记的值并且清空原有的路由,查阅Vue-Router没有相关的API接口,此处参考了 vue-router没有提供remove routes方法情况下,清空router实例中路由的方法,选取了登录页面重新加载作为处理最终处理方案,代码如下:
created() {
// 注意:需要判断在路由已加载的情况下刷新,如果路由未加载则无需刷新
if (this.$router.options.routes[1] !== undefined) {
location.reload();
}
}
结语
感谢以下博客提供的解决方案:
vue-router没有提供remove routes方法情况下,清空router实例中路由的方法