import { useState, useEffect } from 'react'; import { fetchEvents } from '../api'; const TYPE_STYLE = { geopolitical: { color: '#ef4444', bg: 'rgba(239,68,68,.12)', label: '地缘' }, supply: { color: '#10b981', bg: 'rgba(16,185,129,.12)', label: '供给' }, demand: { color: '#f59e0b', bg: 'rgba(245,158,11,.12)', label: '需求' }, macro: { color: '#3b82f6', bg: 'rgba(59,130,246,.12)', label: '宏观' }, policy: { color: '#8b5cf6', bg: 'rgba(139,92,246,.12)', label: '政策' }, }; const IMPACT_COLOR = { bullish: '#10b981', bearish: '#ef4444', neutral: '#94a3b8' }; const IMPACT_LABEL = { bullish: '利多', bearish: '利空', neutral: '中性' }; const FACTOR_ZH = { vix_lag1: 'VIX', usd_index: 'USD指数', supply_saudi: '沙特产量', Brent_spot: 'Brent', mom1m_lag1: '动量', natgas_spot_henry: '天然气', pmi_us_mfg: 'PMI制造', ipi_us: '工业产出', rig_count_us_new: '钻井数', news_oil_sentiment: '新闻情绪', news_geo_tone: '地缘情绪', }; export default function EventTimeline({ limit = 5 }) { const [events, setEvents] = useState([]); const [expanded, setExpanded] = useState(null); const [filter, setFilter] = useState('all'); useEffect(() => { fetchEvents().then(d => { if (Array.isArray(d)) { d.sort((a, b) => b.date.localeCompare(a.date)); setEvents(d); } }).catch(() => {}); }, []); const filtered = filter === 'all' ? events : events.filter(e => e.type === filter); const shown = filtered.slice(0, expanded !== null ? 50 : limit); if (!events.length) return null; return (

🔗 因果叙事链 事件 → 因子异动 → 风险信号 → 对冲建议

{[['all', '全部'], ...Object.entries(TYPE_STYLE).map(([k, v]) => [k, v.label])].map(([k, label]) => ( ))}
{/* Timeline */}
{/* Vertical line */}
{shown.map((ev, i) => { const ts = TYPE_STYLE[ev.type] || TYPE_STYLE.macro; const isOpen = expanded === i; return (
{/* Dot on timeline */}
{/* Event card */}
setExpanded(isOpen ? null : i)} style={{ background: isOpen ? ts.bg : 'rgba(255,255,255,.02)', border: `1px solid ${isOpen ? ts.color + '33' : 'rgba(255,255,255,.06)'}`, borderRadius: 10, padding: '10px 14px', cursor: 'pointer', transition: 'all .2s ease', }}> {/* Header row */}
{ev.icon} {ev.title} {IMPACT_LABEL[ev.impact]} {ev.date}
{/* Expanded content: causal chain */} {isOpen && (
{/* Narrative */}
{ev.narrative}
{/* Causal chain visualization */}
{/* Step 1: Event */}
① 事件触发
{ev.icon}
{ev.title.slice(0, 12)}
{/* Step 2: Factor changes */}
② 因子异动
{ev.factor_changes && Object.entries(ev.factor_changes).slice(0, 3).map(([k, v]) => (
{FACTOR_ZH[k] || k}{' '} 0 ? '#10b981' : '#ef4444', fontWeight: 700 }}> {v > 0 ? '↑' : '↓'}{Math.abs(v).toFixed(1)}
))}
{/* Step 3: Risk signal */}
③ 风险信号
{ev.risk_signal}
{/* Step 4: Hedging action */}
④ 对冲建议
{ev.hedging_action}
{/* Arrow chain */}
事件因子信号对冲
)}
); })}
{/* Show more */} {filtered.length > limit && expanded === null && (
)}
); }