diff --git a/frontend/components/dashboard/Dashboard.module.css b/frontend/components/dashboard/Dashboard.module.css index a5950c7d..c8df9c23 100644 --- a/frontend/components/dashboard/Dashboard.module.css +++ b/frontend/components/dashboard/Dashboard.module.css @@ -1222,10 +1222,10 @@ } .root :global(.home-deb-card) { - display: grid; - grid-template-columns: minmax(0, 1fr) 120px; - gap: 12px; - align-items: center; + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 10px; padding: 12px 14px; } @@ -1259,13 +1259,11 @@ font-weight: 850; } -.root :global(.home-deb-card svg), .root :global(.home-market-metrics svg) { width: 100%; height: 42px; } -.root :global(.home-deb-card polyline), .root :global(.home-market-metrics polyline) { fill: none; stroke: #18e6d4; @@ -1274,10 +1272,6 @@ stroke-width: 2.2; } -.root :global(.home-deb-card circle) { - fill: #18e6d4; -} - .root :global(.home-card-section) { padding: 12px 14px; } @@ -1293,7 +1287,7 @@ .root :global(.home-intraday-chart) { position: relative; - padding: 10px 0 22px; + padding: 10px 0 0; } .root :global(.home-intraday-chart svg) { @@ -1320,21 +1314,6 @@ stroke-width: 1.6; } -.root :global(.home-intraday-axis) { - position: absolute; - inset: auto 0 0; - height: 18px; -} - -.root :global(.home-intraday-axis span) { - position: absolute; - bottom: 0; - transform: translateX(-50%); - color: rgba(148, 163, 184, 0.82); - font-size: 10px; - font-weight: 700; -} - .root :global(.home-intraday-meta) { color: rgba(148, 163, 184, 0.9); font-size: 11px; @@ -1345,6 +1324,41 @@ -webkit-line-clamp: 3; } +.root :global(.home-card-section.forecast) { + border-color: rgba(99, 102, 241, 0.24); +} + +.root :global(.home-forecast-grid) { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 8px; +} + +.root :global(.home-forecast-item) { + min-width: 0; + padding: 10px 8px; + border: 1px solid rgba(148, 163, 184, 0.14); + border-radius: 12px; + background: rgba(15, 23, 42, 0.5); + text-align: center; +} + +.root :global(.home-forecast-item span) { + display: block; + color: rgba(148, 163, 184, 0.84); + font-size: 10px; + font-weight: 800; +} + +.root :global(.home-forecast-item strong) { + display: block; + margin-top: 6px; + color: #f8fafc; + font-size: 16px; + font-weight: 900; + letter-spacing: -0.04em; +} + .root :global(.home-model-stack) { display: flex; flex-wrap: wrap; diff --git a/frontend/components/dashboard/PolyWeatherDashboard.tsx b/frontend/components/dashboard/PolyWeatherDashboard.tsx index 279adf53..a1d4561a 100644 --- a/frontend/components/dashboard/PolyWeatherDashboard.tsx +++ b/frontend/components/dashboard/PolyWeatherDashboard.tsx @@ -222,7 +222,12 @@ type HomeTrendChart = { forecastPath: string; legendText: string; observationDots: Array<{ cx: number; cy: number; key: string }>; - tickLabels: Array<{ key: string; label: string; x: number }>; +}; + +type HomeForecastDay = { + key: string; + label: string; + maxTemp: number; }; function projectHomeTrendPoint( @@ -314,34 +319,56 @@ function buildHomeTrendChart( key: `${point.labelTime}-${index}`, }; }); - const tickLabels = chartData.tickLabels - .map((label, index) => { - if (!label) return null; - const minutes = Number.parseInt(String(chartData.times[index] || "0").split(":")[0] || "0", 10) * 60; - const projected = projectHomeTrendPoint( - minutes, - chartData.min, - chartData.xMin, - chartData.xMax, - chartData.min, - chartData.max, - ); - return { - key: `${label}-${index}`, - label, - x: projected.cx, - }; - }) - .filter((item): item is { key: string; label: string; x: number } => item != null); return { forecastPath, legendText: chartData.legendText, observationDots, - tickLabels, }; } +function buildHomeForecastDays( + detail?: CityDetail | null, + locale = "zh-CN", +): HomeForecastDay[] { + if (!detail) return []; + const todayLabel = locale === "en-US" ? "Today" : "今天"; + const tomorrowLabel = locale === "en-US" ? "Tomorrow" : "明天"; + const formatter = new Intl.DateTimeFormat(locale === "en-US" ? "en-US" : "zh-CN", { + day: "2-digit", + month: "2-digit", + }); + const rows = Array.isArray(detail.forecast?.daily) ? detail.forecast.daily : []; + const byDate = new Map(); + + if (detail.local_date && Number.isFinite(Number(detail.forecast?.today_high))) { + byDate.set(detail.local_date, Number(detail.forecast?.today_high)); + } + rows.forEach((row) => { + if (!row?.date || !Number.isFinite(Number(row.max_temp))) return; + if (!byDate.has(row.date)) { + byDate.set(row.date, Number(row.max_temp)); + } + }); + + return [...byDate.entries()].slice(0, 4).map(([date, maxTemp], index) => { + const parsed = new Date(`${date}T00:00:00`); + const label = + index === 0 + ? todayLabel + : index === 1 + ? tomorrowLabel + : Number.isNaN(parsed.getTime()) + ? date + : formatter.format(parsed); + return { + key: date, + label, + maxTemp, + }; + }); +} + function readNumericField(source: unknown, key: string) { if (!source || typeof source !== "object") return undefined; const value = (source as Record)[key]; @@ -512,6 +539,7 @@ function HomeIntelligencePanel({ snapshots }: { snapshots: CitySnapshot[] }) { : locale === "en-US" ? "Pro locked" : "PRO 锁定"; + const forecastDays = buildHomeForecastDays(detail, locale); const keySignals = [ { active: Number(marketEdge) > 0, @@ -624,10 +652,6 @@ function HomeIntelligencePanel({ snapshots }: { snapshots: CitySnapshot[] }) { {formatTemperature(debPrediction, symbol)} {formatDelta(debPrediction, currentTemp, symbol)} - {trendChart ? ( @@ -648,13 +672,6 @@ function HomeIntelligencePanel({ snapshots }: { snapshots: CitySnapshot[] }) { ))} -
- {trendChart.tickLabels.map((tick) => ( - - {tick.label} - - ))} -
{trendChart.legendText || @@ -665,6 +682,20 @@ function HomeIntelligencePanel({ snapshots }: { snapshots: CitySnapshot[] }) {
) : null} + {forecastDays.length ? ( +
+

{locale === "en-US" ? "Multi-day forecast" : "多日预报"}

+
+ {forecastDays.map((day) => ( +
+ {day.label} + {formatTemperature(day.maxTemp, symbol)} +
+ ))} +
+
+ ) : null} +

{locale === "en-US" ? "Model stack" : "模型栈"}