From 4b4e1834ac5bb9d6e862c6ad3590bf18909d67e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=81=B5=E5=BA=94?= Date: Sun, 5 Jul 2026 00:29:04 +0800 Subject: [PATCH] =?UTF-8?q?feat(assets):=20=E7=B4=A0=E6=9D=90=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E5=8A=A0=E8=BD=BD=E9=AA=A8=E6=9E=B6=E5=B1=8F=20+=20?= =?UTF-8?q?=E8=AF=8A=E6=96=AD=E6=8C=89=E9=92=AE=20loading=20=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 素材列表加载时显示 8 张骨架屏卡片(脉冲动画),替代空白 loading 提示 - 诊断按钮点击后显示 loading 状态(LoadingOutlined + 禁用),防止重复点击 - 新增 SkeletonCard 组件和骨架屏 CSS 动画样式 Closes: 代码审计 P3-素材库缺 loading 态 --- apps/web/src/pages/assets/AssetLibrary.tsx | 41 +++++++++++++++++----- apps/web/src/pages/assets/assets.css | 33 +++++++++++++++++ 2 files changed, 66 insertions(+), 8 deletions(-) diff --git a/apps/web/src/pages/assets/AssetLibrary.tsx b/apps/web/src/pages/assets/AssetLibrary.tsx index e95ec7f47..aebf04b63 100644 --- a/apps/web/src/pages/assets/AssetLibrary.tsx +++ b/apps/web/src/pages/assets/AssetLibrary.tsx @@ -16,6 +16,7 @@ import { CheckOutlined, DeleteOutlined, ExperimentOutlined, + LoadingOutlined, } from "@ant-design/icons"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { @@ -185,16 +186,31 @@ const StatusPill: React.FC<{ status: StatusType; label: string }> = ({ {label} ); +/* ============================================================ + * SkeletonCard — 骨架屏卡片(素材列表加载时占位) + * ============================================================ */ +const SkeletonCard: React.FC = () => ( +
+
+
+
+
+
+
+
+); + /* ============================================================ * AssetCard 组件 * ============================================================ */ const AssetCard: React.FC<{ asset: AssetItem; selected: boolean; + diagnosing?: boolean; onToggle: () => void; onDiagnose: () => void; onPlay: () => void; -}> = ({ asset, selected, onToggle, onDiagnose, onPlay }) => ( +}> = ({ asset, selected, diagnosing, onToggle, onDiagnose, onPlay }) => (
{asset.duration}}
@@ -338,6 +355,9 @@ const AssetLibrary: React.FC = () => { /* 视频播放 */ const [playingAsset, setPlayingAsset] = useState(null); + /* 诊断中状态 — 记录正在诊断的素材 ID */ + const [diagnosingId, setDiagnosingId] = useState(null); + /* 派生数据 */ const filteredAssets = useMemo(() => { let list = assets; @@ -449,8 +469,9 @@ const AssetLibrary: React.FC = () => { } }; - /* 诊断 — 调用真实 API */ + /* 诊断 — 调用真实 API,带 loading 状态 */ const handleDiagnose = async (asset: AssetItem) => { + setDiagnosingId(asset.id); try { const result = await getAssetDiagnosis(asset.id); const score = result.readiness_score ?? "-"; @@ -458,6 +479,8 @@ const AssetLibrary: React.FC = () => { queryClient.invalidateQueries({ queryKey: ["assets"] }); } catch { message.error(`"${asset.name}" 诊断失败`); + } finally { + setDiagnosingId(null); } }; @@ -647,9 +670,10 @@ const AssetLibrary: React.FC = () => { {/* 素材网格 */} {assetsLoading ? ( -
-
⏳
-

加载素材中...

+
+ {Array.from({ length: 8 }).map((_, i) => ( + + ))}
) : assetsError ? (
@@ -670,6 +694,7 @@ const AssetLibrary: React.FC = () => { key={asset.id} asset={asset} selected={selectedIds.has(asset.id)} + diagnosing={diagnosingId === asset.id} onToggle={() => toggleSelect(asset.id)} onDiagnose={() => handleDiagnose(asset)} onPlay={() => setPlayingAsset(asset)} diff --git a/apps/web/src/pages/assets/assets.css b/apps/web/src/pages/assets/assets.css index a51ad36b5..3dd72c6d9 100644 --- a/apps/web/src/pages/assets/assets.css +++ b/apps/web/src/pages/assets/assets.css @@ -357,6 +357,39 @@ color: var(--info-color); } +/* ============================================================ + 骨架屏(素材列表加载占位) + ============================================================ */ +@keyframes xx-skeleton-pulse { + 0% { opacity: 1; } + 50% { opacity: 0.4; } + 100% { opacity: 1; } +} + +.xx-asset-skeleton { + pointer-events: none; +} + +.xx-asset-skeleton .xx-asset-thumb { + background: var(--bg-tertiary, #e5e7eb); +} + +.xx-skeleton-pulse { + animation: xx-skeleton-pulse 1.5s ease-in-out infinite; +} + +.xx-skeleton-line { + height: 14px; + border-radius: var(--radius-xs); + background: var(--bg-tertiary, #e5e7eb); +} + +/* 诊断按钮 loading 态 */ +.xx-asset-diagnose-btn-loading { + opacity: 0.7; + cursor: not-allowed; +} + /* ============================================================ 空状态 ============================================================ */