From 43ab4464edf1944891219ba535e0a72de7f40841 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Tue, 1 Sep 2026 17:03:20 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20UI=20=E7=BC=96=E8=BE=91?= =?UTF-8?q?=E5=99=A8=E4=BF=9D=E5=AD=98=E4=B8=8E=E4=BB=A3=E7=A0=81=E7=94=9F?= =?UTF-8?q?=E6=88=90=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 普通保存和资源切换时清理生成提示与进行中状态。 为生成前置条件拦截补充可见错误,并修正保存确认按钮文案。 补齐 IUiDesignStateStore 测试替身的必需非 nullable generateCode 实现。 修正 Rust HTML 树分隔注释并同步更新 UI 编辑会话约定。 --- .../src-tauri/src/ui_editor/html_renderer/mod.rs | 2 +- apps/ai-game-creator-shell/src/view/ui-editor/index.tsx | 3 ++- .../src/view/ui-editor/useUiEditorPage.ts | 7 +++++++ apps/ai-game-creator-shell/tests/uiEditorPage.test.ts | 7 +++++++ .../technical/【前端架构】UI编辑会话模块边界-2026-08-19.md | 4 ++++ 5 files changed, 21 insertions(+), 2 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/html_renderer/mod.rs b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/html_renderer/mod.rs index 93f71a414..e86bfff7f 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/html_renderer/mod.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/html_renderer/mod.rs @@ -18,7 +18,7 @@ pub(crate) fn render_ui_design_state_html(state: &State) -> Result 0 { - trees.push("\n\n".to_string()); + trees.push("\n\n".to_string()); } trees.push(render_tree(state, tree)?.into_string()); } diff --git a/apps/ai-game-creator-shell/src/view/ui-editor/index.tsx b/apps/ai-game-creator-shell/src/view/ui-editor/index.tsx index 515ba744c..b6f298eed 100644 --- a/apps/ai-game-creator-shell/src/view/ui-editor/index.tsx +++ b/apps/ai-game-creator-shell/src/view/ui-editor/index.tsx @@ -94,6 +94,7 @@ export default function UiEditorPage({ if (!resourceId || session.save.isSaving || session.workflow.isAiRunning) { return; } + setGenerateSuccess(null); setSaveAfterReturn(afterReturn); setGenerateAfterWarning(false); if (session.save.hasWarnings()) { @@ -355,7 +356,7 @@ export default function UiEditorPage({ } }} > - 仍然保存 + {generateAfterWarning ? '仍然保存并生成' : '仍然保存'} 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 06adc9a8f..d2382f24e 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 @@ -257,6 +257,8 @@ export function useUiEditorSession( setPersistedRevision(0); setSavedStateSignature(JSON.stringify(EMPTY_UI_EDITOR_STATE)); setSaveError(null); + setGenerateError(null); + setIsGenerating(false); setHiddenNodeIds(new Set()); return; } @@ -265,6 +267,9 @@ export function useUiEditorSession( setIsLoading(true); setLoadError(null); setPersistedRevision(null); + setSaveError(null); + setGenerateError(null); + setIsGenerating(false); void stateStore .load(resourceId) .then(({ state, revision }) => { @@ -1048,6 +1053,7 @@ export function useUiEditorSession( return false; } setSaveError(null); + setGenerateError(null); setIsSaving(true); const snapshot = structuredClone(editor.state); const snapshotSignature = JSON.stringify(snapshot); @@ -1084,6 +1090,7 @@ export function useUiEditorSession( persistedRevision === null || editor.isLocked ) { + setGenerateError('当前状态不允许生成代码,请稍后重试。'); return null; } setGenerateError(null); diff --git a/apps/ai-game-creator-shell/tests/uiEditorPage.test.ts b/apps/ai-game-creator-shell/tests/uiEditorPage.test.ts index 72fd8b5ad..6c21bc416 100644 --- a/apps/ai-game-creator-shell/tests/uiEditorPage.test.ts +++ b/apps/ai-game-creator-shell/tests/uiEditorPage.test.ts @@ -137,6 +137,7 @@ async function renderLoadedSession(state: State) { const stateStore: IUiDesignStateStore = { load: vi.fn().mockResolvedValue({ revision: 0, state }), save: vi.fn(), + generateCode: vi.fn().mockRejectedValue(new Error('测试未配置代码生成')), }; const hook = renderHook(() => useUiEditorSession('/tmp/ui-editor', 'ui-resource', stateStore), @@ -150,6 +151,7 @@ describe('UiEditorPage', () => { const stateStore: IUiDesignStateStore = { load: vi.fn().mockResolvedValue(structuredClone(EMPTY_SNAPSHOT)), save: vi.fn(), + generateCode: vi.fn().mockRejectedValue(new Error('测试未配置代码生成')), }; render( @@ -193,6 +195,7 @@ describe('UiEditorPage', () => { state: stateWithPages(['page']), }), save: vi.fn(), + generateCode: vi.fn().mockRejectedValue(new Error('测试未配置代码生成')), }; const hook = renderHook( ({ resourceId }) => @@ -248,6 +251,7 @@ describe('UiEditorPage', () => { const stateStore: IUiDesignStateStore = { load: vi.fn(() => new Promise(() => undefined)), save: vi.fn(), + generateCode: vi.fn().mockRejectedValue(new Error('测试未配置代码生成')), }; const hook = renderHook(() => useUiEditorSession('/tmp/ui-editor', 'ui-resource', stateStore), @@ -355,6 +359,7 @@ describe('UiEditorPage', () => { state: stateWithPages(['gameplay-page']), }), save: vi.fn(), + generateCode: vi.fn().mockRejectedValue(new Error('测试未配置代码生成')), }; render( @@ -594,6 +599,7 @@ describe('UiEditorPage', () => { const stateStore: IUiDesignStateStore = { load: vi.fn().mockResolvedValue(structuredClone(EMPTY_SNAPSHOT)), save: vi.fn().mockRejectedValue(new Error('临时存储不可用')), + generateCode: vi.fn().mockRejectedValue(new Error('测试未配置代码生成')), }; render( createElement(UiEditorPage, { @@ -627,6 +633,7 @@ describe('UiEditorPage', () => { status: 'conflict', currentRevision: 1, }), + generateCode: vi.fn().mockRejectedValue(new Error('测试未配置代码生成')), }; render( createElement(UiEditorPage, { diff --git a/docs/technical/【前端架构】UI编辑会话模块边界-2026-08-19.md b/docs/technical/【前端架构】UI编辑会话模块边界-2026-08-19.md index 33d14aea0..6e33010bd 100644 --- a/docs/technical/【前端架构】UI编辑会话模块边界-2026-08-19.md +++ b/docs/technical/【前端架构】UI编辑会话模块边界-2026-08-19.md @@ -16,3 +16,7 @@ 预览树在会话边界归一化为 `UITree | null`:尚未选择界面图、正在加载或尚未识别树时均以 `null` 表示,不能把 `Array.find` 的 `undefined` 传播到视图接口。 保存与代码生成共享同一份持久化 State/revision。会话层在保存或生成进行期间互斥拦截,且代码生成必须基于已加载的持久化 revision;视图层的保存按钮和“保存并返回”按钮同步遵守该互斥状态。 + +`UiDesignStateStore` 的 `generateCode(assetId)` 是必需能力,返回成功结果时不得为 nullable;所有注入的 adapter 与测试替身都必须实现该方法。 + +资源切换时,会话必须清理上一资源的保存/生成错误和生成中状态;普通保存开始时也清理代码生成错误。生成请求若因加载、锁定或 revision 等前置条件被拦截,必须向视图提供可见错误,而不是静默返回。