// src/views/Community/components/HeroPanel.js // 顶部说明面板组件:事件中心 + 沪深指数K线图 + 热门概念3D动画 import React, { useEffect, useState, useMemo, useCallback, useRef } from 'react'; import { Box, Card, CardBody, Flex, VStack, HStack, Text, Heading, useColorModeValue, Icon, Spinner, Center, } from '@chakra-ui/react'; import { AlertCircle, Clock, TrendingUp, Info } from 'lucide-react'; import ReactECharts from 'echarts-for-react'; import { logger } from '../../../utils/logger'; // 定义动画 const animations = ` @keyframes pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.6; transform: scale(1.1); } } @keyframes shimmer { 0% { background-position: -200% 0; } 100% { background-position: 200% 0; } } @keyframes floatSlow { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-10px); } } `; // 注入样式 if (typeof document !== 'undefined') { const styleId = 'hero-panel-animations'; if (!document.getElementById(styleId)) { const styleSheet = document.createElement('style'); styleSheet.id = styleId; styleSheet.innerText = animations; document.head.appendChild(styleSheet); } } /** * 获取指数行情数据(日线数据) */ const fetchIndexKline = async (indexCode) => { try { const response = await fetch(`/api/index/${indexCode}/kline?type=daily`); if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); const data = await response.json(); return data; } catch (error) { logger.error('HeroPanel', 'fetchIndexKline error', { indexCode, error: error.message }); return null; } }; /** * 获取热门概念数据 */ const fetchPopularConcepts = async () => { try { const response = await fetch('/concept-api/search', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: '', size: 60, page: 1, sort_by: 'change_pct' }) }); const data = await response.json(); if (data.results?.length > 0) { return data.results.map(item => ({ name: item.concept, change_pct: item.price_info?.avg_change_pct || 0, })); } return []; } catch (error) { logger.error('HeroPanel', 'fetchPopularConcepts error', error); return []; } }; /** * 判断当前是否在交易时间内 */ const isInTradingTime = () => { const now = new Date(); const timeInMinutes = now.getHours() * 60 + now.getMinutes(); return timeInMinutes >= 570 && timeInMinutes <= 900; }; /** * 精美K线指数卡片 - 类似 KLineChartModal 风格 */ const CompactIndexCard = ({ indexCode, indexName }) => { const [chartData, setChartData] = useState(null); const [loading, setLoading] = useState(true); const [latestData, setLatestData] = useState(null); const upColor = '#ef5350'; // 涨 - 红色 const downColor = '#26a69a'; // 跌 - 绿色 const loadData = useCallback(async () => { const data = await fetchIndexKline(indexCode); if (data?.data?.length > 0) { const latest = data.data[data.data.length - 1]; const prevClose = latest.prev_close || data.data[data.data.length - 2]?.close || latest.open; const changeAmount = latest.close - prevClose; const changePct = prevClose ? ((changeAmount / prevClose) * 100) : 0; setLatestData({ close: latest.close, open: latest.open, high: latest.high, low: latest.low, changeAmount: changeAmount, changePct: changePct, isPositive: changeAmount >= 0 }); const recentData = data.data.slice(-60); // 增加到60天 setChartData({ dates: recentData.map(item => item.time), klineData: recentData.map(item => [item.open, item.close, item.low, item.high]), volumes: recentData.map(item => item.volume || 0), rawData: recentData }); } setLoading(false); }, [indexCode]); useEffect(() => { loadData(); }, [loadData]); const chartOption = useMemo(() => { if (!chartData) return {}; return { backgroundColor: 'transparent', grid: [ { left: 0, right: 0, top: 8, bottom: 28, containLabel: false }, { left: 0, right: 0, top: '75%', bottom: 4, containLabel: false } ], tooltip: { trigger: 'axis', axisPointer: { type: 'cross', crossStyle: { color: 'rgba(255, 215, 0, 0.6)', width: 1 }, lineStyle: { color: 'rgba(255, 215, 0, 0.4)', width: 1, type: 'dashed' } }, backgroundColor: 'rgba(15, 15, 25, 0.98)', borderColor: 'rgba(255, 215, 0, 0.5)', borderWidth: 1, borderRadius: 8, padding: [12, 16], textStyle: { color: '#e0e0e0', fontSize: 12 }, extraCssText: 'box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);', formatter: (params) => { const idx = params[0]?.dataIndex; if (idx === undefined) return ''; const raw = chartData.rawData[idx]; if (!raw) return ''; // 计算涨跌 const prevClose = raw.prev_close || (idx > 0 ? chartData.rawData[idx - 1]?.close : raw.open) || raw.open; const changeAmount = raw.close - prevClose; const changePct = prevClose ? ((changeAmount / prevClose) * 100) : 0; const isUp = changeAmount >= 0; const color = isUp ? '#ef5350' : '#26a69a'; const sign = isUp ? '+' : ''; return `
📅 ${raw.time}
开盘 ${raw.open.toFixed(2)} 收盘 ${raw.close.toFixed(2)} 最高 ${raw.high.toFixed(2)} 最低 ${raw.low.toFixed(2)}
涨跌幅 ${sign}${changeAmount.toFixed(2)} (${sign}${changePct.toFixed(2)}%)
`; } }, xAxis: [ { type: 'category', data: chartData.dates, gridIndex: 0, show: false, boundaryGap: true }, { type: 'category', data: chartData.dates, gridIndex: 1, show: false, boundaryGap: true } ], yAxis: [ { type: 'value', gridIndex: 0, show: false, scale: true }, { type: 'value', gridIndex: 1, show: false, scale: true } ], dataZoom: [{ type: 'inside', xAxisIndex: [0, 1], start: 50, end: 100, zoomOnMouseWheel: true, moveOnMouseMove: true }], series: [ { name: 'K线', type: 'candlestick', data: chartData.klineData, xAxisIndex: 0, yAxisIndex: 0, itemStyle: { color: upColor, color0: downColor, borderColor: upColor, borderColor0: downColor, borderWidth: 1 }, barWidth: '65%' }, { name: '成交量', type: 'bar', data: chartData.volumes, xAxisIndex: 1, yAxisIndex: 1, itemStyle: { color: (params) => { const idx = params.dataIndex; const raw = chartData.rawData[idx]; return raw && raw.close >= raw.open ? 'rgba(239,83,80,0.5)' : 'rgba(38,166,154,0.5)'; } }, barWidth: '65%' } ] }; }, [chartData, upColor, downColor]); if (loading) { return (
加载{indexName}...
); } return ( {/* 顶部:指数名称和数据 */} {indexName} {latestData?.close?.toFixed(2)} {latestData?.isPositive ? '▲' : '▼'} {latestData?.isPositive ? '+' : ''}{latestData?.changePct?.toFixed(2)}% {/* K线图区域 */} {/* 底部提示 */} 滚轮缩放 · 拖动查看 ); }; /** * 流动式热门概念组件 - HeroUI 风格 * 特点: * 1. 三行横向滚动,每行方向不同 * 2. 卡片式设计,带渐变边框 * 3. 悬停时暂停滚动,放大效果 * 4. 流光动画效果 */ const FlowingConcepts = () => { const [concepts, setConcepts] = useState([]); const [loading, setLoading] = useState(true); const [hoveredIdx, setHoveredIdx] = useState(null); const [isPaused, setIsPaused] = useState(false); useEffect(() => { const load = async () => { const data = await fetchPopularConcepts(); setConcepts(data.slice(0, 30)); // 取30个概念 setLoading(false); }; load(); }, []); const getColor = (pct) => { if (pct > 5) return { bg: 'rgba(255,23,68,0.15)', border: '#ff1744', text: '#ff1744', glow: 'rgba(255,23,68,0.4)' }; if (pct > 2) return { bg: 'rgba(255,82,82,0.12)', border: '#ff5252', text: '#ff5252', glow: 'rgba(255,82,82,0.3)' }; if (pct > 0) return { bg: 'rgba(255,138,128,0.1)', border: '#ff8a80', text: '#ff8a80', glow: 'rgba(255,138,128,0.25)' }; if (pct === 0) return { bg: 'rgba(255,215,0,0.1)', border: '#FFD700', text: '#FFD700', glow: 'rgba(255,215,0,0.25)' }; if (pct > -2) return { bg: 'rgba(105,240,174,0.1)', border: '#69f0ae', text: '#69f0ae', glow: 'rgba(105,240,174,0.25)' }; if (pct > -5) return { bg: 'rgba(0,230,118,0.12)', border: '#00e676', text: '#00e676', glow: 'rgba(0,230,118,0.3)' }; return { bg: 'rgba(0,200,83,0.15)', border: '#00c853', text: '#00c853', glow: 'rgba(0,200,83,0.4)' }; }; const handleClick = (name) => { window.open(`https://valuefrontier.cn/htmls/${name}.html`, '_blank'); }; if (loading) { return (
加载热门概念...
); } // 将概念分成三行 const row1 = concepts.slice(0, 10); const row2 = concepts.slice(10, 20); const row3 = concepts.slice(20, 30); // 渲染单个概念卡片 const renderConceptCard = (concept, globalIdx) => { const colors = getColor(concept.change_pct); const isActive = hoveredIdx === globalIdx; return ( handleClick(concept.name)} onMouseEnter={() => { setHoveredIdx(globalIdx); setIsPaused(true); }} onMouseLeave={() => { setHoveredIdx(null); setIsPaused(false); }} position="relative" overflow="hidden" _before={isActive ? { content: '""', position: 'absolute', top: 0, left: '-100%', width: '200%', height: '100%', background: `linear-gradient(90deg, transparent, ${colors.glow}, transparent)`, animation: 'shimmer 1.5s infinite', } : {}} > {concept.name} {concept.change_pct > 0 ? '+' : ''}{concept.change_pct.toFixed(2)}% ); }; // 渲染滚动行 const renderScrollRow = (items, direction, startIdx, duration) => { const animationName = direction === 'left' ? 'scrollLeft' : 'scrollRight'; return ( {/* 复制两份实现无缝滚动 */} {[...items, ...items].map((concept, idx) => renderConceptCard(concept, startIdx + (idx % items.length)) )} ); }; return ( {renderScrollRow(row1, 'left', 0, 35)} {renderScrollRow(row2, 'right', 10, 40)} {renderScrollRow(row3, 'left', 20, 32)} ); }; /** * 详细使用说明提示框 */ const InfoTooltip = () => { const [isOpen, setIsOpen] = useState(false); return ( {/* 触发器:小标签 */} setIsOpen(true)} onMouseLeave={() => setIsOpen(false)} onClick={() => setIsOpen(!isOpen)} > 使用说明 {/* 悬浮提示框 */} setIsOpen(true)} onMouseLeave={() => setIsOpen(false)} > {/* 小箭头 */} {/* 标题 */} 📖 事件中心使用指南 {/* 1. SABC重要度说明 */} 1️⃣ 重要度等级 (SABC) 重要度由 AI大模型 基于事件本身的影响范围和重大程度来判定, 并非收益率预测策略。 S级表示影响范围广、关注度高的重大事件,C级表示影响较小的普通事件。 {/* 2. 利好利空并存说明 */} 2️⃣ 事件筛选机制 事件列表中利好利空并存,需自行判断。 建议在「历史相关事件」中查看: • 历史上类似事件的市场反应 • 事件的超预期程度 • 综合判断事件的投资价值 {/* 3. 数据延迟提醒 */} 3️⃣ 数据延迟提醒 由于模型需要通过算法检索和分析历史数据,事件结果和发生时间会有 2-3分钟 左右的延迟。 切勿追高! {/* 4. 盘前新闻经验 */} 4️⃣ 实用经验分享 一个比较有效的经验是在 9:20左右 研究上一交易日收盘后至盘前的新闻事件, 往往能发现一些当日主线题材 {/* 操作提示 */} 💡 点击事件卡片查看详情 · K线图支持滚轮缩放 ); }; /** * 顶部说明面板主组件 */ const HeroPanel = () => { const gradientBg = 'linear-gradient(135deg, #0a0a0a 0%, #1a1a2e 25%, #16213e 50%, #1a1a2e 75%, #0a0a0a 100%)'; const borderColor = useColorModeValue('rgba(255, 215, 0, 0.3)', 'rgba(255, 215, 0, 0.25)'); return ( {/* 装饰性光晕 */} {/* 标题行:标题 + 使用说明 + 交易状态 */} 事件中心 {/* 使用说明 - 悬浮提示 */} {/* 右侧:交易状态 */} {isInTradingTime() && ( 交易中 )} {/* 内容区:指数 + 概念 */} {/* 左侧:双指数横向排列 */} {/* 上证指数 */} {/* 深证成指 */} {/* 右侧:热门概念 - 流动式设计 */} {/* 标题栏 - 更精致的设计 */} 热门概念 实时涨跌排行 点击查看详情 {/* 流动式概念展示 */} ); }; export default HeroPanel;