From 2202f8e2110cdec81d99a1cb6a3efeb4fcb1de7a Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Thu, 14 May 2026 00:23:03 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A0=8D=E6=8E=89=20/m=20HTML=20=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=EF=BC=8C=E5=8F=AA=E4=BF=9D=E7=95=99=20/m/json=20?= =?UTF-8?q?=E7=BB=99=20React=20=E7=BB=84=E4=BB=B6=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/app/api/m/route.ts | 16 +- .../dashboard/monitoring/MonitorPanel.tsx | 2 +- web/monitor_routes.py | 177 ++---------------- 3 files changed, 20 insertions(+), 175 deletions(-) diff --git a/frontend/app/api/m/route.ts b/frontend/app/api/m/route.ts index 6e7eb4eb..f7e42727 100644 --- a/frontend/app/api/m/route.ts +++ b/frontend/app/api/m/route.ts @@ -5,19 +5,9 @@ const API_BASE = process.env.POLYWEATHER_API_BASE_URL; export async function GET(req: NextRequest) { if (!API_BASE) { - return new NextResponse("API not configured", { status: 500 }); + return NextResponse.json({ error: "API not configured" }, { status: 500 }); } - const isCards = req.nextUrl.searchParams.get("cards") === "1"; - const asJson = req.nextUrl.searchParams.get("json") === "1"; - const path = asJson ? "/m/json" : isCards ? "/m/cards" : "/m"; const auth = await buildBackendRequestHeaders(req, { includeSupabaseIdentity: false }); - const res = await fetch(`${API_BASE}${path}`, { headers: auth.headers }); - if (asJson) { - return NextResponse.json(await res.json()); - } - const html = await res.text(); - return new NextResponse(html, { - status: res.status, - headers: { "Content-Type": "text/html; charset=utf-8" }, - }); + const res = await fetch(`${API_BASE}/m/json`, { headers: auth.headers }); + return NextResponse.json(await res.json()); } diff --git a/frontend/components/dashboard/monitoring/MonitorPanel.tsx b/frontend/components/dashboard/monitoring/MonitorPanel.tsx index b0182ba3..f6830645 100644 --- a/frontend/components/dashboard/monitoring/MonitorPanel.tsx +++ b/frontend/components/dashboard/monitoring/MonitorPanel.tsx @@ -41,7 +41,7 @@ export default function MonitorPanel() { const fetchData = useCallback(async () => { try { - const res = await fetch("/api/m?json=1"); + const res = await fetch("/api/m"); const data = await res.json(); setCities(data); setTime(new Date().toLocaleTimeString()); diff --git a/web/monitor_routes.py b/web/monitor_routes.py index 27dc6946..26a92311 100644 --- a/web/monitor_routes.py +++ b/web/monitor_routes.py @@ -1,20 +1,17 @@ -"""市场监控网页版 — f-string 拼 HTML,零模板引擎依赖。""" +"""市场监控 — JSON API for frontend React component.""" from __future__ import annotations from datetime import datetime, timezone from typing import Any, Dict, List, Optional -from fastapi import APIRouter, Request -from fastapi.responses import HTMLResponse +from fastapi import APIRouter from loguru import logger from web.analysis_service import _analyze router = APIRouter() -# ── city config ── - _CITIES: List[Dict[str, Any]] = [ {"key": "seoul", "en_name": "Seoul", "icao": "RKSI", "airport": "Incheon", "tz": 9, "tz_abbr": "KST", "rw": True}, {"key": "busan", "en_name": "Busan", "icao": "RKPK", "airport": "Gimhae", "tz": 9, "tz_abbr": "KST", "rw": True}, @@ -29,7 +26,6 @@ _CITIES: List[Dict[str, Any]] = [ {"key": "taipei", "en_name": "Taipei", "icao": "466920", "airport": "Songshan", "tz": 8, "tz_abbr": "TST", "rw": False}, ] -# ── helpers ── def _sf(v: Any) -> Optional[float]: if v is None: @@ -39,6 +35,7 @@ def _sf(v: Any) -> Optional[float]: except (ValueError, TypeError): return None + def _trend_info(icao: str) -> tuple: try: from src.utils.telegram_push import _check_rising_trend @@ -56,6 +53,7 @@ def _trend_info(icao: str) -> tuple: pass return ("→", "flat") + def _obs_age(obs_time_str: Optional[str]) -> Optional[int]: if not obs_time_str: return None @@ -77,7 +75,8 @@ def _obs_age(obs_time_str: Optional[str]) -> Optional[int]: pass return None -def _build_cards() -> tuple: + +def _build_cards() -> list: cards = [] for cfg in _CITIES: try: @@ -105,164 +104,20 @@ def _build_cards() -> tuple: cards.append({ "en_name": cfg["en_name"], "airport": cfg["airport"], - "time": obs or local_time, - "ct": ct, "msf": _sf(msf), "mtt": mtt, - "tsym": tsym, "tcss": tcss, "age": age, - "new_high": new_high, "rw_html": rw_html, - "warm": ct is not None and ct >= 30, + "obs_time": obs or local_time, + "current_temp": ct, "max_so_far": _sf(msf), "max_temp_time": mtt, + "trend_sym": tsym, "trend_css": tcss, "obs_age_min": age, + "new_high": new_high, + "temp_warm": ct is not None and ct >= 30, + "runway_pairs": rw_html, }) except Exception: logger.exception("monitor: failed city {}", cfg["key"]) - cards.sort(key=lambda c: (c["ct"] is not None, c["ct"] or -999), reverse=True) - return cards, datetime.now(timezone.utc).isoformat() + cards.sort(key=lambda c: (c["current_temp"] is not None, c["current_temp"] or -999), reverse=True) + return cards -def _render(cards, gts): - """Build card grid HTML.""" - cards_html = [] - for c in cards: - nc = " new-high-card" if c["new_high"] else "" - wc = " warm" if c["warm"] else "" - nv = " new-high-val" if c["new_high"] else "" - nh = '◆新高' if c["new_high"] else "" - ct_str = f'{c["ct"]:.1f}' if c["ct"] is not None else "--" - ct_na = ' na' if c["ct"] is None else "" - msf_str = f'{c["msf"]:.1f}°C' if c["msf"] is not None else "" - msf_na = ' na' if c["msf"] is None else "" - mtt_str = f'{c["mtt"]}' if c["mtt"] else "" - age_str = f'{c["age"]} min ago' if c["age"] is not None else "" - age_na = ' na' if c["age"] is None else "" - - cards_html.append(f'''
-
- {c['en_name']} - / {c['airport']} - {c['time']}{"" + nh + "" if nh else ""} -
-
- {ct_str}°C -
-
-
- High - {msf_str}{mtt_str} - {c['tsym']} -
-
- Obs - {age_str} -
-
{"
" + c['rw_html'] + "
" if c['rw_html'] else ""} -
''') - - return f'''
-{gts[11:19] if len(gts) > 19 else gts} -{chr(10).join(cards_html)} -
-
Refreshing…
''' - - -PAGE_HTML = """ - - - -Market Monitor — PolyWeather - - - - -
-
-

🔥 Market Monitor

-
- - -
-
-%s -
- - - -""" - - -@router.get("/m", response_class=HTMLResponse) -async def monitor_page(request: Request): - cards, gts = _build_cards() - return HTMLResponse(PAGE_HTML % _render(cards, gts)) - -@router.get("/m/cards", response_class=HTMLResponse) -async def monitor_cards(request: Request): - cards, gts = _build_cards() - return HTMLResponse(_render(cards, gts)) @router.get("/m/json") -async def monitor_json(request: Request): - cards, gts = _build_cards() - return [{ - "en_name": c["en_name"], "airport": c["airport"], - "obs_time": c["time"], "current_temp": c["ct"], - "max_so_far": c["msf"], "max_temp_time": c["mtt"], - "trend_sym": c["tsym"], "trend_css": c["tcss"], - "obs_age_min": c["age"], "new_high": c["new_high"], - "temp_warm": c["warm"], "runway_pairs": c["rw_html"], - } for c in cards] +async def monitor_json(): + return _build_cards()