From 8031f27f854bccde0be859d14c2ca49cb87cf5ea Mon Sep 17 00:00:00 2001 From: fish Date: Mon, 6 Jul 2026 10:53:00 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=90=8C=E6=AD=A5=20ext=5Fda?= =?UTF-8?q?ta(=E6=A6=82=E5=BF=B5/=E8=A1=8C=E4=B8=9A)=20=E5=92=8C=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0=E5=8C=96=E8=B4=A2=E5=8A=A1=E6=95=B0=E6=8D=AE=E5=B1=95?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 ext_data 加入 SYNCABLE_PARTS,概念/行业分类数据可从 local 同步到 serve - 去掉 serve 财务分析接口的 Cap.FINANCIAL 能力门控,依赖本地 parquet 文件来展示数据 - serve 财务分析前端在无 API Key 但有同步数据时正常展示,隐藏同步按钮 Co-Authored-By: Claude Opus 4.7 --- local/backend/app/api/data_sync.py | 1 + serve/backend/app/api/data_sync.py | 1 + serve/backend/app/api/financials.py | 25 +---- serve/frontend/src/pages/Financials.tsx | 124 ++++++++++++++---------- 4 files changed, 79 insertions(+), 72 deletions(-) diff --git a/local/backend/app/api/data_sync.py b/local/backend/app/api/data_sync.py index e714552..8f9328e 100644 --- a/local/backend/app/api/data_sync.py +++ b/local/backend/app/api/data_sync.py @@ -36,6 +36,7 @@ SYNCABLE_PARTS = [ "instruments_etf", "financials", "pools", + "ext_data", ] diff --git a/serve/backend/app/api/data_sync.py b/serve/backend/app/api/data_sync.py index de1b2b1..8e66e05 100644 --- a/serve/backend/app/api/data_sync.py +++ b/serve/backend/app/api/data_sync.py @@ -25,6 +25,7 @@ SYNCABLE_PARTS = { "instruments", "instruments_index", "instruments_etf", "financials", "pools", + "ext_data", } diff --git a/serve/backend/app/api/financials.py b/serve/backend/app/api/financials.py index b8414b3..ac39676 100644 --- a/serve/backend/app/api/financials.py +++ b/serve/backend/app/api/financials.py @@ -20,11 +20,7 @@ router = APIRouter(prefix="/api/financials", tags=["financials"]) @router.get("/status") def financial_status(request: Request): - """返回各财务表的同步状态。无需 FINANCIAL 权限(前端根据 available 决定是否展示)。""" - capset = request.app.state.capabilities - if not capset.has(Cap.FINANCIAL): - return {"available": False, "tables": {}} - + """返回各财务表的同步状态。""" data_dir = request.app.state.repo.store.data_dir tables = {} @@ -42,15 +38,16 @@ def financial_status(request: Request): else: tables[table] = {"rows": 0, "symbols": 0} + capset = request.app.state.capabilities + has_cap = capset.has(Cap.FINANCIAL) fs = getattr(request.app.state, "financial_scheduler", None) last_sync = fs.last_sync if fs else {} + has_data = any(t.get("rows", 0) > 0 for t in tables.values()) return { - "available": True, + "available": has_cap or has_data, "tables": tables, "last_sync": last_sync, - # 服务端是否正在同步(手动触发)——前端据此显示"同步中"并防重复点击, - # 且刷新页面后仍能正确反映服务端状态。 "syncing": bool(fs and fs.is_syncing), } @@ -58,9 +55,6 @@ def financial_status(request: Request): @router.get("/metrics") def get_metrics(request: Request, symbol: str | None = None): """查询核心财务指标。""" - capset = request.app.state.capabilities - capset.require(Cap.FINANCIAL) - df = get_financial_df(request.app.state.repo.store.data_dir, "metrics") if df.is_empty(): return {"data": []} @@ -72,9 +66,6 @@ def get_metrics(request: Request, symbol: str | None = None): @router.get("/income") def get_income(request: Request, symbol: str | None = None): """查询利润表。""" - capset = request.app.state.capabilities - capset.require(Cap.FINANCIAL) - df = get_financial_df(request.app.state.repo.store.data_dir, "income") if df.is_empty(): return {"data": []} @@ -86,9 +77,6 @@ def get_income(request: Request, symbol: str | None = None): @router.get("/balance-sheet") def get_balance_sheet(request: Request, symbol: str | None = None): """查询资产负债表。""" - capset = request.app.state.capabilities - capset.require(Cap.FINANCIAL) - df = get_financial_df(request.app.state.repo.store.data_dir, "balance_sheet") if df.is_empty(): return {"data": []} @@ -100,9 +88,6 @@ def get_balance_sheet(request: Request, symbol: str | None = None): @router.get("/cash-flow") def get_cash_flow(request: Request, symbol: str | None = None): """查询现金流量表。""" - capset = request.app.state.capabilities - capset.require(Cap.FINANCIAL) - df = get_financial_df(request.app.state.repo.store.data_dir, "cash_flow") if df.is_empty(): return {"data": []} diff --git a/serve/frontend/src/pages/Financials.tsx b/serve/frontend/src/pages/Financials.tsx index 70741d9..953af5f 100644 --- a/serve/frontend/src/pages/Financials.tsx +++ b/serve/frontend/src/pages/Financials.tsx @@ -57,42 +57,56 @@ export function Financials() { rememberStock(symbol, name) } + // 无 Expert 能力时: 等 status 加载完, 判断是否有从 local 同步来的数据 if (!hasFinancial) { - return ( - <> - -
-
-
- -
-

需要 Expert 套餐

-

- 财务数据接口仅 Expert 套餐可用。升级后此页自动显示财务数据面板。 -

- {/* 当前财务数据源(TickFlow)需付费,后续将接入免费数据源;期间欢迎在 issues 推荐免费源 */} -
-
- - 关于数据源 + if (isLoading) { + return ( + <> + +
+ +
+ + ) + } + if (!status?.available) { + // 既无 Expert 能力, 也无同步数据 → 锁定页 + return ( + <> + +
+
+
+
-

- 当前财务数据源需付费,后续会接入免费数据源。如你常用某个免费财务数据源,欢迎在 Issues 中多多推荐哈 ~ +

需要 Expert 套餐

+

+ 财务数据接口仅 Expert 套餐可用。升级后此页自动显示财务数据面板。

- - 前往 Issues 推荐 - - +
+
+ + 关于数据源 +
+

+ 当前财务数据源需付费,后续会接入免费数据源。如你常用某个免费财务数据源,欢迎在 Issues 中多多推荐哈 ~ +

+ + 前往 Issues 推荐 + + +
-
- - ) + + ) + } + // 有同步数据, 继续展示 } const handleSync = (table: string) => { @@ -171,17 +185,19 @@ export function Financials() { : '同步中…'} )} - + {hasFinancial && ( + + )}
} /> @@ -233,16 +249,20 @@ export function Financials() { )} {label}
- + {hasFinancial ? ( + + ) : ( +
+ )}
{fmtBigNum(info?.rows ?? 0)}