From e1044546add40f089f7389561e0c775aa8491e9e Mon Sep 17 00:00:00 2001 From: Suzumiya Date: Thu, 10 Sep 2026 10:53:13 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8E=A5=E5=85=A5=E7=B4=A0=E6=9D=90=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E5=85=A5=E5=8F=A3=E5=B9=B6=E6=94=B6=E6=95=9B=E8=B5=84?= =?UTF-8?q?=E6=BA=90=E5=91=BD=E4=BB=A4=E5=90=8E=E7=9A=84=20manifest=20?= =?UTF-8?q?=E9=87=8D=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ResourceClassificationPanel 增加删除动作:首次点击进入确认态,二次点击才执行,避免误触不可逆操作 - 面板标题与按钮明确写出只移除登记、素材文件保留在磁盘 - 删除失败时把客户端的拒绝原因原样呈现(例如已被运行槽位绑定),并退出确认态 - index.tsx 抽出 reloadManifestAfterAssetCommand:资源分类更新与素材删除共用同一条重载路径,提交标识分别用 asset-classification / asset-delete 前缀 - 补 2 条面板测试:两次点击才删除且入参带 expectedProjectRevision、被运行槽位拒绝时不触发 onDeleted --- .../ResourceClassificationPanel.tsx | 77 ++++++++++++++- .../src/view/project-development/index.tsx | 39 ++++++-- .../resourceClassificationPanel.test.tsx | 95 +++++++++++++++++++ 3 files changed, 202 insertions(+), 9 deletions(-) diff --git a/apps/ai-game-creator-shell/src/view/project-development/ResourceClassificationPanel.tsx b/apps/ai-game-creator-shell/src/view/project-development/ResourceClassificationPanel.tsx index d8878127b..ebb1a1111 100644 --- a/apps/ai-game-creator-shell/src/view/project-development/ResourceClassificationPanel.tsx +++ b/apps/ai-game-creator-shell/src/view/project-development/ResourceClassificationPanel.tsx @@ -19,6 +19,13 @@ type UpdateLocalProjectResourceClassificationResult = { committedProjectRevision: number; }; +type DeleteLocalProjectAssetResult = { + assetId: string; + localPath: string; + committedProjectRevision: number; + fileRetained: boolean; +}; + const RESOURCE_CLASSIFICATION_CATEGORY_OPTIONS = GAME_CREATION_APP_ASSET_CATEGORIES.map((category) => ({ id: category, @@ -36,12 +43,19 @@ function resourceClassificationErrorMessage(error: unknown) { return '保存资源分类与标签失败'; } +function resourceDeleteErrorMessage(error: unknown) { + if (typeof error === 'string' && error.trim()) return error; + if (error instanceof Error && error.message) return error.message; + return '删除资源失败'; +} + type ResourceClassificationPanelProps = { projectPath: string; projectId: string; asset: GameCreationAppAssetManifestEntry; onClose: () => void; onSaved: (result: UpdateLocalProjectResourceClassificationResult) => void; + onDeleted: (result: DeleteLocalProjectAssetResult) => void; }; export function ResourceClassificationPanel({ @@ -50,6 +64,7 @@ export function ResourceClassificationPanel({ asset, onClose, onSaved, + onDeleted, }: ResourceClassificationPanelProps) { const [category, setCategory] = useState(() => gameCreationAppAssetCategory(asset), @@ -58,6 +73,8 @@ export function ResourceClassificationPanel({ gameCreationAppAssetTags(asset).join('、'), ); const [saving, setSaving] = useState(false); + const [deleting, setDeleting] = useState(false); + const [deleteArmed, setDeleteArmed] = useState(false); const [error, setError] = useState(null); async function saveResourceClassification() { @@ -98,6 +115,51 @@ export function ResourceClassificationPanel({ } } + /** + * 删除素材登记。素材不可变:只摘掉 manifest 登记,磁盘文件保留。 + * 第一次点击只进入确认态,避免误触这个不可逆操作。 + */ + async function deleteResourceClassificationAsset() { + if (!deleteArmed) { + setDeleteArmed(true); + setError(null); + return; + } + const invoke = window.__TAURI__?.core?.invoke; + if (!invoke) { + setError('删除资源需要在客户端内执行'); + return; + } + setDeleting(true); + setError(null); + try { + const status = await invoke<{ revision: number }>( + 'get_local_game_project_revision', + { projectPath }, + ); + if (!Number.isSafeInteger(status.revision) || status.revision < 0) { + throw new Error('项目 revision 无效'); + } + const result = await invoke( + 'delete_local_project_asset', + { + input: { + projectPath, + expectedProjectId: projectId, + expectedProjectRevision: status.revision, + assetId: asset.id, + }, + }, + ); + onDeleted(result); + } catch (deleteError) { + setDeleteArmed(false); + setError(resourceDeleteErrorMessage(deleteError)); + } finally { + setDeleting(false); + } + } + return (