From f171049f605afacfd3cb806f10e8f0282646bb0f Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Wed, 10 Jun 2026 15:52:23 +0800 Subject: [PATCH] Limit terminal grid to 3x2 --- .../dashboard/ScanTerminalDashboard.tsx | 31 ++++++++++++------- .../scan-terminal/GridLayoutSelector.tsx | 2 +- .../scan-terminal/UsageGuideDashboard.tsx | 4 +-- .../__tests__/terminalGridPolicy.test.ts | 28 ++++++++++++----- 4 files changed, 44 insertions(+), 21 deletions(-) diff --git a/frontend/components/dashboard/ScanTerminalDashboard.tsx b/frontend/components/dashboard/ScanTerminalDashboard.tsx index 2dd06db7..c7d07c08 100644 --- a/frontend/components/dashboard/ScanTerminalDashboard.tsx +++ b/frontend/components/dashboard/ScanTerminalDashboard.tsx @@ -271,29 +271,38 @@ const TERM = { closed: { en: "Closed", zh: "已关闭" }, } as const; -const MAX_TERMINAL_GRID_SIDE = 3; -const MAX_TERMINAL_CHARTS = 9; +const MAX_TERMINAL_GRID_COLS = 3; +const MAX_TERMINAL_GRID_ROWS = 2; +const MAX_TERMINAL_CHARTS = 6; const MOBILE_TERMINAL_CHARTS = 1; const DEFAULT_TERMINAL_GRID_SIDE = 2; const TERMINAL_SLOTS_STORAGE_KEY = "polyweather_terminal_slots"; -function clampGridSide(value: number) { +function clampGridDimension(value: number, max: number) { if (!Number.isFinite(value)) return DEFAULT_TERMINAL_GRID_SIDE; - return Math.max(1, Math.min(MAX_TERMINAL_GRID_SIDE, Math.floor(value))); + return Math.max(1, Math.min(max, Math.floor(value))); } -function getStoredGridSide(key: string) { +function clampGridCols(value: number) { + return clampGridDimension(value, MAX_TERMINAL_GRID_COLS); +} + +function clampGridRows(value: number) { + return clampGridDimension(value, MAX_TERMINAL_GRID_ROWS); +} + +function getStoredGridSide(key: string, max: number) { if (typeof window === "undefined") return DEFAULT_TERMINAL_GRID_SIDE; try { const raw = localStorage.getItem(key); if (!raw) return DEFAULT_TERMINAL_GRID_SIDE; - return clampGridSide(parseInt(raw, 10)); + return clampGridDimension(parseInt(raw, 10), max); } catch {} return DEFAULT_TERMINAL_GRID_SIDE; } function getSlotCount(cols: number, rows: number) { - return Math.min(MAX_TERMINAL_CHARTS, clampGridSide(cols) * clampGridSide(rows)); + return Math.min(MAX_TERMINAL_CHARTS, clampGridCols(cols) * clampGridRows(rows)); } function normalizeSlotList(slots: Array, totalSlots: number) { @@ -717,11 +726,11 @@ function PolyWeatherTerminal({ }, []); const [gridCols, setGridCols] = useState(() => { - return getStoredGridSide("polyweather_terminal_grid_cols"); + return getStoredGridSide("polyweather_terminal_grid_cols", MAX_TERMINAL_GRID_COLS); }); const [gridRows, setGridRows] = useState(() => { - return getStoredGridSide("polyweather_terminal_grid_rows"); + return getStoredGridSide("polyweather_terminal_grid_rows", MAX_TERMINAL_GRID_ROWS); }); const totalSlots = getSlotCount(gridCols, gridRows); @@ -783,8 +792,8 @@ function PolyWeatherTerminal({ }, []); const handleSetGridSize = (cols: number, rows: number) => { - const safeCols = clampGridSide(cols); - const safeRows = clampGridSide(rows); + const safeCols = clampGridCols(cols); + const safeRows = clampGridRows(rows); const nextTotalSlots = getSlotCount(safeCols, safeRows); setGridCols(safeCols); diff --git a/frontend/components/dashboard/scan-terminal/GridLayoutSelector.tsx b/frontend/components/dashboard/scan-terminal/GridLayoutSelector.tsx index 7809c7d6..6f973c04 100644 --- a/frontend/components/dashboard/scan-terminal/GridLayoutSelector.tsx +++ b/frontend/components/dashboard/scan-terminal/GridLayoutSelector.tsx @@ -66,7 +66,7 @@ export function GridLayoutSelector({ setHoveredRows(0); }} > - {[1, 2, 3].map((r) => + {[1, 2].map((r) => [1, 2, 3].map((c) => { const isHighlighted = c <= previewCols && r <= previewRows; return ( diff --git a/frontend/components/dashboard/scan-terminal/UsageGuideDashboard.tsx b/frontend/components/dashboard/scan-terminal/UsageGuideDashboard.tsx index 72e03dd2..630ff247 100644 --- a/frontend/components/dashboard/scan-terminal/UsageGuideDashboard.tsx +++ b/frontend/components/dashboard/scan-terminal/UsageGuideDashboard.tsx @@ -83,13 +83,13 @@ const legendItems: Record<"zh" | "en", GuideCopy[]> = { const operations: Record<"zh" | "en", GuideCopy[]> = { zh: [ - { title: "布局", body: "右上角可切换 1x1 到 3x3,适合从单城复盘切到多城巡检。" }, + { title: "布局", body: "右上角可切换 1x1 到 3x2,适合从单城复盘切到多城巡检。" }, { title: "换城市", body: "点击图表标题栏城市名,在当前卡片内搜索并替换城市。" }, { title: "高温模式", body: "卡片右上角高温按钮用于聚焦最高温兑现窗口。" }, { title: "曲线显隐", body: "图例可自定义显示机场报文、模型线和跑道明细。" }, ], en: [ - { title: "Layout", body: "Switch from 1x1 to 3x3 in the top-right control for review or multi-city scanning." }, + { title: "Layout", body: "Switch from 1x1 to 3x2 in the top-right control for review or multi-city scanning." }, { title: "Change city", body: "Click the city name in a chart header to search and replace that slot." }, { title: "High mode", body: "Use the High button to focus the chart on the high-temperature payoff window." }, { title: "Layer toggles", body: "Use the legend to customize airport reports, model lines, and runway details." }, diff --git a/frontend/components/dashboard/scan-terminal/__tests__/terminalGridPolicy.test.ts b/frontend/components/dashboard/scan-terminal/__tests__/terminalGridPolicy.test.ts index beeaadb5..25fcc4eb 100644 --- a/frontend/components/dashboard/scan-terminal/__tests__/terminalGridPolicy.test.ts +++ b/frontend/components/dashboard/scan-terminal/__tests__/terminalGridPolicy.test.ts @@ -16,6 +16,10 @@ export function runTests() { path.join(projectRoot, "components", "dashboard", "scan-terminal", "GridLayoutSelector.tsx"), "utf8", ); + const usageGuideSource = fs.readFileSync( + path.join(projectRoot, "components", "dashboard", "scan-terminal", "UsageGuideDashboard.tsx"), + "utf8", + ); const chartSource = fs.readFileSync( path.join(projectRoot, "components", "dashboard", "scan-terminal", "LiveTemperatureThresholdChart.tsx"), "utf8", @@ -42,17 +46,18 @@ export function runTests() { ); assert( - dashboardSource.includes("MAX_TERMINAL_CHARTS = 9"), - "terminal dashboard must cap the desktop homepage at 9 charts", + dashboardSource.includes("MAX_TERMINAL_CHARTS = 6"), + "terminal dashboard must cap the desktop homepage at 6 charts", ); assert( dashboardSource.includes("MOBILE_TERMINAL_CHARTS = 1"), "terminal dashboard must document that mobile renders exactly one chart", ); assert( - dashboardSource.includes("clampGridSide") && + dashboardSource.includes("MAX_TERMINAL_GRID_ROWS = 2") && + dashboardSource.includes("clampGridRows") && dashboardSource.includes("Math.min(MAX_TERMINAL_CHARTS"), - "terminal grid dimensions must be clamped before rendering or persisting", + "terminal grid dimensions must clamp saved 3x3 layouts down to the 3x2 maximum before rendering or persisting", ); assert( dashboardSource.includes("mobileChartRow") && @@ -83,8 +88,17 @@ export function runTests() { ); assert( selectorSource.includes("[1, 2, 3].map") && - selectorSource.includes("grid grid-cols-3"), - "grid selector must expose at most a 3 by 3 chart layout", + selectorSource.includes("[1, 2].map") && + selectorSource.includes("grid grid-cols-3") && + !selectorSource.includes("[1, 2, 3].map((r)"), + "grid selector must expose at most a 3 by 2 chart layout", + ); + assert( + usageGuideSource.includes("1x1 到 3x2") && + usageGuideSource.includes("1x1 to 3x2") && + !usageGuideSource.includes("1x1 到 3x3") && + !usageGuideSource.includes("1x1 to 3x3"), + "terminal guide must describe the new 3x2 maximum layout instead of 3x3", ); assert( dashboardSource.includes("if (!cityInSlot || !rowForSlot)") && @@ -135,7 +149,7 @@ export function runTests() { chartSource.includes('compact ? "min-h-0" : "min-h-[300px]"') && chartCanvasSource.includes("const minChartHeight = compact ? 120 : 220") && chartCanvasSource.includes('compact ? "min-h-[120px]" : "min-h-[220px]"'), - "compact grid charts must not force desktop minimum heights that get clipped inside 3x3 terminal slots", + "compact grid charts must not force desktop minimum heights that get clipped inside dense terminal slots", ); const signedOutBlock = dashboardSource.slice( dashboardSource.indexOf('if (event === "SIGNED_OUT")'),