在功能模块化的项目里,总是会遇到权限控制问题。不同的角色看见的模块不同,权限也不同,此时需要对路由做不同的处理,这里记录权限的处理思路和形成。
前提概要: 项目文件分类规划,每个文件有自己独立的功能,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做数据权限处理。
一、基础版本--登录权限控制
基本思路: 登陆后保存 token 和 userInfo 数据。
在本地通过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全局前置守卫进行权限判断。根据项目需求,写权限控制逻辑。