mirror of
https://ghfast.top/https://github.com/aeroxw/tick-stock-panel.git
synced 2026-09-12 15:34:16 +08:00
feat(ui): 看板无数据提示优化 + 数据页无 Key 引导 + 设置页文案
Dashboard: - hasNoData 改用 trading_days 判断 (后端 status rows 恒为 0 导致提示无法消除) - 无数据提示改 warning 黄色风格 + AlertTriangle 图标 - 无数据/无 Key 两提示互斥: 无 Key 优先, 有 Key 无数据才显示同步提示 - API Key 横幅: 邀请码加复制按钮, 文案调整 Data: - 无 Key 时立即同步按钮置灰禁用 + 显示未配置提示 - 指数数据卡片设置按钮与其它卡片一致 (有数据才显示) System: - '清理浏览器缓存' 改名 '刷新前端缓存', 避免误导为清数据
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
import sys
|
||||
|
||||
__version__ = "0.1.38"
|
||||
__version__ = "0.1.39"
|
||||
|
||||
# Windows 默认 stdout/stderr 编码为 GBK(cp936),TickFlow SDK 内部输出含 emoji 的
|
||||
# 指数/标的名称(如 \U0001f193)时会抛 UnicodeEncodeError,导致请求失败。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "tickflow-stock-panel-backend"
|
||||
version = "0.1.35"
|
||||
version = "0.1.39"
|
||||
description = "A 股选股 + 监控 + 回测面板 — TickFlow 适配"
|
||||
readme = "../README.md"
|
||||
requires-python = ">=3.11"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "tickflow-stock-panel-frontend",
|
||||
"private": true,
|
||||
"version": "0.1.38",
|
||||
"version": "0.1.39",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useState, type ReactNode } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { motion } from 'framer-motion'
|
||||
import { Activity, ArrowDownRight, ArrowUpRight, BarChart3, BellRing, Database, Flame, Gauge, LineChart, Loader2, RefreshCw, Sparkles, Target, Timer, ExternalLink, Gift } from 'lucide-react'
|
||||
import { Activity, AlertTriangle, ArrowDownRight, ArrowUpRight, BarChart3, BellRing, Check, Copy, Flame, Gauge, LineChart, Loader2, RefreshCw, Sparkles, Target, Timer, ExternalLink } from 'lucide-react'
|
||||
import { DatePicker } from '@/components/DatePicker'
|
||||
import { api, type MarketSnapshotRow, type OverviewDimensionRankItem, type OverviewMarket, type AlertEvent } from '@/lib/api'
|
||||
import { QK } from '@/lib/queryKeys'
|
||||
@@ -471,6 +471,7 @@ function HotRankCard({ title, rank, configUrl }: { title: string; rank?: Overvie
|
||||
export function Dashboard() {
|
||||
const [selectedDate, setSelectedDate] = useState<string | undefined>()
|
||||
const [manualFetching, setManualFetching] = useState(false)
|
||||
const [copiedCode, setCopiedCode] = useState(false)
|
||||
const dataStatus = useDataStatus({ staleTime: 60_000 })
|
||||
const overview = useQuery({
|
||||
queryKey: QK.overviewMarket(selectedDate),
|
||||
@@ -487,8 +488,11 @@ export function Dashboard() {
|
||||
// none 档(无 key / 无效 key)→ 显示升级提示横幅
|
||||
const isNoKey = settings.data?.mode === 'none'
|
||||
// 无本地数据(enriched/daily 都没有)→ 提示去数据页同步
|
||||
// 注: 后端 status 的 rows 为性能刻意返回 0, 用 trading_days 判断是否有数据
|
||||
const ds = dataStatus.data
|
||||
const hasNoData = !!ds && (ds.enriched?.rows ?? 0) === 0 && (ds.daily?.rows ?? 0) === 0
|
||||
const hasNoData = !!ds
|
||||
&& (ds.enriched?.trading_days ?? 0) === 0
|
||||
&& (ds.daily?.trading_days ?? 0) === 0
|
||||
|
||||
// 手动刷新: 显示旋转动画; SSE 自动刷新: 静默, 无体感
|
||||
const handleRefresh = () => {
|
||||
@@ -529,9 +533,9 @@ export function Dashboard() {
|
||||
{/* none 档(无 key)提示横幅 —— 引导用户领取免费 Key 解锁完整能力 */}
|
||||
{isNoKey && (
|
||||
<div className="mb-3 flex items-center gap-2 rounded-card border border-warning/40 bg-warning/10 px-3 py-2 text-xs">
|
||||
<Gift className="h-4 w-4 shrink-0 text-warning" />
|
||||
<AlertTriangle className="h-4 w-4 shrink-0 text-warning" />
|
||||
<span className="text-secondary leading-relaxed">
|
||||
当前未配置 API Key,实时行情、批量同步等能力不可用。
|
||||
当前未配置 API Key,批量同步、实时行情等能力不可用。
|
||||
<a
|
||||
href="https://tickflow.org/auth/register?ref=V3KDKGXPEA"
|
||||
target="_blank"
|
||||
@@ -542,24 +546,41 @@ export function Dashboard() {
|
||||
<ExternalLink className="h-3 w-3 self-center" />
|
||||
</a>
|
||||
免费注册(或填邀请码{' '}
|
||||
<span className="font-mono font-semibold text-warning">V3KDKGXPEA</span>
|
||||
)即可领取免费 API Key,无需付费即可体验完整功能。
|
||||
<span className="font-mono font-semibold text-warning inline-flex items-baseline gap-1">
|
||||
V3KDKGXPEA
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
navigator.clipboard?.writeText('V3KDKGXPEA').then(() => {
|
||||
setCopiedCode(true)
|
||||
setTimeout(() => setCopiedCode(false), 1500)
|
||||
})
|
||||
}}
|
||||
className="text-warning/60 hover:text-warning transition-colors self-center"
|
||||
aria-label="复制邀请码"
|
||||
tabIndex={-1}
|
||||
>
|
||||
{copiedCode ? <Check className="h-3 w-3" /> : <Copy className="h-3 w-3" />}
|
||||
</button>
|
||||
</span>
|
||||
)即可领取免费 API Key,无需付费即可体验。
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{/* 无本地数据提示 —— 引导用户去数据页同步 */}
|
||||
{hasNoData && (
|
||||
<div className="mb-3 flex items-center gap-2 rounded-card border border-accent/40 bg-accent/10 px-3 py-2 text-xs">
|
||||
<Database className="h-4 w-4 shrink-0 text-accent" />
|
||||
{/* 无本地数据提示 —— 引导用户去数据页同步 (仅当已配置 Key 时显示, 无 Key 时优先提示配置 Key) */}
|
||||
{hasNoData && !isNoKey && (
|
||||
<div className="mb-3 flex items-center gap-2 rounded-card border border-warning/40 bg-warning/10 px-3 py-2 text-xs">
|
||||
<AlertTriangle className="h-4 w-4 shrink-0 text-warning" />
|
||||
<span className="text-secondary leading-relaxed">
|
||||
看板暂无数据,请前往数据页面同步行情数据后查看。
|
||||
当前暂无数据,请前往数据页面同步行情数据完成后查看。
|
||||
<Link
|
||||
to="/data"
|
||||
className="ml-1 shrink-0 inline-flex items-center gap-0.5 font-medium text-warning hover:underline"
|
||||
>
|
||||
前往同步数据
|
||||
<ArrowUpRight className="h-3 w-3 self-center" />
|
||||
</Link>
|
||||
</span>
|
||||
<Link
|
||||
to="/data"
|
||||
className="ml-auto shrink-0 inline-flex items-center gap-1 rounded-btn bg-accent px-2.5 py-1 text-[11px] font-medium text-white hover:bg-accent/90 transition-colors"
|
||||
>
|
||||
前往同步
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
<div className="mb-3 flex flex-wrap items-center justify-between gap-2 rounded-card border border-border bg-surface/85 px-3 py-2">
|
||||
|
||||
+40
-13
@@ -1,4 +1,5 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { motion, AnimatePresence } from 'framer-motion'
|
||||
import {
|
||||
@@ -214,6 +215,8 @@ export function Data() {
|
||||
const isRunning = job.data?.status === 'running' || job.data?.status === 'pending'
|
||||
const isStarting = startSync.isPending
|
||||
const hasData = !!(s?.instruments?.rows || s?.daily?.rows)
|
||||
// none 档(无 key / 无效 key) → 禁用立即同步 (同步依赖付费档的批量端点)
|
||||
const isNoKey = settings.data?.mode === 'none'
|
||||
const indexOverviewStats = s ? {
|
||||
rows: 0,
|
||||
earliest_date: s.index_daily?.earliest_date ?? s.index_enriched?.earliest_date ?? null,
|
||||
@@ -290,21 +293,31 @@ export function Data() {
|
||||
subtitle="本地数据画像 · 同步状态 · 历史记录"
|
||||
right={
|
||||
<div className="flex items-center gap-3">
|
||||
{!hasData && !isLoading && (
|
||||
{!hasData && !isLoading && !isNoKey && (
|
||||
<span className="text-xs text-accent animate-pulse">首次使用请点击右侧按钮同步数据</span>
|
||||
)}
|
||||
<button
|
||||
onClick={() => startSync.mutate()}
|
||||
disabled={isStarting}
|
||||
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-btn bg-gradient-to-r from-accent/25 to-accent/10 border border-accent/30 text-accent text-xs font-medium hover:from-accent/35 hover:to-accent/20 disabled:opacity-40 transition-all duration-150"
|
||||
>
|
||||
{(isRunning || isStarting) ? (
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||
) : (
|
||||
{isNoKey ? (
|
||||
<button
|
||||
disabled
|
||||
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-btn bg-gradient-to-r from-accent/25 to-accent/10 border border-accent/30 text-accent text-xs font-medium opacity-40 cursor-not-allowed transition-all duration-150"
|
||||
>
|
||||
<Play className="h-3.5 w-3.5" />
|
||||
)}
|
||||
{isStarting ? '启动中…' : isRunning ? '同步中…' : '立即同步'}
|
||||
</button>
|
||||
立即同步
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={() => startSync.mutate()}
|
||||
disabled={isStarting}
|
||||
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-btn bg-gradient-to-r from-accent/25 to-accent/10 border border-accent/30 text-accent text-xs font-medium hover:from-accent/35 hover:to-accent/20 disabled:opacity-40 transition-all duration-150"
|
||||
>
|
||||
{(isRunning || isStarting) ? (
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||
) : (
|
||||
<Play className="h-3.5 w-3.5" />
|
||||
)}
|
||||
{isStarting ? '启动中…' : isRunning ? '同步中…' : '立即同步'}
|
||||
</button>
|
||||
)}
|
||||
<div className="w-px h-4 bg-border" />
|
||||
<div className="flex items-center gap-1.5">
|
||||
<button
|
||||
@@ -335,6 +348,20 @@ export function Data() {
|
||||
/>
|
||||
|
||||
<div className="px-8 py-6 space-y-6 max-w-6xl">
|
||||
{/* 未配置 API Key 告警条 —— 引导用户去配置 Key 后才能同步 */}
|
||||
{isNoKey && (
|
||||
<div className="flex items-center gap-2 rounded-card border border-warning/40 bg-warning/10 px-3 py-2 text-xs">
|
||||
<AlertTriangle className="h-4 w-4 shrink-0 text-warning" />
|
||||
<span className="text-secondary leading-relaxed">
|
||||
未配置 API Key,无法同步数据,请前往
|
||||
<Link to="/settings?tab=account" className="mx-0.5 font-medium text-warning hover:underline">
|
||||
配置
|
||||
</Link>
|
||||
。
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 实时进度 */}
|
||||
<AnimatePresence>
|
||||
{job.data && (
|
||||
@@ -643,7 +670,7 @@ export function Data() {
|
||||
{ label: '指标', table: 'index_enriched' },
|
||||
] as FieldTab[]}
|
||||
onShowFields={(t) => setSchemaTable(t ?? 'index_daily')}
|
||||
onSettings={() => setOpenSettings(v => v === 'index' ? null : 'index')}
|
||||
onSettings={hasData ? () => setOpenSettings(v => v === 'index' ? null : 'index') : undefined}
|
||||
settingsOpen={openSettings === 'index'}
|
||||
/>
|
||||
<StatCard
|
||||
|
||||
@@ -47,8 +47,8 @@ export function SettingsSystemPanel() {
|
||||
}
|
||||
}, [qc])
|
||||
|
||||
// 清理浏览器缓存: 清除 react-query 缓存 + 强制重载 (绕过浏览器缓存)
|
||||
// 不动 localStorage (用户列配置/策略池等偏好保留)
|
||||
// 刷新前端缓存: 清除 react-query 缓存 + 强制重载 (绕过浏览器缓存)
|
||||
// 不动 localStorage (用户列配置/策略池等偏好保留), 也不影响后端的本地股票数据
|
||||
const handleClearCache = useCallback(() => {
|
||||
setClearing(true)
|
||||
qc.clear()
|
||||
@@ -171,9 +171,9 @@ export function SettingsSystemPanel() {
|
||||
|
||||
<div className="flex items-center justify-between gap-4 py-2">
|
||||
<div className="min-w-0">
|
||||
<div className="text-sm text-foreground">清理浏览器缓存</div>
|
||||
<div className="text-sm text-foreground">刷新前端缓存</div>
|
||||
<div className="text-[11px] text-muted truncate">
|
||||
清除前端缓存并强制重新加载页面 (不影响你的个人配置)
|
||||
清除页面缓存并强制重新加载 (不影响个人配置和本地股票数据)
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user