import { useState } from 'react' import { useNavigate } from 'react-router-dom' import { useMutation, useQueryClient } from '@tanstack/react-query' import { motion, AnimatePresence } from 'framer-motion' import { Eye, EyeOff, Loader2, Save, Check, CheckCircle2, AlertCircle, ArrowRight, ArrowLeft, ExternalLink, Sparkles, LineChart, ScanSearch, Flame, Zap, Radar, ShieldCheck, BellRing, TrendingUp, FileText, Landmark, Database, } from 'lucide-react' import { api } from '@/lib/api' import { useCapabilities, useSettings } from '@/lib/useSharedQueries' import { QK } from '@/lib/queryKeys' import { CAP_LABELS } from '@/lib/capability-labels' import { Logo } from '@/components/Logo' // ===== 引导页:4 步向导 ===== // 0. 欢迎 1. 输入 Key(可跳过) 2. 能力探测结果 3. 完成 → 写标记 → 进面板 const STEPS = ['欢迎', '配置 Key', '能力探测', '完成'] as const const BRAND = '#8B5CF6' const HIGHLIGHTS = [ { icon: LineChart, title: '看板与自选', desc: '市场全景看板、涨跌分布、情绪雷达,自定义自选列表', tint: 'text-accent' }, { icon: ScanSearch, title: '策略选股', desc: '内置多套选股策略,一键扫描全市场命中标的', tint: 'text-bull' }, { icon: TrendingUp, title: '个股分析', desc: 'AI 四维分析个股,关键价位、技术形态一目了然', tint: 'text-warning' }, { icon: Flame, title: '连板梯队', desc: '涨停梯队、封板强度、炸板监控,情绪温度计', tint: 'text-warning' }, { icon: Landmark, title: '概念行业', desc: '概念板块、行业维度的资金流向与热度排名', tint: 'text-accent' }, { icon: FileText, title: '财务分析', desc: 'AI 解读财报,利润、资负、现金流、核心指标', tint: 'text-bear' }, { icon: ShieldCheck, title: '回测验证', desc: '策略历史回测、因子分析,用数据验证逻辑', tint: 'text-accent' }, { icon: Radar, title: '实时监控', desc: '自定义条件 / 策略监控,盘中触发即推送告警', tint: 'text-bear' }, { icon: BellRing, title: '本地优先', desc: '数据本地存储,隐私可控,断网仍可查阅', tint: 'text-bull' }, ] export function Onboarding() { const navigate = useNavigate() const qc = useQueryClient() const [step, setStep] = useState(0) // 完成向导 —— 写后端标记,使守卫放行 const complete = useMutation({ mutationFn: api.completeOnboarding, onSuccess: (data) => { // 用接口返回值同步更新缓存,确保跳转时守卫立即看到 onboarding_completed: true // (避免 invalidate 后台重取未返回时, 守卫用旧缓存 false 误重定向回引导页) qc.setQueryData(QK.settings, (old: any) => old ? { ...old, onboarding_completed: data.onboarding_completed } : old, ) qc.invalidateQueries({ queryKey: QK.settings }) navigate('/', { replace: true }) }, onError: () => { // 标记失败不应阻塞用户进入面板,仍放行 navigate('/', { replace: true }) }, }) const finish = () => complete.mutate() return (
{/* 背景光晕 —— 品牌 + 主色渐变 */}
{/* 极淡网格底纹 */}
{/* 顶栏:logo + 进度指示 */}
TickFlow Stock Panel
{/* 步骤进度条 —— 胶囊式 */}
{STEPS.map((label, i) => (
{i > 0 &&
}
))}
{step + 1} / {STEPS.length}
{/* 步骤内容 */}
{step === 0 && setStep(1)} onSkip={finish} />} {step === 1 && ( setStep(2)} onSkip={() => setStep(2)} onBack={() => setStep(0)} /> )} {step === 2 && setStep(3)} onBack={() => setStep(1)} />} {step === 3 && setStep(2)} pending={complete.isPending} />}
) } // ===== Step 0: 欢迎 ===== function WelcomeStep({ onNext, onSkip }: { onNext: () => void; onSkip: () => void }) { return (
{/* 品牌 badge */}

欢迎使用 TickFlow Stock Panel

一个本地化的 A 股量化分析面板 —— 行情、选股、回测、监控、财务一体化。 花一分钟配置,即可开始使用。

{/* 特性卡片 —— 3×3 网格,横向布局压缩高度 */}
{HIGHLIGHTS.map((h, i) => (
{h.title}
{h.desc}
))}
) } // ===== Step 1: 输入 TickFlow Key ===== function KeyStep({ onNext, onSkip, onBack }: { onNext: () => void; onSkip: () => void; onBack: () => void }) { const qc = useQueryClient() const settings = useSettings() const [keyInput, setKeyInput] = useState('') const [revealing, setRevealing] = useState(false) const [saved, setSaved] = useState(false) const save = useMutation({ mutationFn: () => api.saveTickflowKey(keyInput.trim()), onSuccess: (data) => { qc.invalidateQueries({ queryKey: QK.settings }) qc.invalidateQueries({ queryKey: QK.capabilities }) if (data.ok) { // 仅当 key 有效(被存储)时才进入下一步看探测结果 setSaved(true) setTimeout(() => onNext(), 600) } // ok=false(key 无效):不进入下一步,错误提示由 save.error / save.data 渲染 }, }) // 已配置 key —— 免费档或付费档都算(只要不是 None 档) const alreadyHasKey = settings.data?.mode !== 'none' && settings.data?.mode !== undefined return (

