feat: add AiCityTemperatureChart component and useAiPinnedCityWorkspace hook for deep-analysis city tracking

This commit is contained in:
2569718930@qq.com
2026-05-17 21:37:51 +08:00
parent 1645fd88d0
commit 3351991fe1
2 changed files with 25 additions and 10 deletions
@@ -78,14 +78,25 @@ export const AiCityTemperatureChart = memo(function AiCityTemperatureChart({ det
cityKey: string;
data: TemperatureChartData;
} | null>(null);
if (computedChartData) {
lastChartDataRef.current = { cityKey, data: computedChartData };
}
const chartData =
computedChartData ||
(lastChartDataRef.current?.cityKey === cityKey
? lastChartDataRef.current.data
: null);
// Use a memo so we never mutate refs during render (React anti-pattern).
// When cityKey changes, discard any stale cache; once computedChartData
// arrives, save it into the ref and use it.
const chartData = useMemo(() => {
if (lastChartDataRef.current && lastChartDataRef.current.cityKey !== cityKey) {
// City switched — clear stale cache so the old city's chart cannot
// bleed into the new city card while its detail is still loading.
lastChartDataRef.current = null;
}
if (computedChartData) {
lastChartDataRef.current = { cityKey, data: computedChartData };
return computedChartData;
}
if (lastChartDataRef.current?.cityKey === cityKey) {
return lastChartDataRef.current.data;
}
return null;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [cityKey, computedChartData]);
const forecastLabel = locale === "en-US" ? "DEB baseline" : "DEB 原始路径";
const calibratedLabel =
locale === "en-US"
@@ -205,6 +216,7 @@ export const AiCityTemperatureChart = memo(function AiCityTemperatureChart({ det
}, [
calibratedLabel,
chartData,
cityKey,
detail.temp_symbol,
forecastLabel,
hasCalibratedPath,
@@ -40,7 +40,6 @@ export function useAiPinnedCityWorkspace({
const nextCity = aiHydrationQueueRef.current.shift();
const key = normalizeCityKey(nextCity || "");
if (!nextCity || !key) continue;
const existingDetail = findDetailForCity(store.cityDetailsByName, nextCity);
try {
const detail = await store.ensureCityDetail(
nextCity,
@@ -72,7 +71,7 @@ export function useAiPinnedCityWorkspace({
void runAiHydrationQueue();
}
}
}, [store.cityDetailsByName, store.ensureCityDetail]);
}, [store.ensureCityDetail]);
const queueAiFullHydration = useCallback(
(cityName: string) => {
@@ -97,6 +96,10 @@ export function useAiPinnedCityWorkspace({
getLocalizedCityName(cleanName, prettyName || cleanName, locale) ||
prettyName ||
cleanName;
// Clear the hydration guard so that re-selecting this city always
// triggers a fresh hydration attempt (fixes second-city loading failure).
aiFullHydrationRef.current.delete(key);
aiHydrationRetriesRef.current.delete(key);
setAiPinnedCities((current) => {
const existing = current.findIndex(
(item) => normalizeCityKey(item.cityName) === key,