困扰了好几天的动态路由问题终于解决了,参考过很多篇文章,在此感谢各位博主的分享。
现将个人心得整理如下,希望能给大家带来更多的帮助。
一.动态路由的添加
1.准备工作
//登录接口返回数据格式
{
routes:[],
token:'',
userInfo:{}
}
//静态路由(routes.js)
/**
* 在主框架内显示
*/
const frameIn = [
{
path: '/',
redirect: {
name: 'dashboard-console'
},
component: BasicLayout,
children: [
{
path: 'index',
name: 'index',
redirect: {
name: 'dashboard-console'
}
},
{
path: 'log',
name: 'log',
meta: {
title: '前端日志',
auth: true
},
component: () => import('@/pages/system/log')
},
// 刷新页面 必须保留
{
path: 'refresh',
name: 'refresh',
hidden: true,
component: {
beforeRouteEnter (to, from, next) {
next(instance => instance.$router.replace(from.fullPath));
},
render: h => h()
}
},
// 页面重定向 必须保留
{
path: 'redirect/:route*',
name: 'redirect',
hidden: true,
component: {
beforeRouteEnter (to, from, next) {
next(instance => instance.$router.replace(JSON.parse(from.params.route)));
},
render: h => h()
}
}
]
},
dashboard
];
/**
* 在主框架之外显示
*/
const frameOut = [
// 登录
{
path: '/login',
name: 'login',
meta: {
title: '$t:page.login.title'
},
component: () => import('@/pages/account/login')
}
];
/**
* 错误页面
*/
const errorPage = [
{
path: '/403',
name: '403',
meta: {
title: '403'
},
component: () => import('@/pages/system/error/403')
},
{
path: '/500',
name: '500',
meta: {
title: '500'
},
component: () => import('@/pages/system/error/500')
}
];
// 导出需要显示菜单
export const frameInRoutes = frameIn;
window.frameInRoutes = frameInRoutes;
//*404页面需要单独拿出来,在添加完动态路由之后添加,不然刷新页面会报404,跳转到404页面*
export const page404 = {
path: '*',
name: '404',
meta: {
title: '404'
},
component: () => import('@/pages/system/error/404')
};
// 重新组织后导出
export default [
...frameIn,
...frameOut,
...errorPage
];
// 获取和组装动态路由api,route-util.js
/**
* 动态添加路由,将菜单组装为路由格式
*/
import mg from "@/libs/core/mg";
import BasicLayout from '@/layouts/basic-layout';
const routeUtil = {
/**
* 动态组装路由
*/
getAsyncRoute: function(menus) {
let newDynamicRoute;
if (menus) {
newDynamicRoute = this.generateRoute(menus);
return newDynamicRoute;
}
// 向后端请求路由数据
mg.action.getData(mg.path.login.getRouters,{}).then(res => {
newDynamicRoute = this.generateRoute(res.data);
return newDynamicRoute;
});
},
/**
* 拼装路由
*/
generateRoute: function(menus){
// 动态加载路由,routes 为数组
let routes = backendMenusToRouters(menus);
return routes;
}
}
/**
* @description 将后端菜单树转换为路由树
* @param {Array} menus
* @returns {Array}
*/
export const backendMenusToRouters = (menus) => {
let routers = [];
menus.forEach(menu => {
// 将后端数据转换成路由数据
let route = backendMenuToRoute(menu);
// 如果后端数据有下级,则递归处理下级
if (menu.children && menu.children.length !== 0) {
route.redirect = {
name: menu.children[0].name
};
route.children = backendMenusToRouters(menu.children);
}
routers.push(route);
});
return routers;
}
/**
* @description 将后端菜单转换为路由
* @param {Object} menu
* @returns {Object}
*/
const backendMenuToRoute = (menu) => {
// 原先routers写法是component: () => import('@/view/error-page/404.vue')
let route = Object.assign({}, menu);
if(route.component){
if (route.component === 'BasicLayout') {
route.component = BasicLayout;
} else {
route.component = () => import(`@/${menu.component}.vue`);
}
}
route.meta = {
title: menu.title,
auth: menu.auth
};
return route;
}
export default routeUtil;
2.动态添加路由
在登录接口返回结果中,组装了动态路由相关信息。动态路由的添加时机是在登录后,页面跳转前,不然访问菜单会出现空白或404
// 登录相关api—与后台交互接口accout.js,代码量比较大,这里只整理出登录按钮执行的方法
/**
* 登录
* */
import util from '@/libs/util';
import router from '@/router';
import Setting from '@/setting';
import { AccountLogin, AccountRegister, AccountLogout } from '@/api/account';
import { Modal } from 'view-design';
export default {
namespaced: true,
actions: {
/**
* @description 登录
* @param {Object} param context
* @param {Object} param username {String} 用户账号
* @param {Object} param password {String} 密码
* @param {Object} param route {Object} 登录成功后定向的路由对象 任何 vue-router 支持的格式
*/
login ({ dispatch }, {
username = '',
password = ''
} = {}) {
return new Promise((resolve, reject) => {
// 开始请求登录接口
AccountLogin({
username,
password
})
.then(async res => {
let userInfo = res.userInfo;
util.cookies.set('uuid', userInfo.userId);
util.cookies.set('token', res.token);
// // 设置 vuex 用户信息
await dispatch('admin/user/set', userInfo, { root: true });
window.localStorage.setItem("roles",JSON.stringify(userInfo.roles));
window.localStorage.setItem("loginUser",JSON.stringify(userInfo));
window.localStorage.setItem("menuSider",JSON.stringify(res.routers));
window.localStorage.setItem("asyncMenus",JSON.stringify(res.routers));
// 用户登录后从持久化数据加载一系列的设置
await dispatch('load', { loadOpenedTabs: Setting.page.loadOpenedTabs });
// 结束
resolve();
})
.catch(err => {
console.log('err: ', err);
reject(err);
})
})
}
// 登录相关api----登录按钮事件方法
/**
* @description 登录
* 表单校验已有 iView Pro 自动完成,如有需要修改,请阅读 iView Pro 文档
*/
handleSubmit (valid, values) {
if (valid) {
const { username, password } = values;
this.login({
username,
password
}).then(() => {
//动态添加路由
let menus = JSON.parse(localStorage.getItem('asyncMenus'));
let asyncRoutes = routeUtil.getAsyncRoute(menus);
this.$router.addRoutes(asyncRoutes);
this.$router.addRoute(page404);
window.frameInRoutes = frameInRoutes.concat(asyncRoutes);
this.$store.commit('admin/menu/setAsyncMenu', asyncRoutes);
// 重定向对象不存在则返回顶层路径
this.$router.replace(this.$route.query.redirect || '/').catch(err => err);
}).catch(err => {
console.log('err: ', err);
this.loginAlert.show = true;
});
}
}
至此,第一步添加动态路由的过程就完成了。
但是刷新页面后,页面空白,因为每次刷新后,路由被重置,动态路需要再次添加,添加位置一般在路由守卫,即router.beroreEach中添加,如下代码是router/index.js的全部内容
import Vue from 'vue';
import VueRouter from 'vue-router';
import iView from 'view-design';
import util from '@/libs/util'
import Setting from '@/setting';
import store from '@/store/index';
// 路由数据
import routes, {page404} from './routes';
import routeUtil from "@/router/route-util";
import mg from "@/libs/core/mg";
Vue.use(VueRouter);
// 导出路由 在 main.js 里使用
const router = new VueRouter({
routes,
mode: Setting.routerMode,
base: Setting.routerBase
});
/**
* 路由拦截
* 权限验证
*/
router.beforeEach((to, from, next) => {
if (Setting.showProgressBar) iView.LoadingBar.start();
let token = util.cookies.get('token');
let asyncMenu = JSON.stringify(store.state.admin.menu.asyncMenu);
if (token) {
// 登录过就不能访问登录界面,需要中断这一次路由守卫,执行下一次路由守卫,并且下一次守卫的to是主页
if (to.path === '/login') {
next({ path: '/' });
}
// 保存在store中路由不为空则放行 (如果执行了刷新操作,则 store 里的路由为空,此时需要重新添加路由)
if ('[]' !== asyncMenu || to.name != null) {
//放行
next();
} else {
router.dynamicAddRouter(to,from,next);
}
} else {
// 未登录时,注意 :在这里也许你的项目不只有 logon 不需要登录 ,register 等其他不需要登录的页面也需要处理
if (to.path !== '/login') {
next({ path: '/login' });
} else {
next();
}
}
});
router.afterEach(to => {
if (Setting.showProgressBar) iView.LoadingBar.finish();
// 多页控制 打开新的页面
if (!('meta' in to) || (to.meta && !('tabs' in to.meta)) || (to.meta && to.meta.tabs)) {
store.dispatch('admin/page/open', to);
}
// 更改标题
util.title({
title: to.meta.title
});
// 返回页面顶端
window.scrollTo(0, 0);
});
/**
* 添加动态路由
* @param to 下一个路由
* @param from 当前路由
* @param next 放行
*/
router.dynamicAddRouter = function(to,from,next){
// 将路由添加到 store 中,用来标记已添加动态路由
mg.action.getData(mg.path.login.getRouters,{}).then(res => {
let asyncRoutes = routeUtil.generateRoute(res.data);
store.commit('admin/menu/setAsyncMenu', asyncRoutes);
window.frameInRoutes = frameInRoutes.concat(asyncRoutes).concat(page404);
router.$addRoutes(asyncRoutes);
router.$addRoute(page404);
// 如果 addRoutes 并未完成,路由守卫会一层一层的执行执行,直到 addRoutes 完成,找到对应的路由
next({ ...to, replace: true });
});
}
/**
* 解决问题:动态路由添加时调用addRoutes();它只会帮你注入路由,不会帮你把前面的路由清掉。如此一来就重复添加了。
* @param params
*/
router.$addRoutes = params => {
params.forEach(item => {
router.addRoute(item);
router.options.routes.push(item);
});
};
router.$addRoute = param => {
router.addRoute(param);
router.options.routes.push(param);
};
export default router;
通过此方法,可以解决刷新页面空白和router死循环问题。
关于动态路由刷新页面空白和死循环原因,这篇博客讲的比较明了,参考地址如下:https://blog.csdn.net/weixin_42094764/article/details/116403423