jsTree 是基於 jQuery 的輕量級樹形選單插件,支援無限級節點、右鍵選單、拖拽排序、AJAX 懶加載等企業級功能。本文提供基於 Bootstrap + jsTree 的完整增刪改查整合方案,含前端初始化、JSON 結構設計、後端遞歸組裝、Servlet 處理器全鏈路源碼。
<link rel="stylesheet" href="plugins/jstree/themes/classic/style.css">
<script src="plugins/jstree/_lib/jquery.js"></script>
<script src="plugins/jstree/_lib/jquery.cookie.js"></script>
<script src="plugins/jstree/_lib/jquery.hotkeys.js"></script>
<script src="plugins/jstree/jquery.jstree.js"></script>
<div id="demo2" class="demo"></div>
<script>
$(function() {
$("#demo2").jstree({
"json_data": {
"ajax": {
"url": "https://localhost:8080/MemberManager/DepartmentTreeJson",
"data": function(n) {
return {
"operation": "get_children",
"id": n.attr ? n.attr("id").replace("node_", "") : 1
};
}
}
},
"plugins": ["themes", "json_data", "ui", "crrm", "contextmenu", "search"]
})
.bind("select_node.jstree", function(e, data) {
// 節點點擊事件
})
.bind("create.jstree", function(e, data) {
$.post("DepartmentServlet", {
"operation": "create_node",
"id": data.rslt.parent.attr("id").replace("node_", ""),
"position": data.rslt.position,
"title": data.rslt.name,
"type": data.rslt.obj.attr("rel")
}, function(r) {
if (r.status) $(data.rslt.obj).attr("id", "node_" + r.id);
else { data.inst.refresh(); $.jstree.rollback(data.rlbk); }
});
})
.bind("rename.jstree", function(e, data) { /* 重命名邏輯 */ })
.bind("remove.jstree", function(e, data) { /* 刪除邏輯 */ });
});
</script>
| 插件 | 功能 |
|---|---|
| themes | 主題樣式支援 |
| json_data | AJAX 載入 JSON 資料 |
| crrm | 建立、重命名、刪除、移動節點 |
| contextmenu | 右鍵選單(增刪改查操作入口) |
| search | 節點搜尋 |
jsTree 要求的標準 JSON 格式:
[
{
"data": "節點顯示文字",
"attr": { "id": "唯一標識" },
"children": [ /* 遞歸子節點 */ ],
"state": "open/closed" // 可選,控制展開狀態
}
]
示例:
{
"data": "軟件及數據",
"attr": { "id": "e59365b9-7b2a-43a3-b10a-cfe03d5eb004" },
"children": [
{
"data": "創新組",
"attr": { "id": "73919359-2a1b-4ee7-bcc2-56949e8560e8" },
"children": [
{ "data": "大數據", "attr": { "id": "..." }, "children": [...] }
]
}
]
}
public class Department {
private String departmentId;
private String name;
private String parentId;
private String orders; // 同級排序
private List childNode; // 遞歸子節點
// getter/setter/toString
}
public StringBuilder createTreeNode() {
List roots = queryByParentId("1"); // 根節點 parentId=1
return json(roots);
}
private StringBuilder json(List list) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < list.size(); i++) {
Department d = list.get(i);
sb.append('{')
.append("\"data\":\"").append(d.getName()).append("\",")
.append("\"attr\":{\"id\":\"").append(d.getDepartmentId()).append("\"},")
.append("\"children\":");
List children = queryByParentId(d.getDepartmentId());
sb.append('[');
if (children != null && !children.isEmpty()) {
json(children); // 遞歸
}
sb.append(']').append('}');
if (i < list.size() - 1) sb.append(',');
}
return sb;
}
@WebServlet("/DepartmentTreeJson")
public class DepartmentTreeJson extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setContentType("application/json;charset=UTF-8");
DepartmentService service = new DepartmentServiceImpl();
StringBuilder json = service.createTreeNode();
// 移除最後逗號並包裝陣列
if (json.length() > 0) json.deleteCharAt(json.length() - 1);
PrintWriter out = resp.getWriter();
out.print("[" + json + "]");
out.flush();
}
}
@WebServlet("/DepartmentServlet")
public class DepartmentServlet extends HttpServlet {
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String op = req.getParameter("operation");
DepartmentService service = new DepartmentServiceImpl();
switch (op) {
case "create_node":
String parentId = req.getParameter("id");
String title = req.getParameter("title");
Department dept = new Department();
dept.setDepartmentId(UUID.randomUUID().toString());
dept.setName(title);
dept.setParentId(parentId);
service.insert(dept);
resp.getWriter().print("{\"status\":true,\"id\":\"" + dept.getDepartmentId() + "\"}");
break;
case "rename_node":
String id = req.getParameter("id");
String newName = req.getParameter("title");
service.rename(id, newName);
resp.getWriter().print("{\"status\":true}");
break;
case "remove_node":
String delId = req.getParameter("id");
service.delete(delId);
resp.getWriter().print("{\"status\":true}");
break;
}
}
}
"icon": "jstree-folder" 或自定義 CSS 類dnd 插件,監聽 move_node.jstree 事件持久化 orders 欄位json_data.ajax 僅載入第一層,展開時按需請求子節點完整源碼已整理,可直接應用於部門管理、選單配置、分類目錄等樹形業務場景。