修复 fallback fetch 依赖:rows.length 改为 useRef 标记,避免 terminalData 刷新时无意义 re-run

This commit is contained in:
2569718930@qq.com
2026-05-26 07:59:55 +08:00
parent 598cd17c0b
commit c331c89289
@@ -43,7 +43,7 @@ import { scanRootClass } from "@/components/dashboard/scan-root-styles";
import { useRelativeTime } from "@/hooks/useRelativeTime";
import { Panel } from "@/components/dashboard/scan-terminal/Panel";
import { TrainingDashboard } from "@/components/dashboard/scan-terminal/TrainingDashboard";
import { LiveTemperatureThresholdChart } from "@/components/dashboard/scan-terminal/LiveTemperatureThresholdChart";
import { LiveTemperatureThresholdChart, clearCityDetailCache } from "@/components/dashboard/scan-terminal/LiveTemperatureThresholdChart";
import { KoyfinRowsTable } from "@/components/dashboard/scan-terminal/KoyfinRowsTable";
import { rowName, pct, money, temp, edgeClass } from "@/components/dashboard/scan-terminal/utils";
import { CitySelectorDropdown } from "@/components/dashboard/scan-terminal/CitySelectorDropdown";
@@ -400,26 +400,23 @@ function PolyWeatherTerminal({
const totalSlots = getSlotCount(gridCols, gridRows);
const [slots, setSlots] = useState<Array<string | null>>(() => {
const storedCols = getStoredGridSide("polyweather_terminal_grid_cols");
const storedRows = getStoredGridSide("polyweather_terminal_grid_rows");
const initialSlotCount = getSlotCount(storedCols, storedRows);
if (typeof window !== "undefined") {
try {
const stored = localStorage.getItem("polyweather_terminal_slots");
if (stored) {
const parsed = JSON.parse(stored);
if (Array.isArray(parsed)) {
return normalizeSlotList(parsed, initialSlotCount);
return normalizeSlotList(parsed, MAX_TERMINAL_CHARTS);
}
}
} catch {}
}
return Array(initialSlotCount).fill(null);
return Array(MAX_TERMINAL_CHARTS).fill(null);
});
const [activeSlotIndex, setActiveSlotIndex] = useState<number>(0);
const [maximizedSlotIndex, setMaximizedSlotIndex] = useState<number | null>(null);
const [activeSearchSlotIndex, setActiveSearchSlotIndex] = useState<number | null>(null);
const visibleSlots = useMemo(() => normalizeSlotList(slots, totalSlots), [slots, totalSlots]);
const visibleSlots = useMemo(() => slots.slice(0, totalSlots), [slots, totalSlots]);
const handleSetGridSize = (cols: number, rows: number) => {
const safeCols = clampGridSide(cols);
@@ -434,13 +431,6 @@ function PolyWeatherTerminal({
localStorage.setItem("polyweather_terminal_grid_rows", String(safeRows));
} catch {}
const nextSlots = normalizeSlotList(visibleSlots, nextTotalSlots);
setSlots(nextSlots);
try {
localStorage.setItem("polyweather_terminal_slots", JSON.stringify(nextSlots));
} catch {}
if (activeSlotIndex >= nextTotalSlots) {
setActiveSlotIndex(0);
}
@@ -469,8 +459,8 @@ function PolyWeatherTerminal({
}, [rows, selectedRegionKey]);
useEffect(() => {
if (filteredRegionRows.length && visibleSlots.every((s) => s === null)) {
const next = Array(totalSlots)
if (filteredRegionRows.length && slots.every((s) => s === null)) {
const next = Array(MAX_TERMINAL_CHARTS)
.fill(null)
.map((_, idx) => filteredRegionRows[idx]?.city || null);
setSlots(next);
@@ -478,11 +468,11 @@ function PolyWeatherTerminal({
localStorage.setItem("polyweather_terminal_slots", JSON.stringify(next));
} catch {}
}
}, [filteredRegionRows, visibleSlots, totalSlots]);
}, [filteredRegionRows, slots]);
const handleSelectCityForSlot = (index: number, city: string | null) => {
if (index < 0 || index >= totalSlots) return;
const next = [...visibleSlots];
if (index < 0 || index >= MAX_TERMINAL_CHARTS) return;
const next = [...slots];
next[index] = city;
setSlots(next);
try {
@@ -860,6 +850,8 @@ function PolyWeatherTerminal({
row={rowForSlot}
allRows={filteredRegionRows}
compact={true}
isActive={isSlotActive}
slotIndex={slotIndex}
onSearchClick={() => setActiveSearchSlotIndex(slotIndex)}
onMaximize={() => {
setMaximizedSlotIndex(slotIndex);
@@ -1025,9 +1017,24 @@ function ScanTerminalScreen() {
timezoneOffsetSeconds: useLocalTimezoneDefault ? localTimezoneOffsetSeconds : null,
tradingRegion: selectedRegionKey,
});
const handleRefresh = useCallback(() => {
clearCityDetailCache();
refreshScanTerminalManually();
}, [refreshScanTerminalManually]);
const [cityFallbackRows, setCityFallbackRows] = useState<ScanOpportunityRow[]>([]);
const rows = useMemo(
() => {
const scanRows = terminalData?.rows || [];
return sortRowsByUserTime(scanRows.length ? scanRows : cityFallbackRows);
},
[cityFallbackRows, terminalData?.rows],
);
const fallbackFetchedRef = useRef(false);
useEffect(() => {
if (!isPro || typeof fetch !== "function") return;
if (fallbackFetchedRef.current) return;
const controller = new AbortController();
fetch("/api/cities", {
cache: "no-store",
@@ -1040,18 +1047,12 @@ function ScanTerminalScreen() {
})
.then((payload) => {
if (!payload || !Array.isArray(payload.cities)) return;
fallbackFetchedRef.current = true;
setCityFallbackRows(cityListItemsToScanRows(payload.cities));
})
.catch(() => {});
return () => controller.abort();
}, [isPro]);
const rows = useMemo(
() => {
const scanRows = terminalData?.rows || [];
return sortRowsByUserTime(scanRows.length ? scanRows : cityFallbackRows);
},
[cityFallbackRows, terminalData?.rows],
);
const [searchQuery, setSearchQuery] = useState("");
const searchInputRef = useRef<HTMLInputElement>(null);
@@ -1115,7 +1116,7 @@ function ScanTerminalScreen() {
generatedText={generatedText || ""}
isEn={isEn}
locale={locale}
onRefresh={refreshScanTerminalManually}
onRefresh={handleRefresh}
refreshing={scanLoading}
rows={filteredRows}
selectedRow={selectedRow}