你是否遇到了:
- addRouter后出现白屏
- 路由守卫出现死循环
踩了很多坑之后,我终于悟到了vue动态添加路由的正确打开方式;
为了设计权限,在前端我们通常采取两种方式
1、在用户登录时获取该用户权限下的路由表,动态生成路由和菜单栏(后端给定路由)
2、在用户登录时获取用户权限,根据权限筛选出需要的路由(前端通过权限筛选路由)
本篇文章采用方式一
关键点:
- 使用route中addRouter方法动态添加路由
- 将路由分为
(1)动态路由 myRouterObj (可从后端获取)
(2) 静态路由 staticRoutes 没有权限也可以访问的路由
注:rootRouter 用来组装动态路由。
router index.js页面
import Vue from 'vue'
import Router from 'vue-router'
import home from '@/pages/Home'
import store from '../store/store'
Vue.use(Router)
// 动态路由 模拟后端数据
const myRouterObj = [
{
path: '/dashboard',
name: "dashboard",
meta: {
title: '系统首页'
},
component: "Dashboard"
},
{
path: '/table',
name: "commonTable",
meta: {
title: '表格'
},
component:
"CommonTable"
},
{
path: '/calendar',
name: "calendar",
meta: {
title: '日历'
},
component: "Calendar"
}
]
// 静态路由 没有权限也可以访问的路由
export const staticRoutes = [
{
path: "/login",
name: "login",
component: () => import(
"@/pages/Login.vue"
),
}
];
// 根路由
const rootRouter = {
path: '/',
name: 'home',
component: home,
redirect: '/dashboard',
children: []
};
export const generatorDynamicRouter = () => {
return new Promise((resolve, reject) => {
// myRouterObj 这里直接写在页面中了,实际应用中我们需要进行ajax请求获取
const routesForHis = generatorForHis(myRouterObj);
// routesForHis .push(notFoundRouter); // 可以定义404nofoun单页面路由
rootRouter.children = routesForHis;
resolve(rootRouter);
});
};
export const generatorForHis = (routeMap) => {
return routeMap
.map(item => {
const currentRouter = {
path: item.path,
// 路由名称,建议唯一
name: item.name,
// meta: 页面标题, 菜单图标, 页面权限(供指令权限用,可去掉)
meta: item.meta,
// 该路由对应页面的 组件 (动态加载 @/pages/ 下面的路径文件)
component: () => import(`@/pages/` + item.component + '.vue')
};
// 子菜单,递归处理
if (item.children && item.children.length > 0) {
currentRouter.children = generatorForHis(item.con);
if (currentRouter.children === undefined || currentRouter.children.length <= 0) {
delete currentRouter.children;
}
}
return currentRouter;
})
.filter(item => item);
};
// 开始创建路由时
const router = new Router({
mode: 'history',
base: '/',
linkActiveClass: "active",
routes: staticRoutes
})
router.beforeEach(async (to, from, next) => {
var isLogin = store.state.isLogin;
if (to.path == "/login") {
next()
} else {
if (isLogin) {
if (store.state.allowRouters && store.state.allowRouters.length > 0) {
// 路由的出口 判断addRoute是否已经完成 避免路由守卫进入死循环
next()
} else {
const allowRouters = await store.dispatch("GENERATE_ROUTES_DYNAMIC", "aa")
if (allowRouters.children.length == 0) {
return false;
}
if (allowRouters) {
next({ ...to, replace: true })
}
return false;
}
} else {
next('/login')
}
}
})
export default router;
store store.js文件
import Vue from 'vue'
import Vuex from 'vuex'
import { generatorDynamicRouter } from '@/router'
import { default as router, staticRoutes } from '@/router';
Vue.use(Vuex)
let isLogin = ''
try {
if (localStorage.isLogin) {
isLogin = JSON.parse(localStorage.isLogin)
}
} catch (e) { }
export default new Vuex.Store({
state: {
isLogin: isLogin,
allowRouters: [],
},
mutations: {
login(state) {
state.isLogin = true;
localStorage.isLogin = true;
},
SET_ROUTERS(state, data) {
state.allowRouters = data;
},
},
actions: {
// 产生动态路由
GENERATE_ROUTES_DYNAMIC({ commit }, data) {
return new Promise(resolve => {
generatorDynamicRouter()
.then((routes) => {
const allowRoutes = routes.children || [];
// 添加到路由表
console.log('allowRoutes: ', allowRoutes);
router.addRoute(routes);
commit('SET_ROUTERS', allowRoutes);
resolve(routes);
})
.catch(err => {
console.error('generatorDynamicRouter', err);
});
});
},
}
})
关于next({ ...to, replace: true })
的理解
很多人在使用动态添加路由addRoutes()会遇到下面的情况:
- 问题:在
addRoutes()
之后,第一次访问被添加的路由会白屏。 - 原因:在
addRoutes()
之后,立刻访问被添加的路由时,addRoutes()
没有执行结束,因而找不到刚刚被添加的路由导致白屏。 - 解决方案:需要重新访问一次路由才行。
该如何解决这个问题 ?
此时就要使用next({ ...to, replace: true })
来确保addRoutes()
时动态添加的路由已经被完全加载上去。
关于next({ …to, replace: true })
replace: true
只是一个设置信息,告诉VUE本次操作后,不能通过浏览器后退按钮,返回前一个路由。next({ ...to })
的执行很简单,它会判断:
如果参数to不能找到对应的路由的话,就再执行一次路由守卫(beforeEach((to, from, next)
)直到其中的next({ ...to})
能找到对应的路由为止。
next({ ...to, replace: true })
可以保证addRoutes()
已经执行完成,路由中已经有我们后来添加进去的用来路由了。
找到对应的路由之后,接下来将前往对应路由,并执行路由守卫(beforeEach((to, from, next)
),因此需要用代码来判断是否可以该入改路由(本文的代码根据store中的allowRoutes的长度是否大于零来确定是否进行next()),如果是,就执行next()放行。
如果守卫中没有正确的放行出口的话,会一直next({ ...to})
进入死循环 !!!
因此你还需要确保在当addRoutes()已经完成时,所执行到的这一次路由守卫beforeEach((to, from, next)
中有一个正确的next()
方向出口。
以下就是上述内容的伪代码,敲重点
if (store.state.allowRouters && store.state.allowRouters.length > 0) {
// 路由的出口 addRoute已经完成 避免路由守卫进入死循环
next()
}
else{
router.addRoute('要添加的路由');
// 保证addRoute已经完成,并导航到对应的路由
next({ ...to, replace: true })
}