經過ssm框架項目實現EasyUI 的樹菜單的單選,複選,異步加載樹,同步加載樹和樹權限控制等功能。javascript
效果圖:
css
需求:經過SSM框架,實現EasyUI 樹菜單的單選,多選,異步加載,同步加載的功能
技術:Spring,SpringMVC,Mybatis,EasyUI
明說:使用EasyUI-Tree,必須嚴格遵照它的規則,如異步加載樹節點的 id,異步加載樹返回值的格式等。若是按照其規則來作,你會發現 EasyUI 很簡單。反之處處都是吭!
源碼:見文章底部
場景:樹菜單,在電商中很場景。筆者是在電商公司上班,類目樹菜單隨處可見。好比給廣告員設置類目級別,刊登商品選擇類目加載對應的產品規格參數等等
項目結構:
html
大部分的功能,並不是一步完成。都是從最基礎的功能開始。這裏是EasyUI-Tree 基礎結構java
<ul class="easyui-tree"> <li> <span>根目錄</span> <ul> <li data-options="state:'closed'"> <span>關閉狀態的子目錄</span> <ul> <li>ITDragon</li> <li>博客</li> </ul> </li> <li> <span>默認展開的子目錄</span> <ul> <li>歡迎</li> <li>You!</li> </ul> </li> <li>你是最棒的!</li> </ul> </li> </ul>
項目框架結構是:Spring,SpringMVC,Mybatis。 沒有其餘的額外配置,都是基礎的整合配置。這裏就不貼代碼。讀者也能夠直接從github上clone下來(sql文件也在項目中)。node
本章的主角,類目實體類 Category.javajquery
package com.itdragon.pojo; import java.util.Date; public class Category { private Integer id; private String name; private Integer isLeaf; private Integer parentId; private Date createddate; private Date updateddate; private Integer status; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name == null ? null : name.trim(); } public Integer getIsLeaf() { return isLeaf; } public void setIsLeaf(Integer isLeaf) { this.isLeaf = isLeaf; } public Integer getParentId() { return parentId; } public void setParentId(Integer parentId) { this.parentId = parentId; } public Date getCreateddate() { return createddate; } public void setCreateddate(Date createddate) { this.createddate = createddate; } public Date getUpdateddate() { return updateddate; } public void setUpdateddate(Date updateddate) { this.updateddate = updateddate; } public Integer getStatus() { return status; } public void setStatus(Integer status) { this.status = status; } }
按照EasyUI規範封裝的Tree節點實體類 EUTreeNode.javagit
package com.itdragon.common.pojo; /** * 樹的數據格式(Tree Data Format) * 每一個節點能夠包括下列屬性: * id:節點的 id,它對於加載遠程數據很重要。 * text:要顯示的節點文本。 * state:節點狀態,'open' 或 'closed',默認是 'open'。當設置爲 'closed' 時,該節點有子節點,而且將從遠程站點加載它們。 * checked:指示節點是否被選中。 * attributes:給一個節點添加的自定義屬性。 * children:定義了一些子節點的節點數組 * * 這裏先封裝經常使用的 id,text,state */ public class EUTreeNode { private long id; private long parentId; private String text; private String state; public long getId() { return id; } public void setId(long id) { this.id = id; } public long getParentId() { return parentId; } public void setParentId(long parentId) { this.parentId = parentId; } public String getText() { return text; } public void setText(String text) { this.text = text; } public String getState() { return state; } public void setState(String state) { this.state = state; } }
說明:
① Category.java 屬性 createdDate 和 updatedDate 的類型都是java.util.Date。實際上也能夠是 String 類型,這樣能夠在顯示(日期格式化),排序,篩選時減小不少工做量。
② 這裏的 Category.java,CategoryExample.java,CategoryMapper.java,CategoryMapper.xml 是經過 Mybatis 提供的逆向工程自動生成的。文章底部會提供連接。github
提供查詢類目的接口 CategoryService.java 感受怪怪的 -.-||web
package com.itdragon.service; import java.util.List; import com.itdragon.common.pojo.EUTreeNode; public interface CategoryService { /** * 經過父節點,異步加載樹菜單 * @param parentId */ List<EUTreeNode> getCategoryList(int parentId); /** * 一次所有加載全部樹節點 */ List<EUTreeNode> getCategoryList(); }
類目接口的實現類 CategoryServiceImpl.javaspring
package com.itdragon.service.impl; import java.util.ArrayList; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.itdragon.common.pojo.EUTreeNode; import com.itdragon.mapper.CategoryMapper; import com.itdragon.pojo.Category; import com.itdragon.pojo.CategoryExample; import com.itdragon.pojo.CategoryExample.Criteria; import com.itdragon.service.CategoryService; @Service public class CategoryServiceImpl implements CategoryService { @Autowired private CategoryMapper categoryMapper; @Override public List<EUTreeNode> getCategoryList(int parentId) { // 1. 建立查詢條件 CategoryExample example = new CategoryExample(); Criteria criteria = example.createCriteria(); criteria.andParentIdEqualTo(parentId); // 查詢父節點下的全部子節點 criteria.andStatusEqualTo(0); // 查詢未刪除狀態的菜單 // TODO 權限攔截 // 2. 根據條件查詢 List<Category> list = categoryMapper.selectByExample(example); List<EUTreeNode> resultList = new ArrayList<>(); // 3. 把列表轉換成 EasyUI Tree 須要的json格式 for (Category category : list) { EUTreeNode node = new EUTreeNode(); node.setId(category.getId()); node.setText(category.getName()); node.setState(category.getIsLeaf() == 1?"open":"closed"); resultList.add(node); } // 4. 返回結果 return resultList; } @Override public List<EUTreeNode> getCategoryList() { // 1. 建立查詢條件 CategoryExample example = new CategoryExample(); Criteria criteria = example.createCriteria(); criteria.andStatusEqualTo(0); // 查詢未刪除狀態的菜單 // TODO 權限攔截 // 2. 根據條件查詢 List<Category> list = categoryMapper.selectByExample(example); List<EUTreeNode> resultList = new ArrayList<>(); // 3. 把列表轉換成 EasyUI Tree 須要的json格式 for (Category category : list) { EUTreeNode node = new EUTreeNode(); node.setId(category.getId()); node.setText(category.getName()); node.setState(category.getIsLeaf() == 1?"open":"closed"); node.setParentId(category.getParentId()); resultList.add(node); } // 4. 返回結果 return resultList; } }
說明:樹菜單的權限攔截,並無提供代碼,是考慮到涉及其餘實體類。其實有了思路,其餘的都好說..................好吧!我認可本身懶=。=
用於頁面跳轉的 PageController.java
package com.itdragon.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class PageController { @RequestMapping("/") public String showIndex() { return "tree"; } @RequestMapping("/{page}") public String showpage(@PathVariable String page) { return page; } }
負責加載類目樹菜單的 CategoryController.java
package com.itdragon.controller; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import com.itdragon.common.pojo.EUTreeNode; import com.itdragon.service.CategoryService; @Controller @RequestMapping("/category") public class CategoryController { @Autowired private CategoryService categoryService; /** * http://www.jeasyui.net/plugins/185.html * 當展開一個關閉的節點時,若是該節點沒有子節點加載,它將經過上面定義的 URL 向服務器發送節點的 id 值做爲名爲 'id' 的 http 請求參數,以便檢索子節點。 * 因此這裏的參數value必須是id,如果其餘值則接收不到。缺省值是0,表示初始化一級菜單。 * * @param parentId * @return */ @RequestMapping("/async") @ResponseBody private List<EUTreeNode> getAsyncCatList(@RequestParam(value="id",defaultValue="0") int parentId) { List<EUTreeNode> results = categoryService.getCategoryList(parentId); return results; } @RequestMapping("/sync") @ResponseBody private List<EUTreeNode> getSyncCatList() { List<EUTreeNode> results = categoryService.getCategoryList(); return results; } }
說明:這裏的@RequestParam(value="id",defaultValue="0"),value值必須是id,不能是其餘值。
演示EasyUI-Tree 類目樹菜單的 tree.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>EasyUI-Tree</title> <link rel="stylesheet" type="text/css" href="js/jquery-easyui-1.4.1/themes/default/easyui.css" /> <link rel="stylesheet" type="text/css" href="js/jquery-easyui-1.4.1/themes/icon.css" /> <script type="text/javascript" src="js/jquery-easyui-1.4.1/jquery.min.js"></script> <script type="text/javascript" src="js/jquery-easyui-1.4.1/jquery.easyui.min.js"></script> <script type="text/javascript" src="js/jquery-easyui-1.4.1/locale/easyui-lang-zh_CN.js"></script> </head> <body class="easyui-layout"> <div data-options="region:'west',title:'EasyUI 樹菜單',split:true" style="width:205px;"> <ul id="menu" class="easyui-tree" style="margin-top: 10px;margin-left: 5px;"> <li> <span>EasyUI</span> <ul> <li>靜態樹</li> <li>結構爲ul li 標籤</li> <li>ul定義class爲easyui-tree</li> </ul> </li> <li> <span>本章知識點</span> <ul> <li>建立靜態樹菜單</li> <li>建立異步樹菜單</li> <li>建立異步樹多選菜單</li> <li>樹菜單權限管理</li> </ul> </li> </ul> </div> <div id="content" region="center" title="ITDragon博客" style="padding:5px;"> <span> <h3>建立靜態樹菜單</h3> <ul id="" class="easyui-tree"> <li> <span>父節點</span> <ul> <li>子節點一</li> <li>子節點二</li> </ul> </li> </ul> <h4>使用方法</h4> <p>ul 標籤 定義 class="easyui-tree"</p> <a href="http://www.jeasyui.net/plugins/185.html">EasyUI 樹菜單教程 </a> <br/> <a href="http://www.jeasyui.net/plugins/180.html">EasyUI 窗口教程 </a> </span> <hr/> <span> <h3>建立異步樹菜單</h3> <a href="javascript:void(0)" class="easyui-linkbutton selectCategory">建立異步樹菜單</a> <input type="hidden" name="categoryId" style="width: 280px;"></input> <br/> <h4>建立思路</h4> <p>一:初始加載一級類目菜單,經過點擊一級類目菜單再查詢其子節點菜單</p> <p>二:類目表設計實例,一級類目的parentId爲0,子節點類目的parentId是父節點類目的id</p> <p>三:返回數據結構類型只要知足EasyUI的規範便可</p> </span> <hr/> <span> <h3>建立異步樹多選菜單</h3> <a href="javascript:void(0)" class="easyui-linkbutton selectMoreCategory">建立異步樹多選菜單</a> <input type="hidden" name="categoryIds" style="width: 280px;"></input> <br/> <h4>注意</h4> <p>若採用異步樹加載菜單,會出現勾選父節點。保存後只打印了父節點信息,未打印子節點(由於子節點都沒有加載)</p> <h4>解決思路</h4> <p>讓業務每一個都點開(不合實際);本章節採用同步加載的方式;大家有沒有更好的辦法?</p> <a href="http://www.jeasyui.net/tutorial/57.html"> EasyUI 採用同步加載教程 </a> </span> <hr/> <span> <h3>樹菜單權限管理:</h3> <p>業務邏輯:須要一張用戶組管理表,設置當前登陸用戶所屬組。</p> <p>後臺邏輯:樹菜單表新增字段permission用來匹配用戶所屬組,說簡單點就是多了一層查詢條件。</p> </span> </div> <script type="text/javascript"> $(function(){ initAsyncCategory (); initMoreSyncCategory (); }); // 異步加載樹菜單 function initAsyncCategory (){ $(".selectCategory").each(function(i,e){ var _ele = $(e); _ele.after("<span style='margin-left:10px;'></span>"); // 避免被按鈕遮住 _ele.unbind('click').click(function(){ $("<div>").html("<ul>").window({ // 使用 javascript 建立窗口(window) width:'500', height:"450", modal:true, closed:true, iconCls:'icon-save', title:'異步樹菜單', onOpen : function(){ // 窗口打開後執行 var _win = this; $("ul",_win).tree({ url:'/category/async', // 採用異步加載樹節點,返回數據的格式要知足EasyUI Tree 的要求 animate:true, onClick:function(node){ // 樹菜單點擊後執行 if($(this).tree("isLeaf",node.target)){ // 若是該節點是葉節點就填寫到categoryId中,並關閉窗口 _ele.parent().find("[name=categoryId]").val(node.id); _ele.next().text(node.text).attr("categoryId",node.id); $(_win).window('close'); } } }); }, onClose : function(){ // 窗口關閉後執行 $(this).window("destroy"); } }).window('open'); // 使用 javascript 打開窗口(window) }); }); } // 同步加載複選樹菜單 function initMoreSyncCategory (){ $(".selectMoreCategory").each(function(i,e){ var _ele = $(e); _ele.after("<span style='margin-left:10px;'></span>"); _ele.unbind('click').click(function(){ $("<div>").html("<ul id='moreItemCat'>").window({ // 使用 javascript 建立窗口(window) width:'500', height:"450", modal:true, closed:true, iconCls:'icon-save', title:'多選樹菜單,關閉窗口後保存數據', onOpen : function(){ // 窗口打開後執行 var _win = this; $("ul",_win).tree({ url:'/category/sync', // 採用同步的方式加載全部樹節點 animate:true, checkbox:true, // js 聲明樹菜單能夠複選 loadFilter: function(rows){ return convert(rows); } }); }, onClose : function(){ // 窗口關閉後執行 var nodes = $("#moreItemCat").tree('getChecked'); var categoryIds = ''; var categoryTexts = ''; for(var i = 0; i < nodes.length; i++){ if ('' != categoryIds) { categoryIds += ','; categoryTexts += ' , '; } categoryIds += nodes[i].id; categoryTexts += nodes[i].text; } _ele.parent().find("[name=categoryIds]").val(categoryIds); _ele.next().text(categoryTexts).attr("categoryId",categoryTexts); $(this).window("destroy"); } }).window('open'); // 使用 javascript 打開窗口(window) }); }); } // 官方提供的 js 解析 json 代碼 function convert(rows){ function exists(rows, parentId){ for(var i=0; i<rows.length; i++){ if (rows[i].id == parentId) return true; } return false; } var nodes = []; for(var i=0; i<rows.length; i++){ // get the top level nodes var row = rows[i]; if (!exists(rows, row.parentId)){ nodes.push({ id:row.id, text:row.text, state:row.state }); } } var toDo = []; for(var i=0; i<nodes.length; i++){ toDo.push(nodes[i]); } while(toDo.length){ var node = toDo.shift(); // the parent node for(var i=0; i<rows.length; i++){ // get the children nodes var row = rows[i]; if (row.parentId == node.id){ var child = {id:row.id,text:row.text,state:row.state}; if (node.children){ node.children.push(child); } else { node.children = [child]; } toDo.push(child); } } } return nodes; } </script> </body> </html>
說明:
① tree.jsp 除了EasyUI-Tree 的知識點外,還涉及了一點點窗口的知識
<div id="win"></div> $('#win').window({ width:600, height:400, modal:true });
$('#win').window('open'); // open a window $('#win').window('close'); // close a window
② tree.jsp 主要包含了單選異步加載樹菜單和多選同步加載樹菜單兩大知識點,因此內容較長,請耐心閱讀。
③ 若異步加載樹菜單,支持多選,會出現子節點沒有打印的問題
源碼:
https://github.com/ITDragonBlog/daydayup/tree/master/EasyUI/EasyUI-tree
逆向工程:
https://github.com/ITDragonBlog/daydayup/tree/master/mybatis/generatorSqlmapCustom
最後,EasyUI 樹菜單到這裏就結束了,感謝你們的閱讀。以爲不錯的能夠點個贊!