vue-antd-admin框架(三)路由以及菜單生成

上回書說到同步路由配置是在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方法做了兩件事情:

  1. 把配置項中path不以/開頭的都加上斜杠/
  2. 遍歷各個路由配置項中是否在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))
            ]
          )
        ]
      )
    }

以上就是路由和菜單的生成分析,下回分析一下權限控制如何完成的。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容