最近工作中在寫一個后台管理系統,由於系統同種各個模塊的頁面樣式大體一致,因此師傅決定定制一個生成頁面的功能。昨天在寫到菜單管理模塊的時候,發現通過表格展現出來的菜單數據無法直觀的體現出菜單之間的層級關系,因此就改用了Treeview樹形菜單來展示。但此時碰到了一個問題,師傅想要在樹形菜單的每一行上加上按鈕,包括新增子菜單、修改和刪除。我查了下treeview的api,並未發現有新增按鈕的操作。最后通過google找到了一個解決辦法,在后台接口中將按鈕的代碼拼接到菜單名中。下面是原網頁: https://stackoverflow.com/questions/36397655/add-buttons-to-bootstrap-treeview-node

找到解決辦法了,開始改代碼,我的treeview數據是從后台獲取來的,那么拼接數據就在后台做了:
// 給一級菜單和二級菜單使用的按鈕組 String button1 = "<div style='float:right'>" + "<button type='button' id='btnEdit' class='btn-xs btn-info' onclick='beforeUpdate()'>" + "<i class='glyphicon glyphicon-edit'></i> 修改" + "</button> " + "<button type='button' id='btnEdit' class='btn-xs btn-info' onclick='beforeInsert()'>" + "<i class='glyphicon glyphicon-plus'></i> 新增子菜單" + "</button> " + "<button type='button' id='btnEdit' class='btn-xs btn-danger' onclick='beforeDelete()'>" + "<i class='fa fa-trash'></i> 刪除" + "</button>" + "</div>"; // 給權限菜單使用的按鈕組 String button2 = "<div style='float:right'>" + "<button type='button' id='btnEdit' class='btn-xs btn-info' onclick='beforeUpdate()'>" + "<i class='glyphicon glyphicon-edit'></i> 修改" + "</button> " + "<button type='button' id='btnEdit' class='btn-xs btn-danger' onclick='beforeDelete()'>" + "<i class='fa fa-trash'></i> 刪除" + "</button>" + "</div>"; if (sysMenu.getMenuType().equals("1")) { node.setText(sysMenu.getMenuName() + button1); firstLevelList.add(node); } else if (sysMenu.getMenuType().equals("2")) { node.setText(sysMenu.getMenuName() + button1); secondLevelList.add(node); } else { node.setText(sysMenu.getMenuName() + button2); thirdLevelList.add(node); }

