From 75f180a56831f0595a028077171371e1b6e6b1f6 Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Mon, 25 May 2026 04:28:45 +0800 Subject: [PATCH] =?UTF-8?q?=E7=94=A8=20is=5Fprimary=5Fsignal=20=E6=9B=BF?= =?UTF-8?q?=E4=BB=A3=E5=9F=8E=E5=B8=82=E5=90=8D=E5=8E=BB=E9=87=8D=EF=BC=8C?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E6=B0=94=E6=B8=A9=E8=B5=B0=E5=8A=BF=E5=9B=BE?= =?UTF-8?q?=E7=9A=84=E5=A4=9A=E5=9F=8E=E5=B8=82Tab=E6=A0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit filteredRegionRows 改为保留 is_primary_signal 主信号合约。NormalizedPerformancePanel 移除顶部横向滚动城市切换Tab。 --- .../dashboard/ScanTerminalDashboard.tsx | 65 ++++++++++--------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/frontend/components/dashboard/ScanTerminalDashboard.tsx b/frontend/components/dashboard/ScanTerminalDashboard.tsx index eabc4195..92438d3e 100644 --- a/frontend/components/dashboard/ScanTerminalDashboard.tsx +++ b/frontend/components/dashboard/ScanTerminalDashboard.tsx @@ -472,6 +472,26 @@ function formatChartLabel(value: string) { return value.length > 8 ? value.slice(-8) : value; } +const DAILY_CHART_HOURS = Array.from( + { length: 24 }, + (_, index) => `${String(index).padStart(2, "0")}:00`, +); + +function parseHourOfDay(value?: string | null) { + const raw = String(value || "").trim(); + if (!raw) return null; + const parsed = new Date(raw); + if (!Number.isNaN(parsed.getTime())) { + return parsed.getHours(); + } + const match = raw.match(/(?:^|\D)([01]?\d|2[0-3])[::][0-5]\d/); + if (match?.[1] !== undefined) { + const hour = Number(match[1]); + return Number.isInteger(hour) && hour >= 0 && hour <= 23 ? hour : null; + } + return null; +} + function seriesStats(values: Array) { const nums = values.filter((value): value is number => validNumber(value) !== null); const latest = nums.length ? nums[nums.length - 1] : null; @@ -552,16 +572,18 @@ function extractRunwayPointSeries(row: ScanOpportunityRow | null, length: number function buildEvidenceChart(row: ScanOpportunityRow | null) { const settlement = normalizeObs(row?.settlement_today_obs || row?.metar_context?.settlement_today_obs); const metar = normalizeObs(row?.metar_today_obs || row?.metar_context?.today_obs || row?.metar_recent_obs || row?.metar_context?.recent_obs); - const baseLabels = settlement.length >= metar.length ? settlement.map((point) => point.label) : metar.map((point) => point.label); - const length = Math.max(baseLabels.length, settlement.length, metar.length, 24); - const labels = length === baseLabels.length - ? baseLabels - : Array.from({ length }, (_, index) => baseLabels[index] || `${String(index).padStart(2, "0")}:00`); + const labels = DAILY_CHART_HOURS; + const length = labels.length; const align = (points: Array<{ label: string; value: number }>) => { - if (!points.length) return Array.from({ length }, () => null); - const offset = Math.max(0, length - points.length); - return Array.from({ length }, (_, index) => (index < offset ? null : points[index - offset]?.value ?? null)); + if (!points.length) return Array.from({ length }, (): number | null => null); + const values = Array.from({ length }, (): number | null => null); + points.forEach((point, index) => { + const hour = parseHourOfDay(point.label); + const bucket = hour ?? Math.min(index, length - 1); + values[bucket] = point.value; + }); + return values; }; const series: EvidenceSeries[] = []; @@ -606,7 +628,7 @@ function buildEvidenceChart(row: ScanOpportunityRow | null) { } const data = labels.map((label, index) => { - const point: Record = { label: formatChartLabel(label) }; + const point: Record = { label }; series.forEach((item) => { point[item.key] = item.values[index] ?? null; }); @@ -634,21 +656,6 @@ function NormalizedPerformancePanel({ title={isEn ? "Live Temperature Trend & Option Threshold Lines" : "实时气温走势与期权阈值线"} >
-
- {rows.slice(0, 18).map((item) => ( - - ))} -
@@ -889,14 +896,8 @@ function PolyWeatherTerminal({ const byRegion = selectedRegionKey === "all" ? rows : rows.filter((row) => String(row.trading_region).toLowerCase() === selectedRegionKey); - // Deduplicate: one row per city, keep the first (highest score from sorting) - const seen = new Set(); - return byRegion.filter((row) => { - const key = (row.city || "").toLowerCase(); - if (seen.has(key)) return false; - seen.add(key); - return true; - }); + // Only show the primary signal per city (backend marks is_primary_signal) + return byRegion.filter((row) => row.is_primary_signal !== false); }, [rows, selectedRegionKey]); const watchRows = useMemo(() => {