feat: 看板刷新按钮改为后端缓存重载

原「刷新」只重新请求前端数据(overview.refetch), 不动后端缓存, 跨天
残留时拿到的是旧数据。改为「重载」:
- 新增 POST /api/data/refresh-cache 端点: clear_cache + refresh_cache
  重建 enriched 内存缓存, 同时清 overview 聚合缓存 + screener TTL 缓存
- 看板按钮先调后端重建缓存, 再刷新看板数据
- 与设置页「清理并刷新」(清前端 react-query)互补, 这个重建后端 Polars 缓存
This commit is contained in:
shy3130
2026-07-07 11:43:39 +08:00
parent a688690f15
commit fb7cf921a9
3 changed files with 27 additions and 3 deletions
+19
View File
@@ -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}
+1
View File
@@ -1317,6 +1317,7 @@ export const api = {
dataStatus: () => request<DataStatus>('/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<EnrichedField[]>(`/api/data/schema/${table}`),
testEndpoint: (url: string, rounds?: number) =>
+7 -3
View File
@@ -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"
>
<RefreshCw className={`h-3 w-3 ${manualFetching ? 'animate-spin' : ''}`} />
<RefreshCw className={`h-3 w-3 ${manualFetching ? 'animate-spin' : ''}`} />
</button>
</div>
</div>