Easyui中的Tree组件使用频率颇高,经常遇到的需求如下:
1、在树形结构上,只有叶子节点才能被选中,其他节点不能被选中;
2、在叶子节点上右键出现浮动菜单实现新增、删除、修改操作;
3、在非叶子节点上右键出现浮动菜单实现新增、修改操作。
------------------------------------------------------------------------------------------------------------------
实现方法如下:
1、搭建测试环境(可以参考前文:【原】无脑操作:IDEA + maven + SpringBoot + JPA + EasyUI实现CRUD及分页)
2、建库建表
DROP TABLE biz_department;
CREATE TABLE biz_department
(
departmentid INT AUTO_INCREMENT PRIMARY KEY COMMENT '部门编号',
departmentpid INT NOT NULL COMMENT '部门父编号',
departmentname VARCHAR(10) NOT NULL COMMENT '部门名称'
) ENGINE=INNODB COMMENT='部门信息';
INSERT INTO biz_department VALUES
(NULL, 0, '总部'),
(NULL, 1, '上海分公司'), (NULL, 1, '安徽分公司'),
(NULL, 3, '合肥办事处'), (NULL, 3, '铜陵办事处');
3、创建实体类 Department.java
@Entity
@Table(name = "biz_department")
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "departmentid")
private Integer departmentid;
@Column(name = "departmentpid")
private Integer departmentpid;
@Column(name = "departmentname")
private String departmentname; public Department() {
} public Department(Integer departmentpid, String departmentname) {
this.departmentpid = departmentpid;
this.departmentname = departmentname;
} public Integer getDepartmentid() {
return departmentid;
} public void setDepartmentid(Integer departmentid) {
this.departmentid = departmentid;
} public Integer getDepartmentpid() {
return departmentpid;
} public void setDepartmentpid(Integer departmentpid) {
this.departmentpid = departmentpid;
} public String getDepartmentname() {
return departmentname;
} public void setDepartmentname(String departmentname) {
this.departmentname = departmentname;
}
}
4、编写DAO接口 DepartmentDao.java
/**
* 因为需要使用分页和条件查询,所以从JpaRepository接口 和 JpaSpecificationExecutor接口继承
*/
public interface DepartmentDao extends JpaRepository<Department, Integer>, JpaSpecificationExecutor<Department> { }
5、编写工具类 TreeNode.java 和 TreeUtil.java
/**
* EasyUI Tree的封装类
*/
public class TreeNode {
private Integer id; // 节点的 id
private Integer parentId; // 父节点id,java生成树时使用
private String text; // 显示的节点文字。
private String iconCls; // 节点图标样式 "iconCls":"icon-save", "iconCls":"icon-ok", 等
private String state; // 节点状态, 'open' 或 'closed',默认是 'open'。当设为 'closed' 时,此节点有子节点,并且将从远程站点加载它们。
private String flag; // 节点类型
private Integer trueId; // 应用系统真实 id
private boolean checked; // 指示节点是否被选中。
private LinkedHashMap<?, ?> attributes; // 给一个节点追加的自定义属性。
private List<TreeNode> children; // 定义了一些子节点的节点数组。
private String url; // 扩展属性url public Integer getTrueId() {
return trueId;
} public void setTrueId(Integer trueId) {
this.trueId = trueId;
} public String getFlag() {
return flag;
} public void setFlag(String flag) {
this.flag = flag;
} public Integer getId() {
return id;
} public void setId(Integer id) {
this.id = id;
} public Integer getParentId() {
return parentId;
} public void setParentId(Integer parentId) {
this.parentId = parentId;
} public String getText() {
return text;
} public void setText(String text) {
this.text = text;
} public String getIconCls() {
return iconCls;
} public void setIconCls(String iconCls) {
this.iconCls = iconCls;
} public String getState() {
return state;
} public void setState(String state) {
this.state = state;
} public boolean isChecked() {
return checked;
} public void setChecked(boolean checked) {
this.checked = checked;
} public LinkedHashMap<?, ?> getAttributes() {
return attributes;
} public void setAttributes(LinkedHashMap<?, ?> attributes) {
this.attributes = attributes;
} public List<TreeNode> getChildren() {
return children;
} public void setChildren(List<TreeNode> children) {
this.children = children;
} public String getUrl() {
return url;
} public void setUrl(String url) {
this.url = url;
}
}
/**
* 树工具类
*/
public class TreeUtil {
/**
* Tree装配方法
*
* @param tempTreeNodes
* @param treeNodes
* @return
*/
public static List<TreeNode> Assemble(List<TreeNode> tempTreeNodes, List<TreeNode> treeNodes) {
if (tempTreeNodes != null) {
Map<Integer, TreeNode> map = new LinkedHashMap<>();
for (TreeNode tn : tempTreeNodes) {
map.put(tn.getId(), tn);
} TreeNode treeNode;
TreeNode pTreeNode;
for (Integer id : map.keySet()) {
treeNode = map.get(id);
if (treeNode.getParentId() == 0) {
treeNodes.add(treeNode);
} else {
pTreeNode = map.get(treeNode.getParentId());
List<TreeNode> children = pTreeNode.getChildren();
if (children != null) {
children.add(treeNode);
} else {
children = new ArrayList();
children.add(treeNode);
pTreeNode.setChildren(children);
}
}
}
} return treeNodes;
}
}
6、规划控制器 DepartmentController.java
@Controller
@RequestMapping("/department")
public class DepartmentController {
@Autowired
private DepartmentDao departmentDao; @RequestMapping("/view")
public String view() {
// 跳转至【资源管理】页面
return "department";
} @RequestMapping("/tree")
@ResponseBody
public String tree() {
List<Department> list = departmentDao.findAll();
List<TreeNode> tempTreeNodes = new ArrayList();
List<TreeNode> treeNodes = new ArrayList(); // 组装Easyui的Tree必须要有id、parentId、text属性,转换之
for (Department department : list) {
TreeNode tempTreeNode = new TreeNode();
tempTreeNode.setId(department.getDepartmentid());
tempTreeNode.setParentId(department.getDepartmentpid());
tempTreeNode.setText(department.getDepartmentname());
tempTreeNodes.add(tempTreeNode);
} return JSONObject.toJSON(TreeUtil.Assemble(tempTreeNodes, treeNodes)).toString();
} @RequestMapping("/saveNode")
@ResponseBody
public Map<String, Object> saveNode(Integer departmentpid, String departmentname) {
Department model = new Department();
model.setDepartmentpid(departmentpid);
model.setDepartmentname(departmentname); Map<String, Object> resultMap = new HashMap<>();
departmentDao.save(model);
resultMap.put("success", true);
return resultMap;
} @RequestMapping("/updateNode")
@ResponseBody
public Map<String, Object> updateNode(Department model) {
Map<String, Object> resultMap = new HashMap<>();
departmentDao.save(model);
resultMap.put("success", true);
return resultMap;
} @RequestMapping("/deleteNode")
@ResponseBody
public Map<String, Object> deleteNode(Integer departmentid) {
Map<String, Object> resultMap = new HashMap<>();
departmentDao.deleteById(departmentid);
resultMap.put("success", true);
return resultMap;
}
}
7、编写前端代码
HTML页面:department.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试Tree功能</title>
<link rel="stylesheet" type="text/css" href="../easyui/themes/default/easyui.css">
<link rel="stylesheet" type="text/css" href="../easyui/themes/icon.css">
<script type="text/javascript" src="../easyui/jquery.min.js"></script>
<script type="text/javascript" src="../easyui/jquery.easyui.min.js"></script>
<script type="text/javascript" src="../easyui/locale/easyui-lang-zh_CN.js"></script>
<script type="text/javascript" src="../biz/department.js"></script>
</head>
<body>
<!-- 部门树 -->
<ul id="deptTree" class="easyui-tree"></ul>
<!-- 叶子节点右键菜单 -->
<div id="leaf" class="easyui-menu" style="width: 120px;">
<div onclick="addNode()" iconcls="icon-add">新增节点</div>
<div onclick="removeNode()" iconcls="icon-remove">删除节点</div>
<div onclick="updateNode()" iconcls="icon-edit">编辑节点</div>
</div>
<!-- 非叶子节点右键菜单 -->
<div id="parentNode" class="easyui-menu" style="width: 120px;">
<div onclick="addNode()" iconcls="icon-add">新增节点</div>
<div onclick="updateNode()" iconcls="icon-edit">编辑节点</div>
</div>
<!-- 节点内容对话框 -->
<div id="info" class="easyui-dialog" style="width:300px; height: 120px;" closed=true>
<form id="treefrm" method="post">
<input type="hidden" name="departmentid">
<table style="margin: auto;" cellspacing="10">
<tr>
<td>部门名称</td>
<td><input class="easyui-textbox" name="departmentname" value="" data-options="required:true"></td>
</tr>
</table>
<div style="text-align: center; bottom: 15px; margin-top: 10px;">
<a id="btnSave" class="easyui-linkbutton"
data-options="iconCls:'icon-save'">保存</a>
<a id="btnCancel" class="easyui-linkbutton"
data-options="iconCls:'icon-cancel'">取消</a>
</div>
</form>
</div>
</body>
</html>
对应JS文件:department.js
// 记录添加还是修改
var flag;
// 临时存储选中节点数据
var tempNode; // 页面加载
$(function () {
// 菜单树绑定数据
$('#deptTree').tree({
url: '/department/tree',
animate: true,
lines: true,
onBeforeSelect: function (node) {
// onBeforeSelect事件:节点被选中前触发,返回false则取消选择动作
if (!$(this).tree('isLeaf', node.target)) {
// 不是叶子节点,则不能选中
return false;
}
},
onClick: function (node) {
// alert(node.target.innerText);
},
onContextMenu: function (e, node) {
// 记录选中的节点,为后续增删改操作提供节点数据
tempNode = node; // 阻止右键默认事件
e.preventDefault(); // 判断该结点有没有父结点
var root = $(this).tree('getParent', node.target);
// 没有父节点则为根结点,可以新增、编辑,不可以删除
if (root == null) {
// 如果是根节点,则可以新增、编辑,不可以删除
$('#parentNode').menu('show', {
left: e.pageX,
top: e.pageY
});
} if ($(this).tree('isLeaf', node.target)) {
// 如果是叶子节点,则可以新增、编辑和删除
$('#leaf').menu('show', {
left: e.pageX,
top: e.pageY
});
} else {
// 如果不是叶子节点,则可以新增、编辑,不可以删除
$('#parentNode').menu('show', {
left: e.pageX,
top: e.pageY
});
}
}
}); // 保存按钮押下处理
$('#btnSave').click(function () {
var tempdata, tempurl, tempmsg; if (flag == 'add') {
tempurl = 'saveNode';
tempmsg = '添加成功!';
tempdata = {
departmentpid: tempNode.id,
departmentname: $('#treefrm').find('input[name=departmentname]').val()
};
} else if (flag == 'edit') {
tempurl = 'updateNode';
tempmsg = '编辑成功!';
tempdata = {
departmentid: $('#treefrm').find('input[name=departmentid]').val(),
departmentpid: $('#deptTree').tree('getParent', tempNode.target).id,
departmentname: $('#treefrm').find('input[name=departmentname]').val()
};
} $.ajax({
type: 'post',
async: true,
url: tempurl,
data: tempdata,
dataType: 'json',
success: function (result) {
// 树重新加载
$('#deptTree').tree('reload'); $.messager.show({
title: '提示信息',
msg: tempmsg
});
},
error: function (result) {
// 请求失败时执行该函数
$.messager.show({
title: '错误信息',
msg: result.msg
});
}
}); $('#treefrm').form('clear');
$('#info').dialog('close');
}); // 取消按钮押下处理
$('#btnCancel').click(function () {
$('#treefrm').form('clear');
$('#info').dialog('close');
});
}); // 新增节点
var addNode = function () {
flag = 'add';
// 清空表单数据
$('#treefrm').form('clear');
// 打开dialog
$('#info').dialog('open').dialog('setTitle', '新增');
}; // 编辑节点
var updateNode = function () {
flag = 'edit';
// 清空表单数据
$('#treefrm').form('clear');
$('#treefrm').form('load', {
departmentid: tempNode.id,
departmentname: tempNode.text
});
// 打开dialog
$('#info').dialog('open').dialog('setTitle', '编辑');
}; // 删除节点
var removeNode = function () {
// 前台删除
$('#deptTree').tree('remove', tempNode.target); // 后台删除
$.ajax({
type: "post",
async: true, // 异步请求(同步请求将会锁住浏览器,用户其他操作必须等待请求完成才可以执行)
url: "deleteNode",
data: {departmentid: tempNode.id},
dataType: "json", // 返回数据形式为json
success: function (result) {
// 请求成功时执行该函数内容,result即为服务器返回的json对象
$.messager.show({
title: '提示信息',
msg: '删除成功!'
});
},
error: function (result) {
// 请求失败时执行该函数
$.messager.show({
title: '错误信息',
msg: result.msg
});
}
});
};
8、运行效果