生成器架构
以Ant-design-vue-pro为例:
目录结构
DIR:router #
|-- README.md #
|-- index.js #
`-- generator-routers.js # 存放基础的路由表和路由表的生成函数
- 路由定义
import * as Login from '@/api/login'
import Layout from '@/layouts'
// 根级菜单
const rootRouter = {
key: '',
name: 'index',
path: '',
component: 'Layout',
redirect: '/dashboard',
meta: { title: '首页' },
children: []
}
// 前端路由表
const RouterList = {
// 基础路由
Login,
Layout,
...// 其他页面动态路由
'403': () => import(/* webpackChunkName: "error" */ '@/views/exception/403'),
'404': () => import(/* webpackChunkName: "error" */ '@/views/exception/404'),
'500': () => import(/* webpackChunkName: "error" */ '@/views/exception/500'),
Dashboard: () => import('@/views/dashboard'),
}
- 动态路由获取
import * as loginService from '@/api/login'
/**
* 数组转树形结构
* @param list 源数组
* @param tree 树
* @param parentId 父ID
*/
const listToTree = (list, tree, parentId) => {
list.forEach(item => {
// 判断是否为父级菜单
if (item.parentId === parentId) {
const child = {
...item,
key: item.key || item.name,
children: [],
};
// 迭代 list, 找到当前菜单相符合的所有子菜单
listToTree(list, child.children, item.id);
// 删掉不存在 children 值的属性
if (child.children.length <= 0) {
delete child.children;
}
// 加入到树中
tree.push(child);
}
});
};
// 生成器
/**
* 请求后端,动态生成菜单
* @param token
* @returns {Promise<Router>}
*/
export const generatorDynamicRouter = token => {
return new Promise((resolve, reject) => {
const url = 'xxxx/login'
// 通过token,调用登陆接口
// loginService
axios.get(url)
.getCurrentUserNav(token)
.then(res => {
console.log('generatorDynamicRouter response:', res)
const { result } = res
const menuNav = []
const childrenNav = []
// 后端返回的数据, 根级树数组, 根级 PID
listToTree(result, childrenNav, 0)
rootRouter.children = childrenNav
menuNav.push(rootRouter)
console.log('menuNav', menuNav)
const routers = generator(menuNav)
routers.push(notFoundRouter)
console.log('routers', routers)
resolve(routers)
})
.catch(err => {
reject(err)
})
})
}
/**
* 格式化树形结构数据 生成 vue-router 层级路由表,这里分为两个方案
*
* @param routerMap
* @param parent
* @returns {*}
*/
export const generator = (routerMap, parent) => {
return routerMap.map(item => {
const { title, show, hideChildren, hiddenHeaderContent, target, icon } = item.meta || {};
const currentRouter = {
// 如果路由设置了 path,则作为默认 path,否则 路由地址 动态拼接生成如 /dashboard/workplace
path: item.path || `${(parent && parent.path) || ""}/${item.key}`,
// 路由名称,建议唯一
name: item.name || item.key || "",
// 该路由对应页面的 组件 :方案1
// component: constantRouterComponents[item.component || item.key],
// 该路由对应页面的 组件 :方案2 (动态加载)
component: constantRouterComponents[item.component || item.key] || (() => import(`@/views/${item.component}`)),
// meta: 页面标题, 菜单图标, 页面权限(供指令权限用,可去掉)
meta: {
title: title,
icon: icon || undefined,
hiddenHeaderContent: hiddenHeaderContent,
target: target,
permission: item.name,
},
};
// 是否设置了隐藏菜单
if (show === false) {
currentRouter.hidden = true;
}
// 是否设置了隐藏子菜单
if (hideChildren) {
currentRouter.hideChildrenInMenu = true;
}
// 为了防止出现后端返回结果不规范,处理有可能出现拼接出两个 反斜杠
if (!currentRouter.path.startsWith("http")) {
currentRouter.path = currentRouter.path.replace("//", "/");
}
// 重定向
item.redirect && (currentRouter.redirect = item.redirect);
// 是否有子菜单,并递归处理
if (item.children && item.children.length > 0) {
// Recursion
currentRouter.children = generator(item.children, currentRouter);
}
return currentRouter;
});
};
路由/菜单说明
const routerObject = {
redirect: noredirect,
name: 'router-name',
hidden: true,
meta: {
title: 'title',
icon: 'a-icon',
target: '_blank|_self|_top|_parent',
keepAlive: true,
hiddenHeaderContent: true,
}
}
{ Route }
对象
参数 | 说明 | 类型 | 默认值 |
---|---|---|---|
hidden | 控制路由是否显示在 sidebar | boolean | false |
redirect | 重定向地址, 访问这个路由时,自定进行重定向 | string | - |
name | 路由名称, 必须设置,且不能重名 | string | - |
meta | 路由元信息(路由附带扩展信息) | object | {} |
hideChildrenInMenu | 强制菜单显示为Item而不是SubItem(配合 meta.hidden) | boolean | - |
{ Meta }
路由元信息对象
参数 | 说明 | 类型 | 默认值 |
---|---|---|---|
title | 路由标题, 用于显示面包屑, 页面标题 *推荐设置 | string | - |
icon | 路由在 menu 上显示的图标 | [string,svg] | - |
keepAlive | 缓存该路由 | boolean | false |
target | 菜单链接跳转目标(参考 html a 标记) | string | - |
hidden | 配合hideChildrenInMenu 使用,用于隐藏菜单时,提供递归到父菜单显示 选中菜单项(可参考 个人页 配置方式) | boolean | false |
hiddenHeaderContent | *特殊 隐藏 PageHeader 组件中的页面带的 面包屑和页面标题栏 | boolean | false |
permission | 与项目提供的权限拦截匹配的权限,如果不匹配,则会被禁止访问该路由页面 | array | [] |
路由自定义
Icon
请引入自定义svg
Icon 文件,然后传递给路由的meta.icon
参数即可
路由构建例子方案1
路由例子
const asyncRouterMap = [
{
path: '/',
name: 'index',
component: BasicLayout,
meta: { title: '首页' },
redirect: '/dashboard/analysis',
children: [
{
path: '/dashboard',
component: RouteView,
name: 'dashboard',
redirect: '/dashboard/workplace',
meta: {title: '仪表盘', icon: 'dashboard', permission: ['dashboard']},
children: [
{
path: '/dashboard/analysis',
name: 'Analysis',
component: () => import('@/views/dashboard/Analysis'),
meta: {title: '分析页', permission: ['dashboard']}
},
{
path: '/dashboard/monitor',
name: 'Monitor',
hidden: true,
component: () => import('@/views/dashboard/Monitor'),
meta: {title: '监控页', permission: ['dashboard']}
},
{
path: '/dashboard/workplace',
name: 'Workplace',
component: () => import('@/views/dashboard/Workplace'),
meta: {title: '工作台', permission: ['dashboard']}
}
]
},
// result
{
path: '/result',
name: 'result',
component: PageView,
redirect: '/result/success',
meta: { title: '结果页', icon: 'check-circle-o', permission: [ 'result' ] },
children: [
{
path: '/result/success',
name: 'ResultSuccess',
component: () => import(/* webpackChunkName: "result" */ '@/views/result/Success'),
// 该页面隐藏面包屑和页面标题栏
meta: { title: '成功', hiddenHeaderContent: true, permission: [ 'result' ] }
},
{
path: '/result/fail',
name: 'ResultFail',
component: () => import(/* webpackChunkName: "result" */ '@/views/result/Error'),
// 该页面隐藏面包屑和页面标题栏
meta: { title: '失败', hiddenHeaderContent: true, permission: [ 'result' ] }
}
]
},
...
]
},
]
- 请注意
component: () => import('..')
方式引入路由的页面组件为 懒加载模式。具体可以看 Vue 官方文档- 增加新的路由应该增加在 '/' (index) 路由的
children
内- 子路由的父级路由必须有
router-view
才能让子路由渲染出来,请仔细查阅 vue-router 文档permission
可以进行自定义修改,只需要对这个模块进行自定义修改即可 src/store/modules/permission.js#L10
附权限路由结构:
第二种前端路由由后端动态生成的设计,可以前往官网文档 https://pro.antdv.com/docs/authority-management 参考
后端数据结构
// 后端返回的 JSON 动态路由结构
const servicePermissionMap = {
message: "",
result: [
{
title: "首页",
key: "",
name: "index",
component: "BasicLayout",
redirect: "/dashboard/workplace",
children: [
{
title: "仪表盘",
key: "dashboard",
component: "RouteView",
icon: "dashboard",
children: [
{
title: "分析页",
key: "analysis",
icon: "",
},
...{}, // 其他子路由
],
},
{
title: "系统管理",
key: "system",
component: "PageView",
icon: "setting",
children: [
{
title: "用户管理",
key: "userList",
},
...{}, // 其他子路由
],
},
],
},
],
status: 200,
timestamp: 1534844188679,
};