// 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线指数卡片 */ const CompactIndexCard = ({ indexCode, indexName }) => { const [chartData, setChartData] = useState(null); const [loading, setLoading] = useState(true); const [latestData, setLatestData] = useState(null); const upColor = '#ec0000'; const downColor = '#00da3c'; 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 || latest.close; setLatestData({ close: latest.close, change: prevClose ? (((latest.close - prevClose) / prevClose) * 100).toFixed(2) : '0.00', isPositive: latest.close >= prevClose }); const recentData = data.data.slice(-40); setChartData({ dates: recentData.map(item => item.time), klineData: recentData.map(item => [item.open, item.close, item.low, item.high]), rawData: recentData }); } setLoading(false); }, [indexCode]); useEffect(() => { loadData(); }, [loadData]); const chartOption = useMemo(() => { if (!chartData) return {}; return { backgroundColor: 'transparent', grid: { left: 5, right: 5, top: 5, bottom: 5, containLabel: false }, tooltip: { trigger: 'axis', axisPointer: { type: 'cross', lineStyle: { color: 'rgba(255, 215, 0, 0.5)', width: 1, type: 'dashed' } }, backgroundColor: 'rgba(20, 20, 20, 0.95)', borderColor: '#FFD700', borderWidth: 1, textStyle: { color: '#fff', fontSize: 10, fontFamily: 'monospace' }, padding: [6, 10], formatter: (params) => { const idx = params[0].dataIndex; const raw = chartData.rawData[idx]; if (!raw) return ''; const prevClose = raw.prev_close || raw.open; const changePct = prevClose ? (((raw.close - prevClose) / prevClose) * 100).toFixed(2) : '0.00'; const isUp = raw.close >= prevClose; const color = isUp ? '#ec0000' : '#00da3c'; return `
${raw.time}
收盘: ${raw.close.toFixed(2)}
${isUp ? '+' : ''}${changePct}%
`; } }, xAxis: { type: 'category', data: chartData.dates, show: false }, yAxis: { type: 'value', show: false, scale: true }, series: [{ type: 'candlestick', data: chartData.klineData, itemStyle: { color: upColor, color0: downColor, borderColor: upColor, borderColor0: downColor }, barWidth: '70%' }] }; }, [chartData, upColor, downColor]); if (loading) return
; return ( {/* 左侧数据 */} {indexName} {latestData?.close.toFixed(2)} {latestData?.isPositive ? '▲' : '▼'} {latestData?.isPositive ? '+' : ''}{latestData?.change}% {/* 右侧K线图 */} ); }; /** * 概念漂浮动画 - 稀疏美观版 */ const ConceptFloat = () => { const [concepts, setConcepts] = useState([]); const [loading, setLoading] = useState(true); const [hoveredIdx, setHoveredIdx] = useState(null); const containerRef = useRef(null); useEffect(() => { const load = async () => { const data = await fetchPopularConcepts(); // 只取前12个,更稀疏 setConcepts(data.slice(0, 12)); setLoading(false); }; load(); }, []); const getColor = (pct) => { if (pct > 5) return '#ff1744'; if (pct > 2) return '#ff5252'; if (pct > 0) return '#ff8a80'; if (pct === 0) return '#FFD700'; if (pct > -2) return '#69f0ae'; if (pct > -5) return '#00e676'; return '#00c853'; }; const handleClick = (name) => { window.open(`https://valuefrontier.cn/htmls/${name}.html`, '_blank'); }; if (loading) return
; // 预设12个位置,均匀分布且不重叠 const positions = [ { x: 15, y: 20 }, { x: 50, y: 15 }, { x: 85, y: 25 }, { x: 25, y: 50 }, { x: 60, y: 45 }, { x: 80, y: 55 }, { x: 10, y: 75 }, { x: 40, y: 80 }, { x: 70, y: 75 }, { x: 20, y: 35 }, { x: 55, y: 65 }, { x: 90, y: 40 }, ]; return ( {/* 概念标签 */} {concepts.map((concept, idx) => { const pos = positions[idx] || { x: 50, y: 50 }; const color = getColor(concept.change_pct); const isActive = hoveredIdx === idx; const size = 11 + (idx % 3) * 2; // 11-15px return ( setHoveredIdx(idx)} onMouseLeave={() => setHoveredIdx(null)} onClick={() => handleClick(concept.name)} animation={`floatSlow ${5 + (idx % 3)}s ease-in-out infinite ${idx * 0.3}s`} > {concept.name} {/* 悬停提示 */} {isActive && ( {concept.change_pct > 0 ? '+' : ''}{concept.change_pct.toFixed(2)}% )} ); })} ); }; /** * 极简提示标签 - 悬停显示详情 */ const InfoTooltip = () => { const [isOpen, setIsOpen] = useState(false); return ( {/* 触发器:小标签 */} setIsOpen(true)} onMouseLeave={() => setIsOpen(false)} onClick={() => setIsOpen(!isOpen)} > 使用说明 {/* 悬浮提示框 */} setIsOpen(true)} onMouseLeave={() => setIsOpen(false)} > {/* 小箭头 */} {/* SABC说明 */} SABC 基于事件影响力评级,非收益预测 {/* 涨跌幅说明 */} 涨跌幅 新闻发布时股价 → 当前价格的变化 {/* 延迟提醒 */} 数据延迟 2-3分钟切勿追高 {/* 分隔线 */} {/* 盘前新闻 */} 关注盘前新闻 (收盘后至次日开盘前的消息) {/* 利好利空 */} 事件含 利好/ 利空 ,需自行判断 ); }; /** * 顶部说明面板主组件 */ 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;