Skip to main content

生成器架构

以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控制路由是否显示在 sidebarbooleanfalse
redirect重定向地址, 访问这个路由时,自定进行重定向string-
name路由名称, 必须设置,且不能重名string-
meta路由元信息(路由附带扩展信息)object{}
hideChildrenInMenu强制菜单显示为Item而不是SubItem(配合 meta.hidden)boolean-

{ Meta } 路由元信息对象

参数说明类型默认值
title路由标题, 用于显示面包屑, 页面标题 *推荐设置string-
icon路由在 menu 上显示的图标[string,svg]-
keepAlive缓存该路由booleanfalse
target菜单链接跳转目标(参考 html a 标记)string-
hidden配合hideChildrenInMenu使用,用于隐藏菜单时,提供递归到父菜单显示 选中菜单项(可参考 个人页 配置方式)booleanfalse
hiddenHeaderContent*特殊 隐藏 PageHeader 组件中的页面带的 面包屑和页面标题栏booleanfalse
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' ] }
}
]
},
...
]
},
]
  1. 请注意 component: () => import('..') 方式引入路由的页面组件为 懒加载模式。具体可以看 Vue 官方文档
  2. 增加新的路由应该增加在 '/' (index) 路由的 children
  3. 子路由的父级路由必须有 router-view 才能让子路由渲染出来,请仔细查阅 vue-router 文档
  4. 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,
};