前端权限管理的意义:
1、降低非法操作的可能性
2、尽可能排除不必要的请求,减轻服务器的压力
3、提高用户体验
前端权限控制的思路:
1、菜单控制:在登录请求当中,会得到权限数据,权限需要在多个组件之间共享,所以可以通过vuex保存数据并且展示相应的菜单,页面刷新的情况下会丢失数据,所以把权限数据存入sessionStorage中。
2、界面控制:如果用户没有登录,手动输入url地址,应该阻止并跳转到登录页面,通过路由导航守卫来实现。动态路由可以让不具备权限的界面在路由规则中不存在
3、按钮控制:路由规则中可以添加元数据meta,通过路由对象可以得到当前的路由规则以及存储在路由规则中的元数据,自定义指令可以实现对按钮的控制
4、请求和响应控制:请求拦截和相应拦截的使用,请求方式约定restful
实现代码:
登录login相关代码:
login(){
...
const user = res.data //登录成功后,获取用户信息,包括token,用户名,权限rights等
this.$store.commit('setUser', user) // 将用户信息存储到vuex当中
sessionStorage.setItem('token', res.data.token) //将token信息存储到sessionStorage中
initDynamicRoutes() // 动态添加路由
this.$router.push('/') // 登录成功后跳转
}
store相关代码:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
user: JSON.parse(sessionStorage.getItem('user') || '[]') // 刷新页面的时候从sessionStorage中读取数据
},
mutations: {
setUser (state, user) {
state.user = user // 首次登录将用户信息赋值给user ,并且存入sessionStorage
sessionStorage.setItem('user', JSON.stringify(user))
}
},
actions: {
},
modules: {
}
})
router相关代码:
import Vue from 'vue'
import VueRouter from 'vue-router'
import store from '../store'
import Home from '../views/Home.vue'
import Login from '../views/Login.vue'
import NotFound from '../views/NotFound.vue'
Vue.use(VueRouter)
// 权限路由路径映射
const mappingPath = {
'/menu/one': { path: '/menu/one', component: () => import('@/views/Page1.vue') },
'/menu/two': { path: '/menu/two', component: () => import('@/views/Page1.vue') },
'/menu/three': { path: '/menu/three', component: () => import('@/views/Page1.vue') },
'/menu/four': { path: '/menu/four', component: () => import('@/views/Page1.vue') },
'/menu/five': { path: '/menu/five', component: () => import('@/views/Page1.vue') }
}
const routes = [
{
path: '/',
name: 'Home',
component: Home,
redirect: '/menu/one',
children: []
},
{
path: '/login',
name: 'Login',
component: Login
},
{
path: '*',
name: 'NotFound',
component: NotFound
}
]
const router = new VueRouter({
routes
})
// 动态路由添加方法
export function initDynamicRoutes () {
const currentRoutes = router.options.routes
const rights = store.state.user.rights || []
rights.forEach(item => {
item.children.forEach(item => {
const temp = mappingPath[item.path]
temp.meta = item.rights
currentRoutes[0].children.push(temp)
})
})
router.addRoutes(currentRoutes)
}
// 路由导航守卫判断
router.beforeEach((to, from, next) => {
if (to.path === '/login') {
next()
} else {
const token = sessionStorage.getItem('token')
if (!token) {
next({ path: '/login' })
} else {
next()
}
}
})
export default router
动态路由添加,刷新页面的时候不会重新走login方法,因此在App.vue的生命周期函数中需要再执行一次 initDynamicRoutes()
import { initDynamicRoutes } from './router'
export default {
name: 'app',
components: {
},
created () {
initDynamicRoutes()
}
}
按钮控制是通过指令来实现的:在对应的按钮上加上指令:v-permission='{action:"add",effect:"disabled"}'
import vue from 'vue'
import router from '../router'
vue.directive('permission', {
inserted: function (el, bind) {
const action = bind.value.action
const currentRights = router.currentRoute.meta
if (currentRights) {
if (currentRights.indexOf(action) === -1) {
if (bind.value.effect === 'disabled') {
el.disabled = true
el.classList.add('is-disabled')
} else {
el.parentNode.removeChild(el)
}
}
}
}
})
请求和响应控制是通过添加拦截器来实现的:
import Axios from 'axios'
import router from '../router'
const axios = Axios.create({
// baseURL: process.env.NODE_ENV === 'development' ? '' : '',
})
const map = {
get: 'view',
put: 'edit',
post: 'add',
delete: 'delete'
}
axios.interceptors.request.use(function(req){
const currentUrl = req.url
const rights = router.currentRoute.meta.rights || []
if (currentUrl !== '/login' && !rights.includes(map[req.method])) {
return Promise.reject(new Error('没有权限操作!'))
}
})
axios.interceptors.response.use(function(res){
if(res.data.meta.status === 401){
router.push('/login')
sessionStorage.clear()
window.location.reload()
}
})
export default (url, method = 'get', data = {}) => {
return axios({
url,
method,
data
})
}
权限的mock数据:
const users = [
{
id: 1,
username: '普通用户',
password: 'normal',
token: 'abcdefghijklmnopqrstuvwxyz',
rights: [{
id: 1,
authName: '一级菜单',
icon: 'icon-menu',
children: [{
id: 11,
authName: '一级项目1',
path: '/menu/one',
rights: ['view']
}, {
id: 11,
authName: '一级项目2',
path: '/menu/two',
rights: ['view']
}]
}]
},
{
id: 2,
username: '管理员',
password: 'admin',
token: 'abcdefghijklmnopqrstuvwxyz'.split('').reverse().join(''),
rights: [{
id: 1,
authName: '一级菜单',
icon: 'icon-menu',
children: [{
id: 11,
authName: '一级项目1',
path: '/menu/one',
rights: ['view', 'edit', 'add', 'delete']
}, {
id: 11,
authName: '一级项目2',
path: '/menu/two',
rights: ['view', 'edit', 'add', 'delete']
}]
}, {
id: 2,
authName: '二级菜单',
icon: 'icon-menu',
children: [{
id: 22,
authName: '二级项目1',
path: '/menu/three',
rights: ['view', 'edit', 'add', 'delete']
}, {
id: 22,
authName: '二级项目1',
path: '/menu/four',
rights: ['view', 'edit', 'add', 'delete']
}, {
id: 22,
authName: '二级项目1',
path: '/menu/five',
rights: ['view', 'edit', 'add', 'delete']
}]
}]
}
]