Vue 前端权限控制的优化改进版

  • A+
所属分类:Web前端
摘要

  之前《Vue前端访问控制方案 》一文中提出,使用class=“permissions”结合元素id来标识权限控制相关的dom元素,并通过公共方法checkRights来设置dom元素的可见属性,在实际使用中存在下列问题:


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的可见属性的冲突。

  经测试,达到了权限控制的效果。

  如果权限动态发生改变,只需刷新页面,将重构页面,无需担心移除的节点彻底消失。