【原】无脑操作:EasyUI Tree实现左键只选择叶子节点、右键浮动菜单实现增删改
Easyui中的Tree组件使用频率颇高,经常遇到的需求如下:
1、在树形结构上,只有叶子节点才能被选中,其他节点不能被选中;
2、在叶子节点上右键出现浮动菜单实现新增、删除、修改操作;
3、在非叶子节点上右键出现浮动菜单实现新增、修改操作。
------------------------------------------------------------------------------------------------------------------
实现方法如下:
1、搭建测试环境(可以参考前文:【原】无脑操作:IDEA + maven + SpringBoot + JPA + EasyUI实现CRUD及分页)
2、建库建表
1 DROP TABLE biz_department;
2 CREATE TABLE biz_department
3 (
4 departmentid INT AUTO_INCREMENT PRIMARY KEY COMMENT '部门编号',
5 departmentpid INT NOT NULL COMMENT '部门父编号',
6 departmentname VARCHAR(10) NOT NULL COMMENT '部门名称'
7 ) ENGINE=INNODB COMMENT='部门信息';
8 INSERT INTO biz_department VALUES
9 (NULL, 0, '总部'),
10 (NULL, 1, '上海分公司'), (NULL, 1, '安徽分公司'),
11 (NULL, 3, '合肥办事处'), (NULL, 3, '铜陵办事处');
3、创建实体类 Department.java
1 @Entity
2 @Table(name = "biz_department")
3 public class Department {
4 @Id
5 @GeneratedValue(strategy = GenerationType.IDENTITY)
6 @Column(name = "departmentid")
7 private Integer departmentid;
8 @Column(name = "departmentpid")
9 private Integer departmentpid;
10 @Column(name = "departmentname")
11 private String departmentname;
12
13 public Department() {
14 }
15
16 public Department(Integer departmentpid, String departmentname) {
17 this.departmentpid = departmentpid;
18 this.departmentname = departmentname;
19 }
20
21 public Integer getDepartmentid() {
22 return departmentid;
23 }
24
25 public void setDepartmentid(Integer departmentid) {
26 this.departmentid = departmentid;
27 }
28
29 public Integer getDepartmentpid() {
30 return departmentpid;
31 }
32
33 public void setDepartmentpid(Integer departmentpid) {
34 this.departmentpid = departmentpid;
35 }
36
37 public String getDepartmentname() {
38 return departmentname;
39 }
40
41 public void setDepartmentname(String departmentname) {
42 this.departmentname = departmentname;
43 }
44 }
4、编写DAO接口 DepartmentDao.java
1 /**
2 * 因为需要使用分页和条件查询,所以从JpaRepository接口 和 JpaSpecificationExecutor接口继承
3 */
4 public interface DepartmentDao extends JpaRepository<Department, Integer>, JpaSpecificationExecutor<Department> {
5
6 }
5、编写工具类 TreeNode.java 和 TreeUtil.java
1 /**
2 * EasyUI Tree的封装类
3 */
4 public class TreeNode {
5 private Integer id; // 节点的 id
6 private Integer parentId; // 父节点id,java生成树时使用
7 private String text; // 显示的节点文字。
8 private String iconCls; // 节点图标样式 "iconCls":"icon-save", "iconCls":"icon-ok", 等
9 private String state; // 节点状态, 'open' 或 'closed',默认是 'open'。当设为 'closed' 时,此节点有子节点,并且将从远程站点加载它们。
10 private String flag; // 节点类型
11 private Integer trueId; // 应用系统真实 id
12 private boolean checked; // 指示节点是否被选中。
13 private LinkedHashMap<?, ?> attributes; // 给一个节点追加的自定义属性。
14 private List<TreeNode> children; // 定义了一些子节点的节点数组。
15 private String url; // 扩展属性url
16
17 public Integer getTrueId() {
18 return trueId;
19 }
20
21 public void setTrueId(Integer trueId) {
22 this.trueId = trueId;
23 }
24
25 public String getFlag() {
26 return flag;
27 }
28
29 public void setFlag(String flag) {
30 this.flag = flag;
31 }
32
33 public Integer getId() {
34 return id;
35 }
36
37 public void setId(Integer id) {
38 this.id = id;
39 }
40
41 public Integer getParentId() {
42 return parentId;
43 }
44
45 public void setParentId(Integer parentId) {
46 this.parentId = parentId;
47 }
48
49 public String getText() {
50 return text;
51 }
52
53 public void setText(String text) {
54 this.text = text;
55 }
56
57 public String getIconCls() {
58 return iconCls;
59 }
60
61 public void setIconCls(String iconCls) {
62 this.iconCls = iconCls;
63 }
64
65 public String getState() {
66 return state;
67 }
68
69 public void setState(String state) {
70 this.state = state;
71 }
72
73 public boolean isChecked() {
74 return checked;
75 }
76
77 public void setChecked(boolean checked) {
78 this.checked = checked;
79 }
80
81 public LinkedHashMap<?, ?> getAttributes() {
82 return attributes;
83 }
84
85 public void setAttributes(LinkedHashMap<?, ?> attributes) {
86 this.attributes = attributes;
87 }
88
89 public List<TreeNode> getChildren() {
90 return children;
91 }
92
93 public void setChildren(List<TreeNode> children) {
94 this.children = children;
95 }
96
97 public String getUrl() {
98 return url;
99 }
100
101 public void setUrl(String url) {
102 this.url = url;
103 }
104 }
1 /**
2 * 树工具类
3 */
4 public class TreeUtil {
5 /**
6 * Tree装配方法
7 *
8 * @param tempTreeNodes
9 * @param treeNodes
10 * @return
11 */
12 public static List<TreeNode> Assemble(List<TreeNode> tempTreeNodes, List<TreeNode> treeNodes) {
13 if (tempTreeNodes != null) {
14 Map<Integer, TreeNode> map = new LinkedHashMap<>();
15 for (TreeNode tn : tempTreeNodes) {
16 map.put(tn.getId(), tn);
17 }
18
19 TreeNode treeNode;
20 TreeNode pTreeNode;
21 for (Integer id : map.keySet()) {
22 treeNode = map.get(id);
23 if (treeNode.getParentId() == 0) {
24 treeNodes.add(treeNode);
25 } else {
26 pTreeNode = map.get(treeNode.getParentId());
27 List<TreeNode> children = pTreeNode.getChildren();
28 if (children != null) {
29 children.add(treeNode);
30 } else {
31 children = new ArrayList();
32 children.add(treeNode);
33 pTreeNode.setChildren(children);
34 }
35 }
36 }
37 }
38
39 return treeNodes;
40 }
41 }
6、规划控制器 DepartmentController.java
1 @Controller
2 @RequestMapping("/department")
3 public class DepartmentController {
4 @Autowired
5 private DepartmentDao departmentDao;
6
7 @RequestMapping("/view")
8 public String view() {
9 // 跳转至【资源管理】页面
10 return "department";
11 }
12
13 @RequestMapping("/tree")
14 @ResponseBody
15 public String tree() {
16 List<Department> list = departmentDao.findAll();
17 List<TreeNode> tempTreeNodes = new ArrayList();
18 List<TreeNode> treeNodes = new ArrayList();
19
20 // 组装Easyui的Tree必须要有id、parentId、text属性,转换之
21 for (Department department : list) {
22 TreeNode tempTreeNode = new TreeNode();
23 tempTreeNode.setId(department.getDepartmentid());
24 tempTreeNode.setParentId(department.getDepartmentpid());
25 tempTreeNode.setText(department.getDepartmentname());
26 tempTreeNodes.add(tempTreeNode);
27 }
28
29 return JSONObject.toJSON(TreeUtil.Assemble(tempTreeNodes, treeNodes)).toString();
30 }
31
32 @RequestMapping("/saveNode")
33 @ResponseBody
34 public Map<String, Object> saveNode(Integer departmentpid, String departmentname) {
35 Department model = new Department();
36 model.setDepartmentpid(departmentpid);
37 model.setDepartmentname(departmentname);
38
39 Map<String, Object> resultMap = new HashMap<>();
40 departmentDao.save(model);
41 resultMap.put("success", true);
42 return resultMap;
43 }
44
45 @RequestMapping("/updateNode")
46 @ResponseBody
47 public Map<String, Object> updateNode(Department model) {
48 Map<String, Object> resultMap = new HashMap<>();
49 departmentDao.save(model);
50 resultMap.put("success", true);
51 return resultMap;
52 }
53
54 @RequestMapping("/deleteNode")
55 @ResponseBody
56 public Map<String, Object> deleteNode(Integer departmentid) {
57 Map<String, Object> resultMap = new HashMap<>();
58 departmentDao.deleteById(departmentid);
59 resultMap.put("success", true);
60 return resultMap;
61 }
62 }
7、编写前端代码
HTML页面:department.html
1 <!DOCTYPE html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <title>测试Tree功能</title>
6 <link rel="stylesheet" type="text/css" href="../easyui/themes/default/easyui.css">
7 <link rel="stylesheet" type="text/css" href="../easyui/themes/icon.css">
8 <script type="text/javascript" src="../easyui/jquery.min.js"></script>
9 <script type="text/javascript" src="../easyui/jquery.easyui.min.js"></script>
10 <script type="text/javascript" src="../easyui/locale/easyui-lang-zh_CN.js"></script>
11 <script type="text/javascript" src="../biz/department.js"></script>
12 </head>
13 <body>
14 <!-- 部门树 -->
15 <ul id="deptTree" class="easyui-tree"></ul>
16 <!-- 叶子节点右键菜单 -->
17 <div id="leaf" class="easyui-menu" style="width: 120px;">
18 <div onclick="addNode()" iconcls="icon-add">新增节点</div>
19 <div onclick="removeNode()" iconcls="icon-remove">删除节点</div>
20 <div onclick="updateNode()" iconcls="icon-edit">编辑节点</div>
21 </div>
22 <!-- 非叶子节点右键菜单 -->
23 <div id="parentNode" class="easyui-menu" style="width: 120px;">
24 <div onclick="addNode()" iconcls="icon-add">新增节点</div>
25 <div onclick="updateNode()" iconcls="icon-edit">编辑节点</div>
26 </div>
27 <!-- 节点内容对话框 -->
28 <div id="info" class="easyui-dialog" style="width:300px; height: 120px;" closed=true>
29 <form id="treefrm" method="post">
30 <input type="hidden" name="departmentid">
31 <table style="margin: auto;" cellspacing="10">
32 <tr>
33 <td>部门名称</td>
34 <td><input class="easyui-textbox" name="departmentname" value="" data-options="required:true"></td>
35 </tr>
36 </table>
37 <div style="text-align: center; bottom: 15px; margin-top: 10px;">
38 <a id="btnSave" class="easyui-linkbutton"
39 data-options="iconCls:'icon-save'">保存</a>
40 <a id="btnCancel" class="easyui-linkbutton"
41 data-options="iconCls:'icon-cancel'">取消</a>
42 </div>
43 </form>
44 </div>
45 </body>
46 </html>
对应JS文件:department.js
1 // 记录添加还是修改 2 var flag; 3 // 临时存储选中节点数据 4 var tempNode; 5 6 // 页面加载 7 $(function () { 8 // 菜单树绑定数据 9 $('#deptTree').tree({ 10 url: '/department/tree', 11 animate: true, 12 lines: true, 13 onBeforeSelect: function (node) { 14 // onBeforeSelect事件:节点被选中前触发,返回false则取消选择动作 15 if (!$(this).tree('isLeaf', node.target)) { 16 // 不是叶子节点,则不能选中 17 return false; 18 } 19 }, 20 onClick: function (node) { 21 // alert(node.target.innerText); 22 }, 23 onContextMenu: function (e, node) { 24 // 记录选中的节点,为后续增删改操作提供节点数据 25 tempNode = node; 26 27 // 阻止右键默认事件 28 e.preventDefault(); 29 30 // 判断该结点有没有父结点 31 var root = $(this).tree('getParent', node.target); 32 // 没有父节点则为根结点,可以新增、编辑,不可以删除 33 if (root == null) { 34 // 如果是根节点,则可以新增、编辑,不可以删除 35 $('#parentNode').menu('show', { 36 left: e.pageX, 37 top: e.pageY 38 }); 39 } 40 41 if ($(this).tree('isLeaf', node.target)) { 42 // 如果是叶子节点,则可以新增、编辑和删除 43 $('#leaf').menu('show', { 44 left: e.pageX, 45 top: e.pageY 46 }); 47 } else { 48 // 如果不是叶子节点,则可以新增、编辑,不可以删除 49 $('#parentNode').menu('show', { 50 left: e.pageX, 51 top: e.pageY 52 }); 53 } 54 } 55 }); 56 57 // 保存按钮押下处理 58 $('#btnSave').click(function () { 59 var tempdata, tempurl, tempmsg; 60 61 if (flag == 'add') { 62 tempurl = 'saveNode'; 63 tempmsg = '添加成功!'; 64 tempdata = { 65 departmentpid: tempNode.id, 66 departmentname: $('#treefrm').find('input[name=departmentname]').val() 67 }; 68 } else if (flag == 'edit') { 69 tempurl = 'updateNode'; 70 tempmsg = '编辑成功!'; 71 tempdata = { 72 departmentid: $('#treefrm').find('input[name=departmentid]').val(), 73 departmentpid: $('#deptTree').tree('getParent', tempNode.target).id, 74 departmentname: $('#treefrm').find('input[name=departmentname]').val() 75 }; 76 } 77 78 $.ajax({ 79 type: 'post', 80 async: true, 81 url: tempurl, 82 data: tempdata, 83 dataType: 'json', 84 success: function (result) { 85 // 树重新加载 86 $('#deptTree').tree('reload'); 87 88 $.messager.show({ 89 title: '提示信息', 90 msg: tempmsg 91 }); 92 }, 93 error: function (result) { 94 // 请求失败时执行该函数 95 $.messager.show({ 96 title: '错误信息', 97 msg: result.msg 98 }); 99 } 100 }); 101 102 $('#treefrm').form('clear'); 103 $('#info').dialog('close'); 104 }); 105 106 // 取消按钮押下处理 107 $('#btnCancel').click(function () { 108 $('#treefrm').form('clear'); 109 $('#info').dialog('close'); 110 }); 111 }); 112 113 // 新增节点 114 var addNode = function () { 115 flag = 'add'; 116 // 清空表单数据 117 $('#treefrm').form('clear'); 118 // 打开dialog 119 $('#info').dialog('open').dialog('setTitle', '新增'); 120 }; 121 122 // 编辑节点 123 var updateNode = function () { 124 flag = 'edit'; 125 // 清空表单数据 126 $('#treefrm').form('clear'); 127 $('#treefrm').form('load', { 128 departmentid: tempNode.id, 129 departmentname: tempNode.text 130 }); 131 // 打开dialog 132 $('#info').dialog('open').dialog('setTitle', '编辑'); 133 }; 134 135 // 删除节点 136 var removeNode = function () { 137 // 前台删除 138 $('#deptTree').tree('remove', tempNode.target); 139 140 // 后台删除 141 $.ajax({ 142 type: "post", 143 async: true, // 异步请求(同步请求将会锁住浏览器,用户其他操作必须等待请求完成才可以执行) 144 url: "deleteNode", 145 data: {departmentid: tempNode.id}, 146 dataType: "json", // 返回数据形式为json 147 success: function (result) { 148 // 请求成功时执行该函数内容,result即为服务器返回的json对象 149 $.messager.show({ 150 title: '提示信息', 151 msg: '删除成功!' 152 }); 153 }, 154 error: function (result) { 155 // 请求失败时执行该函数 156 $.messager.show({ 157 title: '错误信息', 158 msg: result.msg 159 }); 160 } 161 }); 162 };
8、运行效果