一、写在前面
关于前端的权限控制,常见的体现用处:
- 不同的用户左边展示的菜单栏不太一样
- 不同的用户根据具有的角色不同实现显示不同的资源及控件
下面具体的展示的实现
二、具体实现
2.1动态菜单栏
1.新建store/permission.js
文件,配合router.js
来实现功能
大致原理:根据登录用户的role
来 在 原来 静态路由constantRouters
的基础上 将待添加路由 addRouters
拼接上
// store/permission.js
// 0. 导入router.js下的静态路由、动态获取的路由映射
import {
asyncRouterMap, constantRouters } from '@/router';
// 1. 判断角色对应的路由映射权限
function hasPermission(roles, route) {
if (route.meta && route.meta.role) {
var index = route.meta.role.indexOf(roles)
if(index === -1)
return false
return true
//return roles.some(role => route.meta.role.indexOf(role) >= 0)
} else {
return true
}
}
// 2. permission定义
const permission = {
state: {
routers: constantRouters,
addRouters: []
},
mutations: {
// 3. 将动态获取的路由 拼接到 静态路由映射列表 功能
SET_ROUTERS: (state, routers) => {
state.addRouters = routers;
state.routers = constantRouters.concat(routers);
}
},
// 4. 行为方法,根据role调用 3 的功能
actions: {
GenerateRoutes({
commit }, data) {
return new Promise(resolve => {
const {
roles } = data;
const accessedRouters = asyncRouterMap.filter(v => {
if (roles.indexOf('admin') >= 0) return true;
//5. 调用第1部的方法
if (hasPermission(roles, v)) {
if (v.children && v.children.length > 0) {
v.children = v.children.filter(child => {
if (hasPermission(roles, child)) {
return child
}
return false;
});
return v
} else {
return v
}
}
return false;
});
// 6. 使用accessRouters接收 该角色对应的列表 映射
commit('SET_ROUTERS', accessedRouters);
resolve();
})
}
}
};
export default permission;
2.在store/index.js
下注册permission
3.在store/getter.js
中定义addRouters
4.修改router/index.js
,将初始化路由 和 动态获取的路由分开
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
/* Layout */
import Layout from '@/layout'
/**
* Note: sub-menu only appear when route children.length >= 1
* Detail see: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html
*
* hidden: true if set true, item will not show in the sidebar(default is false)
* alwaysShow: true if set true, will always show the root menu
* if not set alwaysShow, when item has more than one children route,
* it will becomes nested mode, otherwise not show the root menu
* redirect: noRedirect if set noRedirect will no redirect in the breadcrumb
* name:'router-name' the name is used by <keep-alive> (must set!!!)
* meta : {
roles: ['admin','editor'] control the page roles (you can set multiple roles)
title: 'title' the name show in sidebar and breadcrumb (recommend set)
icon: 'svg-name'/'el-icon-x' the icon show in the sidebar
breadcrumb: false if set false, the item will hidden in breadcrumb(default is true)
activeMenu: '/example/list' if set path, the sidebar will highlight the path you set
}
*/
/**
* constantRoutes
* a base page that does not have permission requirements
* all roles can be accessed
*/
// 1. 任何登录角色都能看到的静态路由
export const constantRouters = [
{
path: '/login',
component: () => import('@/views/login/index'),
hidden: true
},
{
path: '/404',
component: () => import('@/views/404'),
hidden: true
},
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [{
path: 'dashboard',
name: 'Dashboard',
component: () => import('@/views/dashboard/index'),
meta: {
title: '首页', icon: 'dashboard' }
}]
},
]
const createRouter = () => new Router({
// mode: 'history', // require service support
scrollBehavior: () => ({
y: 0 }),
routes: constantRouters
})
const router = createRouter()
// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
export function resetRouter() {
const newRouter = createRouter()
router.matcher = newRouter.matcher // reset router
}
export default router
// 2. 根据角色动态显示的路由
export const asyncRouterMap = [
{
path: '/declaration-form',
component: Layout,
children: [{
path: 'declaration-form',
name: 'DeclarationForm',
//declaration-form/index
component: () => import('@/views/declaration-form/index'),
meta: {
role: ['基层教学组织负责人','系统管理员' ], title: '申报表填报', icon: 'form' }
}]
},
{
path: '/approval-page',
component: Layout,
children: [{
path: 'approval-page',
name: 'ApprovalPage',
component: () => import('@/views/approval-page/index'),
meta: {
role: ['基层教学组织负责人' ,'系统管理员','校级管理员','院级管理员','评审专家'], title: '申报表审核', icon: 'approval' }
}]
},
{
path: '/detail-page',
component: Layout,
children: [{
path: 'detail-page',
name: 'detailPage',
component: () => import('@/views/detail-page/index'),
meta: {
role: ['基层教学组织负责人' ,'系统管理员','校级管理员','院级管理员','评审专家'], title: '申报表详情' },
hidden: true
}]
},
{
path: '/user-management',
component: Layout,
children: [{
path: 'user-management',
name: 'UserManagement',
component: () => import('@/views/user-management/index'),
meta: {
role: ['系统管理员','校级管理员','院级管理员'], title: '用户管理', icon: 'user' }
}]
},
{
path: '/dispatch-form',
component: Layout,
children: [{
path: 'dispatch-form',
name: 'DispatchForm',
component: () => import('@/views/dispatch-form/index'),
meta: {
role: ['系统管理员','校级管理员','院级管理员'], title: '分配管理', icon: 'dispatch' }
}]
},
{
path: '/data-analysis',
component: Layout,
children: [{
path: 'data-analysis',
name: 'DataAnalysis',
component: () => import('@/views/data-analysis/index'),
meta: {
title: '数据分析', icon: 'pie' }
}]
},
{
path: '/information',
component: Layout,
meta: {
role: ['教师'], title: '信息填报', icon: 'user-info' },
children: [{
path: 'teacher-info',
name: 'TeacherInfo',
component: () => import('@/views/information/teacher-info'),
meta: {
title: '个人概况' }
}, {
path: 'research-info',
name: 'ResearchInfo',
component: () => import('@/views/information/research-info'),
meta: {
title: '教学研究内容' }
}, {
path: 'competition-info',
name: 'CompetitionInfo',
component: () => import('@/views/information/competition-info'),
meta: {
title: '教学竞赛内容' }
}]
},
// 404 page must be placed at the end !!!
{
path: '*', redirect: '/404', hidden: true }
]
5.修改layout/Sidebar/index.vue
,修改配置store下的路由显示,将通过调用store/permission方法动态获取的路由映射 拼接到 原始的默认路由映射中
6.在src/permission.js中拉去用户信息的方法中,获取role之后,调用方法根据role生成路由列表
import router from './router'
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' // get token from cookie
import getPageTitle from '@/utils/get-page-title'
NProgress.configure({
showSpinner: false }) // NProgress Configuration
const whiteList = ['/login'] // no redirect whitelist
router.beforeEach(async (to, from, next) => {
// start progress bar
NProgress.start()
// set page title
document.title = getPageTitle(to.meta.title)
// determine whether the user has logged in
const hasToken = getToken()
if (hasToken) {
if (to.path === '/login') {
// if is logged in, redirect to the home page
next({
path: '/' })
NProgress.done()
} else {
const hasGetUserInfo = store.getters.name
if (hasGetUserInfo) {
next()
} else {
if (store.getters.role.length === 0) {
// get user info
await store.dispatch('user/getInfo').then(res => {
const roles = res.role;
store.dispatch('GenerateRoutes', {
roles }).then(() => {
//console.log(store.getters.addRoutes)
router.addRoutes(store.getters.addRoutes.permission.addRouters)
next({
...to, replace: true })
}
)
}
).catch(error => {
console.log(error)
});
}
else {
// remove token and go to login page to re-login
// await store.dispatch('user/resetToken')
// Message.error(error || 'Has Error')
// next(`/login?redirect=${to.path}`)
// NProgress.done()
next()
}
}
}
} else {
/* has no token*/
if (whiteList.indexOf(to.path) !== -1) {
// in the free login whitelist, go directly
next()
} else {
// other pages that do not have permission to access are redirected to the login page.
next(`/login?redirect=${
to.path}`)
NProgress.done()
}
}
})
router.afterEach(() => {
// finish progress bar
NProgress.done()
})
然后就大致完成了
2.2动态资源显示
这个跟传统的开发模式如 springboot + shiro + Thymeleaf模板引擎实现权限控制类似
那种是Thymeleaf整合shiro之后直接在 jsp页面加 shiro-xxx
标签即可
现在的前端控制直接在全部在vue中实现,大致的思路是:
- 首先在
main.js
中定义权限制令
、权限检查方法
,也就是自定义v-has标签
,检查权限roles数组
中无指定权限角色,有就显示。 - 定义
roles
数组,存储当前用户角色,方便动态添加权限角色。 - 定义获取当前
role
,设置权限数组的方法,在资源内部使用v-has="roles"
完成角色资源显示