From 590de5d694e749fa5c7578d621a6655948476e53 Mon Sep 17 00:00:00 2001 From: shy3130 Date: Mon, 13 Jul 2026 22:28:04 +0800 Subject: [PATCH] =?UTF-8?q?fix(monitor):=20=E5=88=86=E6=97=B6=E5=9B=BE?= =?UTF-8?q?=E5=88=B7=E6=96=B0=E5=BC=80=E5=85=B3=E4=B8=8D=E7=94=9F=E6=95=88?= =?UTF-8?q?=20+=20Pro+=20=E9=BB=98=E8=AE=A4=E5=BC=80=E5=90=AF=20+=20?= =?UTF-8?q?=E5=88=B7=E6=96=B0=E6=9D=A1=E4=BB=B6=E7=BB=9F=E4=B8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 三处问题: 1. 后端 API 漏接线 (settings.py): - 入参模型 RealtimeMonitorConfigIn 缺 minute_intraday_refresh → PUT 时 Pydantic 静默丢弃, 开关拨了也不存 - GET /preferences 响应没返回该字段 → 前端永远读 undefined→false, 开关拨完弹回 OFF 服务层 (preferences.py) 本就有 get/set, 纯 API 层漏接。 2. 默认值 (preferences.py): get_minute_intraday_refresh 原默认 False。改为三态: 未设置过的 key 按 Pro+ 权限定默认 (有权限=开, 无=关); 用户主动设置过的 (含 False) 以用户选择为准。用 'in data' 区分 未设置 vs 设为 False。 3. 刷新条件不一致 (Watchlist.tsx): Watchlist 用 || (开关或实时行情任一即刷新), Screener 用 && (两者都要)。 与设置卡片描述「需 Pro+ 权限 + 实时行情运行」对齐, 统一成 &&。 后端 216 测试通过; 前端 tsc 通过。 --- backend/app/api/settings.py | 2 ++ backend/app/services/preferences.py | 16 ++++++++++++++-- frontend/src/pages/Watchlist.tsx | 6 +++--- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/backend/app/api/settings.py b/backend/app/api/settings.py index 5ace21d..f934520 100644 --- a/backend/app/api/settings.py +++ b/backend/app/api/settings.py @@ -428,6 +428,7 @@ def get_preferences() -> dict: "webhook_enabled_default": preferences.get_webhook_enabled_default(), "webhook_default_channels": preferences.get_webhook_default_channels(), "sidebar_index_symbols": preferences.get_sidebar_index_symbols(), + "minute_intraday_refresh": preferences.get_minute_intraday_refresh(), "nav_order": preferences.get_nav_order(), "nav_hidden": preferences.get_nav_hidden(), "screener_auto_run": preferences.get_screener_auto_run(), @@ -752,6 +753,7 @@ class RealtimeMonitorConfigIn(BaseModel): strategy_monitor_ids: list[str] | None = None sidebar_index_symbols: list[str] | None = None screener_auto_run: bool | None = None + minute_intraday_refresh: bool | None = None @router.put("/preferences/realtime-monitor") diff --git a/backend/app/services/preferences.py b/backend/app/services/preferences.py index e929eba..50e7f1b 100644 --- a/backend/app/services/preferences.py +++ b/backend/app/services/preferences.py @@ -91,8 +91,20 @@ def get_minute_sync_enabled() -> bool: def get_minute_intraday_refresh() -> bool: - """自选列表分时图是否跟随实时行情刷新 (默认关闭, 开启后盘中按 SSE 频率刷新)。""" - return load().get("minute_intraday_refresh", False) + """自选列表分时图是否跟随实时行情刷新。 + + 默认值随权限: 有实时行情权限 (Pro+) 的用户默认开启, 否则关闭。 + 用户主动设置过的 (key 存在) 以用户选择为准, 即使是 False 也尊重。 + """ + data = load() + if "minute_intraday_refresh" in data: + return bool(data["minute_intraday_refresh"]) + # 未设置过: 有权限默认开, 无权限默认关。 + try: + from app.services.quote_service import QuoteService + return QuoteService.is_realtime_allowed() + except Exception: + return False def get_minute_sync_days() -> int: diff --git a/frontend/src/pages/Watchlist.tsx b/frontend/src/pages/Watchlist.tsx index e46d06c..357162b 100644 --- a/frontend/src/pages/Watchlist.tsx +++ b/frontend/src/pages/Watchlist.tsx @@ -625,8 +625,8 @@ export function Watchlist() { const klineData = dailyKVisible ? (klineBatch.data?.data ?? {}) : {} // 批量分时数据 (Pro+ 用户, 列可见时才拉) - // 刷新策略: 实时行情运行中自动按 15s 轮询 (不接 SSE 高频, 避免每秒拉 TickFlow 触限流); - // 用户也可在实时监控设置里单独开启 minute_intraday_refresh 强制刷新 (即使未开实时行情) + // 刷新策略: 仅当实时行情运行 且 用户在实时监控设置里开启 minute_intraday_refresh 时 + // 按 15s 轮询 (不接 SSE 高频, 避免每秒拉 TickFlow 触限流); 与 Screener / 设置卡片描述一致。 const { data: prefsData } = usePreferences() const intradayRefreshEnabled = prefsData?.minute_intraday_refresh ?? false const minuteBatch = useQuery({ @@ -634,7 +634,7 @@ export function Watchlist() { queryFn: () => api.klineMinuteBatch(symbols), enabled: intradayVisible && symbols.length > 0, staleTime: 10_000, - refetchInterval: (realtimeRunning || intradayRefreshEnabled) ? 15_000 : false, + refetchInterval: (intradayRefreshEnabled && realtimeRunning) ? 15_000 : false, }) const minuteData = intradayVisible ? (minuteBatch.data?.data ?? {}) : {}