Vue - vue-admin-template模板项目改造:动态获取菜单路由

GitHub Demo 地址

在线预览

前言

1、demo中的项目已经添加了TagsView功能和本地权限控制
关于TagsView功能的添加可以看:Vue - vue-admin-template模板项目改造:增加TagsView功能
关于本地权限控制相关代码参考 vue-admin-template的 permission-control分支

2、并且demo中的项目在1的基础上增加TopHeader(顶栏)功能,在顶栏中显示项目标题和用户信息,即可以支持原有方式展示,又可以通过setting配置显示顶栏
关于增加TopHeader(顶栏)功能的添加可以看:Vue - vue-admin-template模板项目改造:增加TopHeader(顶栏)

所以项目代码可能和原版的 vue-admin-template有点差别,vue-admin-template 代码地址

本地权限控制,具体是通过查询用户信息获取用户角色,在路由守卫中通过角色过滤本地配置的路由,把符合角色权限的路由生成一个路由数组

动态获取菜单路由其实思路是一样的,只不过路由数组变成从服务器获取,通过查询某个角色的菜单列表,然后在路由守卫中把获取到的菜单数组转成路由数组

动态路由实现是参考vue-element-admin的issues写的,相关issues:
vue-element-admin/issues/167
vue-element-admin/issues/293
vue-element-admin/issues/3326#issuecomment-832852647

关键点

主要在接口菜单列表中把父componentLayout 改为字符串 ‘Layout’,
children的component: () => import(‘@/views/table/index’), 改成 字符串’table/index’,然后在获取到数据后再转回来
!!!!!!!!!!!! 接口格式可以根据项目需要自定义,不一定非得按照这里的来

本地路由格式:

  {
    path: '/example',
    component: Layout,
    redirect: '/example/table',
    name: 'Example',
    meta: { title: 'Example', icon: 'el-icon-s-help', roles: ['admin'] },
    children: [
      {
        path: 'table',
        name: 'Table',
        component: () => import('@/views/table/index'),
        meta: { title: 'Table', icon: 'table' }
      },
      {
        path: 'tree',
        name: 'Tree',
        component: () => import('@/views/tree/index'),
        meta: { title: 'Tree', icon: 'tree' }
      }
    ]
  },

接口路由格式:

  {
    path: '/example',
    component: 'Layout',
    redirect: '/example/table',
    name: 'Example',
    meta: { title: '动态Example', icon: 'el-icon-s-help', roles: ['admin'] },
    children: [
      {
        path: 'table',
        name: 'Table',
        component: 'table/index',
        meta: { title: '动态Table', icon: 'table' }
      },
      {
        path: 'tree',
        name: 'Tree',
        component: 'tree/index',
        meta: { title: '动态Tree', icon: 'tree' }
      }
    ]
  },

具体实现

1、接口

因为是通过mock模拟的网络请求,所以需要添加一个获取用户菜单列表的接口(可以把vue-element-admin中的mock文件夹下的role文件夹直接copy到项目中,然后改造)

mock目录下建role文件夹、index.js文件

