欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-03-01 20:27:33
利用Vue的指令,使得权限控制表述更为简洁,无需其它javascript脚本,且解决了class被屏蔽的问题。另外,使用移除元素的方法,避免了与v-if的可见属性的冲突。 ......

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

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

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