拼接成功,這里說明一下,樹形菜單的數據需要特定格式才能展示,因此在后台接口中要自己組裝出相應的格式,我使用如下的格式來傳遞數據:
/** * * @author sxh * @date 2019年8月20日 * @Description * */ public class TreeViewNode{ private String id; private String text; private String parentId; private List<TreeViewNode> nodes; // 用來判斷相應的菜單行的復選框是否需要被勾選,格式:{"checked", true} private Map<String, Boolean> state; }
傳遞給前端的數據格式

按鈕的問題解決了,但下面又出現了一個難題,按鈕點擊之后需要知道點擊的是哪一條數據,然后進行相應的操作。我就想可以通過treeview的點擊事件來確定具體點擊了哪一行,查閱了treeview的api,並未發現點擊事件。treeview只有選中事件和取消選中事件,但在選中和取消選中時都會都會觸發相應的操作,並且treeview會優先執行取消選中事件再執行選中事件。假如在兩個中都放上相應的方法,如果事先選中了一條A數據,那么之后再點擊B數據的話就會先取消選中A數據,這時會執行取消選中事件,並且對應的數據是A,而我們想要執行的數據B卻在A執行完之后才執行。沒辦法,只好改源碼,自己加上點擊事件。(源碼改動參考了 https://blog.csdn.net/qq775410784/article/details/89406777#commentBox,只放上被修改過的代碼)
;(function ($, window, document, undefined) { /*global jQuery, console*/ 'use strict'; var pluginName = 'treeview'; var _default = {}; _default.settings = { injectStyle: true, levels: 2, expandIcon: 'glyphicon glyphicon-plus', collapseIcon: 'glyphicon glyphicon-minus', emptyIcon: 'glyphicon', nodeIcon: '', selectedIcon: '', checkedIcon: 'glyphicon glyphicon-check', uncheckedIcon: 'glyphicon glyphicon-unchecked', color: undefined, // '#000000', backColor: undefined, // '#FFFFFF', borderColor: undefined, // '#dddddd', onhoverColor: '#F5F5F5', selectedColor: '#FFFFFF', selectedBackColor: '#428bca', searchResultColor: '#D9534F', searchResultBackColor: undefined, //'#FFFFFF', enableLinks: false, highlightSelected: true, highlightSearchResults: true, showBorder: true, showIcon: true, showCheckbox: false, showTags: false, multiSelect: false, // Event handlers onNodeChecked: undefined, onNodeCollapsed: undefined, onNodeDisabled: undefined, onNodeEnabled: undefined, onNodeExpanded: undefined, onNodeSelected: undefined, onNodeUnchecked: undefined, onNodeUnselected: undefined, onSearchComplete: undefined, onSearchCleared: undefined, /** * 給 bootstrap treeview 添加 點擊事件 定義 */ onNodeClicked: undefined }; Tree.prototype.unsubscribeEvents = function () { this.$element.off('click'); this.$element.off('nodeChecked'); this.$element.off('nodeCollapsed'); this.$element.off('nodeDisabled'); this.$element.off('nodeEnabled'); this.$element.off('nodeExpanded'); this.$element.off('nodeSelected'); this.$element.off('nodeUnchecked'); this.$element.off('nodeUnselected'); this.$element.off('searchComplete'); this.$element.off('searchCleared'); /** * 給 bootstrap treeview 添加 點擊事件 元素 */ this.$element.off('nodeClicked'); }; Tree.prototype.subscribeEvents = function () { this.unsubscribeEvents(); this.$element.on('click', $.proxy(this.clickHandler, this)); if (typeof (this.options.onNodeChecked) === 'function') { this.$element.on('nodeChecked', this.options.onNodeChecked); } if (typeof (this.options.onNodeCollapsed) === 'function') { this.$element.on('nodeCollapsed', this.options.onNodeCollapsed); } if (typeof (this.options.onNodeDisabled) === 'function') { this.$element.on('nodeDisabled', this.options.onNodeDisabled); } if (typeof (this.options.onNodeEnabled) === 'function') { this.$element.on('nodeEnabled', this.options.onNodeEnabled); } if (typeof (this.options.onNodeExpanded) === 'function') { this.$element.on('nodeExpanded', this.options.onNodeExpanded); } if (typeof (this.options.onNodeSelected) === 'function') { this.$element.on('nodeSelected', this.options.onNodeSelected); } if (typeof (this.options.onNodeUnchecked) === 'function') { this.$element.on('nodeUnchecked', this.options.onNodeUnchecked); } if (typeof (this.options.onNodeUnselected) === 'function') { this.$element.on('nodeUnselected', this.options.onNodeUnselected); } if (typeof (this.options.onSearchComplete) === 'function') { this.$element.on('searchComplete', this.options.onSearchComplete); } if (typeof (this.options.onSearchCleared) === 'function') { this.$element.on('searchCleared', this.options.onSearchCleared); } /** * 給 bootstrap treeview 添加 點擊事件 賦值 */ if (typeof (this.options.onNodeClicked) === 'function') { this.$element.on('nodeClicked', this.options.onNodeClicked); } }; Tree.prototype.clickHandler = function (event) { if (!this.options.enableLinks) event.preventDefault(); var target = $(event.target); var node = this.findNode(target); if (!node || node.state.disabled) return; var classList = target.attr('class') ? target.attr('class').split(' ') : []; if ((classList.indexOf('expand-icon') !== -1)) { this.toggleExpandedState(node, _default.options); this.render(); } else if ((classList.indexOf('check-icon') !== -1)) { this.toggleCheckedState(node, _default.options); this.render(); } else { if (node.selectable) { this.toggleSelectedState(node, _default.options); } else { this.toggleExpandedState(node, _default.options); } this.render(); } /** * clickHandler -- 最后執行點擊事件 */ this.onClicked(node, _default.options); }; /** * 給 bootstrap treeview 添加 點擊事件 * 依賴於clickHandler 方法。最后執行 */ Tree.prototype.onClicked = function (node, options) { if (!node) return; if (!options.silent) { this.$element.trigger('nodeClicked', $.extend(true, {}, node)); } };
源碼改動完畢,下面展示調用:
var clickEvent; $('#treeview-checkable').treeview({ data: getMenus(), // 加載的數據源 showIcon: false, showCheckbox: true, // 展示復選框 levels: 3, onNodeClicked: function(event, node) { console.log("click event"); switch(clickEvent){ case "deleteEvent": deleteMenu(node); break; case "insertEvent": insertMenu(node); break; case "updateEvent": updateMenu(node); break; default: break; } }, }); // 刪除 function beforeDelete () { clickEvent = "deleteEvent"; } function beforeInsert() { clickEvent = "insertEvent"; }; // 處理修改事件 function beforeUpdate() { clickEvent = "updateEvent"; };
用戶在點擊按鈕時,首先執行按鈕的onclick事件,這時候記錄一個標識,之后才會執行treeview的點擊事件,根據標識執行指定的事件,並傳遞相應的數據。
