前言
不管是移动端,还是pc端,可能都会有用户登录操作,不同的用户之间又拥有不同的角色,而不同角色之间势必存在不同的权限;
如果按照类型划分,大概可分为三类:菜单权限、按钮权限、数据权限;
数据权限:前端在请求头统一封装,携带用户信息,由后端解析返回;
菜单权限:通过element菜单组件实现;
按钮权限:通过自定义指令实现;
菜单权限&按钮权限:本质上也是通过数据权限,由后端返回当前用户所有有权限菜单及按钮,然后由前端处理;
login接口返回数据格式
登录接口按照类似格式返回是处理一切权限问题的前提;
内容主要包含用户信息,菜单信息,按钮信息等,其中:
token:用于前端请求头封装,后端根据此字段判断用户信息;
menus:当前登录用户菜单权限列表,用于菜单权限的处理;
permissions:当前登录用户按钮权限列表,用于按钮权限的处理;
具体数据结构如下:
{
"code": 1,
"data": {
"id": "1237361915165020161",
"username": "admin",
"phone": "178****9154",
"nickName": "小d",
"realName": "段段",
"sex": 1,
"deptId": "1237322421447561216",
"deptName": "测试部门",
"status": 1,
"email": "1@qq.com",
"token":"token串", // 用于前端请求头封装,后端根据此字段判断用户信息
"menus": [ // 当前登录用户菜单权限列表
{
"id": "1236916745927790564",
"title": "系统管理",
"icon": "el-icon-star-off",
"path": "/org",
"name": "org",
"children": [
{
"id": "1236916745927790578",
"title": "角色管理",
"icon": "el-icon-s-promotion",
"path": "/roles",
"name": "roles",
"children": [],
},
{
"id": "1236916745927790560",
"title": "菜单管理",
"icon": "el-icon-s-tools",
"path": "/menus",
"name": "menus",
"children": [],
},
{
"id": "1236916745927790575",
"title": "用户管理",
"icon": "el-icon-s-custom",
"path": "/users",
"name": "user",
"children": [],
}
],
"spread": true,
"checked": false
},
{
"id": "1236916745927790569",
"title": "账号管理",
"icon": "el-icon-s-data",
"path": "/user",
"name": "user",
"children": []
}
],
"permissions": [ // 当前登录用户按钮权限
"sys:log:delete",
"sys:user:add",
"sys:role:update",
"sys:dept:list"
]
}
}
数据权限
1.登录接口返回当前用户token
具体的返回形式如login返回数据;
2.login接口数据存储vuex
推荐文章:
链接
3.接口请求二次封装
// 此为移动端配置,引用了vant组件,如要换成其他组件请修改配置
// import Vue from 'vue'
import axios from 'axios'
import qs from 'qs'
import { isEmpty, isFormData, filterObj } from '@/utils/function.js'
import { Toast, Dialog } from 'vant'
let pending = [] // 声明一个数组用于存储每个ajax请求的取消函数和ajax标识
let CancelToken = axios.CancelToken
const TIMEOUT = 3000
const baseURL = process.env.VUE_APP_BASE_UPL
const getErrorString = (errorObj, defaultStr = '') => {
let errStr = defaultStr
// 对象
if (typeof errorObj === 'object') {
if (errorObj.response && errorObj.response.status) {
errStr = `${errorObj.response.status}-${errorObj.response.statusText}`
} else if (errorObj.message && typeof errorObj.message === 'string') {
errStr = `${errorObj.message} 无返回或非正常操作`
}
} else if (typeof errorObj === 'string') {
errStr = errorObj
}
return errStr
}
const removePending = ever => {
for (const p in pending) {
if (pending[p].u === ever.url + '&' + ever.method) {
// 当当前请求在数组中存在时执行函数体
pending[p].f() // 执行取消操作
pending.splice(p, 1) // 把这条记录从数组中移除
}
}
}
function handleResponse (response, options) {
if (!response || !options) return
if (options.showLoading && options.data && !options.data.hideLoading) {
Toast.clear()
}
if (response.status === 200) {
const data = response.data
const contentType = response.headers['content-type']
if (data.code === '40001') {
Dialog.alert({
message: data.message || '内部错误'
}).then(() => {
})
return
}
if (contentType && contentType.indexOf('json') !== -1) {
if (response.data.constructor === window.Blob) {
const reader = new FileReader()
reader.readAsText(response.data)
reader.addEventListener('loadend', function () {
const res = JSON.parse(reader.result)
Toast(`${response.data.message || '未知异常'}`)
})
} else {
const data = eval(response.data)
const { success, message } = data
if (!success && !options.noToast) {
Toast(`${response.data.message || '未知异常'}`)
}
}
}
} else if (response.status === 401 && options.showLoading) {
Toast(`${response.data.message || '未知异常'}`)
} else if (response.status > 401) {
Toast(`${response.data.message || '未知异常'}`)
} else if (response.status === '504') {
Toast('请求错误 网络异常')
}
}
const fetch = options => {
const {
method = 'get',
data,
url,
headers,
config
} = options
const { token } = store.state.login.user
const axiosConfig = {
timeout: TIMEOUT,
baseURL,
withCredentials: true,
headers: {
...headers,
'token': token // 重点:用户请求头的封装
}
}
const instance = axios.create(axiosConfig)
instance.interceptors.request.use(
config => {
// 判断是否需要显示满屏loading遮罩
if (options.showLoading && options.data && !options.data.hideLoading) {
Toast.loading({
mask: true,
loadingType: 'spinner',
// message: '加载中...',
duration: 30000,
getContainer: '#app'
})
}
removePending(config) // 在一个ajax发送前执行一下取消操作
config.cancelToken = new CancelToken(c => {
// 这里的ajax标识我是用请求地址&请求方式拼接的字符串,当然你可以选择其他的一些方式
pending.push({ u: config.url + '&' + config.method, f: c })
})
return config
},
err => {
return Promise.reject(new Error(getErrorString(err, 'request错误')))
}
)
instance.interceptors.response.use(
response => {
try {
handleResponse(response, options)
// ------------------------------------------------------------------------------------------
removePending(options.config) // 在一个ajax响应后再执行一下取消操作,把已经完成的请求从pending中移除
// -------------------------------------------------------------------------------------------
} catch (err) { }
return response
},
err => {
handleResponse(err.response, options)
return Promise.reject(new Error(getErrorString(err, 'response错误')))
}
)
const filterData = isFormData ? data : filterObj(data)
switch (method.toLowerCase()) {
case 'get':
return instance.get(
`${url}${!isEmpty(data) ? `?${qs.stringify(data)}` : ''}`,
config
)
case 'delete':
return instance.delete(url, { data: filterData })
case 'head':
return instance.head(url, filterData)
case 'post':
return instance.post(url, filterData, config)
case 'put':
return instance.put(url, filterData, config)
case 'patch':
return instance.patch(url, filterData)
default:
return instance(options)
}
}
export default function request (options) {
return new Promise((resolve, reject) => {
fetch(options)
.then(response => {
resolve(response.data)
})
.catch(err => {
reject(err)
})
})
}
查看封装是否成功
至此,封装完结;请求头中插入token信息,后端根据token进行用户解析,从而达到数据权限的处理;
菜单权限
1.登录接口返回当前用户权限菜单
具体的返回形式如login返回数据;
2.login接口数据存储vuex
推荐文章:
链接
3.element的menu组件渲染菜单
按钮权限
1.登录接口返回当前用户按钮菜单
具体的返回形式如login返回数据;
2.login接口数据存储vuex
推荐文章:
链接
3.自定义指令处理权限菜单
新建directives文件夹—–index.js文件
import directives from './permission'
export default {
install(Vue, options) {
Vue.directive('has',directives);
}
}
新建directives文件夹—–permission.js文件
import store from '@/store'
export default {
inserted:(el,binding,vnode) => {
let { userInfo = {} } = store.getters
let {permissions = []} = userInfo
permissions && !permissions.some(item => item==binding.value)&&(el.parentNode.removeChild(el));
}
}
按钮权限增加
// 示例如下
<el-button v-has='sys:log:delete' type="primary" round>主要按钮</el-button>