const Mock = require('mockjs')
const { asyncRoutes } = require('./routes.js')
const routes = asyncRoutes
module.exports = [
  // mock get all routes form server
  {
    url: '/vue-element-admin/routes',
    type: 'get',
    response: _ => {
      return {
        code: 20000,
        data: routes
      }
    }
  }

mock目录下建role文件夹、routes.js文件
主要用的是asyncRoutes

// Just a mock data

const constantRoutes = [
  {
    path: '/redirect',
    component: 'Layout',
    hidden: true,
    children: [
      {
        path: '/redirect/:path(.*)',
        component: 'redirect/index'
      }
    ]
  },
  {
    path: '/login',
    component: 'login/index',
    hidden: true
  },
  {
    path: '/404',
    component: '404',
    hidden: true
  },

  {
    path: '/',
    component: 'Layout',
    redirect: '/dashboard',
    children: [{
      path: 'dashboard',
      name: 'Dashboard',
      component: 'dashboard/index',
      meta: { title: 'Dashboard', icon: 'dashboard', affix: true }
    }]
  }
]

/**
 * asyncRoutes
 * the routes that need to be dynamically loaded based on user roles
 */
const asyncRoutes = [
  {
    path: '/example',
    component: 'Layout',
    redirect: '/example/table',
    name: 'Example',
    meta: { title: '动态Example', icon: 'el-icon-s-help', roles: ['admin'] },
    children: [
      {
        path: 'table',
        name: 'Table',
        component: 'table/index',
        meta: { title: '动态Table', icon: 'table' }
      },
      {
        path: 'tree',
        name: 'Tree',
        component: 'tree/index',
        meta: { title: '动态Tree', icon: 'tree' }
      }
    ]
  },
  {
    path: '/form',
    component: 'Layout',
    meta: { roles: ['admin'] },
    children: [
      {
        path: 'index',
        name: 'Form',
        component: 'form/index',
        meta: { title: '动态Form', icon: 'form' }
      }
    ]
  },
  {
    path: '/nested',
    component: 'Layout',
    redirect: '/nested/menu1',
    name: 'Nested',
    meta: { title: '动态Nested', icon: 'nested', roles: ['admin'] },
    children: [
      {
        path: 'menu1',
        component: 'nested/menu1/index', // Parent router-view
        name: 'Menu1',
        meta: { title: '动态Menu1' },
        children: [
          {
            path: 'menu1-1',
            component: 'nested/menu1/menu1-1',
            name: 'Menu1-1',
            meta: { title: '动态Menu1-1' }
          },
          {
            path: 'menu1-2',
            component: 'nested/menu1/menu1-2',
            name: 'Menu1-2',
            meta: { title: '动态Menu1-2' },
            children: [
              {
                path: 'menu1-2-1',
                component: 'nested/menu1/menu1-2/menu1-2-1',
                name: 'Menu1-2-1',
                meta: { title: '动态Menu1-2-1' }
              },
              {
                path: 'menu1-2-2',
                component: 'nested/menu1/menu1-2/menu1-2-2',
                name: 'Menu1-2-2',
                meta: { title: '动态Menu1-2-2' }
              }
            ]
          },
          {
            path: 'menu1-3',
            component: 'nested/menu1/menu1-3',
            name: 'Menu1-3',
            meta: { title: '动态Menu1-3' }
          }
        ]
      },
      {
        path: 'menu2',
        component: 'nested/menu2/index',
        meta: { title: '动态menu2' }
      }
    ]
  },
  {
    path: 'external-link',
    component: 'Layout',
    children: [
      {
        path: 'https://panjiachen.github.io/vue-element-admin-site/#/',
        meta: { title: '动态External Link', icon: 'link' }
      }
    ]
  },
  /** when your routing map is too long, you can split it into small modules **/
  // componentsRouter,
  // chartsRouter,

  // 404 page must be placed at the end !!!
  { path: '*', redirect: '/404', hidden: true }
]

module.exports = {
  constantRoutes,
  asyncRoutes
}

然后在mock/index.js,把新加的role引用一下

const user = require('./user')
const role = require('./role/index') // 新加的
const table = require('./table')
const dict = require('./demos/dict')
const tables = require('./demos/tables')

const mocks = [
  ...user,
  ...role, // 新加的
  ...table,
  ...dict,
  ...tables
]

2、前端调用菜单接口并生成路由数组

先在src / api / roles.js(新建roles.j,或者放到user里) 把mock接口实现一下

import request from '@/utils/request'

export function getUserMenus() {
  return request({
    url: '/vue-element-admin/routes',
    method: 'get'
  })
}

然后修改 src/store/modules/permission.js,把处理菜单数组转成路由数组的方法实现一下

const { deepClone } = require('@/utils')

// 加载路由
export const loadView = (view) => {
  // 路由懒加载
  return (resolve) => require([`@/views/${view}`], resolve)
  // return (resolve) => require([`@${view}`], resolve)
}

/**
 * 通过递归格式化菜单路由 (配置项规则:https://panjiachen.github.io/vue-element-admin-site/zh/guide/essentials/router-and-nav.html#配置项)
 * @param routes
 * @param roles
 */
export function filterAsyncRoutes2(routes) {
  const res = []
  routes.forEach((route) => {
    const tmp = deepClone(route)
    if (route.component === 'Layout') {
      tmp.component = Layout
    } else if (route.component) {
      tmp.component = loadView(route.component)
    }
    if (route.children && route.children.length > 0) {
      tmp.children = filterAsyncRoutes2(route.children)
    }
    res.push(tmp)
  })
  return res
}

在actions中新加一个函数

  generateDynamicRoutes({ commit }, menus) {
    return new Promise(resolve => {
      const accessedRoutes = filterAsyncRoutes2(menus)
      commit('SET_ROUTES', accessedRoutes) // Todo: 内部拼接constantRoutes,所以查出来的菜单不用包含constantRoutes
      resolve(accessedRoutes)
    })
  }

然后修改src\store\modules\user.js
添加获取用户菜单的方法

const state = {
// ```
  menus: [] //这个是我新增的
}

const mutations = {
	// ```
  SET_MENUS: (state, menus) => { //这里是新增的
    state.menus = menus
  }
}

  getUserMenus({ commit, state }) {
    return new Promise((resolve, reject) => {
      getUserMenus(state.token).then(response => {
        const { data } = response
        if (!data) {
          reject('Verification failed, please Login again.')
        }
        const menus = data
        // roles must be a non-empty array
        if (!menus || menus.length <= 0) {
          reject('getMenus: menus must be a non-null array!')
        }
        commit('SET_MENUS', menus)
        resolve(menus)
      }).catch(error => {
        reject(error)
      })
    })
  },

然后在修改src\permission.js的路由守卫
要在 store.dispatch(‘permission/generateRoutes’) 代码附近要改一下,原先从本地配置+roles获得用户路由,现在改从服务端获得之后再 addRoutes

我这里在mock中加了个角色editor2,当editor2登录使用的从服务器获取动态路由,其他角色从本地获取路由

 const { roles } = await store.dispatch('user/getInfo')
 // console.log('roles', JSON.stringify(roles))

 var accessRoutes = []

 if (roles.includes('editor2')) {
   // 根据服务器获取的用户菜单生成路由
   const menus = await store.dispatch('user/getUserMenus')
   const asyncRoutes = await store.dispatch('permission/generateDynamicRoutes', menus)
   accessRoutes = asyncRoutes
 } else {
   // generate accessible routes map based on roles
   accessRoutes = await store.dispatch('permission/generateRoutes', roles)
 }
 
 // dynamically add accessible routes
 router.addRoutes(accessRoutes)

至此结束。

你可能感兴趣的:(Vue,#,Vue-UI,vue.js,前端,javascript)