zoukankan      html  css  js  c++  java
  • 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="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=" 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=" 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)
        				'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)
        				'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({
        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 树菜单到这里就结束了,感谢大家的阅读。觉得不错的可以点个赞!

  • 相关阅读:
    vs code 使用小技巧
    数组22组合
    js--arTemplate引擎
    JAVA -简要记录maven的安装与环境变量的配置
    JAVA -简要记录jdk的安装与环境变量的配置
    浅谈“复制粘贴”对于程序员的伤害
    C#中 IndexOf的使用
    C# Substring函数的总结
    C# 还原Nuget包失败的解决方法
    C# 未能找到类型或命名空间名称“XXXX”(是否缺少 using 指令或程序集引用?)解决方案
  • 原文地址:https://www.cnblogs.com/itdragon/p/7867108.html
Copyright © 2011-2022 走看看