功能菜单配置自动生成路由及代码切割
菜单即路由,共享配置
约定
views
目录中存放业务组件;业务模块分文件夹存放,路由主组件使用index
命名存放在以路由名的目录下。
views **业务组件
└── base 业务分组目录,对应router目录中的菜单组
├── about 业务功能
│ ├── index.vue 业务路由页面主组件
│ └── ...others.vue 相关的分块组件
├── welcome
│ └── index.vue
└── index.vue 子路由组件,在菜单开启子路由时可用
配置
每一个配置文件对应一组菜单,文件名对应views下面的子目录,配置结构如下:
// base.ts
const menu: IMenu = {
name: 'base', // 菜单名称,对应views目录下的子目录
title: '基本信息', // 菜单标题
isPath: true, // 是否为访问父路径,默认情况下菜单下的目录为一级路由,开启此项将读取目录下index.vue作为子路由组件,文件不存在时自动生成组件。
routes: [ // 菜单目录
{
name: 'about', // 菜单名称,对应路由名称
title: '关于', // 菜单标题,对应路由meta信息
filename: 'about', // 组件文件名,默认为name
path: 'about', // 访问路径,配置方式同vue-router,默认为name
...RouterConfig // 其它vue-router的路由配置
},
{
name: 'welcome',
title: '欢迎'
}
]
}
export default menu
实现
菜单转路由
menuToRoute
方法将菜单配置转换为路由配置
参数
-
menu
:单个菜单配置对象或多个配置对象数组 -
isRoot
:是否为根路由;路由配置中根路由path
需要以/
开头
// menuToRoute.ts
export default function formatRoute(menu: IMenu | IMenu[], isRoot?: boolean) {
let _routes: RouteConfig[] = []
if (Array.isArray(menu)) {
for (const item of menu) {
_routes = _routes.concat(formatRoute(item, isRoot))
}
return _routes
}
const { name: menuName, isPath, routes } = menu
const children = routes.map(route => {
const { name, filename = name, path, title, meta, ...config } = route
return {
name,
path: (!isPath && isRoot ? '/' : '') + (path || name),
component: () =>
// 使用chunks方法对代码按照菜单分块打包,详见后面说明
chunks(menuName, filename).then(e => {
// 在使用keep-alive动态缓存include属性时需要组件name
// 加上'v-'前缀避免“Do not use built-in or reserved HTML elements as component id”
;(e.default.options || e.default).name = 'v-' + name
return e
}),
meta: title ? { title, ...meta } : meta
...config,
}
})
_routes = isPath
? [
{
path: (isRoot ? '/' : '') + menuName,
// 如果使用二级路由,读取菜单目录下的index为子路由组件,没有找到使用默认组件
component: () => chunks(menuName, 'index').catch(() => subView),
children
}
]
: children
return _routes
}
与其它路由配置结合使用:
// @/router/routes.ts
import Home from '@/views/Home.vue'
/* 导入所有菜单配置数组 */
import menu from './menu/'
import menuToRoute from './menuToRoute'
const ISROOT = true
const routes = [
{
path: '/',
name: 'home',
component: Home
},
...menuToRoute(menu, ISROOT)
]
export default routes
代码切割
路由对应的组件使用 webpack的import()
方法(查看官方文档)懒加载,可根据需求修改menuToRoute.ts
文件中的chunk
方法进行代码切割打包。
一般小的项目中,可以直接使用import( `@/views/${name}/index` )
就可以了,此方法将每个目录下的index默认导出类型文件为入口将相关引用的文件进行打包;结合前面约定路由主组件以index命名的规范,可以避免将其它没有引用到的文件打包。
/**
* menuToRoute.ts
* 代码切割打包
* @param type 菜单组(文件夹)名称
* @param name 路由名称或路由对应文件名
*/
function chunks(type: string, name: string) {
switch (type) {
case 'guide':
// guide菜单下的路由如果没有指定filename,将默认导入md文件,由于md非默认导入类型,所以需要加上扩展名
if (!/(^index)|(\.\w+$)/.test(name)) {
name += '.md'
}
return import(/* webpackChunkName: 'guide' */ `@/views/guide/${name}`)
case 'example':
// 将views/example/下的所有vue和tsx文件打成一个包
return import(
/* webpackChunkName: 'example',webpackMode: "lazy-once",webpackInclude: /\.(vue|tsx)$/ */
`@/views/example/${name}`
)
default:
// 将views目录下(包括子目录)所有index文件的默认导入类型分块打包
return import( `@/views/${name}/index` )
}
}
菜单应用及权限控制
应用到elment-ui的NavMenu菜单导航组件示例:
<el-menu :default-active="$route.name" @select="$router.push({ name:$event })">
<el-menu-item index="home">
<template slot="title">首页</template>
</el-menu-item>
<el-submenu :index="menu.name" v-for="menu of menuList" :key="menu.name">
<template slot="title">{{ menu.title }}</template>
<el-menu-item :index="item.name" v-for="item of menu.routes" :key="item.name">
{{ item.title }}
</el-menu-item>
</el-submenu>
</el-menu>
权限过滤
- 后台按菜单结构名称建立多级功能列表,如:菜单->目录->tab->按钮
- 将不同的功能权限分配到角色
- 每个用户可以指定多个角色
- 用户登录返回用户角色组,进行权限合并
/**
* @param menuList 后台配置的所有权限信息列表
* @param roleList 用户多个角色信息
**/
function buildUserRule (menuList, roleList) {
// 合并角色权限列表
const menuIdList = [].concat(roleList.map(role=>role.menuIdList))
// 过滤生成用户权限
const rules = menuList.filter(item => menuIdList.includes(item.id))
// 通过权限信息中的id和parentId对应关系生成目录树结构
return formatTree(rules)
}
将生成的权限树结构存放在store中,推荐格式如下:
{
菜单名:{ // 对应菜单组
目录名:{ // 对应路由
tab名:{ // 路由页面中多个页签
edit: true // 操作按钮
delete: true
}
}
}
}
通过这种格式在菜单显示的时候方便进行权限过滤,进入到某个路由时也方便取到当前路由下的权限进行权限适配
原创文章,转载请声明,欢迎一起讨论! @nicefan.cn
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。