import { useState, useEffect } from 'react' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import { motion, AnimatePresence } from 'framer-motion' import { X, RefreshCw, Clock } from 'lucide-react' import { api } from '@/lib/api' import { QK } from '@/lib/queryKeys' import { cnSignal } from '@/lib/signals' import { StockPanel, getDefaultRange } from '@/components/StockPanel' import { DatePicker } from '@/components/DatePicker' import { RuleEditor } from '@/components/monitor/RuleEditor' interface Props { symbol: string | null name?: string onClose: () => void /** 触发信息 (来自监控触发记录, 有值时在顶栏下方显示) */ triggerInfo?: { price?: number | null changePct?: number | null ts?: number signals?: string[] message?: string } | null } // ===== 板块标识(与 Screener 列表一致)===== // 预设快捷范围(只保留半年和1年) const PRESETS: { label: string; months: number }[] = [ { label: '半年', months: 6 }, { label: '1年', months: 12 }, ] function boardTag(symbol: string): { label: string; color: string } | null { if (/^(300|301)/.test(symbol)) return { label: '创', color: 'text-[#f97316] bg-[#f97316]/12 border-[#f97316]/25' } if (/^688/.test(symbol)) return { label: '科', color: 'text-purple-400 bg-purple-400/12 border-purple-400/25' } if (/^[48]/.test(symbol)) return { label: '北', color: 'text-cyan-400 bg-cyan-400/12 border-cyan-400/25' } return null } export function StockPreviewDialog({ symbol, name, onClose, triggerInfo }: Props) { const [showIntraday, setShowIntraday] = useState(false) const [dateRange, setDateRange] = useState(getDefaultRange) const [showMonitorEditor, setShowMonitorEditor] = useState(false) const qc = useQueryClient() const watchlist = useQuery({ queryKey: QK.watchlist, queryFn: api.watchlistList, enabled: !!symbol, }) const inWatchlist = (watchlist.data?.symbols ?? []).some((s: any) => s.symbol === symbol) const toggleWatchlist = useMutation({ mutationFn: () => inWatchlist ? api.watchlistRemove(symbol!) : api.watchlistAdd(symbol!), onSuccess: () => { qc.invalidateQueries({ queryKey: QK.watchlist }) qc.invalidateQueries({ queryKey: QK.watchlistEnriched() }) }, }) // ESC 关闭 useEffect(() => { if (!symbol) return const handler = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose() } document.addEventListener('keydown', handler) return () => document.removeEventListener('keydown', handler) }, [symbol, onClose]) const handleRefresh = () => { if (!symbol) return qc.invalidateQueries({ queryKey: ['kline', symbol!] }) if (showIntraday) { qc.invalidateQueries({ queryKey: ['kline-minute', symbol!] }) } } return ( {symbol && (
{/* 遮罩 */} {/* 弹窗主体 */} {/* 顶栏 */}
{(() => { const board = symbol ? boardTag(symbol) : null return board ? ( {board.label} ) : null })()} {symbol} {name && {name}}
{/* 日期范围快捷 */} {PRESETS.map(p => { const now = new Date() const s = new Date(now) s.setMonth(s.getMonth() - p.months) const expected = s.toISOString().slice(0, 10) const isActive = dateRange.start === expected return ( ) })} setDateRange(prev => ({ ...prev, start: v }))} max={dateRange.end} /> ~ setDateRange(prev => ({ ...prev, end: v }))} min={dateRange.start} /> | {/* 分时开关 */} | {/* 刷新 */} {/* 关闭 */}
{/* 触发信息条 (来自监控触发记录) */} {triggerInfo && (
{/* 左: 触发标记 + 时间 */}
⚡ 触发 {triggerInfo.ts && ( {new Date(triggerInfo.ts).toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })} )}
{/* 中: 价格 + 涨跌幅 */}
{triggerInfo.price != null && ( {triggerInfo.price.toFixed(2)} )} {triggerInfo.changePct != null && ( = 0 ? 'text-danger' : 'text-bear'}`}> {triggerInfo.changePct >= 0 ? '+' : ''}{(triggerInfo.changePct * 100).toFixed(2)}% )}
{/* 右: 消息 + 信号标签 */}
{triggerInfo.message && ( {triggerInfo.message} )} {triggerInfo.signals && triggerInfo.signals.length > 0 && (
{triggerInfo.signals.map((s, j) => ( {cnSignal(s)} ))}
)}
)} {/* K 线内容 */}
{ if (!showIntraday) setShowIntraday(true) }} dateRange={dateRange} onMonitor={() => setShowMonitorEditor(true)} inWatchlist={inWatchlist} onToggleWatchlist={() => toggleWatchlist.mutate()} />
{/* 加监控编辑器弹层 */} {showMonitorEditor && symbol && ( setShowMonitorEditor(false)} >
e.stopPropagation()}> setShowMonitorEditor(false)} onSaved={() => setShowMonitorEditor(false)} />
)}
)}
) }