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ě)組件完成上述需求。
先上效果圖:
基本可以滿(mǎn)足需求,樣式稍微比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)文章
vue前端頁(yè)面數(shù)據(jù)加載添加loading效果的實(shí)現(xiàn)
這篇文章主要介紹了vue前端頁(yè)面數(shù)據(jù)加載添加loading效果的實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07Vue如何動(dòng)態(tài)改變列表搜索出關(guān)鍵詞的字體顏色
這篇文章主要介紹了Vue如何動(dòng)態(tài)改變列表搜索出關(guān)鍵詞的字體顏色問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-10-10關(guān)于element el-input的autofocus失效的問(wèn)題及解決
這篇文章主要介紹了關(guān)于element el-input的autofocus失效的問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12