上回書說到同步路由配置是在router/config.js中,與此同時,還可以通過開關asyncRoutes設置異步路由的加載。不得不說,作者思路確實思路清晰,不僅把項目中很多配置項如主題、導航布局、動畫效果等開關抽象到了配置文件中,還設置了異步路由的配置以及根據路由匹配的菜單權限。
權限咱們暫且不聊,先看看路由以及左側菜單樹是如何生成的。
先看下生成后的完整路由配置項如下:
{
"path": "/", //路由路徑 (必有)
"name": "首頁", //路由名稱
"component": { //路由加載的組件
"name": "TabsView",
"i18n": {
},
"components": { //子組件
},
"computed": {}, //計算屬性
"beforeDestroy": [], //銷毀鉤子事件
"watch": {}, //屬性監聽
"methods": {},
"staticRenderFns": [], //render函數使用的靜態方法
"_compiled": true,
"_scopeId": "data-v-012d64c5",
"beforeCreate": [],
"__file": "src/layouts/tabs/TabsView.vue" //文件路徑
},
"redirect": "/login",
"children": [
{
"path": "dashboard",
"name": "Dashboard",
"meta": {
"icon": "dashboard",
"authority": { //用戶角色權限
"permission": "*"
},
"pAuthorities": [ //操作權限
{
"permission": "*"
}
]
},
"component": {
"name": "BlankView",
"components": {
},
"computed": {},
"staticRenderFns": [],
"_compiled": true,
"_scopeId": "data-v-2172e1ea",
"beforeCreate": [
null
],
"beforeDestroy": [
null
],
"__file": "src/layouts/BlankView.vue"
},
"children": [
{
"path": "workplace",
"name": "工作臺",
"meta": {
"page": {
"closable": false
},
"authority": {
"permission": "*"
},
"pAuthorities": [
{
"permission": "*"
},
{
"permission": "*"
}
]
}
},
{
"path": "analysis",
"name": "分析頁",
"meta": {
"authority": {
"permission": "*"
},
"pAuthorities": [
{
"permission": "*"
},
{
"permission": "*"
}
]
}
}
]
}
]
}
比在config.js中配置的多了不少配置項呀,那是怎么生成的呢?
在第一篇中,我們看到主入口main中調用了initRouter方法
const router = initRouter(store.state.setting.asyncRoutes) //加載路由
//@router/index.js中如下方法
function initRouter(isAsync) {
const options = isAsync ? require('./async/config.async').default : require('./config').default
formatRoutes(options.routes)
return new Router(options)
}
其中asyncRouters為true表示加載異步路由,并引用了config.async.js(其實在項目實戰中,這一步是要調用服務端接口去獲取配置);asyncRouters為t為false,加載本地config.js配置。
formatRoutes方法做了兩件事情:
- 把配置項中path不以/開頭的都加上斜杠/
- 遍歷各個路由配置項中是否在meta元數據中加了權限配置,沒有則都加上permisson:*
做完后就通過new Router(options)返回router對象。
獲取到router對象后,接下來就要把路由對象掛載到Vue中
bootstrap({router, store, i18n, message: Vue.prototype.$message}) //初始化路由以及路由守衛、axios攔截器
//對應的方法
function bootstrap({router, store, i18n, message}) {
// 設置應用配置
setAppOptions({router, store, i18n})
// 加載 axios 攔截器
loadInterceptors(interceptors, {router, store, i18n, message})
// 加載路由
loadRoutes()
// 加載路由守衛
loadGuards(guards, {router, store, i18n, message})
}
通過loadRoutes方法加載路由,loadRoutes中做了哪些事情呢?以下是關鍵部分
// 如果 routesConfig 有值,則更新到本地,否則從本地獲取
if (routesConfig) { //從服務端獲取的route配置
store.commit('account/setRoutesConfig', routesConfig)
} else {
routesConfig = store.getters['account/routesConfig']
}
// 如果開啟了異步路由,則加載異步路由配置
const asyncRoutes = store.state.setting.asyncRoutes
if (asyncRoutes) {
if (routesConfig && routesConfig.length > 0) {
const routes = parseRoutes(routesConfig, routerMap) //解析路由配置
const finalRoutes = mergeRoutes(basicOptions.routes, routes) //合并路由
formatRoutes(finalRoutes)
router.options = {...router.options, routes: finalRoutes}
router.matcher = new Router({...router.options, routes:[]}).matcher
router.addRoutes(finalRoutes) //重新掛載路由
}
}
// 提取路由國際化數據
mergeI18nFromRoutes(i18n, router.options.routes)
// 初始化Admin后臺菜單數據
const rootRoute = router.options.routes.find(item => item.path === '/')
const menuRoutes = rootRoute && rootRoute.children
if (menuRoutes) { //把菜單數據放入到store中
store.commit('setting/setMenuData', menuRoutes)
}
把配置的路由數據和同步路由數據做一次合并,重新掛載到Vue中。同時把菜單數據存入到store中,原來是在解析路由時就把數據存儲了。那后面就是拿到數據,去渲染!
在AdminLayout.vue中加載menuData數據
//AdminLayout.vue
<template>
<a-layout :class="['admin-layout', 'beauty-scroll']">
<drawer v-if="isMobile" v-model="drawerOpen">
<side-menu :theme="theme.mode" :menuData="menuData" :collapsed="false" :collapsible="false" @menuSelect="onMenuSelect"/>
</drawer>
<side-menu :class="[fixedSideBar ? 'fixed-side' : '']" :theme="theme.mode" v-else-if="layout === 'side' || layout === 'mix'" :menuData="sideMenuData" :collapsed="collapsed" :collapsible="true" />
...
</a-layout>
</template>
//@components/menu/SideMenu.vue
<template>
<a-layout-sider :theme="sideTheme" :class="['side-menu', 'beauty-scroll', isMobile ? null : 'shadow']" width="256px" :collapsible="collapsible" v-model="collapsed" :trigger="null">
<div :class="['logo', theme]">
<router-link to="/dashboard/workplace">
<img src="@/assets/img/logo.png">
<h1>{{systemName}}</h1>
</router-link>
</div>
<i-menu :theme="theme" :collapsed="collapsed" :options="menuData" @select="onSelect" class="menu"/>
</a-layout-sider>
</template>
渲染左側菜單需要用到menu.js,里面繞來繞去有一套復雜的邏輯,到底是怎么生成的呢?這里就需要提到組件的render函數了。
Vue 推薦在絕大多數情況下使用模板來創建你的 HTML。然而在一些場景中,你真的需要 JavaScript 的完全編程的能力。這時你可以用渲染函數,它比模板更接近編譯器。
render函數是調用createElement(Vue 將 h 作為 createElement 的別名是 生態系統中的一個通用慣例)來創建dom元素,參數如下
// @returns {VNode}
createElement(
// {String | Object | Function}
// 一個 HTML 標簽名、組件選項對象,或者
// resolve 了上述任何一種的一個 async 函數。必填項。
'div',
// {Object}
// 一個與模板中 attribute 對應的數據對象。可選。
{
// (詳情見下一節)
},
// {String | Array}
// 子級虛擬節點 (VNodes),由 `createElement()` 構建而成,
// 也可以使用字符串來生成“文本虛擬節點”。可選。
[
'先寫一些文字',
createElement('h1', '一則頭條'),
createElement(MyComponent, {
props: {
someProp: 'foobar'
}
})
]
)
理解了createElement函數,我們再回回過頭來看menu.js的調用
render (h) {
return h(
Menu, //antd中的menu組件
{
props: {
theme: this.menuTheme, //主題
mode: this.$props.mode, //模式
selectedKeys: this.selectedKeys, //選擇的菜單path
openKeys: this.openKeys ? this.openKeys : this.sOpenKeys
},
on: { //事件
'update:openKeys': (val) => {
this.sOpenKeys = val
},
click: (obj) => {
obj.selectedKeys = [obj.key]
this.$emit('select', obj)
}
}
}, this.renderMenu(h, this.options) //子節點
)
}
//遍歷store中的menuData數據,調用renderMenuItem方法嵌套生成dom樹
renderMenuItem: function (h, menu) {
let tag = 'router-link' //每個菜單使用router-link標簽創建
let config = {props: {to: menu.fullPath}, attrs: {style: 'overflow:hidden;white-space:normal;text-overflow:clip;'}} //屬性配置
if (menu.meta && menu.meta.link) {
tag = 'a'
config = {attrs: {style: 'overflow:hidden;white-space:normal;text-overflow:clip;', href: menu.meta.link, target: '_blank'}} //元數據包含link屬性,則使用a標簽創建
}
return h(
Item, {key: menu.fullPath},
[
h(tag, config,
[
this.renderIcon(h, menu.meta ? menu.meta.icon : 'none', menu.fullPath),
this.$t(getI18nKey(menu.fullPath))
]
)
]
)
}
以上就是路由和菜單的生成分析,下回分析一下權限控制如何完成的。