修复城市决策卡切换时地图白屏和第二城市加载失败

- MapCanvas 改为始终挂载,视图切换用 display:none 隐藏而非卸载,
  避免 Leaflet 重初始化时容器尺寸为 0 导致白屏
- handleMapCitySelect 在 matchedRow 为空时主动调用 ensureCityDetail
  预加载城市详情,不再依赖 hydration 队列异步补拉
This commit is contained in:
2569718930@qq.com
2026-05-17 21:20:17 +08:00
parent deec8221ea
commit 9d8c59f741
2 changed files with 39 additions and 28 deletions
@@ -289,8 +289,15 @@ function ScanTerminalScreen() {
setMapSelectedCityName(cityName);
lastMapSelectedCityRef.current = normalizeCityKey(cityName);
const matchedRow = findRowForCity(timeSortedRows, cityName);
if (matchedRow) store.preloadCityFromRow(matchedRow);
setSelectedRowId(matchedRow?.id || null);
if (matchedRow) {
store.preloadCityFromRow(matchedRow);
setSelectedRowId(matchedRow.id);
} else {
// City not in scan rows — still preload its detail so the decision
// card can render immediately instead of showing a loading spinner.
void store.ensureCityDetail(cityName, false, "panel").catch(() => {});
setSelectedRowId(null);
}
addAiPinnedCity(cityName);
setActiveView("analysis");
}, [addAiPinnedCity, store, timeSortedRows]);
@@ -352,30 +359,6 @@ function ScanTerminalScreen() {
/>
);
}
if (resolvedView === "map") {
return (
<div className="scan-map-view">
<div className="scan-map-shell">
<MapCanvas
onCitySelect={handleMapCitySelect}
selectionMode="select"
/>
</div>
</div>
);
}
if (resolvedView === "analysis") {
return (
<AiPinnedForecastView
items={aiPinnedCities}
rows={timeSortedRows}
detailsByName={store.cityDetailsByName}
locale={locale}
onRefreshCityDetail={refreshAiPinnedCityDetail}
onRemoveCity={removeAiPinnedCity}
/>
);
}
if (!isPro) {
return (
<div className="scan-table-shell empty">
@@ -392,7 +375,35 @@ function ScanTerminalScreen() {
</div>
);
}
return null;
// Keep MapCanvas always mounted — hiding with CSS avoids Leaflet
// reinitialization that causes a white background on tab switches.
// The analysis view overlays on top when active.
return (
<>
<div
className="scan-map-view"
style={{ display: resolvedView === "map" ? undefined : "none" }}
>
<div className="scan-map-shell">
<MapCanvas
onCitySelect={handleMapCitySelect}
selectionMode="select"
/>
</div>
</div>
{resolvedView === "analysis" ? (
<AiPinnedForecastView
items={aiPinnedCities}
rows={timeSortedRows}
detailsByName={store.cityDetailsByName}
locale={locale}
onRefreshCityDetail={refreshAiPinnedCityDetail}
onRemoveCity={removeAiPinnedCity}
/>
) : null}
</>
);
};
if (proAccess.loading) {
@@ -78,7 +78,7 @@ export function runTests() {
"Ankara chart should build the DEB original path from MGM hourly data when Open-Meteo hourly is unavailable",
);
assert(
ankaraChartData?.datasets.debSeries.length >= 4,
(ankaraChartData?.datasets.debSeries.length ?? 0) >= 4,
"Ankara chart should build the DEB original path from MGM hourly data",
);
assert(