修复图表时间轴不全与城市决策卡模型补齐卡顿

- 后端 Open-Meteo 请求加 past_days=1,小时数据从 00:00 开始
- 前端图表层填充完整 00:00-23:00 时间轴,缺失时段置空
- 城市深度分析门槛从 >1 降为 >=1,单模型城市不再被拦
- hydration 队列加最大重试 3 次,防止永久卡在等待模型补齐
- 移除右侧面板历史对账按钮
This commit is contained in:
2569718930@qq.com
2026-05-16 20:14:05 +08:00
parent b27bedbca3
commit 2e02133696
5 changed files with 37 additions and 38 deletions
+5 -30
View File
@@ -6,7 +6,6 @@ import { useRouter } from "next/navigation";
import { useEffect, useMemo, useRef, useState } from "react";
import { ForecastTable } from "@/components/dashboard/PanelSections";
import {
useDashboardHistory,
useDashboardModal,
useDashboardStore,
useProAccess,
@@ -38,7 +37,6 @@ export function DetailPanel({
} = {}) {
const store = useDashboardStore();
const modal = useDashboardModal();
const history = useDashboardHistory();
const { proAccess } = useProAccess();
const { locale, t } = useI18n();
const router = useRouter();
@@ -76,7 +74,7 @@ export function DetailPanel({
);
const [heavyContentReady, setHeavyContentReady] = useState(false);
const isOverlayOpen =
Boolean(modal.futureModalDate) || history.historyState.isOpen;
Boolean(modal.futureModalDate);
const isVisible = isRail
? Boolean(store.selectedCity) && !isOverlayOpen
: store.isPanelOpen && Boolean(store.selectedCity) && !isOverlayOpen;
@@ -139,24 +137,20 @@ export function DetailPanel({
}
};
const handleFeatureAccess = (feature: "today" | "history") => {
const handleTodayAccess = () => {
blurActiveElement();
if (!isPro) {
trackAppEvent("paywall_feature_clicked", {
entry: "detail_panel",
feature,
feature: "today",
city: store.selectedCity,
user_state: isAuthenticated ? "logged_in" : "guest",
});
}
if (isPro) {
if (feature === "today") {
void modal.openTodayModal();
return;
}
void history.openHistory();
void modal.openTodayModal();
return;
}
@@ -165,11 +159,7 @@ export function DetailPanel({
return;
}
if (feature === "today") {
void modal.openTodayModal();
return;
}
void history.openHistory();
void modal.openTodayModal();
};
useEffect(() => {
@@ -348,21 +338,6 @@ export function DetailPanel({
{locale === "en-US" ? "Market" : "市场页"}
</a>
) : null}
<button
type="button"
className={clsx(
"panel-action-button",
"panel-action-button-secondary",
!isPro && "pro-locked",
)}
title={
isPro ? t("detail.history") : `${t("detail.history")} (Pro)`
}
onClick={() => handleFeatureAccess("history")}
disabled={!store.selectedCity}
>
{isPro ? t("detail.history") : `${t("detail.history")} · Pro`}
</button>
</div>
</div>
</div>
@@ -47,8 +47,8 @@ export function isFullEnoughForDeepAnalysis(detail?: CityDetail | null) {
return false;
}
return (
countDetailModels(detail, detail.local_date) > 1 &&
countDetailForecastDays(detail) > 1
countDetailModels(detail, detail.local_date) >= 1 &&
countDetailForecastDays(detail) >= 1
);
}
@@ -30,6 +30,7 @@ export function useAiPinnedCityWorkspace({
const aiFullHydrationRef = useRef<Set<string>>(new Set());
const aiHydrationQueueRef = useRef<string[]>([]);
const aiHydrationRunningRef = useRef(false);
const aiHydrationRetriesRef = useRef<Map<string, number>>(new Map());
const runAiHydrationQueue = useCallback(async () => {
if (aiHydrationRunningRef.current) return;
@@ -47,10 +48,22 @@ export function useAiPinnedCityWorkspace({
"full",
);
if (!isFullEnoughForDeepAnalysis(detail)) {
aiFullHydrationRef.current.delete(key);
const retries = aiHydrationRetriesRef.current.get(key) || 0;
if (retries >= 3) {
aiFullHydrationRef.current.delete(key);
aiHydrationRetriesRef.current.delete(key);
} else {
aiHydrationRetriesRef.current.set(key, retries + 1);
}
}
} catch {
aiFullHydrationRef.current.delete(key);
const retries = aiHydrationRetriesRef.current.get(key) || 0;
if (retries >= 3) {
aiFullHydrationRef.current.delete(key);
aiHydrationRetriesRef.current.delete(key);
} else {
aiHydrationRetriesRef.current.set(key, retries + 1);
}
}
}
} finally {
@@ -132,6 +145,8 @@ export function useAiPinnedCityWorkspace({
aiPinnedCities.forEach((item) => {
const key = normalizeCityKey(item.cityName);
if (!key || aiFullHydrationRef.current.has(key)) return;
const retries = aiHydrationRetriesRef.current.get(key) || 0;
if (retries >= 3) return;
const detail = findDetailForCity(store.cityDetailsByName, item.cityName);
const needsFullHydration = !isFullEnoughForDeepAnalysis(detail);
if (!needsFullHydration) return;
+12 -4
View File
@@ -376,10 +376,18 @@ export function getTemperatureChartData(
value: Number(rawTemps[index]),
}))
.filter((entry) => entry.tail !== "");
const times = validEntries.map((entry) => entry.tail);
const temps = validEntries.map((entry) =>
Number.isFinite(entry.value) ? entry.value : null,
);
const dataByHour = new Map<string, number | null>();
validEntries.forEach((entry) => {
dataByHour.set(entry.tail, Number.isFinite(entry.value) ? entry.value : null);
});
const times: string[] = [];
const temps: Array<number | null> = [];
for (let h = 0; h < 24; h++) {
const hh = String(h).padStart(2, "0");
const key = `${hh}:00`;
times.push(key);
temps.push(dataByHour.has(key) ? dataByHour.get(key)! : null);
}
const suppressAnkaraMgmObservation = isTurkishMgmCity(detail);
if (!times.length) return null;
@@ -386,6 +386,7 @@ class NwsOpenMeteoSourceMixin:
"daily": "temperature_2m_max,apparent_temperature_max,sunrise,sunset,sunshine_duration",
"timezone": "auto",
"forecast_days": forecast_days,
"past_days": 1,
}
# 显式指定单位,防止 API 默认行为漂移