diff --git a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/commands/merge.rs b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/commands/merge.rs index 85c0801cc..254d43774 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/commands/merge.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/commands/merge.rs @@ -3,7 +3,6 @@ use crate::config::load_game_creator_app_config; use crate::ui_editor::commands::utils::{ parse_limited_llm_tool_arguments, request_ui_editor_llm, strict_json_schema, }; -use crate::ui_editor::layout::offset::NodeOffset; use crate::ui_editor::state::{State, UITree}; use platform_llm::{LlmFunctionTool, LlmMessage, LlmRunRequest, LlmToolChoice}; use serde::{Deserialize, Serialize}; diff --git a/apps/ai-game-creator-shell/src/view/ui-editor/useUiEditorPage.ts b/apps/ai-game-creator-shell/src/view/ui-editor/useUiEditorPage.ts index 2698992f5..98eef005c 100644 --- a/apps/ai-game-creator-shell/src/view/ui-editor/useUiEditorPage.ts +++ b/apps/ai-game-creator-shell/src/view/ui-editor/useUiEditorPage.ts @@ -1028,9 +1028,13 @@ export function useUiEditorSession( state: snapshot, }); const nextState = applyRecognitionResult(snapshot, result); - nextState.ui_trees = nextState.ui_trees.map((tree) => - createTree(nextState, tree.src_ui_design, tree.root), - ); + const recognizedTrees = nextState.ui_trees; + nextState.ui_trees = []; + for (const tree of recognizedTrees) { + nextState.ui_trees.push( + createTree(nextState, tree.src_ui_design, tree.root), + ); + } editor.replaceState(nextState); setHasRecognized(true); setSelectedNodeId(null); @@ -1066,9 +1070,13 @@ export function useUiEditorSession( await editor.runWithStateLocked(async (snapshot) => { const result = await invoke('merge_ui', { state: snapshot }); const nextState = applyMergeResult(snapshot, result); - nextState.ui_trees = nextState.ui_trees.map((tree) => - createTree(nextState, tree.src_ui_design, tree.root), - ); + const mergedTrees = nextState.ui_trees; + nextState.ui_trees = []; + for (const tree of mergedTrees) { + nextState.ui_trees.push( + createTree(nextState, tree.src_ui_design, tree.root), + ); + } editor.replaceState(nextState); setSelectedNodeId(null); setMergeStatus('已生成合并后的单棵界面树。'); diff --git a/docs/technical/【前端架构】UI编辑会话模块边界-2026-08-19.md b/docs/technical/【前端架构】UI编辑会话模块边界-2026-08-19.md index 12460d0e0..120aecb49 100644 --- a/docs/technical/【前端架构】UI编辑会话模块边界-2026-08-19.md +++ b/docs/technical/【前端架构】UI编辑会话模块边界-2026-08-19.md @@ -19,6 +19,14 @@ 保存与代码生成共享同一份持久化 State/revision。会话层在保存或生成进行期间互斥拦截,且代码生成必须基于已加载的持久化 revision;视图层的保存按钮和“保存并返回”按钮同步遵守该互斥状态。 +## 2026-09-14 多树预览与树级偏移 + +UI 编辑器预览同时渲染 State 中全部 `ui_trees`。每棵树的 `root.offset` 包含 `min` / `max`;当前仅读取 `min` 作为树 wrapper 的左上角,`max` 由 `min + 对应界面图 pixel_size / pixels_per_unit` 派生。所有新树必须经 `createTree` 创建:首棵树位置为 `[0, 0]`,后续树按现有树实际右边界最大值加固定 padding 横向排列,并与现有树最小 top 对齐。删除或排序不重排已有树。 + +预览中原图与树 root 共享同一空间,root 空白区域可拖动整树,拖动结束一次性写回 root offset 并进入撤销/重做;root 不提供 resize,子节点沿用既有手势。预览选中节点不改变左侧图片面板的 `activeImageId`,Inspector 通过节点所属树反查编辑目标。只有首次进入预览和用户手动点击“适配画布”使用全部树联合边界进行 fit。 + +原有 render mode 已拆为三个会话级临时开关:`showFrame=true`、`showOriginImage=true`、`showComponent=false`。普通节点框线/名称受 `showFrame` 控制,选中节点强调始终保留;原图和组件显示互不耦合。 + `UiDesignStateStore` 的 `generateCode(assetId)` 是必需能力,返回成功结果时不得为 nullable;所有注入的 adapter 与测试替身都必须实现该方法。 资源切换时,会话必须清理上一资源的保存/生成错误和生成中状态;普通保存开始时也清理代码生成错误。生成请求若因加载、锁定或 revision 等前置条件被拦截,必须向视图提供可见错误,而不是静默返回。