动态路由在antd-pro中的使用,文中提到的文件路径是用antd-pro搭建的项目通用的文件,所以本文只适用于antd-pro项目
什么是ant-design-pro?
- 具体教程自查
- 简单来说就是一个系统的、条理清晰的、普适的管理平台网站搭建框架
- 除了自适应的基本UI框架,andt-pro还清晰地划分了网站的功能模块,包括:api、vuex、router、assets等以及登录注册页、404页、个人页和基本功能页。减少了不少项目开发量,这样的模块分类也有助于培养开发思维
- antd-pro的UI框架中有页面菜单栏,可通过这里的菜单进行页面路由管理,而这些菜单选项的路由绑定,就是通过动态路由实现的
什么是动态路由?
- 动态路由概念
- 分析下:当一个网站的页面路由是根据用户等级来分配,即不同级别或不同类型的用户登录成功后看到的路由菜单不一样,所以能够访问的页面也不一样时,就应该用动态路由来处理各个级别用户的路由权限。而相对的,我们常规的在router里写死路由配置就是静态路由,这部分路由是每种用户都能访问的,可能你并没有给用户提供直接访问这部分静态路由页面的入口,但是如果用户够聪明找到了你的路由路径,就可以直接通过url访问。
- 在使用动态路由规划的时候,可将路由分为constantRouter(静态路由)和dynamicRouter(动态路由)两部分,其中constantRouter包括login、404、index等每个用户都能访问的页面,而dynamicRouter则处理根据用户权限来获取的不同路由,并通过
router.addRoute()
来添加路由
动态路由规划具体分析
- 首先看到main.js同级目录下的permission.js,这就是andt-pro项目的权限管理文件。
- 开始先进行路由拦截
router.beforeEach((to, from, next) => func())
, 即未登录的(无ACCESS_TOKEN)用户只能访问login、register、registerResult等免登录页,如果未登录访问需要权限的页面,则在这里重定向到login页。
- 然后便可根据登录成功后后台返回的menu-list来添加动态路由。
store.dispatch('GenerateRoutes', { roles })
可见这里调用了store的GenerateRoutes
异步action来生成路由,而这里的roles就是登录后拿到的menu-list,生成路由完成后,通过router.addRoute(addRouters[i])
逐条添加。
// 路由拦截示例代码
router.beforeEach((to, from, next) => {
NProgress.start() // start progress bar
to.meta && typeof to.meta.title !== 'undefined' && setDocumentTitle(`${to.meta.title} - ${domTitle}`)
if (to.path === loginRoutePath) {
storage.set(ACCESS_TOKEN, '')
}
if (storage.get(ACCESS_TOKEN)) {
if (to.path === loginRoutePath) { // 有token证明已登录,访问login页面可直接跳首页
next({ path: defaultRoutePath })
NProgress.done()
} else {
const roles = store.state.menuList // vuex中存的路由菜单
// 调用vuex中的action将后台返回的menuList处理成路由对象,然后router.addRoute进行添加
store.dispatch('GenerateRoutes', { roles }).then(() => {
const addRouters = store.getters.addRouters
for (let i = 0; i < addRouters.length; i++) {
router.addRoute(addRouters[i])
}
const redirect = decodeURIComponent(from.query.redirect || to.path)
if (to.path === redirect) {
next({ ...to, replace: true })
} else {
// 跳转到目的路由
next({ path: redirect })
}
})
}
} else {
if (allowList.includes(to.name)) {
// 在免登录名单,直接进入
next()
} else {
next({ path: loginRoutePath, query: { redirect: to.fullPath } })
NProgress.done()
}
}
})
- 然后我们跳到状态管理的store/modules/async-router.js来查看
GenerateRoutes
函数,可见整个routers是由generatorDynamicRouter(data)
处理返回的router与constantRouter拼接而成。再跳到router/generator-routers.js查看generatorDynamicRouter
函数,是根据传入的json数据来配置路由,主要是引入组件,控制菜单隐藏及处理添加重定向。(关于控制菜单隐藏,提出来说下:就是有些子页面路由时不想让它出现在菜单里的,是通过菜单目录里的页面传参之后才能跳转的子页面,一开始用antd-pro的时候,不知道怎么控制子菜单隐藏,所以就在constantRouterMap里添加了一些额外路由,这样写就违背了动态路由用户分级的初衷。复盘才知道只要在配置的json里加一个show字段为false就能隐藏了)
// 根据json数据生成路由表代码示例
/**
* 动态生成菜单
* @param jsonRouter //从后台拿到的该用户可以访问的路由表json数据
* @returns {Promise<Router>}
*/
export const generatorDynamicRouter = (jsonRouter) => {
return new Promise((resolve, reject) => {
const routers = generator(jsonRouter.roles)
resolve(routers)
})
}
/**
* 格式化树形结构数据 生成 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 (currentRouter.name === 'index') {
currentRouter.component = BasicLayout
}
// 是否设置了隐藏菜单
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
})
}
- 至此动态路由规划主要部分就基本好了,最后看看后台传的json对象数据的样子
const menu = [{
path: '/',
name: 'index',
component: BasicLayout,
meta: { title: '首页' },
redirect: '/path/to/defaultPage',
children: [{
path: '/path/to/defaultPage',
name: 'name1',
component: 'RouteView',
redirect: '/path/to/defaultPage',
meta: { title: '目录名1', icon: 'dashboard' },
children: [{
path: '/path/to/sonPage1',
name: 'sonname1',
component: 'component1',
meta: { title: '子目录名1' }
}]
}, {
path: '/path/to/page2',
name: 'name2',
component: 'RouteView',
meta: { title: '目录名2', icon: 'form' },
children: [{
path: '/path/to/sonPage2',
name: 'sonname2',
component: 'component2',
meta: { title: '子目录名2', show: false } // 这里这个路径就不会在目录中显示
}]
}
]
}, {
path: '*',
redirect: '/404',
hidden: true
}]