修复终端首屏图表加载动画
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user