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


//@components/menu/SideMenu.vue

渲染左侧菜单需要用到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))
            ]
          )
        ]
      )
    }

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

你可能感兴趣的:(vue-antd-admin框架(三)路由以及菜单生成)