Vue 前端权限控制的优化改进版
1、前言
之前《vue前端访问控制方案 》一文中提出,使用class=“permissions”结合元素id来标识权限控制相关的dom元素,并通过公共方法checkrights来设置dom元素的可见属性,在实际使用中存在下列问题:
- checkrights指定上级节点的domkey,结果document.getelementsbyclassname获取了更上级的节点或其它子树的节点,没在指定上级节点下,结果节点没找到,导致错误禁用其它节点的权限。
- style.display与v-if存在可见属性冲突。
- 更为致命的是,document.getelementsbyclassname找不到插槽的节点,如下列形式:
<el-table-column label="操作"> <template slot-scope="scope"> <el-tooltip class="item,permissions" effect="dark" content="编辑" id="edituser" placement="left-start"> <el-button size="mini" type="primary" icon="el-icon-edit" circle @click="edituser(scope.row)"></el-button> </el-tooltip> <el-tooltip class="item,permissions" effect="dark" content="禁用" id="disableuser" placement="left-start" v-if="!scope.row.deleteflag"> <el-button size="mini" type="primary" icon="el-icon-lock" circle @click="disableuser(scope.row)"></el-button> </el-tooltip> <el-tooltip class="item,permissions" effect="dark" content="启用" id="enableuser" placement="left-start" v-if="scope.row.deleteflag"> <el-button size="mini" type="primary" icon="el-icon-unlock" circle @click="enableuser(scope.row)"></el-button> </el-tooltip> </template> </el-table-column>
此时,document.getelementsbyclassname方法找不到class中有permissions的对象。vue与jquery的思想有很大不同。
综上所述问题,因此需要对方案进行优化改进。
2、新的方案
借鉴v-permission的vue指令方法,并且不再使用可见属性,而是移除无权限节点的dom元素。具体方案如下:
2.1、定义v-permissions指令
为区别"v-permission"及":v-permission",这里使用v-permissions。
创建/src/common/permissions.js文件,代码如下:
import treenode from './treenode.js' /** * 对使用v-permissions指令的dom元素,检查权限;如果无权限,则移除该元素 * 绑定参数形式: * v-permissions 无参数值形式,表示不指定上级节点的domkey * v-permissions="''",设置空串,注意里面需要包含单引号,也是无参数 * v-permissions="'somesuperdomkey'",设置上级节点的domkey,注意里面需要包含单引号 * @param {element对象} el * @param {绑定参数} binding */ function checkrights(el,binding){ // 确保权限树已经加载 if (treenode.rightstree == null){ let rights = localstorage.getitem('rights'); if (rights === null || rights === ''){ // 没有权限树,移除当前节点 if(el.parentnode){ el.parentnode.removechild(el); } return; } // 加载权限树 treenode.rightstree = treenode.loaddata(rights); } // 获取dom元素的id var elementid = el.id; if (elementid == undefined) { console.log("format error! without id property of the element with v-permissions:" + el); return; } // 获取上级节点的domkey //console.log(binding); var superdomkey = binding.value; var supernode = null; if(superdomkey != undefined && superdomkey != ""){ // 如果指定上级节点,先查找上级节点 supernode = treenode.lookupnodebydomkey(treenode.rightstree, superdomkey); if (supernode == null){ // 上级key未找到,设置错误 console.log("wrong superdomkey value for element:" + el); // 忽略上级节点 } } // 设置搜索的根节点 var rootnode = null; if (supernode == null){ // 上级节点为空 rootnode = treenode.rightstree; } else{ rootnode = supernode; } // 查找当前节点 var node = null; node = treenode.lookupnodebydomkey(rootnode, elementid); if(node == null){ // 如果未在权限树中找到此节点,表示没有权限 // 移除此element对象 if(el.parentnode){ el.parentnode.removechild(el); } } } export default { inserted(el,binding) { checkrights(el,binding) }, update(el,binding) { checkrights(el,binding) } }
2.2、注册该指令
在main.js中,注册该指令。main.js代码如下:
// the vue build version to load with the `import` command // (runtime-only or standalone) has been set in webpack.base.conf with an alias. import vue from 'vue' import app from './app' import router from './router' import store from './store' import elementui from 'element-ui' import 'element-ui/lib/theme-chalk/index.css' import md5 from 'js-md5'; import axios from 'axios' import vueaxios from 'vue-axios' import treenode_ from './common/treenode.js' import commonfuncs_ from './common/commonfuncs.js' import instance_ from './api/index.js' import global_ from './common/global.js' import permissions from './common/permissions.js' vue.use(vueaxios,axios) vue.prototype.$md5 = md5 vue.prototype.treenode = treenode_ vue.prototype.$baseurl = process.env.api_root vue.prototype.instance = instance_ //axios实例 vue.prototype.global = global_ vue.prototype.commonfuncs = commonfuncs_ vue.use(elementui) vue.config.productiontip = false // 注册一个全局自定义指令 v-permissions vue.directive('permissions', permissions) /* eslint-disable no-new */ var vue = new vue({ el: '#app', router, store, components: { app }, template: '<app/>', render:h=>h(app) }) export default vue
2.3、删除checkrights方法
在commonfuncs.js文件中,删除checkrights方法代码,因为不再调用此方法了。
原来在app.vue和其它vue文件中调用checkrights方法的代码,也删除。
2.4、模板文件示例
dom元素如果需要进行权限控制,则使用v-permissions指令,同时还要用id属性,匹配约定的domkey。这样就行了,无需编写其它javascript代码。
2.4.1、app.vue文件
app.vue文件,代码如下:
<template> <div id="app"> <!-- 其他页 --> <el-container style="min-height: calc(100% - 50px);" v-if="$route.meta.keepalive"> <!-- 无头部导航栏 --> <el-container> <el-aside :style="{width:collpasewidth}"> <!-- 侧边栏 --> <keep-alive> <left></left> </keep-alive> </el-aside> <el-main> <!-- body --> <router-view></router-view> </el-main> </el-container> <!-- 无足部 --> </el-container> <!-- 登录页 --> <router-view v-if="!$route.meta.keepalive"></router-view> </div> </template> <script> import left from './components/left.vue' export default { name: 'app', components: { left: left }, data(){ return { collpasewidth:200 } }, mounted:function(){ }, methods: { } } </script> <style> #app { font-family: 'avenir', helvetica, arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 20px; } .el-main { padding-top : 0px; } </style>
现在不需要任何权限控制的代码了。
2.4.2、left.vue文件
侧边导航栏组件left.vue文件,代码如下:
<template> <div class="left-sidebar"> <el-menu :default-openeds="['1']" style="background:#f0f6f6;"> <el-submenu index="1"> <el-menu-item-group > <el-menu-item index="1-1"> <router-link class="menu" tag="li" to="/home" exact-active-class="true" id="homemenu" active-class="_active"> <i class="el-icon-s-home"></i>首页 </router-link> </el-menu-item> <el-submenu index="1-2" v-permissions id="usermanagementmain"> <template slot="title" ><i class="el-icon-user-solid"></i>用户管理</template> <el-menu-item index="1-2-1" v-permissions id="usermanagementsub"> <router-link class="menu" tag="li" to="/usermanagement"> <i class="el-icon-user"></i>用户管理 </router-link> </el-menu-item> <el-menu-item index="1-2-2" v-permissions id="changepassword"> <router-link class="menu"tag="li" to="/changepassword"> <i class="el-icon-key"></i>修改密码 </router-link> </el-menu-item> </el-submenu> <el-menu-item index="1-3" v-permissions id="questionnairemanagement"> <router-link class="menu" tag="li" to="/questionnairemanagement"> <i class="el-icon-document"></i>问卷内容管理 </router-link> </el-menu-item> <el-submenu index="1-4" v-permissions id="issuemanagementmain"> <template slot="title"><i class="el-icon-message"></i>问卷发布管理</template> <el-menu-item index="1-4-1" v-permissions id="issuemanagementsub"> <router-link class="menu" tag="li" to="/issuemanagement"> <i class="el-icon-phone"></i>发布问卷查询 </router-link> </el-menu-item> <el-menu-item index="1-4-2" v-permissions id="issuetaskquery"> <router-link class="menu" tag="li" to="/issuetaskquery"> <i class="el-icon-tickets"></i>发布任务查询 </router-link> </el-menu-item> </el-submenu> <el-menu-item index="1-5" v-permissions id="answersheetmanagement"> <router-link class="menu" tag="li" to="/answersheetmanagement"> <i class="el-icon-receiving"></i>答卷管理 </router-link> </el-menu-item> </el-menu-item-group> </el-submenu> </el-menu> </div> </template> <style> /* 去掉右边框 */ .el-menu { border-right: none; } .el-submenu { background-color: rgb(231, 235, 220) ; } </style>
注意,需要权限控制的dom元素,都有v-permissions,并且有id的值。
2.4.3、业务模块vue模板示例
业务模块vue模板示例,代码如下:
<template> <div id="contentwrapper"> <el-form ref="form" :model="formdata" label-width="80px"> <el-card> <el-row> <!--占整行--> <el-col :span="24"> <h5 class="heading" align=left>用户管理 / 用户管理</h5> <!-- 分隔线 --> <el-divider></el-divider> </el-col> </el-row> <el-row> <el-col align="left" :span="6"> <el-button type="primary" v-permissions="'usermanagementsub'" id="adduser" size="small" @click="adduser"> <i class="el-icon-circle-plus"></i>添加用户 </el-button> </el-col> <!-- 查询条件 --> <el-col align="left" :span="6"> <el-form-item label="用户类型:" label-width="100px"> <el-select v-model="formdata.usertypelabel" size="small" @change="selectusertype"> <el-option v-for="(item,index) in usertypelist" :key="index" :label="item.itemvalue" :value="item" /> </el-select> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="用户状态:" label-width="100px"> <el-select v-model="formdata.userstatuslabel" size="small" @change="selectuserstatus"> <el-option v-for="item in userstatuslist" :key="item.itemkey" :label="item.itemvalue" :value="item" /> </el-select> </el-form-item> </el-col> <el-col align="right" :span="6"> <el-button type="primary" v-permissions="'usermanagementsub'" id="queryuser" size="small" @click="queryusers"> <i class="el-icon-search"></i>查询 </el-button> </el-col> </el-row> <!-- 用户列表数据 --> <el-table :data="userinfolist" border stripe :row-style="{height:'30px'}" :cell-style="{padding:'0px','text-align':'center'}" style="font-size: 10px" :header-cell-style="{'text-align':'center'}"> <el-table-column label="用户id" width="60px" prop="userid"></el-table-column> <el-table-column label="用户类型" width="100px" prop="usertype"> <template slot-scope="scope"> <span v-if="usertypemap.get(scope.row.usertype) != null"> {{usertypemap.get(scope.row.usertype).itemvalue}} </span> </template> </el-table-column> <el-table-column label="登录名" width="100px" prop="loginname"></el-table-column> <el-table-column label="真实名称" width="80px" prop="username"></el-table-column> <el-table-column label="手机号码" width="100px" prop="phonenumber"></el-table-column> <el-table-column label="email" prop="email" width="160px"></el-table-column> <el-table-column label="性别" width="60px" prop="gender"> <template slot-scope="scope"> <span v-if="gendermap.get(scope.row.gender) != null"> {{gendermap.get(scope.row.gender).itemvalue}} </span> </template> </el-table-column> <el-table-column label="部门" width="100px" prop="deptid"> <template slot-scope="scope"> <span v-if="deptmap.get(scope.row.deptid) != null"> {{deptmap.get(scope.row.deptid).itemvalue}} </span> </template> </el-table-column> <el-table-column label="状态" width="60px" prop="deleteflag"> <template slot-scope="scope"> <span v-if="userstatusmap.get(scope.row.deleteflag) != null"> {{userstatusmap.get(scope.row.deleteflag).itemvalue}} </span> </template> </el-table-column> <el-table-column label="角色" width="100px" prop="roles" :formatter="rolesformatter"></el-table-column> <el-table-column label="操作"> <template slot-scope="scope"> <el-tooltip class="item" effect="dark" content="编辑" v-permissions="'usermanagementsub'" id="edituser" placement="left-start"> <el-button size="mini" type="primary" icon="el-icon-edit" circle @click="edituser(scope.row)"></el-button> </el-tooltip> <el-tooltip class="item" effect="dark" content="禁用" v-permissions="'usermanagementsub'" id="disableuser" placement="left-start" v-if="!scope.row.deleteflag"> <el-button size="mini" type="primary" icon="el-icon-lock" circle @click="disableuser(scope.row)"></el-button> </el-tooltip> <el-tooltip class="item" effect="dark" content="启用" v-permissions="'usermanagementsub'" id="enableuser" placement="left-start" v-if="scope.row.deleteflag"> <el-button size="mini" type="primary" icon="el-icon-unlock" circle @click="enableuser(scope.row)"></el-button> </el-tooltip> </template> </el-table-column> </el-table> <!-- 分页区域 --> <el-pagination @size-change="handlesizechange" @current-change="handlecurrentchange" :current-page="formdata.pageinfo.pagenum" :page-sizes="[5, 10, 15, 20]" :page-size="formdata.pageinfo.pagesize" layout="total, sizes, prev, pager, next, jumper" :total="formdata.pageinfo.total" background> </el-pagination> </el-card> </el-form> <!-- 新增、编辑 --> <add-or-edit-user v-if="editvisible" ref="addoredituser"></add-or-edit-user> </div> </template>
权限控制项,都设置了:v-permissions="'usermanagementsub'",指明了上级节点的domkey为usermanagementsub。无需其它javascipt调用控制代码。
还有,id="disableuser"和id="enableuser"的元素,都使用了v-if指令,现在也不会有可见属性的冲突。
2.5、方案总结
利用vue的指令,使得权限控制表述更为简洁,无需其它javascript脚本,且解决了class被屏蔽的问题。另外,使用移除元素的方法,避免了与v-if的可见属性的冲突。
经测试,达到了权限控制的效果。
如果权限动态发生改变,只需刷新页面,将重构页面,无需担心移除的节点彻底消失。