新建 UI 设计资源复用无冲突编号规则

- ui_editor/resource_bridge.rs:`next_ui_design_path` 提升为 `pub(crate)` 并补注释,作为两个写入侧唯一的「UI 设计 N」命名规则。
- commands.rs:`create_ui_design_resource` 不再按已登记文档数直接取名,改为复用该规则,resourceId 与文件名同一编号。
- tests/project.rs:新增已存在 `ui/UI 设计 1.json` 时仍能创建并落到编号 2 的回归用例。
This commit is contained in:
2026-09-16 23:24:15 +08:00
parent 41930b858e
commit d23c808376
3 changed files with 40 additions and 13 deletions
@@ -2197,17 +2197,8 @@ pub(crate) fn create_ui_design_resource(
if manifest.project_id != expected_project_id.trim() {
return Err("project-identity-conflict".to_string());
}
let next_index = manifest
.assets
.iter()
.filter(|asset| {
asset.kind == crate::ui_editor::persistence::UI_DESIGN_DOC_ASSET_KIND
&& asset.media_type == crate::ui_editor::persistence::UI_DESIGN_DOC_MEDIA_TYPE
})
.count()
+ 1;
let resource_name = format!("UI 设计 {next_index}");
let relative_path = format!("ui/{resource_name}.json");
let (resource_name, relative_path) =
crate::ui_editor::resource_bridge::next_ui_design_path(root, &manifest)?;
let absolute_path = resolve_local_project_path(root, &relative_path)?;
if let Some(parent) = absolute_path.parent() {
ensure_game_creator_private_directory_tree(parent, "UI 资源目录")?;
@@ -2243,7 +2234,10 @@ pub(crate) fn create_ui_design_resource(
GameCreationAppAssetSource {
kind: GameCreationAppAssetSourceKind::Generated,
canvas_project_id: None,
resource_id: Some(format!("ui:{next_index}")),
resource_id: Some(format!(
"ui:{}",
resource_name.trim_start_matches("UI 设计 ")
)),
asset_object_id: None,
task_id: None,
prompt: None,
@@ -2259,6 +2259,37 @@ fn register_local_asset_derives_category_from_real_write_side_kinds() {
fs::remove_dir_all(root).ok();
}
/// 已存在的 `ui/UI 设计 N.json` 不能让新建 UI 设计资源直接失败。
///
/// 编号按已登记的 UI 文档数推导,旧项目里 kind 已被收口为 `unknown` 的文档不再计入,
/// 只按计数取名就会撞上仍然存在的同名文件并报「路径已存在」。两个写入侧必须共用
/// `ui_editor::resource_bridge` 的「第一个空闲编号」规则,既不改名既有文件也不覆盖。
#[test]
fn create_ui_design_resource_skips_a_taken_ui_design_path() {
let root = unique_project_path();
init_local_game_project_at(&root, "project-1", "UI 设计编号避让").expect("project init");
write_local_project_file_at(&root, "ui/UI 设计 1.json", "{}").expect("ui asset file");
let result = crate::commands::create_ui_design_resource(
root.to_string_lossy().to_string(),
"project-1".to_string(),
)
.expect("create UI design resource");
assert_eq!(result.asset.local_path, "ui/UI 设计 2.json");
let registered = result
.manifest
.assets
.iter()
.find(|asset| asset.id == result.asset.id)
.expect("registered UI design asset");
assert_eq!(registered.source.resource_id.as_deref(), Some("ui:2"));
assert_eq!(registered.kind, GameCreationAppAssetKind::UiDesignDoc);
assert!(root.join("ui/UI 设计 1.json").is_file());
fs::remove_dir_all(root).ok();
}
#[test]
fn register_local_asset_rejects_missing_or_unsafe_path() {
let root = unique_project_path();
@@ -190,7 +190,9 @@ pub(crate) fn ensure_ui_design_resource_for_prototype(
})
}
fn next_ui_design_path(
/// UI 设计文档的确定性命名:从已登记文档数 + 1 起找第一个既未被占用、
/// 也未登记进 manifest 的 `ui/UI 设计 N.json`,不覆盖任何既有文件。
pub(crate) fn next_ui_design_path(
root: &Path,
manifest: &GameCreationAppManifest,
) -> Result<(String, String), String> {