配置 TickFlow API Key

本项目基于 TickFlow 这款稳定的数据源为基座进行开发,正在适配其他第三方数据源。 如果有任何建议或意见,欢迎发送邮件至{' '} 415333856@qq.com

{/* 档位对比说明 —— None 档 vs Free 档 */}
{/* None 档 —— 不配置时默认 */}
None 不配置(默认)
  • · 仅历史日K数据,无实时行情
  • · 数据有延迟,盘后约 1-2 小时更新当天
  • · 可用于策略回测、盘后分析
{/* Free 档 —— 免费注册即可获取 */}
Free 注册免费获取 推荐
  • · 无需付费,注册即享
  • · 历史日K + 限定范围内的实时数据
  • · 可指定个股进行实时监控
{/* Key 已配置提示 */} {alreadyHasKey && !save.isPending && (
已检测到配置好的 Key({settings.data?.tickflow_api_key_masked})。 可直接下一步查看能力,或在下方粘贴新 Key 替换。
)} {/* 获取 Key 的说明 —— 黄框卡片 */}
Key 可在{' '} tickflow.org 获取。 当前数据源基于 TickFlow 基座,其他第三方数据源正在开发适配中。
{/* 输入 */}
{ e.preventDefault() if (keyInput.trim()) save.mutate() }} className="mt-4 space-y-2" >
{ setKeyInput(e.target.value) if (saved) setSaved(false) }} className="w-full px-3 py-2.5 pr-9 rounded-input bg-base border border-border text-sm font-mono focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent/30 transition-all" />
{/* 保存中提示 */} {save.isPending && (
正在验证 Key 并探测能力,验证通过前请不要离开当前页面。
)} {save.isError && (
保存失败:{String((save.error as any).message)}
)} {/* 无效 key —— 探测失败(key 无效/乱填)未存储,提示用户 */} {save.data && !save.data.ok && (
{save.data.reason === 'invalid' ? 'Key 无效或已过期,请检查后重试(未保存该 Key)。' : save.data.error ?? '保存失败'}
)}
{/* 底部操作 */}
) } // ===== Step 2: 能力探测结果 ===== function ResultStep({ onNext, onBack }: { onNext: () => void; onBack: () => void }) { const settings = useSettings() const caps = useCapabilities() // 是否配置成功 —— 免费档(free)或付费档(api_key)都算;None 档算未配置 const hasKey = settings.data?.mode === 'free' || settings.data?.mode === 'api_key' const capList = caps.data ? Object.entries(caps.data.capabilities) : [] return (

能力探测结果

{hasKey ? ( <>

Key 已生效,以下是你当前可用的全部能力。后续可在 设置 → 账户 中重新检测或更换 Key。

订阅档位 {caps.data?.label ?? settings.data?.tier_label ?? '—'}
{caps.isLoading ? (
正在探测能力…
) : capList.length > 0 ? (
{capList.slice(0, 8).map(([cap]) => { const meta = CAP_LABELS[cap] return (
{meta?.name ?? cap}
) })} {capList.length > 8 && (
…等共 {capList.length} 项
)}
) : (
暂未探测到能力
)}
) : (
将以 None 档继续

当前未配置有效 Key,仍可使用看板、选股、回测等功能 —— 进入看板后可直接获取近 1 年历史日K数据。配置 Key 后可解锁实时行情监控等能力,随时在 设置 → 账户 填写。

)} {/* 底部操作 */}
) } // ===== Step 3: 完成 ===== function FinishStep({ onNext, onBack, pending }: { onNext: () => void; onBack: () => void; pending: boolean }) { const settings = useSettings() // 是否已配置 Key(free 或 api_key 都算,None 档算未配置) const hasKey = settings.data?.mode === 'free' || settings.data?.mode === 'api_key' // 首要行动:获取数据(不管配没配 Key, 新用户都需要先拉数据) // 快速上手入口(精简为核心功能) const tips = [ { icon: TrendingUp, text: '「个股分析」:输入代码,AI 四维分析 + 关键价位' }, { icon: ScanSearch, text: '「选股」页:内置多套策略,一键扫描全市场' }, { icon: ShieldCheck, text: '「回测」页:用历史数据验证策略表现,用数据说话' }, ] return (
{/* 光晕脉冲 */}

一切就绪!

{hasKey ? 'Key 已生效,进入面板后系统会自动引导你获取行情数据,完成后即可使用全部功能。' : '当前为 None 档,进入面板后系统会自动引导你获取历史日K数据(无需 Key),即可开始体验。'}

{/* 首要行动:获取数据 */}
下一步:获取行情数据

进入面板后,看板会自动引导你拉取近 1 年全 A 股日K(约 5500 只,预计 1-3 分钟)。同步期间可浏览其他页面。

{/* 快速上手入口 */}
{tips.map((t, i) => (
{t.text}
))}
{/* 底部操作 */}
) }