在開發(fā)的過程中,一般都是后端去做權(quán)限的設(shè)置和配置前端的路由,前端根據(jù)后臺(tái)編寫相應(yīng)的路由。這樣就很做到權(quán)限的控制。在某些情況,需要添加的路由不確定,需要從后端獲取數(shù)據(jù),并且后端更新相關(guān)的路由時(shí),頁面也能夠立即渲染出來,這時(shí)候就需要使用動(dòng)態(tài)路。
原來的路由目錄結(jié)構(gòu)
例如OA管理系統(tǒng)中,菜單中的很多路由都是不確定的,即使你寫了10個(gè)路由,但是后端那邊新增了10個(gè)路由,那么這時(shí)候設(shè)置動(dòng)態(tài)添加路由后,就可以自動(dòng)在第一時(shí)間創(chuàng)建出所有的路由,而不需要你手動(dòng)的寫了。
1、設(shè)置默認(rèn)路由
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 為系統(tǒng)中的默認(rèn)菜單。
2、獲取系統(tǒng)模板以及處理后臺(tái)返回的菜單
// 獲取views中以.vue結(jié)尾的文件
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結(jié)尾的組件,并且過濾掉components中的.vue文件。這一步主要是解決ES6 import() 中不能使用變量加載組件的問題。 files中的數(shù)據(jù)格式為
{
"/uesr/calendar/index":components,
"/uesr/duty/analyse":components,
...
}
3、處理后臺(tái)返回的菜單及生成路由
/**
* 刪除不需要配置路由
* @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;
}
}
}
}
};
/**
* 處理平臺(tái)菜單
* @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, '');
}
});
};
/**
* 過濾路由所需要的數(shù)據(jù)
* @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;
});
};
/**
* 創(chuàng)建一條路由記錄
* @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也不會(huì)顯示問題
routeRecord.component = { render: (e) => e('router-view') };
} else {
routeRecord.component = files[route.component];
}
return routeRecord;
};
/**
* 根據(jù)后臺(tái)菜單動(dòng)態(tài)生成路由
*/
export const getRouterMenus = () => {
const entMenuList = JSON.parse(localStorage.getItem('entMenuList'));
let entRouter = [];
if (entMenuList && entMenuList.length > 0) {
// 移除不需要處理路由
removeItems(entMenuList, ['/admin/user/work']);
// 處理后臺(tái)返回的路由結(jié)構(gòu)
replaceMenuPath(entMenuList, '/');
entRouter = filterAsyncRoutes(entMenuList);
constantRoutes.push(...entRouter);
// 路由重置加載
resetRouter();
}
};
其中constantRoutes為開始定義的數(shù)組,便于存儲(chǔ)路由。
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結(jié)尾的文件
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;
}
}
}
}
};
/**
* 處理平臺(tái)菜單
* @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, '');
}
});
};
/**
* 過濾路由所需要的數(shù)據(jù)
* @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;
});
};
/**
* 創(chuàng)建一條路由記錄
* @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也不會(huì)顯示問題
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];
/**
* 根據(jù)后臺(tái)菜單動(dòng)態(tài)生成路由
*/
export const getRouterMenus = () => {
const entMenuList = JSON.parse(localStorage.getItem('entMenuList'));
let entRouter = [];
if (entMenuList && entMenuList.length > 0) {
// 移除不需要處理路由
removeItems(entMenuList, ['/admin/user/work']);
// 處理后臺(tái)返回的路由結(jié)構(gòu)
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;
最終的目錄結(jié)構(gòu)。簡化了好多需要自己手動(dòng)創(chuàng)建的路由。