整體架構
採用 後端遞歸掃描目錄 → 生成 JavaScript 數組結構 → 前端遞歸渲染樹狀 UI 的經典模式,分離數據獲取與展示邏輯。
後端:Java 遞歸生成目錄數據
package com.example.util;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class FileTreeBuilder {
private int index = 1; // 節點層級/序號
private boolean hasNextBrother = false; // 是否有下一個兄弟節點
/**
* 生成目錄樹 JavaScript 數組字符串
* @param rootPath 根目錄絕對路徑
* @return 可直接嵌入 JSP 的 JS 數組定義片段
*/
public String buildTreeJson(String rootPath) {
StringBuilder sb = new StringBuilder();
File root = new File(rootPath);
if (root.exists() && root.isDirectory()) {
buildNode(sb, root);
}
// 終結標記
sb.append("new Array(1, \"\", false)");
return sb.toString();
}
private void buildNode(StringBuilder sb, File dir) {
// 處理路徑特殊字符:單引號、空格
String absPath = dir.getAbsolutePath()
.replace("'", "\\'") // 轉義單引號
.replace(" ", "@"); // 空格用 @ 佔位,前端還原
// 節點格式: [層級, 'HTML標題', 有無下一兄弟]
sb.append("new Array(")
.append(index).append(",")
.append("'<a href=fileList.jsp?path=").append(absPath)
.append(" target=mainFrame>").append(dir.getName()).append("</a>',")
.append(hasNextBrother).append("), ");
hasNextBrother = false;
index++;
// 遞歸處理子目錄
File[] children = dir.listFiles(File::isDirectory);
if (children != null) {
List dirList = new ArrayList<>();
for (File f : children) dirList.add(f);
for (int i = 0; i < dirList.size(); i++) {
if (i != dirList.size() - 1) hasNextBrother = true;
buildNode(sb, dirList.get(i));
}
}
index--; // 回溯層級
}
}
JSP 页面嵌入調用
<%@ page import="com.example.util.FileTreeBuilder" %>
<%
FileTreeBuilder builder = new FileTreeBuilder();
// 可從配置/參數獲取根路徑
String treeArray = builder.buildTreeJson("E:/data");
%>
前端:JavaScript 遞歸渲染樹狀菜單
核心渲染函數
/**
* 遞歸構建樹 HTML
* @param n 期望層級 (初始調用傳 0 或 1)
* @returns HTML 字符串
*/
function tree(n) {
// 圖標資源
var icons = {
bar: "<img src='images/bar.gif'>", // 無子節點
pad: "<img src='images/pad.gif'>", // 有子節點 (可展開)
open: "<img src='images/open.gif'>", // 展開狀態
close: "<img src='images/close.gif'>", // 折疊狀態
line: "<img src='images/line.gif'>", // 連接線
lastLine: "<img src='images/lastline.gif'>" // 最後一個節點連接線
};
// 初始化
if (n == 0) { n = 1; i = 0; s = ""; }
s += "<table border=0 cellspacing=0 cellpadding=0>";
for (; i < tree_ar.length - 1; i++) {
var node = tree_ar[i];
var level = node[0];
var title = node[1];
var hasBrother = node[2];
// 判斷當前節點類型
var isLast = !hasBrother;
var isParent = (level < tree_ar[i+1][0]); // 下一節點層級更深 => 有子節點
// 構建圖標 HTML
var iconHtml = "";
if (isParent) {
iconHtml = icons.pad + (isLast ? icons.close : icons.open); // 簡化
} else {
iconHtml = icons.bar + (isLast ? icons.lastLine : icons.line);
}
// 生成行
s += "<tr id='" + (isParent ? "pad" : "bar") + "' value='" + i + "'>"
+ "<td>" + iconHtml + "</td>"
+ "<td>" + title + "</td></tr>";
// 遞歸處理子節點
if (isParent) {
s += "<tr style='display:none'><td>";
if (hasBrother) s += "background='images/line.gif'";
s += "></td><td>";
var retLevel = tree(tree_ar[++i][0]); // 遞歸,傳入子節點層級
s += "</td></tr>";
if (retLevel < n) { // 回溯到更上層
s += "</table>";
return retLevel;
}
}
// 同層最後一個節點,結束當前表格
if (level > tree_ar[i+1][0]) {
s += "</table>";
return tree_ar[i+1][0];
}
}
s += "</table>";
return s;
}
節點點擊事件處理
// 展開/折疊分支節點
document.addEventListener('click', function(e) {
var pad = e.target.closest('tr[id="pad"]');
if (!pad) return;
var nextRow = pad.parentElement.rows[pad.rowIndex + 1];
var isHidden = nextRow.style.display === 'none';
// 切換顯示
nextRow.style.display = isHidden ? '' : 'none';
// 更新圖標
var iconCell = pad.cells[0];
iconCell.innerHTML = isHidden
? "<img src='images/pad.gif'><img src='images/open.gif'>"
: "<img src='images/pad.gif'><img src='images/close.gif'>";
// 可選:單展開模式 (關閉其他分支)
// closeOtherBranches(pad.value);
});
初始化渲染
|
|
<!-- 樹渲染容器 -->
<!-- 右側內容區 -->
|
關鍵技術點總結
| 環節 | 關鍵點 |
| 後端遞歸 | 用 index 追蹤層級,hasNextBrother 標記兄弟關係,生成扁平數組 |
| 路徑編碼 | 單引號轉義、空格替換為 @,前端解碼還原 |
| 數據結構 | 扁平數組 [level, titleHtml, hasNextBrother],前端按層級重構樹 |
| 前端遞歸 | 函數 tree(n) 返回渲染後層級,利用返回值控制表格閉合 |
| 狀態持久化 | 可擴展:用 Cookie/LocalStorage 記錄展開節點 ID,刷新恢復 |
現代化改造建議
- 後端輸出 JSON:替換 JS 數組字符串,用
Jackson 序列化 TreeNode 對象,前端 fetch 獲取。
- 前端組件化:Vue/React 遞歸組件
<TreeNode :node="node" />,狀態管理更清晰。
- 懶加載**:大目錄只加載一級,展開時 AJAX 請求子節點。
- 虛擬滾動**:節點超過千級使用虛擬列表渲染。