功能菜單配置自動生成路由及代碼切割
菜單即路由,共享配置
約定
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