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))
            ]
          )
        ]
      )
    }

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容