diff --git a/backend/app/api/data.py b/backend/app/api/data.py index 08cee45..4aaaf39 100644 --- a/backend/app/api/data.py +++ b/backend/app/api/data.py @@ -862,3 +862,22 @@ def get_version(request: Request) -> dict: return {"version": v} return {"version": "v0.0.0"} + + +@router.post("/refresh-cache") +def refresh_cache(request: Request) -> dict: + """重建 Polars 内存缓存 (clear + refresh), 不清数据、不清 alerts。 + + 用于跨天后缓存残留、或维表更新后手动刷新场景。 + 与设置页「清理并刷新」的区别: 那个清前端 react-query, 这个重建后端 enriched 缓存。 + """ + repo = request.app.state.repo + repo.clear_cache() + repo.refresh_cache() + # 清除 Overview 总览聚合结果缓存 + Screener 历史 TTL 缓存 + from app.api.overview import invalidate_overview_cache + invalidate_overview_cache() + from app.services.screener import ScreenerService + ScreenerService.clear_history_cache() + logger.info("refresh-cache: Polars 缓存已重建") + return {"ok": True} diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 00a2565..c3a22e8 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -1317,6 +1317,7 @@ export const api = { dataStatus: () => request('/api/data/status'), dataClear: () => request<{ deleted_files: number }>('/api/data/clear', { method: 'POST' }), + refreshCache: () => request<{ ok: boolean }>('/api/data/refresh-cache', { method: 'POST' }), enrichedSchema: (table: string) => request(`/api/data/schema/${table}`), testEndpoint: (url: string, rounds?: number) => diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index feff432..be56704 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -573,10 +573,14 @@ export function Dashboard() { }).catch(() => { /* 查询失败不阻塞, 用户仍可手动点击获取 */ }) }, [hasNoData, fetchJobId]) - // 手动刷新: 显示旋转动画; SSE 自动刷新: 静默, 无体感 + // 手动刷新: 先重建后端 Polars 缓存(解决跨天残留), 再重新拉看板数据 const handleRefresh = () => { setManualFetching(true) - overview.refetch().finally(() => setManualFetching(false)) + api.refreshCache() + .then(() => qc.invalidateQueries({ queryKey: ['overview-market'] })) + .finally(() => { + overview.refetch().finally(() => setManualFetching(false)) + }) } if (overview.isLoading && !data) { @@ -671,7 +675,7 @@ export function Dashboard() { disabled={manualFetching} className="inline-flex items-center gap-1 rounded-btn border border-border bg-elevated px-2 py-1 text-[11px] text-secondary transition-colors hover:text-foreground disabled:opacity-50" > - 刷新 + 重载