修复终端首屏图表加载动画

This commit is contained in:
2569718930@qq.com
2026-06-16 04:29:24 +08:00
parent 14e356c3c9
commit 1afd3c6976
3 changed files with 82 additions and 3 deletions
@@ -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 (
<div
onClick={onSelectSlot}
className={clsx(
"relative flex h-full min-h-0 flex-col overflow-hidden rounded-[4px] border bg-white cursor-default",
isActive ? "border-blue-500 ring-2 ring-blue-500/20 shadow-md z-10" : "border-[#d2d9e2]",
)}
>
<div className="flex h-9 shrink-0 items-center justify-between border-b border-slate-200 px-3">
<div className="flex min-w-0 items-center gap-2 text-[11px] font-bold text-slate-600">
<span className="h-2 w-2 animate-pulse rounded-full bg-blue-500" />
<span className="truncate">{cityLabel}</span>
<span className="text-slate-300">·</span>
<span className="text-slate-400">{isEn ? "Loading chart" : "加载图表"}</span>
</div>
<span className="h-3 w-3 animate-spin rounded-full border-2 border-slate-200 border-t-blue-500" />
</div>
<div className="relative min-h-0 flex-1 overflow-hidden bg-white">
<div className="absolute inset-x-3 bottom-7 top-5 rounded-sm border border-slate-100">
{Array.from({ length: 6 }).map((_, index) => (
<span
key={`h-${index}`}
className="absolute left-0 right-0 border-t border-dashed border-sky-100"
style={{ top: `${(index / 5) * 100}%` }}
/>
))}
{Array.from({ length: 7 }).map((_, index) => (
<span
key={`v-${index}`}
className="absolute bottom-0 top-0 border-l border-dashed border-sky-100"
style={{ left: `${(index / 6) * 100}%` }}
/>
))}
<div className="absolute left-8 right-8 top-1/3 h-10 animate-pulse rounded bg-gradient-to-r from-slate-100 via-blue-100 to-slate-100" />
<div className="absolute inset-y-0 -left-1/3 w-1/3 animate-[pulse_1.2s_ease-in-out_infinite] bg-gradient-to-r from-transparent via-white/80 to-transparent" />
</div>
</div>
</div>
);
}
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 (
<LoadingSlotCard
key={slotIndex}
city={cityInSlot}
isActive={isSlotActive}
isEn={isEn}
onSelectSlot={() => setActiveSlotIndex(slotIndex)}
/>
);
}
if (!cityInSlot || !rowForSlot) {
return (
@@ -89,7 +89,8 @@ function TemperatureChartSkeleton({ compact }: { compact: boolean }) {
style={{ left: `${(index / Math.max(1, verticalLines - 1)) * 100}%` }}
/>
))}
<div className="absolute inset-x-10 top-1/3 h-10 rounded bg-slate-100/60" />
<div className="absolute inset-x-10 top-1/3 h-10 animate-pulse rounded bg-gradient-to-r from-slate-100 via-blue-100 to-slate-100" />
<div className="absolute inset-y-0 -left-1/3 w-1/3 animate-pulse bg-gradient-to-r from-transparent via-white/80 to-transparent" />
</div>
</div>
);
@@ -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,