From 8166f2c9395cd963d1a83b40eccef634c7a7f071 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Sat, 18 Jul 2026 14:26:43 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=B4=A0=E6=9D=90?= =?UTF-8?q?=E5=BA=93=E6=89=B9=E9=87=8F=E5=88=A0=E9=99=A4=E5=90=8E=E7=99=BD?= =?UTF-8?q?=E5=B1=8F=E5=B4=A9=E6=BA=83=EF=BC=8C=E6=89=B9=E9=87=8F=E6=93=8D?= =?UTF-8?q?=E4=BD=9C=E7=BB=93=E6=9E=9C=E5=BD=92=E4=B8=80=E5=8C=96=20+=20?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E9=98=B2=E5=BE=A1=E6=80=A7=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因:batchDeleteAssets 直接返回 response.data,当后端返回字段缺失或格式不一致时, Drawer 中 operationResult.succeeded.length 会报 Cannot read properties of undefined (reading 'length'), 导致整个页面白屏崩溃。 修复: 1. API 层新增 normalizeBatchResult(),统一归一化4个批量操作返回值,succeeded/failed 缺失时默认空数组 2. AssetLibrary 组件层增加 Array.isArray 防御,apiAssets / apiLibraries 非数组时兜底为空数组 3. 覆盖场景:删除全部素材、删除部分素材、删除后再上传,均不会白屏 --- apps/web/src/api/assets.ts | 18 ++++++++++++++---- apps/web/src/pages/assets/AssetLibrary.tsx | 10 ++++++++-- 2 files changed, 22 insertions(+), 6 deletions(-) mode change 100644 => 100755 apps/web/src/api/assets.ts mode change 100644 => 100755 apps/web/src/pages/assets/AssetLibrary.tsx diff --git a/apps/web/src/api/assets.ts b/apps/web/src/api/assets.ts old mode 100644 new mode 100755 index 987590a79..162ea74c9 --- a/apps/web/src/api/assets.ts +++ b/apps/web/src/api/assets.ts @@ -390,6 +390,16 @@ export interface BatchOperationResult { failure_count: number; } +/** 统一批量操作结果归一化,防御后端字段缺失或格式不一致 */ +const normalizeBatchResult = (raw: Record): BatchOperationResult => { + const succeeded = Array.isArray(raw.succeeded) ? raw.succeeded as string[] : []; + const failed = Array.isArray(raw.failed) ? raw.failed as string[] : []; + const success_count = typeof raw.success_count === "number" ? raw.success_count : succeeded.length; + const failure_count = typeof raw.failure_count === "number" ? raw.failure_count : failed.length; + const total = typeof raw.total === "number" ? raw.total : success_count + failure_count; + return { succeeded, failed, total, success_count, failure_count }; +}; + /** 批量删除素材 */ export const batchDeleteAssets = async ( assetIds: string[], @@ -397,7 +407,7 @@ export const batchDeleteAssets = async ( const response = await apiClient.post("/assets/batch-delete", { asset_ids: assetIds, }); - return response.data; + return normalizeBatchResult((response.data || {}) as Record); }; /** 批量打标签 */ @@ -407,7 +417,7 @@ export const batchTagAssets = async (data: { mode: "add" | "replace"; }): Promise => { const response = await apiClient.post("/assets/batch-tag", data); - return response.data; + return normalizeBatchResult((response.data || {}) as Record); }; /** 批量改分类 */ @@ -416,7 +426,7 @@ export const batchClassifyAssets = async (data: { category: string; }): Promise => { const response = await apiClient.post("/assets/batch-classify", data); - return response.data; + return normalizeBatchResult((response.data || {}) as Record); }; /** 批量智能标记 */ @@ -425,5 +435,5 @@ export const batchMarkAssets = async (data: { smart_view: "recommended" | "caution" | "high_risk"; }): Promise => { const response = await apiClient.post("/assets/batch-mark", data); - return response.data; + return normalizeBatchResult((response.data || {}) as Record); }; diff --git a/apps/web/src/pages/assets/AssetLibrary.tsx b/apps/web/src/pages/assets/AssetLibrary.tsx old mode 100644 new mode 100755 index d10f881c3..35555cd9d --- a/apps/web/src/pages/assets/AssetLibrary.tsx +++ b/apps/web/src/pages/assets/AssetLibrary.tsx @@ -355,7 +355,10 @@ const AssetLibrary: React.FC = () => { staleTime: 60_000, }); - const libraries = useMemo(() => apiLibraries.map(mapLibrary), [apiLibraries]); + const libraries = useMemo( + () => (Array.isArray(apiLibraries) ? apiLibraries : []).map(mapLibrary), + [apiLibraries], + ); /* ── 当前选中的素材库 ── */ const [activeLibId, setActiveLibId] = useState(""); @@ -377,7 +380,10 @@ const AssetLibrary: React.FC = () => { staleTime: 30_000, }); - const assets = useMemo(() => apiAssets.map(mapAsset), [apiAssets]); + const assets = useMemo( + () => (Array.isArray(apiAssets) ? apiAssets : []).map(mapAsset), + [apiAssets], + ); /* ── Mutations ── */ const createLibMutation = useMutation({ -- 2.54.0 From 4a73d810d2f641333e73dc83c720c3c2eda7aeb5 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Sat, 18 Jul 2026 15:18:38 +0800 Subject: [PATCH 2/2] style: fix prettier formatting in assets.ts --- apps/web/src/api/assets.ts | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/apps/web/src/api/assets.ts b/apps/web/src/api/assets.ts index 162ea74c9..55dd6706c 100755 --- a/apps/web/src/api/assets.ts +++ b/apps/web/src/api/assets.ts @@ -391,12 +391,21 @@ export interface BatchOperationResult { } /** 统一批量操作结果归一化,防御后端字段缺失或格式不一致 */ -const normalizeBatchResult = (raw: Record): BatchOperationResult => { - const succeeded = Array.isArray(raw.succeeded) ? raw.succeeded as string[] : []; - const failed = Array.isArray(raw.failed) ? raw.failed as string[] : []; - const success_count = typeof raw.success_count === "number" ? raw.success_count : succeeded.length; - const failure_count = typeof raw.failure_count === "number" ? raw.failure_count : failed.length; - const total = typeof raw.total === "number" ? raw.total : success_count + failure_count; +const normalizeBatchResult = ( + raw: Record, +): BatchOperationResult => { + const succeeded = Array.isArray(raw.succeeded) + ? (raw.succeeded as string[]) + : []; + const failed = Array.isArray(raw.failed) ? (raw.failed as string[]) : []; + const success_count = + typeof raw.success_count === "number" + ? raw.success_count + : succeeded.length; + const failure_count = + typeof raw.failure_count === "number" ? raw.failure_count : failed.length; + const total = + typeof raw.total === "number" ? raw.total : success_count + failure_count; return { succeeded, failed, total, success_count, failure_count }; }; -- 2.54.0