Tree 控制項
Tree 適合呈現商品分類、組織架構、資料夾、地區等階層資料。資料量少且穩定時,可直接在頁面結構中維護;資料由業務系統管理時,則可透過 API 載入根節點與子節點。Tree 也支援搜尋、節點操作與拖放排序。
1. 選擇實作方式
| 情境 | 建議設定 |
|---|---|
| 由頁面作者維護的少量固定項目 | 靜態 Tree Nodes |
| 由業務系統管理的階層資料 | Tree Data Source |
| 新增、編輯、刪除或節點快捷操作 | 動態資料來源 + Node Actions |
| 調整同層順序或跨層移動 | 動態資料來源 + onDrop |
1.1 Tree:靜態商品分類
靜態範例會先將 [[treeNode.name]] 寫入 Page Context 的 selectedCategory,再呼叫右側 ProTable 的 reloadTable。ProTable Query Context 則把 selectedCategory 對應到請求參數 category。
1.2 Tree02:動態載入與階層編輯
Tree02 在頁面開啟時載入根節點,展開目錄時再以 parentId 請求直接子節點。它也示範伺服器搜尋、節點詳情、編輯、新增子節點、刪除及拖放儲存。建議依序完成根節點、延遲載入、搜尋、點擊聯動與節點操作,最後才啟用拖放。
2. 新增 Tree
- 在 Page Designer 開啟目標頁面。
- 選取頁面結構中的 Screen。
- 在 Navigation Tree 子元素區域新增 Tree。
- 靜態資料新增 Tree Nodes;動態資料則在 Data Source 新增 Tree Data Source。
- 在 Panels 放置 ProTable、Form 或其他內容控制項。
Navigation Tree 會形成左側導覽區;Panels 與 Grids 會形成內容區,不需要另以 Grid 重建相同版面。
3. 設定 Tree
| 屬性 | 用途 |
|---|---|
| Title | 樹狀導覽上方的選用標題 |
| Width | 桌面版寬度;窄畫面會自動改為整列 |
| Show Search | 顯示搜尋列 |
| Checkable | 顯示核取方塊 |
| Default Expand All | 初次顯示時展開已載入節點 |
| Node Draggable | 允許拖放並觸發 onDrop |
4. 靜態節點
在 Tree Nodes 新增 TreeNode,設定 Title、Value、Disabled、Selectable、Checkable、Is Leaf、Icon 與 Extra Info。Value 應在整棵樹中保持唯一且穩定;後續互動會以它辨識節點。
靜態節點適合小型、固定的分類。若節點由後端維護,請改用動態資料來源,避免頁面設定與業務資料重複。
5. 動態資料來源
在 Data Source 新增 TreeDataSource,先完成根節點請求:
| 設定 | Tree02 範例 |
|---|---|
| Root Data URL | /spark/api/sample/navigation/tree/nodes |
| Request Method | GET |
| Response List Path | data.nodes |
| Title Field | label |
| Value Field | id |
| Children Field | children |
| Leaf Field | leaf |
| Extra Field | count |
5.1 使用 Test 驗證請求
按一下 Test。Test、設計畫布與執行頁面共用同一套 Tree 請求建構與回應解析邏輯,因此結果可直接協助判斷問題位於 URL、後端資料、Response List Path 或欄位對應。平台會自動攜帶:
X-Spark-Page-Environment: DEV
X-Spark-Page-Render-Mode: DESIGN_PREVIEW
Data Source 不需要手動加入這兩個 Header。完整規範請參閱頁面請求內容與後端偵錯。
後端應使用共用常數解析 Header,再依列舉值決定是否回傳穩定的設計預覽資料:
SparkPageRequestContext context = SparkPageRequestHeaders.resolve(
environment, renderMode);
boolean developmentPreview = context != null
&& context.getEnvironment() == SparkPageEnvironment.DEV
&& context.getRenderMode() == SparkPageRenderMode.DESIGN_PREVIEW;
不要以手寫字串、Map 或自訂查詢參數重複判斷模式。公開 Test 資料也不得包含正式環境密鑰、個資或敏感業務資料。
5.2 完整載入與延遲載入
小型資料可在 children 中一次回傳完整階層。大型資料則設定 Child Nodes URL 與 Parent Parameter;展開非葉節點時,Tree 會把目前節點 Value 帶到例如 parentId 的參數。非葉節點必須回傳 leaf: false,即使當下的 children 是空陣列。
6. 搜尋
靜態或已完整載入的資料可使用本機搜尋。延遲載入時建議設定 Search URL 與查詢參數,由後端搜尋未載入分支,並同時回傳命中節點的祖先路徑,避免結果成為沒有上下文的孤立葉節點。
7. 目前節點與事件內容
| 事件 | 常用資料 |
|---|---|
onClick | treeNode、selectedTreeNode |
onCheck | treeCheck.checkedKeys、treeCheck.checkedNodes |
onExpand | treeExpand.expandedKeys、treeExpand.node |
onLoad | treeLoad.node、treeLoad.children |
onDrop | treeDrop.dragNode、treeDrop.dropNode、treeDrop.position |
常用路徑包含 treeNode.value、treeNode.title、treeNode.isLeaf 與 treeNode.data.category。Node Action 會更新目前節點,但不會同時觸發 Tree 的 onClick。
8. 設定節點操作
在 Node Actions 新增 TreeNodeAction,設定標題、統一 Icon 選擇器、觸發方式、語意色彩、顯示條件與停用條件。More 按鈕與桌面右鍵選單共用同一份操作清單;觸控裝置使用 More,不依賴長按。
Edit、Add Child 與 Delete 可分別呼叫:
PUT /spark/api/sample/navigation/tree/nodes/[[treeNode.value]]
POST /spark/api/sample/navigation/tree/nodes
DELETE /spark/api/sample/navigation/tree/nodes/[[treeNode.value]]
請把互動設定在 Tree Node Action 本身,並只在請求成功後呼叫 reloadTree。隱藏操作不是安全邊界;後端仍須驗證權限與刪除規則。
9. 拖放與儲存
啟用 Node Draggable 後,Tree 先完成畫面中的移動,再觸發 onDrop。treeDrop.position 可能為 BEFORE、INSIDE 或 AFTER。若要保留新順序,請在 onDrop 新增 Send Request:
POST /spark/api/sample/navigation/tree/reorder
參數分別為 dragId = [[treeDrop.dragNode.key]]、dropId = [[treeDrop.dropNode.key]]、position = [[treeDrop.position]]。請求失敗或中斷時,Tree 會回復到拖放前的結構。後端仍須檢查循環引用、父節點、權限與並行版本。
10. 點擊節點後重新整理內容
靜態 Tree 的 onClick 應先執行 Set Page Context,將 [[treeNode.name]] 寫入 selectedCategory,再以 Component API 呼叫 ProTable 的 reloadTable。ProTable Query Context 將 category 對應到 selectedCategory。
動態 Tree02 則先寫入 selectedNodeId = [[treeNode.value]] 與 selectedCategory = [[treeNode.data.category]],再重新載入 ProTable。順序不可顛倒,否則列表會使用上一次的選取值。
11. 元件函式
| Function | 作用 |
|---|---|
reloadTree | 重新請求根節點,並保留仍有效的選取 |
expandAll | 展開目前已載入的所有目錄 |
collapseAll | 收合所有目錄 |
expandAll 不會為了展開大型樹而自動載入所有尚未請求的分支。
12. Java 後端範例
Tree02 對應 Java 17 與 Spring Boot 4。節點 DTO 至少應包含穩定的 id、label、parentId、leaf 與 children;寫入請求包含 parentId、label、category,拖放請求包含 dragId、dropId、position。
| 用途 | 方法與路徑 |
|---|---|
| 根節點、子節點與搜尋 | GET /spark/api/sample/navigation/tree/nodes |
| 新增子節點 | POST /spark/api/sample/navigation/tree/nodes |
| 編輯節點 | PUT /spark/api/sample/navigation/tree/nodes/{nodeId} |
| 刪除節點 | DELETE /spark/api/sample/navigation/tree/nodes/{nodeId} |
| 儲存拖放 | POST /spark/api/sample/navigation/tree/reorder |
查詢服務可依標準內容選擇可重複的預覽資料或各環境 Repository:
Map<String, NodeRecord> source = isDevelopmentPreview(context)
? designPreviewNodes : nodes;
String keyword = StringUtils.trimToNull(query);
if (keyword != null) {
return children(source, null).stream()
.map(root -> filterTree(source, root, keyword))
.filter(Objects::nonNull)
.toList();
}
return children(source, StringUtils.trimToNull(parentId)).stream()
.map(node -> toDto(source, node))
.toList();
新增、編輯、刪除與拖放都必須在後端重新驗證節點、父節點、循環引用與權限。使用者可見錯誤應透過 LatticeRuntimeException(Message.code(...)) 與服務端 i18n 資源回傳,不要在 Service 拼接字串。拖放更新父節點和同層排序時應位於同一交易中;失敗則回傳非成功結果,讓 Tree 回復畫面。
右側 ProTable 可在一般商品查詢中加入 nodeId。分類節點沿用 category;Operations、Support 等業務節點則由後端依 nodeId 轉換為對應條件。
13. 驗證與問題排查
建議依資料載入、選取聯動、節點操作、拖放儲存的順序驗證:先以 Test 確認根節點與欄位對應,再測試展開和搜尋;接著點選多個節點確認右側列表改變;最後驗證新增、編輯、刪除與拖放失敗時的回復。
| 現象 | 優先檢查 |
|---|---|
| Test 請求失敗 | URL、方法、Connection、驗證狀態與 CORS |
| HTTP 200 但節點數為 0 | Response List Path 是否指向陣列 |
| 有數量但沒有標題 | Title Field、Value Field 與實際欄位名 |
| 目錄無法展開 | Leaf Field 是否錯誤回傳 true |
| 不同節點顯示相同列表 | Page Context 執行順序與 ProTable Query Context 路徑 |
| 拖放後重新整理恢復舊順序 | onDrop 參數與後端是否持久化 |