使用vue开发项目时进行权限判断
本文通过使用vue-element-admin以及iview-admin整理得出,侵删。
路由表
- 路由表
路由目录结构:
systemUser.js
//封装好的localstorge方法
import { getLocalStorage } from '../../utils/auth'
//声明两个变量
var loginPath = ''
var loginPathPlus = ''
//获取用户信息,通过用户不同权限控制重定向的路由
if(getLocalStorage('userInfo')){
if(getLocalStorage('userInfo').roles[0] == 'admin'){
loginPath = '/xxx'
loginPathPlus = '/ttt'
}else if(getLocalStorage('userInfo').roles[0] == 'tourist'){
loginPath = 'yyy'
}
}
//创建路由
const asyncRoutes = [
{
path: '/',
// 这个地方有需求进行重定向不同页面可以进行引用变量
redirect: loginPath,
component: () => import('@/layout/index'),
children:[
{
path: '/xxx',
//进入后如若还需进行权限判断展示不同页面再次引用另一个变量
redirect: loginPathPlus,
name: 'xxx',
component: () => import('@/views/common/xxx/index'),
//meta对象存放次路由的信息,role为进入或显示此路由需要的权限、title为路由显示时的标题,icon为显示时的icon
meta: {
role: ['admin'],
title:'xxx',
icon:'iconfont icon-huoche'
},
children:[
{
path: '/xxx',
name: 'xxx',
component: () => import('@/views/common/xxx'),
meta: {
role: ['admin']
},
},
{
path: '/xxx',
name: 'xxx',
component: () => import('@/views/common/xxx'),
meta: {
role: ['admin']
},
}
]
},
{
path: '/xxx',
name: 'xxx',
redirect: '/xxx',
component: () => import('@/views/common/xxx/index'),
meta: {
role: ['admin'],
title:'xxx',
icon:'iconfont icon-huojian'
},
children:[
{
path: '/xxx',
name: 'xxx',
component: () => import('@/views/common/xxx'),
meta: {
role: ['admin']
}
},
{
path: '/xxxl/:id',
name: 'xxx',
component: () => import('@/views/common/xxx'),
meta: {
role: ['admin']
}
}
]
},
{
path: '/xxx',
name: 'xxx',
component: () => import('@/views/common/xxx'),
meta: {
role: ['admin'],
title:'xxx',
icon:'iconfont icon-xiaoxi'
},
},
{
path: '/xxx',
name: 'xxx',
component: () => import('@/views/common/xxx'),
meta: {
role: ['admin'],
title:'xxx',
icon:'iconfont icon-huaban'
},
},
{
path: '/xxx',
redirect: 'xxx',
name: 'shape',
component: () => import('@/views/common/xxx'),
meta: {
role: ['admin'],
title:'xxx',
iocn:''
},
children:[
{
path: '/xxx',
name: 'xxx',
component: () => import('@/views/common/xxx'),
meta: {
role: ['admin']
},
},
{
path: '/xxx',
name: 'xxx',
component: () => import('@/views/common/xxx'),
meta: {
role: ['admin']
},
}
]
}
]
}
]
//抛出
export default asyncRoutes
index.js
import Vue from 'vue'
import Router from 'vue-router'
// 引入系统路由
import systemsRouter from './modules/systemUser'
Vue.use(Router)
//建立基本路由
export const publicRoutes = [
{
path: '/login',
name: 'login',
component: () => import('@/components/login')
},
{
path: '/401',
name: '401',
component: () => import('@/components/ErrPage/401')
},
{
path: '/404',
name: '404',
component: () => import('@/components/ErrPage/404')
}
]
const $router = new Router({
mode: 'hash', // 开启 history 模式需要服务端支持 history
scrollBehavior: () => ({ y: 0 }),
routes: publicRoutes
})
export default $router;
//若有多个路由可以多个引入拼接
var personsRouter = []
export const asyncRoutes = systemsRouter.concat(personsRouter)
- vuex
目录结构:
state.js:
export default {
myToken: '', // 要存储的 token的值
roles: [],
userInfo: {}, // 用户信息
NavWidth:true, // 二级菜单样式
}
index.js
import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import mutations from './mutations'
import actions from './actions'
import getters from './getters'
import app from './modules/app'
import pression from './modules/pression'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
app,
pression
},
state,
mutations,
actions,
getters
})
getter.js
export default {
sidebar: state => state.app.sidebar,
device: state => state.app.device,
myToken: state => state.myToken,
permission_routes: state => state.pression.routers,
NavWidth: state => state.NavWidth
}
pression.js
import { publicRoutes, asyncRoutes } from '../../router';
function hasPermission(roles, route) {
if (route.meta && route.meta.role) {
return roles.some(role => route.meta.role.indexOf(role) >= 0)
} else {
return true
}
}
var concat_ = function(arr1,arr2){
//不要直接使用var arr = arr1,这样arr只是arr1的一个引用,两者的修改会互相影响
var arr = arr1.concat();
//或者使用slice()复制,var arr = arr1.slice(0)
for(var i=0;i<arr2.length;i++){
arr.indexOf(arr2[i]) === -1 ? arr.push(arr2[i]) : 0;
}
return arr;
}
const pression = {
state: {
routers: publicRoutes,
addRouters: []
},
mutations: {
SET_ROUTERS: (state, routers) => {
state.addRouters = routers;
// state.routers = constantRouter.concat(routers)
state.routers = concat_(routers,publicRoutes)
}
},
actions: {
GenerateRoutes ({ commit }, { roles }) {
const accessedRouters = asyncRoutes.filter(v => {
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
})
commit('SET_ROUTERS', accessedRouters)
}
}
}
export default pression
actions.js
/*
包含间接更新状态数据的一类方法的对象
*/
// md5 加密的库
import crypto from 'crypto'
import { reqGetUserInfo, reqLogin, checkCode } from '../api/common'
import {
SET_TOKEN_DATA,
SAVE_USER_ROLES,
} from '../store/mutation-types'
import { setToken, setLocalStorage } from "../utils/auth"
export default {
async GetUserInfo ({ commit }) {
const result = await reqGetUserInfo();
if (result.resultCode === 0) {
const userInfo = result.data;
setLocalStorage('userInfo', userInfo)
commit(SAVE_USER_ROLES, userInfo)
}
},
async Login ({ commit }, reqData) {
const {account, verificationCode} = reqData
// md5 加密
const md5 = crypto.createHash("md5");
// 需要加密的密码
md5.update(reqData.password);
// password 加密完的密码
reqData.password = md5.digest('hex');
// 获取异步请求后的数据
// 首先校验验证码,成功之后在进行登录
var checkCodeData = {
account,
verificationCode
}
const res = await checkCode(checkCodeData);
if(res.resultCode === 0 ){
const result = await reqLogin(reqData);
if (result.resultCode === 0) {
// 取出数据中的 token的值
const { token } = result.data;
// 数据获取成功
setToken(token);
commit(SET_TOKEN_DATA, { token })
}
}else{
this.$Message.error(res.message);
}
}
}
- 全局路由守卫
在src目录下创建一个js文件,并在main.js中引入
文件内容如下
import router from './router'
import store from './store'
//封装获取token
import { getToken} from './utils/auth'
// 是否有权限
function hasPermission(roles, permissionRoles) {
if (roles.indexOf('admin') >= 0) return true; // admin 角色拥有所有权限
if (!permissionRoles) return true;
return roles.some(role => permissionRoles.indexOf(role) >= 0)
}
// 白名单,设置不需权限的路由
const whiteList = ['/frontPage', '/login', '/registry', '/servicePage'];
// 全局路由导航卫士
router.beforeEach((to, from, next) => {
//首先获取是否有token
if(getToken()){
//如果是登录页面,直接进入
if(to.path === '/login'){
next({path:'/'})
}else{
//如果有注册或其他不需要权限的页面也直接进入
if(['/registry'].indexOf(to.path) !== -1){
next()
}else{
//判断是否获取到用户权限
if (store.state.roles.length === 0) {
//如果没有则执行方法获取用户信息
store.dispatch('GetUserInfo')
.then(() => {
const roles = store.state.roles;
store.dispatch('GenerateRoutes', { roles }).then(() => { // 生成可访问的路由表
// console.log(store.state.pression.addRouters, '路由表');
router.addRoutes(store.state.pression.addRouters); // 动态添加可访问路由表
next({ ...to, replace: true }) // hack方法 确保 addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
// 获取所有路由path
var Alreadyarr = ['/']
function FindPath(Routers){
if(Routers.length>0){
Routers.forEach((item,index)=>{
if(item.children){
FindPath(item.children)
}else{
return Alreadyarr.push(item.path)
}
})
}else{
return Alreadyarr.push(Routers.path)
}
}
FindPath(store.state.pression.addRouters)
//判断如果去的path不在已拥有的路由里就去404页面
if(Alreadyarr.indexOf(to.path) == -1){
if((to.path.split('/')[1] == 'cashConcentrationCountDetail' || to.path.split('/')[1] == 'StatementDetail') && to.path.split('/').length>2 && to.path.split('/')[2] !==''){
next()
}else{
next({ path: '/404', replace: true, query: { noGoBack: true }})
}
}
})
})
.catch(err => {
console.log(err)
})
}else{
//判断如果取得页面权限是否在用户权限之中
if (hasPermission(store.state.roles, to.meta.role)) {
// console.log(store.state.roles, to.meta.role)
next()
} else {
next({ path: '/401', replace: true, query: { noGoBack: true }})
}
}
}
}
}else{
if(whiteList.indexOf(to.path) !== -1){
next()
}else{
next('/login')
}
}
});
router.afterEach(() => {
// NProgress.done() // 结束 Progress
});
- 登录页面
此处直接从点击登录按钮之后的逻辑说起
//执行login
this.$store.dispatch('Login', reqData).then(() => {
this.loading = false;
this.getUserInfo()
}).catch(() => {
this.loading = false
})
//获取用户信息,根据不同用户权限判断进入不同路由
async getUserInfo () {
const result = await reqGetUserInfo();
if (result.resultCode === 0) {
const userInfo = result.data;
setLocalStorage('userInfo', userInfo)
if (userInfo.roles.includes('admin')) {
// this.$router.replace({ name: 'xxx'});
this.$router.replace({ name: 'xxx'});
} else if (userInfo.roles.includes('yyy')) {
this.$router.replace({ name: 'yyy'})
} else if (userInfo.roles.includes('zzz')) {
this.$router.replace({ name: 'zzz'})
}
}
},
5.导航菜单
在项目布局里增加导航,这里在layout里使用iview中的Menu组件来进行
<Menu :active-name='active' theme="dark" width="auto" :class="menuitemClasses">
<MenuItem v-for="(item, index) in permission_routes[0].children" :key="index" :name='item.name' :to="item.path">
<i :class='item.meta.icon'></i>
<span>{{item.meta.title}}</span>
</MenuItem>
</Menu>
//获取全局状态里的路由表
...mapGetters([
'permission_routes'
])
这是比较简单易懂的一种
6.二级菜单
二级菜单就比较简单了,可以在二级惨淡页面直接加路由,通过权限判断是否渲染到页面即可,比如这样:
<MenuItem name="Cash_withdrawal_management" to="Cash_withdrawal_management" v-if="CashWithdrawalRight.indexOf(role) !== -1">
<!-- v-if="CashWithdrawalRight.indexOf(role) !== -1" -->
提现管理
</MenuItem>
CashWithdrawalRight是这个页面需要的权限,role是获取到的用户权限,不必担心手动输入路由进入该页面,因为在全局收尾里面已经进行了判断,进入401页面
目前先搞这么多,等会后有新发现在做补充,第一次接触权限问题,特此记录。