From 1afd3c69762210a3f8372e6f927ddc064b792c90 Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Tue, 16 Jun 2026 04:29:24 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=BB=88=E7=AB=AF=E9=A6=96?= =?UTF-8?q?=E5=B1=8F=E5=9B=BE=E8=A1=A8=E5=8A=A0=E8=BD=BD=E5=8A=A8=E7=94=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dashboard/ScanTerminalDashboard.tsx | 71 +++++++++++++++++++ .../scan-terminal/TemperatureChartCanvas.tsx | 3 +- .../__tests__/terminalGridPolicy.test.ts | 11 ++- 3 files changed, 82 insertions(+), 3 deletions(-) diff --git a/frontend/components/dashboard/ScanTerminalDashboard.tsx b/frontend/components/dashboard/ScanTerminalDashboard.tsx index 70762753..45ea84c3 100644 --- a/frontend/components/dashboard/ScanTerminalDashboard.tsx +++ b/frontend/components/dashboard/ScanTerminalDashboard.tsx @@ -499,6 +499,64 @@ function EmptySlotCard({ ); } +function LoadingSlotCard({ + city, + isActive, + isEn, + onSelectSlot, +}: { + city: string; + isActive: boolean; + isEn: boolean; + onSelectSlot: () => void; +}) { + const cityLabel = city + .split(/[-_\s]+/) + .filter(Boolean) + .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) + .join(" ") || city; + + return ( +
+
+
+ + {cityLabel} + · + {isEn ? "Loading chart" : "加载图表"} +
+ +
+
+
+ {Array.from({ length: 6 }).map((_, index) => ( + + ))} + {Array.from({ length: 7 }).map((_, index) => ( + + ))} +
+
+
+
+
+ ); +} + const TerminalSidebar = memo(function TerminalSidebar({ activeNavKey, isEn, @@ -1186,6 +1244,19 @@ function PolyWeatherTerminal({ (r) => String(r.city || "").toLowerCase() === cityInSlot ) || null : null; + const isSavedSlotLoading = Boolean(cityInSlot && !rowForSlot && refreshing); + + if (isSavedSlotLoading && cityInSlot) { + return ( + setActiveSlotIndex(slotIndex)} + /> + ); + } if (!cityInSlot || !rowForSlot) { return ( diff --git a/frontend/components/dashboard/scan-terminal/TemperatureChartCanvas.tsx b/frontend/components/dashboard/scan-terminal/TemperatureChartCanvas.tsx index f3f51093..10e89d76 100644 --- a/frontend/components/dashboard/scan-terminal/TemperatureChartCanvas.tsx +++ b/frontend/components/dashboard/scan-terminal/TemperatureChartCanvas.tsx @@ -89,7 +89,8 @@ function TemperatureChartSkeleton({ compact }: { compact: boolean }) { style={{ left: `${(index / Math.max(1, verticalLines - 1)) * 100}%` }} /> ))} -
+
+
); diff --git a/frontend/components/dashboard/scan-terminal/__tests__/terminalGridPolicy.test.ts b/frontend/components/dashboard/scan-terminal/__tests__/terminalGridPolicy.test.ts index 25fcc4eb..87b35d13 100644 --- a/frontend/components/dashboard/scan-terminal/__tests__/terminalGridPolicy.test.ts +++ b/frontend/components/dashboard/scan-terminal/__tests__/terminalGridPolicy.test.ts @@ -101,9 +101,10 @@ export function runTests() { "terminal guide must describe the new 3x2 maximum layout instead of 3x3", ); assert( - dashboardSource.includes("if (!cityInSlot || !rowForSlot)") && + dashboardSource.includes("LoadingSlotCard") && + dashboardSource.includes("cityInSlot && !rowForSlot && refreshing") && dashboardSource.includes("handleSelectCityForSlot(slotIndex, null);"), - "stale saved chart slots must render the empty city picker instead of a row=null Temperature Chart", + "terminal must show a loading chart card for saved cities while the first scan rows are still loading, then fall back to the empty picker for stale slots", ); assert( dashboardSource.includes("absolute left-1/2 top-12 z-50") && @@ -223,6 +224,12 @@ export function runTests() { }), "temperature chart should show the loading skeleton while the first detail fetch is in flight and no drawable data exists", ); + assert( + chartCanvasSource.includes("animate-pulse") && + chartCanvasSource.includes("Loading chart") && + chartCanvasSource.includes("bg-gradient-to-r"), + "temperature chart loading skeleton must include visible animation on cold first-open charts", + ); assert( !__shouldKeepTemperatureChartLoadingForTest({ row: { city: "Moscow" } as any,