根据用户权限动态生成菜单路由

2019-04-29

思路:

我这里使用了脚手架vue-element-admin,其菜单是sidebar组件根据路由(router.options.routes)自动生成的,所以菜单和路由的控制就变成了仅路由的控制。

首先路由要保留一些基础路由,如首页、404等,动态路由数据由后端根据用户权限返回,然后由前端转换成vue可用的路由,然后使用router.addRoutes()动态添加路由即可。

另外值得一说的是router.addRoutes()可以动态添加路由,却不会改变router.options.routes的值,需要自己手动往里面添加路由。

这波骚操作要在路由导航router.beforeEach()中完成。

重点:

路由的JSON数据结构

[
  {
    "path": '/pms',
    "component": 'Layout',
    "redirect": '/pms/application',
    "name": 'PMS',
    "meta": { "title": 'PMS', "icon": 'example' },
    "children": [
      {
        "path": 'resource',
        "name": 'Resource',
        "component": '/pms/resource/index.vue',
        "meta": { "title": '资源管理', "icon": 'table' }
      },
      {
        "path": 'application',
        "name": 'Application',
        "component": '/pms/application/index.vue',
        "meta": { "title": '应用管理', "icon": 'table' }
      },
      {
        "path": 'group',
        "name": 'Group',
        "component":'/pms/organization/index.vue',
        "meta": { "title": '组织管理', "icon": 'table' }
      },
      {
        "path": 'permission',
        "name": 'Permission',
        "component": '/pms/permission/index.vue',
        "meta": { "title": '权限管理', "icon": 'table' }
      }
    ]
  }
]

将JSON转换成vue路由对象的方法

export function rFormat(routers) {
  // 简单检查是否是可以处理的数据
  if (!(routers instanceof Array)) {
    return false
  }
  // 处理后的容器
  const fmRouters = []
  routers.forEach(router => {
    const path = router.path
    const component = router.component
    const name = router.name
    const hidden = router.hidden
    let children = router.children
    const meta = router.meta

    // 如果有子组件,递归处理
    if (children && children instanceof Array) {
      children = rFormat(children)
    }
    const fmRouter = {
      path: path,
      component(resolve) {
        // 拼出相对路径,由于component无法识别变量
        // 利用Webpack 的 Code-Splitting 功能
        if (router.component === 'Layout') {
		// Layout作为特殊组件处理,当然后端也可以写成'/layout/Layout.vue
          require(['@/views/layout/Layout.vue'], resolve)
        } else {
		// '@/views'要拼接进去,组件直接返回'@/views/xxxx/xxx.vue',然后require([compoent],resolve)会报错
          require(['@/views' + component], resolve)
        }
      },
      name: name,
      hidden: hidden,
      children: children,
      meta: meta
    }
    fmRouters.push(fmRouter)
  })
  return fmRouters
}

保留的基础路由

import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

export const constantRouterMap = [
  { path: '/login', component: () => import('@/views/login/index'), hidden: true },
  { path: '/404', component: () => import('@/views/404'), hidden: true },
  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    name: 'Dashboard',
    hidden: true,
    children: [{
      path: 'dashboard',
      component: () => import('@/views/dashboard/index')
    }]
  }
]

export default new Router({
  mode: 'history', // 后端支持可开
  scrollBehavior: () => ({ y: 0 }),
  routes: constantRouterMap
})

路由导航守卫

逻辑:
导航守卫中要做的事就是判断用户是否登录,没登录就去登录,登录完就去获取用户信息。获取用户信息这一步就会拿到用户的信息(姓名,角色,权限[url/element/菜单])。所以“拿到路由json数据,转换成vue可用路由,添加到当前路由”这波操作就放到登录成功后获取用户信息这里完成即可。

# 下面就是判断是否登录成功的逻辑,重点在这个store.dispatch('GetInfo'),就是获取用户信息的动作。
router.beforeEach((to, from, next) => {

  NProgress.start()
  if (getToken()) {
    if (store.getters.roles.length === 0) {
      store.dispatch('GetInfo').then(() => { // 拉取用户信息
        next({ ...to, replace: true })
      }).catch((err) => {
        store.dispatch('FedLogOut').then(() => {
          Message.error(err || 'Verification failed, please login again')
          next({ path: '/' })
        })
      })
    } else {
      next()
      NProgress.done()
    }
  } else if (to.query.code) {
    // 没有获取到本地token,从url获取code
    const code = to.query.code
    store.dispatch('Login', code).then(() => {
      if (store.getters.roles.length === 0) {
        store.dispatch('GetInfo', { code }).then(() => { 
        }).catch((err) => {
          store.dispatch('FedLogOut').then(() => {
            Message.error(err || 'Verification failed, please login again')
            next({ path: '/' })
          })
        })
        next({ ...to, replace: true })
      } else {
        next()
        NProgress.done()
      }
    })
  } else {
    // 没有登录状态,需要跳转到sso
    if (whiteList.indexOf(to.path) !== -1) {
      next()
    } else {
      const redirect = (url, asLink = true) => asLink ? (window.location.href = url) : window.location.replace(url)
      redirect(process.env.SSO_URL + '&type=login')
      NProgress.done()
    }
  }
})

GetInfo这个action具体的实现

actions: {
    // 获取用户信息
    GetInfo({ commit, state }) {
      return new Promise((resolve, reject) => {
        getInfo(state.token).then(response => {
          const data = response.data
		  // 拿到后端返回的路由json数据
          const rout = data.routers
		  // 转换成vue可用的路由
          const routers = rFormat(rout)
		  // 动态添加路由,404要放到最后,不然刷新会404
          router.addRoutes(routers.concat([{ path: '*', redirect: '/404', hidden: true }]))
		  // 往router.option.routes里添加路由
          routers.forEach((item, index) => {
            router.options.routes[index] = item
          })
          if (data.roles && data.roles.length > 0) { // 验证返回的roles是否是一个非空数组
            commit('SET_ROLES', data.roles)
          } else {
            // reject('getInfo: roles must be a non-null array !')
          }
		  commit('SET_MENUROUTERS', routers)
          commit('SET_NAME', data.name)
          commit('SET_AVATAR', data.avatar)
          localStorage.setItem('element_perms', JSON.stringify(data.element_perms))
          // const a = JSON.parse(localStorage.getItem('element_perms'))
          resolve(response)
        }).catch(error => {
          reject(error)
        })
      })
    }

效果

用户权限不同,登陆后的菜单和路由也不同

管理员

普通用户


标题:根据用户权限动态生成菜单路由
作者:fish2018
地址:http://devopser.org/articles/2019/04/29/1556524856420.html