From 9694faa620930124a26f51091840953fcb512e41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Thu, 24 Sep 2026 15:45:25 +0800 Subject: [PATCH] =?UTF-8?q?=E8=AE=A9=E8=8A=82=E7=82=B9=20offset=20?= =?UTF-8?q?=E5=9C=A8=E5=8F=8D=E5=BA=8F=E5=88=97=E5=8C=96=E6=97=B6=E7=BC=BA?= =?UTF-8?q?=E7=9C=81=E6=88=90=E9=9B=B6=E5=81=8F=E7=A7=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ui_editor/layout/node.rs 给 Node.offset 加 #[serde(default)],历史设计文档缺该字段时按零偏移解析 ui_editor/persistence.rs 补 legacy_nodes_without_offset_field_fall_back_to_zero_offset 锁定该回退口径 --- .../src-tauri/src/ui_editor/layout/node.rs | 2 + .../src-tauri/src/ui_editor/persistence.rs | 38 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/layout/node.rs b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/layout/node.rs index ae93e2bd2..17ecbd346 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/layout/node.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/layout/node.rs @@ -15,6 +15,8 @@ pub struct Node { pub component: Option, pub children_display_mode: ChildrenDisplayMode, pub children: Vec, + // 偏移量是后加的字段:历史设计文档没有它,默认回落到零偏移,保证旧文档仍可读。 + #[serde(default)] pub offset: NodeOffset, } diff --git a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/persistence.rs b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/persistence.rs index 6c64ebdf0..a95d17803 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/persistence.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/persistence.rs @@ -832,6 +832,7 @@ fn validate_id(value: &str, label: &str) -> Result<(), String> { #[cfg(test)] mod tests { use super::*; + use crate::ui_editor::layout::offset::NodeOffset; const PROJECT_ID: &str = "ui-design-persistence-project"; @@ -1444,6 +1445,43 @@ mod tests { .is_err()); } + /// 历史设计文档没有节点 `offset` 字段:缺字段必须按零偏移解析,不能判成缺字段损坏。 + #[test] + fn legacy_nodes_without_offset_field_fall_back_to_zero_offset() { + let mut value = serde_json::to_value(state_with_sprite_and_dragged_transform()) + .expect("serialize dragged UI design state"); + strip_offset_fields(&mut value); + let state: State = serde_json::from_value(value).expect("节点缺 offset 时仍可解析"); + assert_eq!( + state.ui_trees[0].root.offset, + NodeOffset::default(), + "根节点缺 offset 时回落零偏移" + ); + assert_eq!( + state.ui_trees[0].root.children[0].offset, + NodeOffset::default(), + "子节点缺 offset 时回落零偏移" + ); + } + + /// 递归删除夹具里所有节点级 `offset` 字段,模拟该字段引入前落盘的文档。 + fn strip_offset_fields(value: &mut serde_json::Value) { + match value { + serde_json::Value::Object(fields) => { + fields.remove("offset"); + for field in fields.values_mut() { + strip_offset_fields(field); + } + } + serde_json::Value::Array(items) => { + for item in items { + strip_offset_fields(item); + } + } + _ => {} + } + } + #[test] fn rejects_text_component_with_missing_font_asset() { let (directory, asset_id) = fixture();