vue权限控制之路由动态加载

2,976 阅读2分钟

在功能模块化的项目里,总是会遇到权限控制问题。不同的角色看见的模块不同,权限也不同,此时需要对路由做不同的处理,这里记录权限的处理思路和形成。

前提概要: 项目文件分类规划,每个文件有自己独立的功能,router文件夹分为两个.js文件,在main.js中引入index.js即可。

  • index.js写本地路由。
{
    path: '/login',
    component: () => import('@/views/login/index'),
    hidden: true,
    meta: {
    requireAuth: false
  }
  • router.js文件处理 router的beforeEach,afterEach等拦截功能,后面会提到。大部分都是围绕这两个方法,结合vuex,和localStorage或Cookie做数据权限处理。

一、基础版本--登录权限控制


基本思路: 登陆后保存 tokenuserInfo 数据。 在本地通过router.beforeEach((to, from, next) => { // ... })控制。

注意: next()必须调用。

router.beforeEach((to, from, next) => {
  if(token) {
    next();
  } else {
  next('/login');
  } 
  next(); // 必须调用
})

二、进阶--本地路由维护


基本思路: 在本地写好角色数组,每个路由在meta标签中对路由权限进行本地维护, roles内标明何种角色可以访问。然后请求用户信息,携带用户角色数据,在router.js文件中通过角色数据对比,控制访问权限。

index.js

{
    path: '/module',
    name: 'module',
    component: Module, // Module组件
    meta: {
    requireAuth: true, //需要登录才能访问
    },
    children: [{
    path: '',
    redirect: 'map'
    }, {
    path: 'map',
    name: 'map',
    component: Map,
    meta: {
      title: '地图',
      icon: 'map',
      roles: [...admin,...superiorUser]
    }]
}

router.js

import Vue from 'vue'
import store from '@/store'
import router from '@/router'
import getToken from './getToken'
import {
  getUserInfo
} from '@/http/api/header/index'
import {Message} from 'element-ui'

router.beforeEach((to, from, next) => {
    if (to.matched.some(res => res.meta.requireAuth)) { //是否需要登录
      if (getToken()) { //token存在
        $cookies.set('Admin-Token', getToken())
        if(_.intersection(to.meta.roles, getRole()).length > 0){
          next()
        }else{
          Message.error('无权访问')
        }
      } else { //token不存在
        next({
          path: '/login',
          query: {
            redirect: to.fullPath
          }
        })
      }
    } else {
      next()
    }
  // next()

})

三: 高级权限控制--后台路由维护


基本思路: 按照本地路由的书写方式,在后台维护路由数据,按钮权限以及层级结构。根据权限需求设计,将路由权限分配给用户,前端请求接口,将权限list解析成树结构,并通过router.addRoutes(routes: Array<RouteConfig>) 动态添加路由规则。 菜单的层级,一级菜单,子菜单和按钮, 主要方便前端生成路由规则进行判断。

管理端逻辑: 路由数据结构,子菜单配置一下上级菜单,方便处理数结构。

二级菜单需保存上级菜单,才能有菜单树形结构。

// 数据结构
{
  code: "", // button的权限
  component: '', // 组件名称
  hidden: 0, // 隐藏则不在侧边栏显示
  id: '',
  isCharge: 0, // 是否收费
  icon: '', // icon
  title: '', // 菜单名称
  requireAuth: '', // 是否需要登陆
  name: '', // 
  parentId: 0, // 
  meta: {}, // meta
  metaList: [], // 临时存储meta增加的条数
  path: '', 
  redirect: '',
  sortNo: '', // 排序
  systemId: '', // 如果有多个web管理系统需要配置
  systemName: '',
  type: '', // 菜单类型 1: 根菜单,2,子菜单,3: button
}

前端逻辑: 一: 根据路由全局前置守卫router.beforeEach判断是否登陆,路由是否配置,(最好结合Vuex, 可动态更新路由配置)。使用router.addRoutes添加路由规则。

router.js

/** **************导入********************/
import Vue from 'vue'
import Router from 'vue-router'
import { constantRouterMap } from './index'
import store from '@/store'
import { Message } from 'element-ui'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css'// progress bar style
import { getToken } from '@/utils/auth' // getToken from cookie
Vue.use(Router)

const _ = require('lodash')
const router = new Router({
  scrollBehavior: () => ({ y: 0 }),
  routes: constantRouterMap
})

NProgress.configure({ showSpinner: false })
const whiteList = ['/', '/login', '/auth-redirect', '/401', '/404']

/** **************router********************/
router.beforeEach((to, from, next) => {
  NProgress.start()
  if (getToken()) {
    /* has token*/
    if (whiteList.includes(to.path)) {
      next()
      NProgress.done()
    } else {
      if (!store.state.user.user) { // 判断当前用户是否已拉取完user_info信息
        store.dispatch('GetUserInfo').then(res => { // 拉取user_info
          store.dispatch('GenerateRoutes', res.data.data.modules).then(() => { // 根据roles权限生成可访问的路由表
            router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表
            next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
          })
        }).catch((err) => {
          store.dispatch('FedLogOut').then(() => {
            Message.error(err)
            next({ path: '/' })
          })
        })
      } else {
        if (_.findIndex(store.getters.originRouters, (e) => { return e.path === to.path }) !== -1) {
          next()
        } else {
          next({ path: '/401', replace: true, query: { noGoBack: true }})
        }
      }
    }
  } else {
    /* has no token*/
    if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入
      next()
    } else {
      next(`/login?redirect=${to.path}`) // 否则全部重定向到登录页
      NProgress.done()
    }
  }
})

router.afterEach(() => {
  NProgress.done()
})

export default router

vuex中permission.js

// import { constantRouterMap } from '@/router'
import _ from 'lodash'
import { queryUserAccessModules } from '@/api-cool/user'

function createTree(data) {
  if (data.length < 1) return []
  const parent = checkChild(data, 0)
  return parent
}

function checkChild(data, pid) {
  const arr = []
  data.forEach((e, index) => {
    if (e.parentId == pid) {
      const obj = e
      if (!e.meta.hasOwnProperty('fullScreen')) {
        e.children = checkChild(data, e.moduleId)
      } else {
        e.children = []
      }
      arr.push(obj)
    }
  })
  return arr
}

function getComponent(route) {
  return resolve => require([`@/views${route}`], resolve)
}

const permission = {
  state: {
    originRouters: [], //
    addRouters: []
  },
  mutations: {
    SET_ADDROUTERS: (state, routers) => {
      state.addRouters = routers
    },
    SET_ORIGINROUTERS: (state, originRouters) => {
      state.originRouters = originRouters
    }
  },
  actions: {
    // data直接是授权成功的角色
    GenerateRoutes({ commit }, data) {
      return new Promise(resolve => {
        queryUserAccessModules({}).then(res => {
          const routerArr = _.compact(res.data.data.map(e => {
            if (e.type != 3) {
              return {
                ...e,
                component: getComponent(e.component),
                name: e.moduleName
              }
            } else {
              return {
                ...e,
                name: e.moduleName
              }
            }
          }))
          var fullScreenList = _.compact(routerArr.map(e => {
            if (e.meta.hasOwnProperty('fullScreen')) { return e } else { return null }
          }))
          commit('SET_ORIGINROUTERS', fullScreenList.concat(routerArr))
          commit('SET_ADDROUTERS', fullScreenList.concat(createTree(routerArr)))
          resolve()
        })
      })
    }
  }
}

export default permission

菜单组件menu.js

<template>
  <el-scrollbar wrap-class="scrollbar-wrapper">
    <el-menu
      :default-active="$route.path"
      :collapse="isCollapse"
      :background-color="variables.menuBg"
      :text-color="variables.menuText"
      :active-text-color="variables.menuActiveText"
      mode="vertical">
      <sidebar-item v-for="route in addRouters" :key="route.moduleId" :item="route" :base-path="route.path"/>
    </el-menu>
  </el-scrollbar>
</template>

<script>
import { mapGetters } from 'vuex'
import SidebarItem from './SidebarItem'
import variables from '@/styles/variables.scss'

export default {
  components: { SidebarItem },
  computed: {
    ...mapGetters([
      'addRouters',
      'sidebar'
    ]),
    variables() {
      return variables
    },
    isCollapse() {
      return !this.sidebar.opened
    }
  }
}
</script>

总结: 使用router.beforeEach全局前置守卫进行权限判断。根据项目需求,写权限控制逻辑。