Vue2.0權(quán)限樹(shù)組件實(shí)現(xiàn)代碼
項(xiàng)目使用的餓了么的Element-Ui,權(quán)限樹(shù)使用其樹(shù)形控件:
<el-tree :data="data" ></el-tree>

剛開(kāi)始沒(méi)有特殊需求,三級(jí)分支,效果看著還可以。但是接下來(lái)的新需求:增加頁(yè)面操作按鈕權(quán)限,即達(dá)到四級(jí)分支,同時(shí)要求四級(jí)權(quán)限布局方式為橫向,而且操作按鈕權(quán)限非固定四級(jí)樹(shù),但是樣式要求一致。這樣子就很難操作了,如果單單是四級(jí)樹(shù)為橫向,還可以調(diào)調(diào)樣式完成。本來(lái)想修改element的tree控件源碼來(lái)實(shí)現(xiàn),網(wǎng)上查了一些資料,還沒(méi)有很好的辦法生成其編譯文件。最終決定自己寫(xiě)組件完成上述需求。
先上效果圖:

基本可以滿足需求,樣式稍微比element差點(diǎn),后期再優(yōu)化。
組件代碼如下:
<template>
<li :class="[isButton, hasBorder]" style="list-style:none;">
<span @click="toggle" v-show="model.menuLevel!==1" >
<i v-if="isFolder" class="icon" :class="[open ? 'folder-open': 'folder']" style="margin-bottom: 3px;"></i>
<i v-if="!isFolder" class="icon file-text"></i>
<input type="checkbox" class="checkCls" @click.stop="selTree(model)" :id="'menu'+model.id" :class="'group'+label">
{{ model.menuName }}
</span>
<ul v-show="open" v-if="isFolder">
<tree-menu v-for="(item, index) in model.childNode" :model="item" :key="index" :menuList="menuList" :label="label" :selectKeys="selectKeys" ></tree-menu>
</ul>
</li>
</template>
<script type="text/ecmascript-6">
import $ from 'jquery'
export default {
name: 'treeMenu',
props: ['model', 'menuList', 'label', 'selectKeys'],
data () {
return {
open: true, // 默認(rèn)打開(kāi)彩單樹(shù)
selAllkeys: []
}
},
computed: {
isFolder: function () {
return this.model.childNode && this.model.childNode.length
},
isButton: function () {
if (this.model.buttonControl === '1') {
return 'btnCls'
} else {
return 'menuCls'
}
},
hasBorder: function () {
if (this.model.menuLevel === 1) {
return 'blk_border'
}
}
},
methods: {
getAllKeys () {
var keys = []
var objs = $('.group' + this.label + ':checked')
for (let i = 0; i < objs.length; i++) {
let id = objs[i].id
id = id.substring(4)
keys.push((id - 0)) // 保存選中菜單id
}
return keys
},
toggle: function () {
if (this.isFolder) {
this.open = !this.open
}
},
// 根據(jù)id獲取menu對(duì)象
getMeunById (id, allMenuList) {
var menu = {}
if (allMenuList.id === id) { // 一級(jí)菜單
menu = allMenuList
} else if (allMenuList.childNode && allMenuList.childNode.length) { // 二級(jí)菜單
for (let i = 0; i < allMenuList.childNode.length; i++) {
if (allMenuList.childNode[i].id === id) {
menu = allMenuList.childNode[i]
break
} else if (allMenuList.childNode[i].childNode && allMenuList.childNode[i].childNode.length) { // 三級(jí)
for (let j = 0; j < allMenuList.childNode[i].childNode.length; j++) {
if (allMenuList.childNode[i].childNode[j].id === id) {
menu = allMenuList.childNode[i].childNode[j]
break
}
}
}
}
}
return menu
},
// checkbox點(diǎn)擊事件
selTree (model) {
var obj = $('#menu' + model.id)[0] // checkbox DOM對(duì)象
if (obj.checked) { // 選中
// 若存在下級(jí),下級(jí)全部選中
if (model.childNode && model.childNode.length) {
this.subMenusOp(model.childNode, 1)
}
// 若存在上級(jí),確認(rèn)是否需要選中上級(jí)CheckBox
if (model.supMenuID !== 0 && model.menuLevel > 2) {
this.supMenusOp(model.supMenuID, 1)
}
} else { // 取消
// 若存在下級(jí),下級(jí)全部取消
if (model.childNode && model.childNode.length) {
this.subMenusOp(model.childNode, 0)
}
// 若存在上級(jí),確認(rèn)是否需要取消上級(jí)CheckBox
if (model.supMenuID !== 0 && model.menuLevel > 2) {
this.supMenusOp(model.supMenuID, 0)
}
}
this.getAllKeys()
},
// 下級(jí)菜單操作 flag=1為選中,flag=0為取消
subMenusOp (childNodes, flag) {
for (let i = 0; i < childNodes.length; i++) {
var menu = childNodes[i]
var id = menu.id
if (flag === 1) { // 選中
$('#menu' + id)[0].checked = true
} else { // 取消
$('#menu' + id)[0].checked = false
}
if (menu.childNode && menu.childNode.length) {
this.subMenusOp(menu.childNode, flag)
}
}
},
// 上級(jí)菜單操作(選中:flag=1,取消:flag=0)
supMenusOp (id, flag) {
var menu = this.getMeunById(id, this.menuList)
if (menu.childNode && menu.childNode.length) {
var childLength = menu.childNode.length // 直接子級(jí)個(gè)數(shù)
var selectCount = 0
for (let i = 0; i < childLength; i++) {
let id1 = menu.childNode[i].id
if ($('#menu' + id1)[0].checked) {
selectCount++
}
}
if (flag === 1) { // 選中
if (childLength === selectCount) {
$('#menu' + id)[0].checked = true
if (menu.supMenuID !== 0 && menu.menuLevel > 2) {
this.supMenusOp(menu.supMenuID, flag)
}
}
} else if (flag === 0) {
if (childLength !== selectCount) {
$('#menu' + id)[0].checked = false
if (menu.supMenuID !== 0 && menu.menuLevel > 2) {
this.supMenusOp(menu.supMenuID, flag)
}
}
}
}
},
// 計(jì)算所有下級(jí)節(jié)點(diǎn)是否全部選中,是返回true,否返回false
isAllSel (childNodes, selectKeys) {
var nodeKeys = [] // 選中的id集合
this.addKeys(childNodes, selectKeys, nodeKeys)
var allKeys = []
this.getNodesCount(childNodes, allKeys)
if (nodeKeys.length === allKeys.length) {
return true
} else {
return false
}
},
// 計(jì)算childNodes下選中的id集合
addKeys (childNodes, selectKeys, Arrs) {
for (let i = 0; i < childNodes.length; i++) {
if (selectKeys.indexOf(childNodes[i].id) >= 0) {
Arrs.push(childNodes[i].id)
}
if (childNodes[i].childNode && childNodes[i].childNode.length) {
this.addKeys(childNodes[i].childNode, selectKeys, Arrs)
}
}
},
// 計(jì)算childNodes的子級(jí)數(shù)
getNodesCount (childNodes, allKeys) {
for (let i = 0; i < childNodes.length; i++) {
allKeys.push(childNodes[i].id)
if (childNodes[i].childNode && childNodes[i].childNode.length) {
this.getNodesCount(childNodes[i].childNode, allKeys)
}
}
}
},
mounted () {
// 禁止復(fù)選框的冒泡事件
$("input[type='checkbox']").click(function (e) {
e.stopPropagation()
})
// 選中菜單使能
if (this.selectKeys instanceof Array && this.selectKeys.length > 0 && this.selectKeys.indexOf(this.model.id) >= 0) {
if (this.model.childNode && this.model.childNode.length && this.model.menuLevel !== 1) { // 包含子級(jí),一級(jí)菜單除外
// 計(jì)算所有子節(jié)點(diǎn)是否全部選中
if (this.isAllSel(this.model.childNode, this.selectKeys)) {
$('#menu' + this.model.id)[0].checked = true
}
} else {
$('#menu' + this.model.id)[0].checked = true
}
}
}
}
</script>
<style>
.blk_border{
border:1px solid #d1dbe5;
padding-bottom: 15px;
}
.blk_border ul{
padding-left: 15px;
}
ul {
list-style: none;
}
i.icon {
display: inline-block;
width: 15px;
height: 15px;
background-repeat: no-repeat;
vertical-align: middle;
}
.icon.folder {
background-image: url(../../images/close.png);
}
.icon.folder-open {
background-image: url(../../images/open.png);
}
.tree-menu li {
line-height: 1.5;
}
li.btnCls {
float: left;
margin-right: 10px;
}
li.menuCls {
clear: both;
line-height:30px;
}
.checkCls {
vertical-align: middle;
}
.el-tabs__content{
color:#48576A;
}
</style>
權(quán)限樹(shù)的數(shù)據(jù)結(jié)構(gòu)有一定要求,比element的tree控件數(shù)據(jù)結(jié)構(gòu)屬性稍多一些,否則實(shí)現(xiàn)也不會(huì)這么簡(jiǎn)單了,優(yōu)化后的權(quán)限樹(shù)數(shù)據(jù)結(jié)構(gòu)在選中菜單返回上簡(jiǎn)化了很多,也沒(méi)有用到vuex。
權(quán)限樹(shù)數(shù)據(jù)結(jié)構(gòu)為:
{
'childNode': [
{
'childNode': [
{
'icon': '',
'id': 242,
'menuLevel': 3,
'menuName': '旅游訂單',
'menuTop': 1,
'menuUrl': '/',
'buttonControl': '0',
'supMenuID': 241
},
{
'icon': '',
'id': 243,
'menuLevel': 3,
'menuName': '簽證訂單',
'menuTop': 2,
'menuUrl': '/',
'buttonControl': '0',
'supMenuID': 241
},
{
'icon': '',
'id': 244,
'menuLevel': 3,
'menuName': '出團(tuán)通知書(shū)',
'menuTop': 3,
'menuUrl': '/',
'buttonControl': '0',
'supMenuID': 241
}
],
'icon': '',
'id': 241,
'menuLevel': 2,
'menuName': '訂單管理',
'menuTop': 1,
'menuUrl': '/',
'buttonControl': '0',
'supMenuID': 240
},
{
'childNode': [
{
'icon': '',
'id': 246,
'menuLevel': 3,
'menuName': '旅游產(chǎn)品',
'menuTop': 1,
'menuUrl': '/tourProduct',
'buttonControl': '0',
'supMenuID': 245
},
{
'icon': '',
'id': 247,
'menuLevel': 3,
'menuName': '圖庫(kù)',
'menuTop': 2,
'menuUrl': '/basePicStore',
'buttonControl': '0',
'supMenuID': 245
},
{
'icon': '',
'id': 248,
'menuLevel': 3,
'menuName': '簽證產(chǎn)品',
'menuTop': 3,
'menuUrl': '/',
'buttonControl': '0',
'supMenuID': 245
}
],
'icon': '',
'id': 245,
'menuLevel': 2,
'menuName': '產(chǎn)品管理',
'menuTop': 2,
'menuUrl': '/',
'buttonControl': '0',
'supMenuID': 240
},
{
'childNode': [
{
'icon': '',
'id': 250,
'menuLevel': 3,
'menuName': '旅游廣告',
'menuTop': 1,
'menuUrl': '/',
'buttonControl': '0',
'supMenuID': 249
}
],
'icon': '',
'id': 249,
'menuLevel': 2,
'menuName': '廣告管理',
'menuTop': 3,
'menuUrl': '/',
'buttonControl': '0',
'supMenuID': 240
}
],
'icon': '',
'id': 240,
'menuLevel': 1,
'menuName': '業(yè)務(wù)中心',
'menuTop': 1,
'menuUrl': '/',
'buttonControl': '0',
'supMenuID': 0
}
實(shí)際數(shù)據(jù)為上述對(duì)象的數(shù)組。
這里主要增加了buttonControl和supMenuId,方便實(shí)現(xiàn)按鈕權(quán)限的樣式判斷和選中、取消操作的checkbox級(jí)聯(lián)操作。
引用組件代碼:
<el-tab-pane v-for="(menu, index) in theModel" :key="index" :label="menu.menuName"> <my-tree :model="menu" ref="tree" :menuList="menu" :label="index" :selectKeys="selectKeys"></my-tree> </el-tab-pane>
theModel即為權(quán)限樹(shù)數(shù)組,selectKeys為選中的權(quán)限數(shù)組集合,即id集合。
mounted()實(shí)現(xiàn)初始化操作:禁止checkbox的冒泡時(shí)間,selectKeys的賦值操作。
其實(shí)權(quán)限樹(shù)或者說(shuō)菜單樹(shù)的要點(diǎn)就在遞歸算法上,按鈕的選中或取消,都需要執(zhí)行遞歸操作。這里使用jQuery來(lái)協(xié)助操作,簡(jiǎn)化了許多事情,應(yīng)該還是數(shù)據(jù)綁定的精神沒(méi)有掌握好吧。getAllKeys()獲取checkbox為true的權(quán)限id返回。
實(shí)際獲取選中的權(quán)限菜單的數(shù)據(jù)如下圖:

