diff --git a/backend/app/__init__.py b/backend/app/__init__.py
index 7efdd83..4778f59 100644
--- a/backend/app/__init__.py
+++ b/backend/app/__init__.py
@@ -2,7 +2,7 @@
import sys
-__version__ = "0.1.40"
+__version__ = "0.1.41"
# Windows 默认 stdout/stderr 编码为 GBK(cp936),TickFlow SDK 内部输出含 emoji 的
# 指数/标的名称(如 \U0001f193)时会抛 UnicodeEncodeError,导致请求失败。
diff --git a/backend/pyproject.toml b/backend/pyproject.toml
index 17607b3..ba458f0 100644
--- a/backend/pyproject.toml
+++ b/backend/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "tickflow-stock-panel-backend"
-version = "0.1.40"
+version = "0.1.41"
description = "A 股选股 + 监控 + 回测面板 — TickFlow 适配"
readme = "../README.md"
requires-python = ">=3.11"
diff --git a/frontend/package.json b/frontend/package.json
index b76ab79..582ed66 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,7 +1,7 @@
{
"name": "tickflow-stock-panel-frontend",
"private": true,
- "version": "0.1.40",
+ "version": "0.1.41",
"type": "module",
"scripts": {
"dev": "vite",
diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx
index debe33a..0af878b 100644
--- a/frontend/src/components/Layout.tsx
+++ b/frontend/src/components/Layout.tsx
@@ -1,4 +1,4 @@
-import { useEffect } from 'react'
+import { useEffect, useRef, useState } from 'react'
import { NavLink, Outlet, useNavigate } from 'react-router-dom'
import { useQuery, useQueryClient } from '@tanstack/react-query'
import { motion } from 'framer-motion'
@@ -26,6 +26,7 @@ import {
Key,
Database,
Timer,
+ Loader2,
LayoutDashboard,
Tags,
TrendingUp,
@@ -36,6 +37,7 @@ import {
Landmark,
Cable,
RadioTower,
+ CheckCircle2,
} from 'lucide-react'
import { Logo } from './Logo'
import { api, type IndexQuote } from '@/lib/api'
@@ -258,6 +260,30 @@ export function Layout() {
queryFn: api.analysisMenus,
})
+ // 数据同步状态轮询: 有活跃 job 时「数据」菜单项显示转圈
+ const { data: pipelineJobs } = useQuery({
+ queryKey: QK.pipelineJobs,
+ queryFn: () => api.pipelineJobs(1),
+ refetchInterval: (query) => (query.state.data?.active_id ? 2000 : 15000),
+ refetchIntervalInBackground: true,
+ })
+ const isDataSyncing = !!pipelineJobs?.active_id
+
+ // 数据同步完成的"瞬时反馈": isDataSyncing 从 true→false 时显示绿色对勾,
+ // 闪烁约 3 秒后自动消失。
+ const [dataSyncJustDone, setDataSyncJustDone] = useState(false)
+ const prevSyncingRef = useRef(false)
+ useEffect(() => {
+ // 仅在"刚结束"(true→false)且非首次挂载时触发
+ if (prevSyncingRef.current && !isDataSyncing) {
+ setDataSyncJustDone(true)
+ const t = setTimeout(() => setDataSyncJustDone(false), 3000)
+ prevSyncingRef.current = isDataSyncing
+ return () => clearTimeout(t)
+ }
+ prevSyncingRef.current = isDataSyncing
+ }, [isDataSyncing])
+
const qc = useQueryClient()
const navigate = useNavigate()
const version = versionData?.version
@@ -392,6 +418,13 @@ export function Layout() {
<>
{label}
+ {/* 数据同步状态: 同步中转圈, 刚完成显示绿色对勾闪烁 3 秒 */}
+ {to === '/data' && isDataSyncing && (
+
+ )}
+ {to === '/data' && !isDataSyncing && dataSyncJustDone && (
+
+ )}
{/* 监控中心徽标: 仅非监控页且有未读时显示 */}
{to === '/monitor' && }
>