公司的管理框架权限控制是针对某个自定义的角色,没有固定的角色名称,所以下面的代码主要是针对添加角色时,保存的菜单id;
自定义菜单,菜单即是一个路由页面,每个菜单都有自己唯一的id,所以创建角色的时候,赋予相对应的权限,只需要保存赋予菜单权限的id即可;
保存时,直接跟后台同事沟通,直接保存菜单id即可;
一、router文件夹下的index.js是项目总路由入口
index.js对模块注册后默认将constantRoutes无权限的路由增加进去,
在store/modules/permission.js中维护静态+动态路由权限,并在根目录下的permission.js实现导航守卫逻辑,
在/router/index.js中:
import VueRouter from 'vue-router';
//constantRoutes 是静态路由,不需要动态权限判断
export const constantRoutes = [
{
path: '/',
component: resolve => require(['@/pages/layout/index.vue'], resolve),
children: [
{
path: '/',
name: 'homePage',
component: resolve =>
require(['@/pages/homePage/homePage.vue'], resolve),
meta: {
title: '首页'
},
useLayout: true
},
{
path: '/demo',
name: 'demo',
component: resolve =>
require(['@/pages/basic/demo.vue'], resolve),
meta: {
title: '测试'
},
useLayout: true
},
]
},
{
path: '/login',
name: 'login',
component: resolve =>
require(['@/pages/login/login.vue'], resolve),
meta: {
title: '登录'
},
useLayout: true
},
]
//asyncRoutes 是动态路由,需要动态权限判断
export const asyncRoutes = [
{
path: '/',
component: resolve => require(['@/pages/layout/index.vue'], resolve),
children: [
{
path: '/role',
name: 'role',
component: resolve =>
require(['@/pages/system/role.vue'], resolve),
meta: {
title: '角色管理'
},
useLayout: true
},
{
path: '/menuManager',
name: 'menuManager',
component: resolve =>
require(['@/pages/system/menuManager.vue'], resolve),
meta: {
title: '菜单管理'
},
useLayout: true
},
{
path: '/workers',
name: 'workers',
component: resolve =>
require(['@/pages/system/workers.vue'], resolve),
meta: {
title: '用户管理'
},
useLayout: true
},
{
path: '/demo2',
name: 'demo2',
component: resolve =>
require(['@/pages/basic/demo2.vue'], resolve),
meta: {
title: '测试2'
},
useLayout: true
},
]
}
]
const createRouter = () => new VueRouter({
// mode: 'history', // require service support
scrollBehavior: () => ({ y: 0 }),
routes: constantRoutes
})
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
二、配置vuex
在src目录下新建一个vuex 文件夹,以便于存放所有关于vuex的文件
1、安装vuex(命令)
npm install vuex --save
2、在新建一个modules文件夹 ,然后在新建一个permission.js文件,vuex/modules/permission.js中维持了一个全局state数组,
维持当前登录用户所拥有的菜单权限,并且在路由守卫时候完成动态路由的切换,
store/modules/permission.js中动态路由state状态的维护逻辑如下:
// 首先,从index.js中引入已经定义过的2个router数组
import { asyncRoutes, constantRoutes } from '@/router'
// 全局变量state,routes和addRoutes数组
const state = {
routes: [],
addRoutes: []
}
// mutations 是唯一可以更改state的函数,使用SET_ROUTES定义更改方法,SET_ROUTES(state, routes)的入参routes赋值给addRoutes数组,将constantRoutes静态路由数组增加routes;
const mutations = {
SET_ROUTES: (state, routes) => {
state.addRoutes = routes
state.routes = constantRoutes.concat(routes)
}
}
// vue中store状态管理,通过actions调用 mutations 中封装的方法来实现对于state更改,
// 这里是vue-element-admin中动态路由的主要判断逻辑发生地方,首先判定用户角色含访问页面权限,调用filterAsyncRoutes函数,递归地判定asyncRoutes中的路由path,是否是用户用户拥有的权限,将有权限的router赋值accessedRoutes 后加入到constantRoutes;
const actions = {
generateRoutes({ commit }, hasMenu) {
return new Promise(resolve => {
/**每个用户进入都要判断权限 */
let accessedRoutes = filterAsyncRoutes(asyncRoutes,hasMenu)
commit('SET_ROUTES', accessedRoutes);
resolve(accessedRoutes)
})
}
}
/**根据用户拥有权限菜单地址判断添加路由
* hasMenu 用户拥有权限
* route 所有挂在路由权限
*/
export function filterAsyncRoutes(routes, hasMenu) {
const res = []
routes.forEach(route => {
const tmp = { ...route }
if (hasMenu.includes(tmp.path)) {
if (tmp.children) {
tmp.children = filterAsyncRoutes(tmp.children, hasMenu)
}
res.push(tmp)
}
})
return res
}
export default {
namespaced: true,
state,
mutations,
actions
}
3、vuex/modelus下面创建一个 user.js文件
import Cookies from 'js-cookie';
import common from '@/api/common'// 这里是封装请求后台的接口
import {getInfo} from '@/api/user' // 这里是封装请求后台的接口
// import { getToken, setToken, removeToken } from '@/utils/auth'
import router, { resetRouter } from '@/router';
const state = {
token: Cookies.get('token'),
name: '',
avatar: '',
introduction: '',
roles: [],
allMenu:[],/**x 全部菜单 */
hasMenu:[],/**x 可访问页面地址 */
showMenu:[],/**x 展示菜单 */
}
const mutations = {
SET_TOKEN: (state, token) => {
state.token = token
},
SET_INTRODUCTION: (state, introduction) => {
state.introduction = introduction
},
SET_NAME: (state, name) => {
state.name = name
},
SET_AVATAR: (state, avatar) => {
state.avatar = avatar
},
SET_ROLES: (state, roles) => {
state.roles = roles
},
SET_ALLMENU: (state, allMenu) => {
state.allMenu = allMenu
},
SET_HASMENU: (state, hasMenu) => {
state.hasMenu = hasMenu
},
SET_SHOWMENU: (state, showMenu) => {
state.showMenu = showMenu
},
}
const actions = {
// user login
login({ commit }, userInfo) {
const { username, password } = userInfo
return new Promise((resolve, reject) => {
login({ username: username.trim(), password: password }).then(response => {
const { data } = response
commit('SET_TOKEN', data.token)
setToken(data.token)
resolve()
}).catch(error => {
reject(error)
})
})
},
// get user info
getInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getInfo(state.token).then(response => {
const { data } = response;
if (!data) {
reject('Verification failed, please Login again.')
}
const { roles, name,avatar,introduction} = data
// roles must be a non-empty array
if (!roles || roles.length <= 0) {
reject('getInfo: roles must be a non-null array!')
}
commit('SET_ROLES', roles)
commit('SET_NAME', name)
commit('SET_AVATAR', avatar)
commit('SET_INTRODUCTION', introduction)
resolve(data)
}).catch(error => {
reject(error)
})
})
},
// user logout
logout({ commit, state, dispatch }) {
return new Promise((resolve, reject) => {
logout(state.token).then(() => {
commit('SET_TOKEN', '')
commit('SET_ROLES', [])
removeToken()
resetRouter()
// reset visited views and cached views
// to fixed https://github.com/PanJiaChen/vue-element-admin/issues/2485
dispatch('tagsView/delAllViews', null, { root: true })
resolve()
}).catch(error => {
reject(error)
})
})
},
// remove token
resetToken({ commit }) {
return new Promise(resolve => {
commit('SET_TOKEN', '')
commit('SET_ROLES', [])
removeToken()
resolve()
})
},
// dynamically modify permissions
async changeRoles({ commit, dispatch }, role) {
const token = role + '-token'
commit('SET_TOKEN', token)
setToken(token)
const { roles } = await dispatch('getInfo')
resetRouter()
// generate accessible routes map based on roles
const accessRoutes = await dispatch('permission/generateRoutes', roles, { root: true })
// dynamically add accessible routes
router.addRoutes(accessRoutes)
// reset visited views and cached views
dispatch('tagsView/delAllViews', null, { root: true })
},
/**x 获取用户拥有权限菜单地址 */
getUserMenu({ commit },toUrl){
return new Promise(resolve => {
let that = this;
let userInfo = Cookies.get("userInfo")
? JSON.parse(Cookies.get("userInfo"))
: {};
let subData = {
userId: userInfo.id || 0,
};
//这里通过接口获取当前用户拥有的所有权限(路由地址)
common.getUserHasMenu(subData).then(res => {
if (res.code == "000000") {
let newMenu = [];
let hasMenuPerm = [];
/**x 当前用户拥有权限访问的页面 */
let menuUrlList = [];
/**x 这里菜单只开放给超级管理员 */
let onlySystemMenu = ['/organization'];
/**x 不开放菜单 */
let notOpenMenu = ['/menu'];
res.data.map(item =>{
// console.log(item.name,item.icon)
/**x 超级管理员 */
if(userInfo.userName == "system"){
/**x 不开放给管理员的菜单 */
let notOpenForSystem = ['/department'];
/**x 菜单权限就显示 (菜单管理不开放) */
if(item.type == "MENU" && !notOpenMenu.includes(item.href) && !notOpenForSystem.includes(item.href)){
newMenu.push({
id:item.id,
parentId:item.parentId,
name:item.name,
url:item.href,
icon:item.icon,
children: []
});
}
}else{
/**x 菜单权限就显示 (菜单管理不开放) */
if(item.type == "MENU" && !notOpenMenu.includes(item.href) && !onlySystemMenu.includes(item.href)){
newMenu.push({
id:item.id,
parentId:item.parentId,
name:item.name,
url:item.href,
icon:item.icon,
children: []
});
}
}
/**x 获取用户拥有权限 */
hasMenuPerm.push(item.perm);
if(item.href != ""){
/**x 获取用户拥有权限访问页面地址 (在动态添加权限时使用) */
menuUrlList.push(item.href);
}
});
/**x将用户拥有菜单标识存入本地 */
sessionStorage.setItem("menuPerm",hasMenuPerm);
/**x 设置全部菜单 */
commit('SET_ALLMENU',newMenu);
/**x 设置有权限菜单 */
commit('SET_HASMENU',menuUrlList);
} else {
that.$message.warning(res.mesg);
}
let seriesOne = [];
state.allMenu.map(item =>{
if(item.parentId == '-1'){
item.icon = require(`../../assets/images/${item.icon}`);
seriesOne.push(item)
}
})
/**x 菜单排序 */
recursionMenuList(seriesOne).then(res =>{
/**x 设置展示菜单 */
commit('SET_SHOWMENU',res);
/**x 获取完以后跳转到用户想进入的地址 */
router.push({path: toUrl});
});
resolve(state.hasMenu);
});
})
},
}
/**x 整理菜单权限 fatherMenu:父级菜单 */
async function recursionMenuList(fatherMenu){
return await new Promise((resolve,reject) =>{
let oldMenuList = JSON.parse(JSON.stringify(state.allMenu));
fatherMenu.map((fItem,fKey) =>{
/**x 过滤出 属于当前id下面的子菜单 */
let isChildrenList = oldMenuList.filter(item2 =>{ return fItem.id == item2.parentId});
// console.log(isChildrenList)
/**x 如果有子菜单列表 */
if(isChildrenList.length != 0){
fItem.children = isChildrenList;
/**x 递归调用 */
recursionMenuList(isChildrenList).then((res) =>{
resolve(res);
});
}else{
resolve(fatherMenu);
}
});
});
}
export default {
namespaced: true,
state,
mutations,
actions
}
4、vuex文件下新建一个getters.js文件
//和计算属性功能相同,基于多个状态生成新的状态
const getters = {
allMenu: state => state.user.allMenu,
hasMenu: state => state.user.hasMenu,
showMenu: state => state.user.showMenu,
roles: state => state.user.roles,
}
export default getters
5、在vuex文件下新建一个store.js文件
import getters from './getters';
const modulesFiles = require.context('./modules', true, /\.js$/)
// you do not need `import app from './modules/app'`
// it will auto require all vuex module from modules file
const modules = modulesFiles.keys().reduce((modules, modulePath) => {
// set './app.js' => 'app'
const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1')
const value = modulesFiles(modulePath)
modules[moduleName] = value.default
return modules
}, {});
const store = new Vuex.Store({
getters,
modules
});
export default store;
三、最后再看根目录下permission.js中路由守卫的逻辑,
// vue-element-admin中permission.js中导航守卫逻辑
import router from './router/index.js'
import store from './vuex/store.js'
import { Message } from 'element-ui'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
import Cookies from 'js-cookie';
// import getPageTitle from '@/util/get-page-title'
// 进度条
NProgress.configure({ showSpinner: false }) // NProgress Configuration
// 白名单
const whiteList = ['/login',] // no redirect whitelist
// Cookies.remove('token');
// 路由之前,逻辑判定
router.beforeEach(async(to, from, next) => {
// 开始进度条
NProgress.start()
// set page title
// document.title = getPageTitle(to.meta.title)
document.title = to.meta.title
// determine whether the user has logged in
const hasToken = Cookies.get('token');
// 有token,如果想去login则调到首页,如果是其他页面先判定是否有角色,有的话就跳过去,没有的话发请求得到永不信息,再调用函数维护store路由列表,报错要么没权限,要么是请求超时,就要返回error,清除token,返回登录页
// 没有token,且不是白名单内的,返回登录页
if (hasToken) {
if (to.path === '/login') {
// if is logged in, redirect to the home page
next({ path: '/' })
NProgress.done() // hack: https://github.com/PanJiaChen/vue-element-admin/pull/2939
} else {
// determine whether the user has obtained his permission roles through getInfo
/**x获取用户可以访问菜单地址*/
const hasMenuArr = store.getters.hasMenu && store.getters.hasMenu.length > 0;
if (hasMenuArr) {
next()
} else {
try {
const { roles } = await store.dispatch('user/getInfo');
/** 先获取用户拥有菜单权限(获取地址) */
const menuUrlList = await store.dispatch('user/getUserMenu',to.path);
const accessRoutes = await store.dispatch('permission/generateRoutes', menuUrlList)
router.addRoutes(accessRoutes)
next({ ...to, replace: true })
} catch (error) {
// remove token and go to login page to re-login
// await store.dispatch('user/resetToken')
Message.error(error || 'Has Error')
next(`/login`)
NProgress.done()
}
}
}
} else {
if (whiteList.indexOf(to.path) !== -1) {
next()
} else {
// other pages that do not have permission to access are redirected to the login page.
next(`/login`)
NProgress.done()
}
}
})
// 进度条完毕
router.afterEach(() => {
// finish progress bar
NProgress.done()
})
四、在main.js引入permission.js文件
import './permission'
下面只有在登录页面,登录成功后,设置token即可;
// 示例代码:
this.$api.login.loginFun(subData).then((res) => {
if (res.code == "000000") {
//登录成功后,保存token,然后跳转到首页
Cookies.set("token", res.data.token, {
expires: 7,
});
that.$router.push({
path: "/",
});
} else {
// this.$message.warning(res.mesg || '');
this.loginForm.code = "";
this.getCodePic();
}
});
版权声明:本文为qq_34322905原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。