總結(jié)
以上所述是小編給大家介紹的Vue2.0權(quán)限樹(shù)組件實(shí)現(xiàn)代碼,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
如何構(gòu)建 vue-ssr 項(xiàng)目的方法步驟
這篇文章主要介紹了如何構(gòu)建 vue-ssr 項(xiàng)目的方法步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
vue router 通過(guò)路由來(lái)實(shí)現(xiàn)切換頭部標(biāo)題功能
在做單頁(yè)面應(yīng)用程序時(shí),一般頁(yè)面布局頭尾兩塊都是固定在布局頁(yè)面,中間為是路由入口。這篇文章主要介紹了vue-router 通過(guò)路由來(lái)實(shí)現(xiàn)切換頭部標(biāo)題 ,需要的朋友可以參考下2019-04-04
vue 微信分享回調(diào)iOS和安卓回調(diào)出現(xiàn)錯(cuò)誤的解決
這篇文章主要介紹了vue 微信分享回調(diào)iOS和安卓回調(diào)出現(xiàn)錯(cuò)誤的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09
vue3.0 vue.config.js 配置基礎(chǔ)的路徑問(wèn)題
這篇文章主要介紹了vue3.0 vue.config.js 配置基礎(chǔ)的路徑問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10
Vue 3 中使用 Element Plus 的 `el-t
在 Vue 3 中使用 Element Plus 的 `el-table` 組件實(shí)現(xiàn)自適應(yīng)高度,你可以根據(jù)容器的高度動(dòng)態(tài)設(shè)置表格的高度,下面通過(guò)示例代碼給大家展示,感興趣的朋友一起看看吧2024-12-12
vue emit之Property or method “$$v“ i
這篇文章主要介紹了vue emit之Property or method “$$v“ is not defined的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-06-06
使用WebStorm導(dǎo)入已有Vue項(xiàng)目并運(yùn)行的詳細(xì)步驟與注意事項(xiàng)
這篇文章主要介紹了如何使用WebStorm導(dǎo)入、運(yùn)行和管理Vue項(xiàng)目,包括環(huán)境配置、Node.js和npm版本管理、項(xiàng)目依賴(lài)管理以及常見(jiàn)問(wèn)題的解決方案,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-11-11
vue2.0實(shí)現(xiàn)的tab標(biāo)簽切換效果(內(nèi)容可自定義)示例
這篇文章主要介紹了vue2.0實(shí)現(xiàn)的tab標(biāo)簽切換效果,結(jié)合實(shí)例形式分析了vue.js實(shí)現(xiàn)內(nèi)容可自定義的tab點(diǎn)擊切換功能相關(guān)操作技巧,需要的朋友可以參考下2019-02-02
vue報(bào)錯(cuò)之exports is not defined問(wèn)題的解決
這篇文章主要介紹了vue報(bào)錯(cuò)之exports is not defined問(wèn)題的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07
vue 自動(dòng)化路由實(shí)現(xiàn)代碼
這篇文章主要介紹了vue 自動(dòng)化路由實(shí)現(xiàn)代碼,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-09-09

