From 64ab74f7fcd19d3988a266ed8292d71c24283e44 Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Mon, 25 May 2026 20:28:09 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E5=8C=BA=E5=9F=9F=E6=A0=87?= =?UTF-8?q?=E7=AD=BE=E6=A0=8F=E5=92=8C=E8=87=AA=E9=80=89=E6=97=B6=E5=8C=BA?= =?UTF-8?q?=E9=9D=A2=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dashboard/ScanTerminalDashboard.tsx | 362 ++++++++++++++---- 1 file changed, 283 insertions(+), 79 deletions(-) diff --git a/frontend/components/dashboard/ScanTerminalDashboard.tsx b/frontend/components/dashboard/ScanTerminalDashboard.tsx index 50dd123b..8f03bd86 100644 --- a/frontend/components/dashboard/ScanTerminalDashboard.tsx +++ b/frontend/components/dashboard/ScanTerminalDashboard.tsx @@ -167,11 +167,15 @@ function CityRegionList({ rows, selectedCity, onSelectCity, + slots = [], + activeSlotIndex = 0, }: { isEn: boolean; rows: ScanOpportunityRow[]; selectedCity: string | null; onSelectCity: (city: string) => void; + slots?: Array; + activeSlotIndex?: number; }) { const cities = useMemo(() => { const seen = new Set(); @@ -191,28 +195,110 @@ function CityRegionList({ return ( -
- {cities.map(({ city, name, localTime }) => ( - - ))} +
+ {cities.map(({ city, name, localTime }) => { + const isActive = selectedCity === city; + const displaySlotIndices = slots + .map((s, idx) => (s === city ? idx : -1)) + .filter((idx) => idx !== -1); + + return ( + + ); + })}
); } +function EmptySlotCard({ + slotIndex, + isActive, + isEn, + availableCities, + onSelectSlot, + onSelectCity, +}: { + slotIndex: number; + isActive: boolean; + isEn: boolean; + availableCities: { city: string; name: string }[]; + onSelectSlot: () => void; + onSelectCity: (city: string) => void; +}) { + return ( +
+
+ + +
+
+ {isEn ? `Slot ${slotIndex + 1}: Empty` : `槽位 ${slotIndex + 1}: 空白`} +
+
+ {isEn + ? "Select this card and click a city on the left, or select below:" + : "激活此卡片并从左侧选择城市,或从下方直接选择:"} +
+ +
+ ); +} + function PolyWeatherTerminal({ generatedText, isEn, @@ -278,6 +364,19 @@ function PolyWeatherTerminal({ const [navExpanded, setNavExpanded] = useState(false); const [activeNavKey, setActiveNavKey] = useState("contracts"); + const [slots, setSlots] = useState>(() => { + try { + const stored = localStorage.getItem("polyweather_terminal_slots"); + if (stored) { + const parsed = JSON.parse(stored); + if (Array.isArray(parsed) && parsed.length === 4) return parsed; + } + } catch {} + return [null, null, null, null]; + }); + const [activeSlotIndex, setActiveSlotIndex] = useState(0); + const [maximizedSlotIndex, setMaximizedSlotIndex] = useState(null); + const NAV_ITEMS = [ { key: "contracts", Icon: Table2, labelEn: "Contracts", labelZh: "天气合约" }, { key: "markets", Icon: Activity, labelEn: "Markets", labelZh: "市场概览" }, @@ -290,10 +389,44 @@ function PolyWeatherTerminal({ const filteredRegionRows = useMemo(() => { return rows.filter( - (row) => getCityRegion(row) === selectedRegionKey, + (row) => resolveTradingRegionKey(row) === selectedRegionKey, ); }, [rows, selectedRegionKey]); + useEffect(() => { + if (filteredRegionRows.length && slots.every((s) => s === null)) { + const next = [filteredRegionRows[0].city, null, null, null]; + setSlots(next); + try { + localStorage.setItem("polyweather_terminal_slots", JSON.stringify(next)); + } catch {} + } + }, [filteredRegionRows, slots]); + + const handleSelectCityForSlot = (index: number, city: string | null) => { + const next = [...slots]; + next[index] = city; + setSlots(next); + try { + localStorage.setItem("polyweather_terminal_slots", JSON.stringify(next)); + } catch {} + }; + + const availableCities = useMemo(() => { + const seen = new Set(); + const result: { city: string; name: string }[] = []; + filteredRegionRows.forEach((row) => { + const key = String(row.city || "").toLowerCase(); + if (seen.has(key)) return; + seen.add(key); + result.push({ + city: key, + name: rowName(row), + }); + }); + return result; + }, [filteredRegionRows]); + const watchRows = useMemo(() => { return filteredRegionRows .filter((row) => decisionLabel(row) === "Watch" || !row.tradable) @@ -502,64 +635,6 @@ function PolyWeatherTerminal({ ) : ( <> - {/* Region tabs */} -
- {REGIONS.filter((r) => visibleRegions.has(r.key)).map((r) => ({ - key: r.key, - labelEn: r.labelEn.toUpperCase(), - labelZh: r.labelZh, - })).map((tab) => ( - - ))} - {/* Region selector gear */} -
- -
- {REGIONS.map((r) => { - const checked = visibleRegions.has(r.key); - return ( - - ); - })} -
-
-
{/* Mobile layout */}
handleSelectCityForSlot(activeSlotIndex, city)} + slots={slots} + activeSlotIndex={activeSlotIndex} />
- + {maximizedSlotIndex !== null ? ( + // Maximized view +
setActiveSlotIndex(maximizedSlotIndex)} + className={clsx( + "relative h-full rounded-[4px] border overflow-hidden border-blue-500 ring-2 ring-blue-500/20 shadow-md z-10" + )} + > + {/* Floating actions toolbar */} +
+ + +
+ + String(r.city || "").toLowerCase() === slots[maximizedSlotIndex]) || null} + allRows={filteredRegionRows} + compact={false} + /> +
+ ) : ( + // 2x2 grid layout +
+ {[0, 1, 2, 3].map((slotIndex) => { + const isSlotActive = activeSlotIndex === slotIndex; + const cityInSlot = slots[slotIndex]; + + if (!cityInSlot) { + return ( + setActiveSlotIndex(slotIndex)} + onSelectCity={(city) => handleSelectCityForSlot(slotIndex, city)} + /> + ); + } + + const rowForSlot = filteredRegionRows.find( + (r) => String(r.city || "").toLowerCase() === cityInSlot + ) || null; + + return ( +
setActiveSlotIndex(slotIndex)} + className={clsx( + "relative h-full rounded-[4px] border overflow-hidden transition-all", + isSlotActive + ? "border-blue-500 ring-2 ring-blue-500/20 shadow-md z-10" + : "border-[#d2d9e2] hover:border-slate-400" + )} + > + {/* Floating actions toolbar */} +
+ + +
+ + +
+ ); + })} +
+ )}