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的可見屬性的沖突。
經測試,達到了權限控制的效果。
如果權限動態發生改變,只需刷新頁面,將重構頁面,無需擔心移除的節點徹底消失。