iview-admin2.0--动态添加路由

576 阅读3分钟

本例基于iview-admin2.0实现动态加载路由,参考一个已实现的PR,但是过程中有一些坑,写下记录一下。通过mock方式模拟后台数据,将mock数据格式可以自己定义,这里先按照原router类的参数格式来做演示。 创建@/mock/login.js,把要动态添加的router数据放在这里,@router/routers.js中不需要动态添加的路由不动,指定为homeName的页面也不动(默认是home)。把原来404的router删除

然后举例我们动态添加一个文档多级菜单路由,原路由中对应的部分删除,编辑@/mock/login.js

export const getUserMenus = req => {
  return [    // 粘贴到这里就可以了    // 需要把所有的component: () => import('@/view/**/*.vue')    // 修改为component: 'view/**/*.vue'    // 注意main和parent-view    {      path: '',      name: 'doc',      meta: {        title: '文档',        href: 'https://lison16.github.io/iview-admin-doc/#/',        icon: 'ios-book'      }    },    {      path: '/multilevel',      name: 'multilevel',      meta: {        icon: 'md-menu',        title: '多级菜单'      },      component: 'components/main',      children: [        {          path: 'level_2_1',          name: 'level_2_1',          meta: {            icon: 'md-funnel',            title: '二级-1'          },          component: 'view/multilevel/level-2-1.vue'        },        {          path: 'level_2_2',          name: 'level_2_2',          meta: {            // access: ['super_admin'],
            icon: 'md-funnel',
            showAlways: true,
            title: '二级-2'
          },
          component: 'components/parent-view',
          children: [
            {
              path: 'level_2_2_1',
              name: 'level_2_2_1',
              meta: {
                icon: 'md-funnel',
                title: '三级'
              },
              component: 'view/multilevel/level-2-2/level-2-2-1.vue'
            }
          ]
        },
        {
          path: 'level_2_2_2',
          name: 'level_2_2_2',
          meta: {
            icon: 'md-funnel',
            title: '三级'
          },
          component: 'view/multilevel/level-2-2/level-2-2-2.vue'
        }
      ]
    }
  ]
}

@/mock/index.js中增加解析

import { login, logout, getUserInfo, getUserMenus } from './login'

Mock.mock(/\/get_user_menus/, getUserMenus)

@/api/user.js中增加方法

export const listUserMenus = () => {
  return axios.request({
    url: 'get_user_menus',
    method: 'get'
  })
}

@/libs/utils.js中添加后端数据转换成路由的工具函数

/**
 * @description 将后端菜单树转换为路由树
 * @param {Array} menus
 * @returns {Array}
 */
export const backendMenusToRouters = (menus) => {
  let routers = []
  forEach(menus, (menu) => {
    // 将后端数据转换成路由数据
    let route = backendMenuToRoute(menu)
    // 如果后端数据有下级,则递归处理下级
    if (menu.children && menu.children.length !== 0) {
      route.children = backendMenusToRouters(menu.children)
    }
    routers.push(route)
  })
  return routers
}

/**
 * @description 将后端菜单转换为路由
 * @param {Object} menu
 * @returns {Object}
 */
const backendMenuToRoute = (menu) => {
  // 具体内容根据自己的数据结构来定,这里需要注意的一点是
  // 原先routers写法是component: () => import('@/view/error-page/404.vue')
  // 经过json数据转换,这里会丢失,所以需要按照上面提过的做转换,下面只写了核心点,其他自行处理
  let route = Object.assign({}, menu)
  // route.component = () => import(`@/${menu.component}`)
  route.component = resolve => require([`@/${menu.component}`], resolve)
  return route
}

注意:如果写成

route.component = () => import(`@/${menu.component}`)

会报错

 ERROR  Failed to compile with 1 error                                                                                                                             下午6:24:47

 error  in ./src/libs/util.js

Syntax Error: TypeError: Cannot read property 'range' of null
    at Array.forEach (<anonymous>)


 @ ./src/router/index.js 16:0-70 30:6-15 39:14-22 67:8-16 76:2-10
 @ ./src/main.js
 @ multi (webpack)-dev-server/client?http://192.168.3.81:8081/sockjs-node (webpack)/hot/dev-server.js ./src/main.js

@/store/module/app.js中做如下修改,只标注新增和修改的内容

import {backendMenusToRouters} from '@/libs/util'
import { listUserMenus } from '@/api/user'

export default {
  state: {
    routers: [],
    hasGetRouter: false
  },
  getters: {
  // menuList: (state) => (state, getters, rootState) => getMenuByRouter(state.routers, rootState.user.access),
    menuList: (state, getters, rootState) => getMenuByRouter(routers.concat(state.routers), rootState.user.access),
  },
  mutations: {
    setRouters (state, routers) {
      state.routers = routers
    },
    setHasGetRouter (state, status) {
      state.hasGetRouter = status
    }
  },
  actions: {
    getRouters ({ commit }) {
      return new Promise((resolve, reject) => {
        try {
          listUserMenus().then(res => {
            // 在获得routers数据后,再添加一个404兜底
            const data = res.data.concat(
              {
                path: '*',
                name: 'error_404',
                meta: {
                  hideInMenu: true
                },
                component: 'view/error-page/404.vue'
              }
            )
            const routers = backendMenusToRouters(res.data)
            commit('setRouters', routers)
            commit('setHasGetRouter', true)
            resolve(routers)
          }).catch(err => {
            reject(err)
          })
        } catch (error) {
          reject(error)
        }
      })
    }
  }
}

注意: 如果写成下面这样会报错,因为menuList的期望返回值是一个数组,而这里返回一个方法。

menuList: (state) => (state, getters, rootState) => getMenuByRouter(state.routers, rootState.user.access)

@/router/index.js中,修改turnTo函数和beforeEach钩子里面最后一个else中的代码

const turnTo = (to, access, next) => {
  if (canTurnTo(to.name, access, store.state.app.routers.concat(routes))) next() // 有权限,可访问
  else next({ replace: true, name: 'error_401' }) // 无权限,重定向到401页面
}


// 修改这部分代码
if (store.state.user.hasGetInfo && store.state.app.hasGetRouter) {
  turnTo(to, store.state.user.access, next)
} else {
  // 加载用户信息
  store.dispatch('getUserInfo').then(user => {
    // 加载用户菜单
    store.dispatch('getRouters').then(routers => {
      // commonRoutes需要追加到路由解析最后的404,把原先的routers.js中的404删掉即可
      router.addRoutes(routers)
      next({ ...to })
    })
  }).catch(() => {
    setToken('')
    next({ name: 'login' })
  })
}