feat(sidebar): 数据菜单项同步状态指示器 (转圈/完成闪烁)

- 轮询 pipelineJobs: 有活跃 job 每 2s, 无活跃每 15s
- 同步中: 数据菜单项右侧显示蓝色转圈 Loader2
- 同步完成: 切换为绿色对勾 CheckCircle2 + pulse 闪烁, 3 秒后消失
  (用 ref 检测 isDataSyncing true→false 跳变触发)
This commit is contained in:
shy3130
2026-06-24 23:24:51 +08:00
parent 6c0a1bd5e6
commit 5e1e1a7529
4 changed files with 37 additions and 4 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
import sys
__version__ = "0.1.40"
__version__ = "0.1.41"
# Windows 默认 stdout/stderr 编码为 GBK(cp936),TickFlow SDK 内部输出含 emoji 的
# 指数/标的名称(如 \U0001f193)时会抛 UnicodeEncodeError,导致请求失败。
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "tickflow-stock-panel-frontend",
"private": true,
"version": "0.1.40",
"version": "0.1.41",
"type": "module",
"scripts": {
"dev": "vite",
+34 -1
View File
@@ -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() {
<>
<Icon className="h-4 w-4 shrink-0" />
<span className="flex-1">{label}</span>
{/* 数据同步状态: 同步中转圈, 刚完成显示绿色对勾闪烁 3 秒 */}
{to === '/data' && isDataSyncing && (
<Loader2 className="h-3.5 w-3.5 shrink-0 animate-spin text-accent" />
)}
{to === '/data' && !isDataSyncing && dataSyncJustDone && (
<CheckCircle2 className="h-3.5 w-3.5 shrink-0 text-bull animate-pulse" />
)}
{/* 监控中心徽标: 仅非监控页且有未读时显示 */}
{to === '/monitor' && <MonitorBadge active={isActive} />}
</>