From a984c22b627f19d2e31da69407edb8d62df8d10b Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Thu, 30 Apr 2026 08:05:10 +0800 Subject: [PATCH] This version of Antigravity is no longer supported. Please upgrade to receive the latest features. --- .../dashboard/ScanTerminalBoard.module.css | 16 ++- .../dashboard/ScanTerminalDashboard.tsx | 2 + .../dashboard/ScanTerminalList.module.css | 61 +++++++++- .../scan-terminal/AiPinnedForecastView.tsx | 111 +++++++++++------- 4 files changed, 138 insertions(+), 52 deletions(-) diff --git a/frontend/components/dashboard/ScanTerminalBoard.module.css b/frontend/components/dashboard/ScanTerminalBoard.module.css index 673190aa..89032ffc 100644 --- a/frontend/components/dashboard/ScanTerminalBoard.module.css +++ b/frontend/components/dashboard/ScanTerminalBoard.module.css @@ -29,23 +29,27 @@ background: rgba(8, 17, 30, 0.8); } -.root :global(.scan-terminal.map-view-active .scan-upgrade-announcement), -.root :global(.scan-terminal.map-view-active .scan-kpi-bar) { +.root :global(.scan-terminal.focus-view-active .scan-upgrade-announcement), +.root :global(.scan-terminal.focus-view-active .scan-kpi-bar) { display: none; } -.root :global(.scan-terminal.map-view-active .scan-data-grid) { +.root :global(.scan-terminal.focus-view-active .scan-data-grid) { gap: 12px; } -.root :global(.scan-terminal.map-view-active .scan-list-section) { - min-height: min(760px, calc(100dvh - 164px)); +.root :global(.scan-terminal.focus-view-active .scan-list-section) { + min-height: min(780px, calc(100dvh - 136px)); } -.root :global(.scan-terminal.map-view-active .scan-list-header) { +.root :global(.scan-terminal.focus-view-active .scan-list-header) { margin-bottom: 8px; } +.root :global(.scan-terminal.focus-view-active .scan-topbar) { + padding-bottom: 12px; +} + @media (max-width: 1680px), (max-height: 900px) { .root :global(.scan-terminal.map-view-active > .detail-panel.scan-city-detail-rail) { display: none; diff --git a/frontend/components/dashboard/ScanTerminalDashboard.tsx b/frontend/components/dashboard/ScanTerminalDashboard.tsx index b2bcc943..d7b8aff1 100644 --- a/frontend/components/dashboard/ScanTerminalDashboard.tsx +++ b/frontend/components/dashboard/ScanTerminalDashboard.tsx @@ -465,6 +465,8 @@ function ScanTerminalScreen() { className={clsx( "scan-terminal", resolvedView === "map" && "map-view-active", + resolvedView !== "opportunities" && "focus-view-active", + resolvedView === "analysis" && "analysis-view-active", themeMode === "light" && "light", )} > diff --git a/frontend/components/dashboard/ScanTerminalList.module.css b/frontend/components/dashboard/ScanTerminalList.module.css index d5e81a81..16b2286d 100644 --- a/frontend/components/dashboard/ScanTerminalList.module.css +++ b/frontend/components/dashboard/ScanTerminalList.module.css @@ -489,10 +489,11 @@ max-height: 100%; overflow-y: auto; overscroll-behavior: contain; + scroll-padding-top: 64px; border: 1px solid rgba(77, 163, 255, 0.16); border-radius: 18px; background: #0b1220; - padding: 18px; + padding: 14px; } .root :global(.scan-ai-workspace::-webkit-scrollbar) { @@ -514,7 +515,7 @@ justify-content: space-between; gap: 18px; align-items: flex-end; - margin-bottom: 18px; + margin-bottom: 10px; padding: 0 2px; } @@ -529,7 +530,7 @@ .root :global(.scan-ai-workspace-head strong) { display: block; color: #e6edf3; - font-size: 22px; + font-size: 20px; line-height: 1.2; margin-top: 4px; } @@ -542,3 +543,57 @@ line-height: 1.55; text-align: right; } + +.root :global(.scan-ai-city-jumpbar) { + position: sticky; + top: 0; + z-index: 8; + display: flex; + gap: 8px; + margin: 0 0 12px; + padding: 8px; + overflow-x: auto; + border: 1px solid rgba(77, 163, 255, 0.14); + border-radius: 14px; + background: rgba(11, 18, 32, 0.92); + box-shadow: 0 12px 28px rgba(0, 0, 0, 0.22); + scrollbar-width: none; +} + +.root :global(.scan-ai-city-jumpbar::-webkit-scrollbar) { + display: none; +} + +.root :global(.scan-ai-city-jumpbar button) { + flex: 0 0 auto; + min-height: 32px; + padding: 0 12px; + border: 1px solid rgba(77, 163, 255, 0.26); + border-radius: 999px; + background: rgba(77, 163, 255, 0.1); + color: #cfe5ff; + font-size: 12px; + font-weight: 900; + cursor: pointer; + white-space: nowrap; +} + +.root :global(.scan-ai-city-jumpbar button:hover) { + border-color: rgba(111, 183, 255, 0.62); + background: rgba(77, 163, 255, 0.18); +} + +.root :global(.scan-ai-city-anchor) { + scroll-margin-top: 66px; +} + +.root :global(.scan-ai-city-card:not(.collapsed)) { + overflow: visible; +} + +.root :global(.scan-ai-city-card:not(.collapsed) .scan-ai-city-hero) { + position: sticky; + top: 54px; + z-index: 6; + border-radius: 18px 18px 0 0; +} diff --git a/frontend/components/dashboard/scan-terminal/AiPinnedForecastView.tsx b/frontend/components/dashboard/scan-terminal/AiPinnedForecastView.tsx index ca9e5d4e..56f6f874 100644 --- a/frontend/components/dashboard/scan-terminal/AiPinnedForecastView.tsx +++ b/frontend/components/dashboard/scan-terminal/AiPinnedForecastView.tsx @@ -23,38 +23,37 @@ export function AiPinnedForecastView({ onRemoveCity: (cityName: string) => void; }) { const isEn = locale === "en-US"; - const [collapsedCities, setCollapsedCities] = useState>( - () => new Set(), - ); + const [expandedCityKey, setExpandedCityKey] = useState(null); const [removingCities, setRemovingCities] = useState>( () => new Set(), ); + const cityCardRefs = useRef>({}); const knownCityKeysRef = useRef>(new Set()); + const previousFirstCityKeyRef = useRef(null); const removeTimersRef = useRef>>(new Map()); useEffect(() => { - const activeKeys = new Set( - items.map((item) => normalizeCityKey(item.cityName) || item.cityName), + const activeKeyList = items.map( + (item) => normalizeCityKey(item.cityName) || item.cityName, ); - setCollapsedCities((current) => { - const next = new Set(); - let changed = false; - current.forEach((key) => { - if (activeKeys.has(key)) { - next.add(key); - } else { - changed = true; - } - }); - items.forEach((item) => { - const stableKey = normalizeCityKey(item.cityName) || item.cityName; - if (!knownCityKeysRef.current.has(stableKey)) { - changed = true; - } - }); - return changed ? next : current; + const activeKeys = new Set(activeKeyList); + const firstCityKey = activeKeyList[0] ?? null; + const hasNewCity = activeKeyList.some((key) => !knownCityKeysRef.current.has(key)); + const firstCityChanged = firstCityKey !== previousFirstCityKeyRef.current; + + setExpandedCityKey((current) => { + if (!firstCityKey) return null; + if (hasNewCity || firstCityChanged) return firstCityKey; + if (current && activeKeys.has(current)) return current; + return firstCityKey; + }); + Object.keys(cityCardRefs.current).forEach((key) => { + if (!activeKeys.has(key)) { + delete cityCardRefs.current[key]; + } }); knownCityKeysRef.current = activeKeys; + previousFirstCityKeyRef.current = firstCityKey; }, [items]); useEffect(() => { @@ -86,6 +85,18 @@ export function AiPinnedForecastView({ [onRemoveCity], ); + const scrollToCity = useCallback((stableKey: string) => { + cityCardRefs.current[stableKey]?.scrollIntoView({ + behavior: "smooth", + block: "start", + }); + }, []); + + const focusCity = useCallback((stableKey: string) => { + setExpandedCityKey(stableKey); + window.requestAnimationFrame(() => scrollToCity(stableKey)); + }, [scrollToCity]); + if (!items.length) { return (
@@ -120,36 +131,50 @@ export function AiPinnedForecastView({ : "地图点击会把城市加入这里;城市分析会保留,直到你手动移除。"}

+
+ {items.map((item) => { + const stableKey = normalizeCityKey(item.cityName) || item.cityName; + return ( + + ); + })} +
{items.map((item) => { const detail = findDetailForCity(detailsByName, item.cityName); const row = findRowForCity(rows, item.cityName); const key = normalizeCityKey(item.cityName); const stableKey = key || item.cityName; - const isKnownCity = knownCityKeysRef.current.has(stableKey); return ( - removeCityWithMotion(item, stableKey)} - onToggleCollapsed={() => { - setCollapsedCities((current) => { - const next = new Set(current); - if (next.has(stableKey)) { - next.delete(stableKey); - } else { - next.add(stableKey); - } - return next; - }); + ref={(node) => { + cityCardRefs.current[stableKey] = node; }} - /> + className="scan-ai-city-anchor" + > + removeCityWithMotion(item, stableKey)} + onToggleCollapsed={() => { + setExpandedCityKey((current) => + current === stableKey ? null : stableKey, + ); + }} + /> +
); })}