将权限管理应用到系统,首先做好登录, 点击登录按钮后,触发以下动作
- vuex 中的 login 动作,设置 cookie
- vuex 中的 getuserinfo , 获取权限、用户名、头像等
由于目前未使用连接后端服务器,所以使用 mockjs 拦截请求并返回。
在github中查看
1 全局请求拦截
使用axios 封装好请求和响应
src/utils/request.js
import axios from 'axios'
const clearRequest = {
source: {
token: null,
cancel: null
}
}
const cancelToken = axios.CancelToken
const source = cancelToken.source()
// 创建 axios 实例
const service = axios.create({
cancelToken: source.token,
timeout: 6000, // 请求超时时间
})
// request 拦截器
service.interceptors.request.use(
config => {
config.cancelToken = clearRequest.source.token
return config
},
error => {
return Promise.reject(error)
}
)
// response 拦截器
service.interceptors.response.use(
resp => resp,
error => {
return Promise.reject(error)
}
)
export { clearRequest }
export default service
2 封装一些常用的函数
src/utils/index.js
export function param2Obj(url){
const search = url.split('?')[1]
if(!search){
return {}
}
return JSON.parse(
'{"' +
decodeURIComponent(search)
.replace(/"/g, '\"')
.replace(/&/g, '","')
.replace(/=/g, '":"') +
'"}'
)
}
src/utils/auth.js
import Cookies from 'js-cookie'
const tokenKey = 'X-Token'
export function getToken(){
return Cookies.get(tokenKey)
}
export function setToken(token){
return Cookies.set(tokenKey, token)
}
export function removeToken(){
return Cookies.remove(tokenKey)
}
3 编写登录api
src/api/login.js 文件
import request from '@/utils/request'
export function loginByUsernameApi(username, password){
return request({
url: '/api/auth/api-token-auth',
method: 'post',
data: {
username,
password
}
})
}
export function getUserInfoApi(token){
return request({
url: '/api/userinfo',
method: 'get',
params: {token}
})
}
export function logoutApi(){
return request({
url: '/api/auth/logout',
method: 'post'
})
}
4 mock 拦截
src/mock/index.js
import Mock from 'mockjs'
import login from './login'
// 登录相关
Mock.mock(//api/auth/api-token-auth/, 'post', login.loginByUsername)
Mock.mock(//api/auth/logout/, 'post', login.logout)
Mock.mock(//api/userinfo/, 'get', login.getUserInfo)
export default Mock
src/mock/login.js
import { param2Obj } from '@/utils'
const usermap = {
admin: {
token: 'admin',
introduction: '我是超级管理员',
name: 'Super Admin',
pass: 'e10adc3949ba59abbe56e057f20f883e',
roles: ['admin']
},
developer: {
token: 'developer',
introduction: '我是开发',
name: '工程师小王',
pass: 'e10adc3949ba59abbe56e057f20f883e',
roles: ['/system', '/system/permit', '/system/permit/account']
}
}
export default {
loginByUsername: config => {
const { username, password } = JSON.parse(config.body)
console.log('loginByUsername username, password: ', username, password)
if(username === 'admin'){
if(usermap[username].pass === password){
return usermap['admin']
}else{
return []
}
}
return usermap[username]
},
getUserInfo: config => {
console.log('getUserInfo config: ', config)
const { token } = param2Obj(config.url)
let tok = false
for(let key in usermap){
if(token.indexOf(usermap[key].token) !== -1){
tok = usermap[key]
break;
}
}
return tok
},
logout: () => 'success'
}
5 vuex 的使用
src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
import permission from './modules/permissions'
import getters from './getters'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
permission,
user,
},
getters
})
export default store
src/store/modules/user.js
import { loginByUsernameApi, logoutApi, getUserInfoApi } from '@/api/login'
import { getToken, setToken, removeToken } from '@/utils/auth'
const user = {
state: {
token: getToken(),
name: '',
avatar: '',
roles: []
},
mutations: {
SET_TOKEN: (state, token) => {
state.token = token
},
SET_NAME: ( state, name) => {
state.name = name
},
SET_AVATAR: (state, avatar) => {
state.avatar = avatar
},
SET_ROLES: (state, roles) => {
state.roles = roles
}
},
actions: {
// 登入
LoginByUsername({commit}, userinfo){
const username = userinfo.username.trim()
return new Promise((resolve, reject) => {
loginByUsernameApi(username, userinfo.password)
.then(resp => {
const data = resp.data
setToken(data.token)
console.log('in LoginByUsername, setToken: ', data.token)
commit('SET_TOKEN', data.token)
resolve()
})
.catch(err => {
reject(err)
})
})
},
// 获取用户权限等
GetUserInfo({commit, state}) {
console.log('in GetUserInfo')
return new Promise((resolve, reject) => {
getUserInfoApi(state.token)
.then(resp => {
if(!resp.data){
reject('error')
}
const data = resp.data
if(data.roles && data.roles.length){
commit('SET_ROLES', data.roles)
}else {
reject('getUserInfoApi: roles must be a non-null array!')
}
if(data.name){
commit('SET_NAME', data.name)
}
if(data.avatar){
commit('SET_AVATAR', data.avatar)
}
resolve(resp)
})
.catch(err => {
reject(err)
})
})
},
// 登出
LogOut({commit, state}){
return new Promise((resolve, reject) => {
logoutApi(state.token)
.then(() => {
commit('SET_TOKEN', '')
commit('SET_ROLES', [])
removeToken()
resolve()
})
.catch(err => {
reject(err)
})
})
},
// 前端登出
FedLogOut({commit}){
return new Promise(resolve => {
commit('SET_TOKEN', '')
removeToken()
resolve()
})
}
}
}
export default user
src/store/modules/permissions.js
import { asyncRouterMap, constantRouterMap } from '@/router'
/**
* 通过 meta.role 判断是否与当前用户权限匹配
*/
function hasRoles (roles, route){
if(route.meta && route.meta.roles){
return roles.some(role => route.meta.roles.includes(role))
}else{
return true
}
}
/**
* 递归过滤异步路由表,返回符合用户角色权限的路由表
*/
function filterAsyncRouter(asyncRouterMap, roles){
const accessedRouters = asyncRouterMap.filter(route => {
// 404
if(route.path === '*'){
return true
}else if(hasRoles(roles, route)){
if(route.children && route.children.length){
route.children = filterAsyncRouter(route.children, roles)
}
return true
}
return false
})
return accessedRouters
}
// 在有权限的路由表里,查找是否有到目标path的路由
// 为了保持路由唯一性,拼接父子路由
function hasDestRoute (froute, permitRouterMap, to) {
let r = froute === '/' ? '' : froute
return permitRouterMap.some(route => {
let path = r + '/' + route.path
if (to.path.indexOf(path) !== -1) {
return true;
}
if (route.children && route.children.length) { //如果有孩子就遍历孩子
return hasDestRoute(path, route.children, to)
}
})
}
const permission = {
state: {
routers: constantRouterMap,
addRouters: [],
sidebar_routers: {},
},
mutations: {
SET_ROUTERS: (state, routers) => {
state.addRouters = routers,
state.routers = constantRouterMap.concat(routers)
},
SET_NOW_ROUTERS: (state, to) => {
console.log('in SET_NOW_ROUTERS')
// 由于首页重定向到 /dashboard,并且不参与权限控制,特殊处理
if(to.path === '/dashboard'){
let dashboard = state.routers.filter(v => v.path === '/' )
state.sidebar_routers = dashboard[0]
}else{
// 递归访问 accessedRouters,找到包含to 的那个路由对象,设置给 sidebar_routers
state.addRouters.forEach(e => {
if (e.children && e.children.length) {
if ( hasDestRoute(e.path, e.children, to)){
if(state.sidebar_routers.path){
// 存在 sidebar_routers 且与目标路由不同
if(state.sidebar_routers.path !== e.path){
state.sidebar_routers = e;
}
}else{
state.sidebar_routers = e;
}
}
}
})
}
}
},
actions: {
GenerateRoutes({commit}, data) {
console.log('in GenerateRoutes')
return new Promise(resolve => {
const {roles} = data
let accessedRouters
if(roles.includes('admin')){
accessedRouters = asyncRouterMap
}else{
accessedRouters = filterAsyncRouter(asyncRouterMap, roles)
}
commit('SET_ROUTERS', accessedRouters)
resolve()
})
},
GenSidebarRoutes({commit}, data) {
console.log('in GenSidebarRoutes')
return new Promise(resolve => {
commit('SET_NOW_ROUTERS', data)
resolve()
})
}
}
}
export default permission
src/store/getters.js
const getters = {
token: state => state.user.token,
roles: state => state.user.roles,
avatar: state => state.user.avatar,
name: state => state.user.name,
addRouters: state => state.permission.addRouters,
permission_routers: state => state.permission.routers,
sidebar_routers: state => state.permission.sidebar_routers,
}
export default getters
6 修改 login 页面
src/views/TheLogin.vue
handleSubmit(event) {
this.$refs.ruleForm2.validate((valid) => {
if (valid) {
this.logining = true;
// 触发 vuex 中 LoginByUsername 事件
const username = this.ruleForm2.username
let password = md5(this.ruleForm2.password)
if(this.hidePassword !== '' && this.ruleForm2.password === '********'){
password = this.hidePassword
}
this.$store.dispatch('LoginByUsername', {'username': username, 'password': password})
.then(() => {
this.logining = false
if(this.rememberme){
this.setCookie(this.ruleForm2.username, password, 7)
}else{
this.clearCookie()
}
// 重定向到首页
this.$router.push({ path: this.redirect || '/' })
})
.catch(err => {
this.logining = false
this.$alert(err, {
type: 'warning',
confirmButtonText: 'ok'
})
})
} else {
console.log('error submit!');
return false;
}
})
}
7 修改main 主入口
src/main.js
/** ...*/
import store from './store'
import '@/login'
import '@/mock'
Vue.use(ElementUI)
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
render: h => h(App)
})
8 关键的权限控制
src/login.js
import router from './router'
import NProgress from 'nprogress'
import {clearRequest} from '@/utils/request'
import axios from 'axios'
import store from './store'
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 = ['/login', ] // 不重定向白名单
router.beforeEach((to, from, next) => {
console.log('to.path: ' + to.path)
console.log('store.getters.token: ' + store.getters.token)
// 切换路由时清空上个路由未完成的所有请求
const cancelToken = axios.CancelToken
clearRequest.source.cancel && clearRequest.source.cancel()
clearRequest.source = cancelToken.source()
if(whiteList.indexOf(to.path) !== -1){
next()
}else{
if(store.getters.token){
if(to.path === '/login'){
next({path: '/'})
NProgress.done()
}else{
// 拉取用户信息
if(store.getters.roles.length === 0){
store.dispatch('GetUserInfo')
.then(resp => {
const roles = resp.data.roles
console.log('roles: ', roles)
// 根据权限生成可访问的路由表
store.dispatch('GenerateRoutes', {roles})
.then(() => {
// 动态添加路由表
router.addRoutes(store.getters.addRouters)
next({...to, replace: true}) // 确保 addRouters 已完成
})
})
.catch(err => {
store.dispatch('FedLogOut')
.then(() => {
console.log('认证失败,请重新登陆')
next({path: '/login'})
})
})
}else{
console.log('call GenSidebarRoutes')
store.dispatch('GenSidebarRoutes', to)
.then(() => {
if(hasPermission(store.getters.roles, to.meta.role)){
next()
}else{
next({path: '/', query: {noGoBack: true}})
}
})
}
}
}else{
// 重定向到 /login
next({path: '/login', query: {redirect: to.fullpath}})
NProgress.done()
}
}
})
router.afterEach(() => {
NProgress.done()
})
目录结构
│ App.vue
│ login.js
│ main.js
├─api
│ login.js
│ system.js
├─assets
│ logo.png
├─components
│ HelloWorld.vue
│ Navbar.vue
│ NavbarItem.vue
│ Sidebar.vue
│ SidebarItem.vue
├─containers
│ Container.vue
├─mock
│ article.js
│ index.js
│ login.js
│ system.js
├─router
│ │ index.js
│ │
│ └─modules
│ system.js
├─store
│ │ getters.js
│ │ index.js
│ │
│ └─modules
│ permissions.js
│ user.js
├─styles
│ animate.scss
│ browser-prefix.scss
│ index.scss
├─utils
│ auth.js
│ index.js
│ request.js
└─views
│ 404.vue
│ Home.vue
│ TheLogin.vue
├─article
│ index.vue
├─dashboard
│ index.vue
├─SidebarMenu
│ index.vue
└─system
└─permit
│ account.vue
│ accountgroup.vue
│ authorize.vue
│ index.vue
│ role.vue
└─components
DialogRoleMenu.vue
admin 用户权限:
developer 权限: