EasyUI 树菜单
EasyUI 树菜单
通过ssm框架项目实现EasyUI 的树菜单的单选,复选,异步加载树,同步加载树和树权限控制等功能。
本章知识点
效果图:

需求:通过SSM框架,实现EasyUI 树菜单的单选,多选,异步加载,同步加载的功能
技术:Spring,SpringMVC,Mybatis,EasyUI
明说:使用EasyUI-Tree,必须严格遵守它的规则,如异步加载树节点的 id,异步加载树返回值的格式等。如果按照其规则来做,你会发现 EasyUI 很简单。反之到处都是吭!
源码:见文章底部
场景:树菜单,在电商中很场景。笔者是在电商公司上班,类目树菜单随处可见。比如给广告员设置类目级别,刊登商品选择类目加载对应的产品规格参数等等
项目结构:

初始化静态树
大部分的功能,并非一步完成。都是从最基础的功能开始。这里是EasyUI-Tree 基础结构
<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>
Maven Web项目实战
项目框架结构是:Spring,SpringMVC,Mybatis。 没有其他的额外配置,都是基础的整合配置。这里就不贴代码。读者也可以直接从github上clone下来(sql文件也在项目中)。
POJO层
本章的主角,类目实体类 Category.java
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.java
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 提供的逆向工程自动生成的。文章底部会提供链接。
Service 层
提供查询类目的接口 CategoryService.java 感觉怪怪的 -.-||
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.java
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;
	}
}
说明:树菜单的权限拦截,并没有提供代码,是考虑到涉及其他实体类。其实有了思路,其他的都好说..................好吧!我承认自己懒=。=
Controller 层
用于页面跳转的 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,不能是其他值。
Views视图层
演示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 的知识点外,还涉及了一点点窗口的知识
- 使用 javascript 创建窗口(window)
<div id="win"></div>
$('#win').window({
    width:600,
    height:400,
    modal:true
});
- 打开和关闭窗口(window)
$('#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 树菜单到这里就结束了,感谢大家的阅读。觉得不错的可以点个赞!
EasyUI 树菜单的更多相关文章
- 雷林鹏分享:jQuery EasyUI 树形菜单 - 使用标记创建树形菜单
		jQuery EasyUI 树形菜单 - 使用标记创建树形菜单 一个树形菜单(Tree)可以从标记创建.easyui 树形菜单(Tree)也可以定义在 元素中.无序列表的 元素提供一个基础的树(Tre ... 
- 雷林鹏分享:jQuery EasyUI 树形菜单 - 树形菜单添加节点
		jQuery EasyUI 树形菜单 - 树形菜单添加节点 本教程向您展示如何附加节点到树形菜单(Tree).我们将创建一个包含水果和蔬菜节点的食品树,然后添加一些其他水果到已存在的水果节点. 创建食 ... 
- 雷林鹏分享:jQuery EasyUI 树形菜单 - 创建异步树形菜单
		jQuery EasyUI 树形菜单 - 创建异步树形菜单 为了创建异步的树形菜单(Tree),每一个树节点必须要有一个 'id' 属性,这个将提交回服务器去检索子节点数据. 创建树形菜单(Tree) ... 
- 雷林鹏分享:jQuery EasyUI 树形菜单 - 树形菜单拖放控制
		jQuery EasyUI 树形菜单 - 树形菜单拖放控制 当在一个应用中使用树(Tree)插件,拖拽(drag)和放置(drop)功能要求允许用户改变节点位置.启用拖拽(drag)和放置(drop) ... 
- 雷林鹏分享:jQuery EasyUI 树形菜单 - 创建带复选框的树形菜单
		jQuery EasyUI 树形菜单 - 创建带复选框的树形菜单 easyui 的树(Tree)插件允许您创建一个复选框树.如果您点击一个节点的复选框,这个点击的节点信息将向上和向下继承.例如:点击 ... 
- 雷林鹏分享:jQuery EasyUI 树形菜单 - 树形菜单加载父/子节点
		jQuery EasyUI 树形菜单 - 树形菜单加载父/子节点 通常表示一个树节点的方式就是在每一个节点存储一个 parentid. 这个也被称为邻接列表模型. 直接加载这些数据到树形菜单(Tree ... 
- 雷林鹏分享:jQuery EasyUI 树形菜单 - 创建基础树形网格
		jQuery EasyUI 树形菜单 - 创建基础树形网格 树形网格(TreeGrid)组件从数据网格(DataGrid)继承,但是允许在行之间存在父/子节点关系.许多属性继承至数据网格(DataGr ... 
- C#动态加载树菜单
		在做权限系统的时候,需要有一个树形的菜单.下图就是一个树形菜单的样式 但问题是,我们可以实现写死的树形菜单.什么是写死的?就是在前台代码中写好要加载的树形菜单是什么样子的.但是我们权限系统的要求是动态 ... 
- 雷林鹏分享:jQuery EasyUI 树形菜单 - 创建复杂树形网格
		jQuery EasyUI 树形菜单 - 创建复杂树形网格 树形网格(TreeGrid)可以展示有限空间上带有多列和复杂数据电子表格.本教程将演示如何将表格数据排列在分割的网格和多行表头中,以便组织共 ... 
随机推荐
- Node Inspector 代理实现
			本文首发于 https://github.com/whxaxes/blog/issues/9 背景 平时做 node 开发的时候,通过 node inspector 来进行断点调试是一个很常用的 de ... 
- linux 守护进程编程
			概述: Daemon运行在后台也称作"后台服务进程". 它是没有控制终端与之相连的进程.它独立于控制终端.通常周期的执行某种任务. 守护进程脱离终端是为了避免进程在执行过程中的信息 ... 
- 上海2017QCon个人分享总结
			有幸作为讲师受邀参加InfoQ在上海举办的QCon2017,不得不说,不论是从讲师还是听众的角度衡量,QCon进一步扩大了技术视野.虽然前端专题只有四场,但每一场分享都是目前的热门话题.并且Qcon的 ... 
- 自学LinkedBlockingQueue源码
			自学LinkedBlockingQueue源码 参考:http://www.jianshu.com/p/cc2281b1a6bc 本文需要关注的地方 生产者-消费者模式好处: 读取和插入操作所使用的锁 ... 
- Arduino.最小系统面包板搭建
			最早试过用万用板做过最小系统,主要用来烧录芯片 后来为了方便,用面包板也搭了一个最小系统, 但不采用杜邦线,因为飞来飞去的线太乱了 因此就有了这个简洁的版本,先上个成品图 用个烧录器就可以很方便的烧写 ... 
- C++向量(08)
			在数组生存期内,数组的大小是不会改变的.向量是一维数组的类版本,它与数组相似,其中的元素项总是连续存储的,但它和数组不同的是:向量中存储元素的多少可以在运行中根据需要动态地增长或缩小.向量是类模板,具 ... 
- windows 上rsync客户端使用方法
			1.1 获取 windows上实现rsync的软件(cwRsync) cwRsync是Windows 客户端GUI的一个包含Rsync的包装.您可以使用cwRsync快速远程文件备份和同步. 1.1. ... 
- linux的特殊符号与正则表达式
			第1章 linux的特殊符号 1.1 通配符 * {} 1.1.1 含义 方便查找文件 通配符是用来找文件名字的. 1.1.2 * 通过find 命令找以 .sh 结尾的文件,使用*替代文件名字. ... 
- 解析 C# 7中的元组类型(ValueTuple)
			System.Tuple 类型是在.NET 4.0中引入的,但是有两个明显的缺点: (1) Tuple 类型是引用类型. (2) 没有构造函数支持. 为了解决这些问题,C# 7 引入了新的语言功能以及 ... 
- IdentityServer4 登录成功后,跳转到原来页面
			IdentityServer4 登录成功后,默认会跳转到Config.Client配置的RedirectUris地址http://localhost:5003/callback.html,用于获取 T ... 
