如果仅从能够修改服务器中数据库中的数据层⾯上讲,确实只在后端做控制就⾜够了, 那为什么越来越多的项⽬也进⾏了前端权限的控制, 主要有这⼏⽅⾯的好处:
- 降低⾮法操作的可能性
- 尽可能排除不必要请求,减轻服务器压⼒
- 提⾼⽤户体验
# 一、菜单栏控制
在登录请求中, 会得到权限数据, 当然, 这个需要后端返回数据的⽀持. 前端根据权限数据, 展示对应的菜 单.点击菜单,才能查看相关的界⾯.
# 1、login 接口要求
在登录后获取的数据:
除了有用户基本信息外,还要有 token、rights 两个关键字段。
- 返回的数据示例
{
"data": {
"id": 500,
"rid": 0,
"username": "admin",
"mobile": "13999999999",
"email": "[email protected]",
"token": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjUwMCwicmlkIjowLCJpYXQiOjE1M TI1NDQyOTksImV4cCI6MTUxMjYzMDY5OX0.eGrsrvwHmtPsO9r_ pxHIQ5i5L1kX9RX444uwnRGaIM"
},
"rights": [
{
"id": 103,
"authName": "⻆⾊管理",
"icon": "icon-tijikongjian",
"children": [
{
"id": 111,
"authName": "⻆⾊列表",
"path": "roles",
"rights": ["view", "edit", "add", "delete"]
}
]
},
{
"id": 101,
"authName": "商品管理",
"icon": "icon-shangpin",
"children": [
{
"id": 104,
"authName": "商品列表",
"path": "goods",
"rights": ["view", "edit", "add", "delete"]
},
{
"id": 121,
"authName": "商品分类",
"path": "categories",
"rights": ["view", "edit", "add", "delete"]
}
]
}
],
"meta": {
"msg": "登录成功",
"status": 200
}
}
# 2、持久化存储
对用户信息、token 等数据进行持久化存储。
menu_loginout(){
// 1、清除sessionStorage中的数据
// 要么:(大量数据时)
sessionStorage.clear()
// 或者:(少量数据时)
this.$store.commit('setToken','')
this.$store.commit('setUserInfo','')
// 2、删除vuex中的数据(F11刷新)
window.location.reload()
this.$router.push('/login')
}
详情:vuex 进阶
# 二、页面访问权限
如果⽤户没有登录,⼿动在地址栏敲⼊管理界⾯的地址, 则需要跳转到登录界⾯
如果⽤户已经登录, 可是⼿动敲⼊⾮权限内的地址, 则需要跳转 404 界⾯
# 1、导航守卫
- route / index.js
const whiteList = ['/login', '/register']
router.beforeEach((to, from, next) => {
if (whiteList.includes(to.path)) {
next()
} else {
const token = sessionStorage.getItem('token')
if (!token) {
next('/login')
} else {
next()
}
}
})
- 登录成功时
// 方式1
// this.$store.commit('updateToken', res.token)
// 方式2
this.updateToken(res.token)
// 方式3
// sessionStorage.setItem('token',res.token)
this.$router.push('/layout')
# 2、动态路由
- router /index.js
routes: [
{
path: '/home',
component: Home,
redirect: '/welcome',
children: [
{ path: '/welcome', component: Welcome }
// { path: '/users', component: Users },
// { path: '/roles', component: Roles },
// { path: '/goods', component: GoodsList },
// { path: '/categories', component: GoodsCate }
]
},
{
path: '*',
component: NotFound
}
]
// 路由规则
const userRule = { path: '/users', component: Users }
const roleRule = { path: '/roles', component: Roles }
const goodsRule = { path: '/goods', component: GoodsList }
const categoryRule = { path: '/categories', component: GoodsCate }
import $store from '@/store/index'
// 登录请求path路由 与 路由规则 的映射🚩
const ruleMapping = {
users: userRule,
roles: roleRule,
goods: goodsRule,
categories: categoryRule
}
// 根据用户权限,设置动态路由规则
export function initDynamicRoutes() {
console.log(router)
// 1、获取路由数组
const currentRoutes = router.options.routes
const rightList = $store.state.rightList
rightList.forEach((item) => {
item.children.forEach((item) => {
// 2、动态添加二级路由(自己查看下标)
const temp = ruleMapping[item.path]
currentRoutes[2].children.push(temp)
})
})
// 2、正式重置路由
router.addRoutes(currentRoutes)
}
- 登录成功时
import { initDynamicRoutes } from '@/router/index.js'
// ……
login() {
// ……
initDynamicRoutes()
}
- 根组件防止数据丢失
import { initDynamicRoutes } from '@/router/index.js'
export default {
name: 'app',
created() {
initDynamicRoutes()
}
}
# 三、按钮操作权限
在某个菜单的界⾯中, 还得根据权限数据, 展示出可进⾏操作的按钮, ⽐如删除,修改,增加
# 1、自定义指令
- main.js
import './utils/permission.js'
- router / index.js
// 根据用户权限,设置动态路由规则
export function initDynamicRoutes() {
const currentRoutes = router.options.routes
const rightList = $store.state.rightList
rightList.forEach((item) => {
item.children.forEach((item) => {
// item 二级权限
const temp = ruleMapping[item.path]
temp.meta = item.rights // 利用路由元信息附加上权限信息(备用👏)
currentRoutes[2].children.push(temp)
})
})
router.addRoutes(currentRoutes)
}
- utils / permission.js
import Vue from 'vue'
import router from '@/router.js'
// 自定义全局指定
Vue.directive('permission', {
inserted: function (el, binding) {
// console.log(el)
// console.log(binding)
const action = binding.value.action
const effect = binding.value.effect
// 根据用户权限操作element按钮(view、edit、add、delete)
const currentRight = router.currentRoute.meta
if (currentRight) {
// 1、若没有有权限
if (currentRight.indexOf(action) === -1) {
// 2、要禁用还是移除
if (effect === 'disabled') {
el.disabled = true
el.classList.add('is-disabled') //使用class禁用element按钮
} else {
el.parentNode.removeChild(el) // 删除element按钮
}
}
}
}
})
- 使用示例
<!-- 添加按钮,禁用状态 -->
<el-button
type="primary"
@click="addDialogVisible = true"
v-permission="{action: 'add',effect: 'disabled'}"
>添加用户</el-button
>
# 四、API 访问权限
如果⽤户通过⾮常规操作, ⽐如通过浏览器调试⼯具将某些禁⽤的按钮变成启⽤状态, 此时发的请求, 也应该被前端所拦截
# 1、axios 拦截器
- utils / request.js
import $router from '../router'
// 引入element-ui弹窗
import { Message } from 'element-ui'
// 请求类型 与 操作类型 的映射
const actionMapping = {
get: 'view',
post: 'add',
put: 'edit',
delete: 'delete'
}
- 请求拦截器(身份过期校验)
axios.interceptors.request.use(
function (config) {
// 在发送请求之前做些什么
const currentUrl = config.url
const whiteList = ['/login', '/register']
if (whiteList.includes(currentUrl)) {
// 1、token验证
config.headers.Authorization = $store.state.token
// config.headers.Authorization = sessionStorage.getItem('token')
// 2、权限验证✨
const method = config.method
const action = actionMapping[method]
const currentRight = router.currentRoute.meta
if (currentRight && currentRight.indexOf(action) === -1) {
// 没有权限
// alert('没有权限')
Message.error('没有权限')
return Promise.reject(new Error('没有权限'))
}
}
return config
},
function (error) {
return Promise.reject(error)
}
)
- 响应拦截器
// (请求超时或被篡改)
axios.interceptors.response.use(
function (response) {
return response
},
function (error) {
if (error.response.status === 401) {
// 1、清除sessionStorage中的数据
// 要么:(大量数据时)
sessionStorage.clear()
// 或者:(少量数据时)
$store.commit('setToken', '')
$store.commit('setUserInfo', '')
// 2、删除vuex中的数据(F11刷新)
window.location.reload()
$router.push('/login')
// 弹窗提示
Message.error('用户身份以过期!!')
}
return Promise.reject(error)
}
)