需求背景
后台系统经常需要给用户划分不同的角色,不同角色的操作权限不同,这样可以有效避免人为的误操作。
项目框架为 Vue2+Element
实现方案
盘点项目中需要权限控制的地方,可以归为三类。
- 整体页面——通过后端接口控制
- 侧边菜单栏——路由中添加权限标识
- 页面中的组件元素——vue自定义指令
1. 整体页面控制
对于整个页面的控制,前端没有专门做限制。而是通过后端在当前页面接口返回无权限错误码,前端拿到错误码后,将页面重定向到一个无权限的落地页。
2. 侧边栏控制
侧边菜单栏是通过router数组遍历出来的,要实现对应角色只能看到相应权限的菜单,就需要在菜单渲染之前进行过滤。
{
path: '/equipment/info',
name: 'Equipment',
component: () => import('@/views/equipment/index.vue'),
icon: 'icon-equipment',
permission: 'p_equipment', //在路由结构中添加对应的权限标识
meta: {
title: 'menu.equipment',
keepAlive: true,
},
},
后端需要提供一个根据角色查询权限的接口,在main.js文件中调用,根据返回的权限列表对路由进行过滤
getPermissionRouter() {
getPermission(roleId).then((res) => {
if (res.code === 0) {
// 过滤没有权限展示的路由,侧边栏渲染时会忽略hidden为true的路由
if (res.data.permission_list) {
this.$router.options.routes.forEach((route) => {
if (route.permission) {
if (Array.isArray(route.permission)) {
route.hidden = !res.data.permission_list.filter((item) => route.permission.includes(item)).length;
} else {
route.hidden = !res.data.permission_list.find((item) => item === route.permission);
}
}
});
}
}
});
}
这里只列出了核心的过滤代码,实际还会有很多优化逻辑,比如只有角色变化才进行过滤等
3. 页面组件元素控制
对于页面上的文案、图片、按钮、输入框等元素的权限,如果每一个都用v-if
或v-show
来判断,工作量非常大。我这里采用了vue中的自定义指令来实现。
首先新建一个permission.js文件,这里我将DOM元素分为查看和编辑两种。
// permission.js
import store from '@/store';
/**
* @param binding.arg arg.show表示是否能够展示,arg.edit表示是否能够编辑
* @param binding.value value表示指令的绑定值,根据绑定的元素位置传递,本次传递粒度仅页面
*/
function checkPermission(el, binding) {
const { value, arg } = binding;
// admin或未传权限标识则退出
if (!value || store.state.roleId === '1') {
return;
}
// permissionList是后端返回的权限列表,判断元素携带的权限标识是否在列表中
let key = store.state.permissionList.includes(value);
if (arg === 'show') {
if (!key) {
el.parentNode && el.parentNode.removeChild(el);
}
} else if (arg === 'edit') {
if (!key) {
el.style.pointerEvents = 'none';
disabledNode(el); // 操作DOM节点将其置为不可用状态
}
}
}
function disabledNode(node) {
if (node.classList) {
node.classList.add('is-disabled');
if (Array.from(node.classList).includes('el-input__suffix-inner')) {
const childs = node.childNodes;
childs.forEach(element => node.parentNode && node.parentNode.appendChild(element));
}
}
if (node.nodeName === 'INPUT' || node.nodeName === 'TEXTAREA') {
node.setAttribute('disabled', 'disabled');
return;
} else if (node.nodeName === 'BUTTON') {
node.disabled = true;
return;
} else {
node.childNodes.length > 0 && node.childNodes.forEach(item => disabledNode(item));
}
}
export default {
inserted(el, binding) {
checkPermission(el, binding);
},
update(el, binding) {
checkPermission(el, binding);
},
};
// main.js
import permission from '@/utils/permission.js';
Vue.directive('permission', permission); // 注册自定义指令
使用方式
<el-button v-permission:edit="'xxx'">click</el-button>
<span v-permission:show="'yyy'">hello</span>