图表 stats bar 温度数字 60 秒级轻量轮询

liveTemp state 独立于 hourly 缓存,每 60s fetch /api/city/{city}/summary。
只取 current.temp,不触发 _analyze() 重算。
图表曲线保持 5min TTL,顶部数字最快 1min 级更新。
This commit is contained in:
2569718930@qq.com
2026-05-26 08:30:33 +08:00
parent 5b19a49ffb
commit 23203f6ebb
2 changed files with 26 additions and 3 deletions
@@ -1117,6 +1117,7 @@ export function LiveTemperatureThresholdChart({
const city = String(row?.city || "").toLowerCase().trim();
const [timeframe, setTimeframe] = useState<"1D" | "3D">("1D");
const [userToggledKeys, setUserToggledKeys] = useState<Record<string, boolean>>({});
const [liveTemp, setLiveTemp] = useState<number | null>(null);
useEffect(() => {
setUserToggledKeys({});
@@ -1167,6 +1168,24 @@ export function LiveTemperatureThresholdChart({
};
}, [city, row, isActive, slotIndex]);
// ── 60s lightweight live-temp poll ──
useEffect(() => {
if (!city) return;
const fetchLiveTemp = () => {
fetch(`/api/city/${encodeURIComponent(city)}/summary`)
.then((res) => (res.ok ? res.json() : null))
.then((payload) => {
if (!payload) return;
const temp = validNumber(payload?.current?.temp);
if (temp !== null) setLiveTemp(temp);
})
.catch(() => {});
};
fetchLiveTemp();
const id = setInterval(fetchLiveTemp, 60_000);
return () => clearInterval(id);
}, [city]);
const { data, series } = useMemo(() => {
if (timeframe === "3D") {
return build3DayChartData(row, hourly);
@@ -1247,6 +1266,7 @@ export function LiveTemperatureThresholdChart({
() => getObservationDisplayMetrics(row, hourly, settlementPlate),
[row, hourly, settlementPlate],
);
const displayRunwayTemp = liveTemp ?? currentRunwayTemp;
const wundergroundDailyHigh = validNumber(hourly?.airportCurrent?.max_so_far ?? hourly?.airportPrimary?.max_so_far) ?? null;
const modelValues = Object.values(row?.model_cluster_sources || {})
@@ -1412,7 +1432,7 @@ export function LiveTemperatureThresholdChart({
<div className="flex items-center gap-4 text-[11px]">
<span className="font-semibold text-slate-500">
{isEn ? "Runway" : runwayHeaderLabel}:{" "}
<strong className="text-[#009688] font-mono">{temp(currentRunwayTemp)}</strong>
<strong className="text-[#009688] font-mono">{temp(displayRunwayTemp)}</strong>
</span>
<span className="text-slate-300">|</span>
<span className="font-semibold text-slate-500">
@@ -1454,7 +1474,7 @@ export function LiveTemperatureThresholdChart({
{isEn ? "Runway Live (1m)" : `${runwayHeaderLabel}`}
</span>
<span className="text-2xl font-bold font-mono text-[#009688] mt-1">
{temp(currentRunwayTemp)}
{temp(displayRunwayTemp)}
</span>
</div>
<div className="flex flex-col">
@@ -34,10 +34,13 @@ export function runTests() {
);
assert(
chartSource.includes("DASHBOARD_REFRESH_POLICY_MS.metar") &&
!chartSource.includes("setInterval(") &&
!chartSource.includes("window.setInterval"),
"selected city detail chart cache should align with 5-minute scan/metar cadence",
);
assert(
chartSource.includes("setInterval(fetchLiveTemp, 60_000)"),
"selected city chart should poll live temperature every 60 seconds via lightweight summary endpoint",
);
assert(
chartSource.includes("_hourlyRequestCache") &&
chartSource.includes("seedHourlyForecastFromRow") &&