在开发的过程中,一般都是后端去做权限的设置和配置前端的路由,前端根据后台编写相应的路由。这样就很做到权限的控制。在某些情况,需要添加的路由不确定,需要从后端获取数据,并且后端更新相关的路由时,页面也能够立即渲染出来,这时候就需要使用动态路。
原来的路由目录结构
例如OA管理系统中,菜单中的很多路由都是不确定的,即使你写了10个路由,但是后端那边新增了10个路由,那么这时候设置动态添加路由后,就可以自动在第一时间创建出所有的路由,而不需要你手动的写了。
1、设置默认路由
import Vue from 'vue';
import Router from 'vue-router';
import { roterPre } from '@/settings';
Vue.use(Router);
/* Layout */
import Layout from '@/layout';
import defaultRoutes from '@/router/routes';
import companyRouter from '@/router/company';
export const constantRoutes = [...defaultRoutes, companyRouter];
const createRouter = () =>
new Router({
mode: 'history', // require service support
scrollBehavior: () => ({ y: 0 }),
routes: constantRoutes,
});
const router = createRouter();
// 解决刷新页面后路由重置
getRouterMenus();
export function resetRouter() {
const newRouter = createRouter();
router.matcher = newRouter.matcher; // reset router
}
export default router;
其中defaultRoutes ,companyRouter 为系统中的默认菜单。
2、获取系统模板以及处理后台返回的菜单
// 获取views中以.vue结尾的文件
const contextInfo = require.context('../views', true, /.vue$/);
/**
* 过滤/components中的vue文件
* @returns {{}}
*/
const filteredFileNames = () => {
const files = {};
contextInfo.keys().forEach((fileName) => {
if (!fileName.includes('/components')) {
const pathConfig = contextInfo(fileName);
let path = '/' + fileName.substring(2, fileName.length - 4);
files[path] = pathConfig.default;
}
});
return files;
};
const files = filteredFileNames();
以上的处理就是为了的到views中以.vue结尾的组件,并且过滤掉components中的.vue文件。这一步主要是解决ES6 import() 中不能使用变量加载组件的问题。 files中的数据格式为
{
"/uesr/calendar/index":components,
"/uesr/duty/analyse":components,
...
}
3、处理后台返回的菜单及生成路由
/**
* 删除不需要配置路由
* @param items
* @param paths
*/
const removeItems = (items, paths) => {
for (let i = 0; i < items.length; i++) {
const item = items[i];
for (let j = 0; j < paths.length; j++) {
const path = paths[j];
if (item.menu_path === path) {
items.splice(i, 1);
i--;
break;
} else if (item.children) {
removeItems(item.children, paths);
if (item.children.length === 0) {
delete item.children;
}
}
}
}
};
/**
* 处理平台菜单
* @param menus
* @param parentPath
*/
const replaceMenuPath = (menus, parentPath) => {
menus.forEach((menu, index) => {
if (menu.children) {
replaceMenuPath(menu.children, menu.menu_path);
}
if (parentPath !== '/' && menu.menu_path.indexOf(parentPath) > -1) {
menu.new_path = menu.menu_path.replace(parentPath + '/', '');
menu.component = menu.menu_path.replace(roterPre, '');
}
if (menu.menu_path !== '/') {
menu.component = menu.menu_path.replace(roterPre, '');
}
});
};
/**
* 过滤路由所需要的数据
* @param routes
* @returns {*}
*/
const filterAsyncRoutes = (routes) => {
return routes.map((route) => {
const routeRecord = createRouteRecord(route);
if (route.children != null && route.children && route.children.length) {
routeRecord.children = filterAsyncRoutes(route.children);
}
return routeRecord;
});
};
/**
* 创建一条路由记录
* @param route
* @returns {{path: (*|string), meta: {noCache: boolean, icon, title: *}, name: *}}
*/
const createRouteRecord = (route) => {
const routeRecord = {
path: route.pid === 0 ? route.menu_path : route.new_path ? route.new_path : '/',
name: route.unique_auth,
meta: {
title: route.menu_name,
icon: route.icon,
noCache: true,
},
};
if (route.pid === 0) {
routeRecord.component = Layout;
} else if (route.pid > 0 && route.children && route.children.length > 0) {
// 解决父级不写component 属性,子集的component也不会显示问题
routeRecord.component = { render: (e) => e('router-view') };
} else {
routeRecord.component = files[route.component];
}
return routeRecord;
};
/**
* 根据后台菜单动态生成路由
*/
export const getRouterMenus = () => {
const entMenuList = JSON.parse(localStorage.getItem('entMenuList'));
let entRouter = [];
if (entMenuList && entMenuList.length > 0) {
// 移除不需要处理路由
removeItems(entMenuList, ['/admin/user/work']);
// 处理后台返回的路由结构
replaceMenuPath(entMenuList, '/');
entRouter = filterAsyncRoutes(entMenuList);
constantRoutes.push(...entRouter);
// 路由重置加载
resetRouter();
}
};
其中constantRoutes为开始定义的数组,便于存储路由。
4、最终index.js逻辑源码
import Vue from 'vue';
import Router from 'vue-router';
import { roterPre } from '@/settings';
Vue.use(Router);
/* Layout */
import Layout from '@/layout';
// 获取views中以.vue结尾的文件
const contextInfo = require.context('../views', true, /.vue$/);
/**
* 过滤/components中的vue文件
* @returns {{}}
*/
const filteredFileNames = () => {
const files = {};
contextInfo.keys().forEach((fileName) => {
if (!fileName.includes('/components')) {
const pathConfig = contextInfo(fileName);
let path = '/' + fileName.substring(2, fileName.length - 4);
files[path] = pathConfig.default;
}
});
return files;
};
const files = filteredFileNames();
/**
* 删除不需要配置路由
* @param items
* @param paths
*/
const removeItems = (items, paths) => {
for (let i = 0; i < items.length; i++) {
const item = items[i];
for (let j = 0; j < paths.length; j++) {
const path = paths[j];
if (item.menu_path === path) {
items.splice(i, 1);
i--;
break;
} else if (item.children) {
removeItems(item.children, paths);
if (item.children.length === 0) {
delete item.children;
}
}
}
}
};
/**
* 处理平台菜单
* @param menus
* @param parentPath
*/
const replaceMenuPath = (menus, parentPath) => {
menus.forEach((menu, index) => {
if (menu.children) {
replaceMenuPath(menu.children, menu.menu_path);
}
if (parentPath !== '/' && menu.menu_path.indexOf(parentPath) > -1) {
menu.new_path = menu.menu_path.replace(parentPath + '/', '');
menu.component = menu.menu_path.replace(roterPre, '');
}
if (menu.menu_path !== '/') {
menu.component = menu.menu_path.replace(roterPre, '');
}
});
};
/**
* 过滤路由所需要的数据
* @param routes
* @returns {*}
*/
const filterAsyncRoutes = (routes) => {
return routes.map((route) => {
const routeRecord = createRouteRecord(route);
if (route.children != null && route.children && route.children.length) {
routeRecord.children = filterAsyncRoutes(route.children);
}
return routeRecord;
});
};
/**
* 创建一条路由记录
* @param route
* @returns {{path: (*|string), meta: {noCache: boolean, icon, title: *}, name: *}}
*/
const createRouteRecord = (route) => {
const routeRecord = {
path: route.pid === 0 ? route.menu_path : route.new_path ? route.new_path : '/',
name: route.unique_auth,
meta: {
title: route.menu_name,
icon: route.icon,
noCache: true,
},
};
if (route.pid === 0) {
routeRecord.component = Layout;
} else if (route.pid > 0 && route.children && route.children.length > 0) {
// 解决父级不写component 属性,子集的component也不会显示问题
routeRecord.component = { render: (e) => e('router-view') };
} else {
routeRecord.component = files[route.component];
}
return routeRecord;
};
import defaultRoutes from '@/router/routes';
import companyRouter from '@/router/company';
export const constantRoutes = [...defaultRoutes, companyRouter];
/**
* 根据后台菜单动态生成路由
*/
export const getRouterMenus = () => {
const entMenuList = JSON.parse(localStorage.getItem('entMenuList'));
let entRouter = [];
if (entMenuList && entMenuList.length > 0) {
// 移除不需要处理路由
removeItems(entMenuList, ['/admin/user/work']);
// 处理后台返回的路由结构
replaceMenuPath(entMenuList, '/');
entRouter = filterAsyncRoutes(entMenuList);
constantRoutes.push(...entRouter);
// 路由重置加载
resetRouter();
}
};
const createRouter = () =>
new Router({
mode: 'history', // require service support
scrollBehavior: () => ({ y: 0 }),
routes: constantRoutes,
});
const router = createRouter();
// 解决刷新页面后路由重置
getRouterMenus();
export function resetRouter() {
const newRouter = createRouter();
router.matcher = newRouter.matcher; // reset router
}
export default router;
最终的目录结构。简化了好多需要自己手动创建的路由。