// src/views/Community/components/HeroPanel.js // 顶部说明面板组件:产品功能介绍 + 沪深指数折线图 + 热门概念词云图 import React, { useEffect, useState, useMemo, useCallback } from 'react'; // 定义 pulse 动画 const pulseAnimation = ` @keyframes pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.6; transform: scale(1.1); } } `; // 注入样式到页面 if (typeof document !== 'undefined') { const styleSheet = document.createElement('style'); styleSheet.type = 'text/css'; styleSheet.innerText = pulseAnimation; document.head.appendChild(styleSheet); } import { Box, Card, CardBody, Flex, VStack, HStack, Text, Heading, useColorModeValue, SimpleGrid, Icon, Spinner, Center, } from '@chakra-ui/react'; import { TrendingUp, Activity, Globe, Zap } from 'lucide-react'; import ReactECharts from 'echarts-for-react'; import { logger } from '../../../utils/logger'; import { PROFESSIONAL_COLORS } from '../../../constants/professionalTheme'; /** * 获取指数行情数据(日线数据) */ const fetchIndexKline = async (indexCode) => { try { // 使用日线数据,获取最近60个交易日 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(); logger.debug('HeroPanel', 'fetchIndexKline success', { indexCode, dataLength: data?.data?.length }); 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, // 获取前60个概念 page: 1, sort_by: 'change_pct' }) }); const data = await response.json(); logger.debug('HeroPanel', 'fetchPopularConcepts response', { total: data.total, resultsCount: data.results?.length }); if (data.results && data.results.length > 0) { return data.results.map(item => ({ name: item.concept, value: Math.abs(item.price_info?.avg_change_pct || 1) + 5, // 使用涨跌幅绝对值 + 基础权重 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 hours = now.getHours(); const minutes = now.getMinutes(); const timeInMinutes = hours * 60 + minutes; // 9:30 - 15:00 (570分钟 - 900分钟) return timeInMinutes >= 570 && timeInMinutes <= 900; }; /** * 迷你K线图组件(支持实时更新) */ const MiniIndexChart = ({ indexCode, indexName }) => { const [chartData, setChartData] = useState(null); const [loading, setLoading] = useState(true); const [latestData, setLatestData] = useState(null); const [currentDate, setCurrentDate] = useState(''); const chartBg = useColorModeValue('transparent', 'transparent'); // 中国市场惯例:涨红跌绿 const upColor = '#ec0000'; // 上涨:红色 const downColor = '#00da3c'; // 下跌:绿色 // 加载日线数据 const loadDailyData = useCallback(async () => { const data = await fetchIndexKline(indexCode); if (data && data.data && 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 }); setCurrentDate(latest.time); // 准备K线图数据(最近60个交易日) const recentData = data.data.slice(-60); setChartData({ dates: recentData.map(item => item.time), klineData: recentData.map(item => [ item.open, item.close, item.low, item.high ]), rawData: recentData // 保存原始数据用于 tooltip }); } setLoading(false); }, [indexCode]); // 加载分钟线数据(仅在交易时间) const loadMinuteData = useCallback(async () => { try { const response = await fetch(`/api/index/${indexCode}/kline?type=minute`); if (!response.ok) return; const data = await response.json(); if (data && data.data && data.data.length > 0) { // 取最新分钟数据 const latest = data.data[data.data.length - 1]; // 分钟线没有 prev_close,使用第一条数据的 open 作为开盘价 const dayOpen = data.data[0].open; setLatestData({ close: latest.close, change: dayOpen ? (((latest.close - dayOpen) / dayOpen) * 100).toFixed(2) : '0.00', isPositive: latest.close >= dayOpen }); logger.debug('HeroPanel', 'Minute data updated', { indexCode, close: latest.close, time: latest.time, change: (((latest.close - dayOpen) / dayOpen) * 100).toFixed(2) }); } } catch (error) { logger.error('HeroPanel', 'loadMinuteData error', error); } }, [indexCode]); // 初始加载和定时更新 useEffect(() => { let isMounted = true; let intervalId = null; const init = async () => { setLoading(true); await loadDailyData(); if (isMounted) { // 如果在交易时间,立即加载一次分钟数据 if (isInTradingTime()) { await loadMinuteData(); } setLoading(false); } }; init(); // 设置定时器:交易时间内每分钟更新 if (isInTradingTime()) { intervalId = setInterval(() => { if (isInTradingTime()) { loadMinuteData(); } else { // 如果超出交易时间,清除定时器 if (intervalId) { clearInterval(intervalId); } } }, 60000); // 每60秒更新一次 } return () => { isMounted = false; if (intervalId) { clearInterval(intervalId); } }; }, [indexCode, loadDailyData, loadMinuteData]); const chartOption = useMemo(() => { if (!chartData) return {}; return { backgroundColor: chartBg, grid: { left: 10, right: 10, top: 5, bottom: 20, 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: 11, fontFamily: 'monospace' }, padding: [8, 12], formatter: function (params) { const dataIndex = params[0].dataIndex; const rawDataItem = chartData.rawData[dataIndex]; if (!rawDataItem) return ''; const open = rawDataItem.open; const high = rawDataItem.high; const low = rawDataItem.low; const close = rawDataItem.close; const prevClose = rawDataItem.prev_close || open; const change = close - prevClose; const changePct = prevClose ? ((change / prevClose) * 100).toFixed(2) : '0.00'; const isUp = close >= prevClose; // Bloomberg 风格格式化(涨红跌绿) const changeColor = isUp ? '#ec0000' : '#00da3c'; const changeSign = isUp ? '+' : ''; return `
📅 ${rawDataItem.time}
开盘${open.toFixed(2)} 最高${high.toFixed(2)} 最低${low.toFixed(2)} 收盘${close.toFixed(2)} 涨跌${changeSign}${change.toFixed(2)} 涨跌幅${changeSign}${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: '60%' }] }; }, [chartData, chartBg, upColor, downColor]); if (loading) { return (
); } return ( {indexName} {latestData?.close.toFixed(2)} 📅 {currentDate} {latestData?.isPositive ? '↗' : '↘'} {latestData?.isPositive ? '+' : ''}{latestData?.change}% {isInTradingTime() && ( 实时更新 )} ); }; /** * 概念流动动画组件(替代词云) */ const ConceptFlowAnimation = () => { const [concepts, setConcepts] = useState([]); const [loading, setLoading] = useState(true); const [isPaused, setIsPaused] = useState(false); const [hoveredConcept, setHoveredConcept] = useState(null); useEffect(() => { let isMounted = true; const loadConcepts = async () => { setLoading(true); const data = await fetchPopularConcepts(); if (isMounted && data && data.length > 0) { setConcepts(data); } if (isMounted) { setLoading(false); } }; loadConcepts(); return () => { isMounted = false; }; }, []); // 根据涨跌幅获取颜色 const getColor = (changePct) => { if (changePct > 7) return '#ff0000'; // 超大涨:纯红 if (changePct > 5) return '#ff1744'; // 大涨:亮红色 if (changePct > 3) return '#ff4d4f'; // 中涨:红色 if (changePct > 1) return '#ff7875'; // 小涨:浅红 if (changePct > 0) return '#ffa940'; // 微涨:橙色 if (changePct === 0) return '#FFD700'; // 平盘:金色 if (changePct > -1) return '#73d13d'; // 微跌:浅绿 if (changePct > -3) return '#52c41a'; // 小跌:绿色 if (changePct > -5) return '#00c853'; // 中跌:深绿 if (changePct > -7) return '#00a152'; // 大跌:更深绿 return '#00796b'; // 超大跌:墨绿 }; // 处理概念点击 const handleConceptClick = (conceptName) => { const url = `https://valuefrontier.cn/htmls/${conceptName}.html`; window.open(url, '_blank'); }; if (loading) { return (
); } if (concepts.length === 0) { return (
暂无热门概念数据
); } // 将 60 个概念均匀分成 3 行(每行 20 个) const rowCount = 3; const conceptsPerRow = Math.ceil(concepts.length / rowCount); const rows = []; for (let i = 0; i < rowCount; i++) { const start = i * conceptsPerRow; const end = Math.min(start + conceptsPerRow, concepts.length); rows.push(concepts.slice(start, end)); } // 根据涨跌幅计算字体大小(涨跌幅越大,字体越大) const getFontSize = (changePct) => { const absChange = Math.abs(changePct); if (absChange >= 10) return 'xl'; // 超大涨跌 - 20px if (absChange >= 7) return 'lg'; // 大涨跌 - 18px if (absChange >= 5) return 'md'; // 中涨跌 - 16px if (absChange >= 3) return 'md'; // 小涨跌 - 16px return 'sm'; // 微涨跌 - 14px }; return ( {rows.map((row, rowIndex) => { // 每行不同的速度:第一行50秒,第二行60秒,第三行70秒(更慢) const duration = 50 + rowIndex * 10; return ( {/* 渲染两次以实现无缝循环 */} {[...row, ...row].map((concept, idx) => { const changePct = concept.change_pct; const color = getColor(changePct); const sign = changePct > 0 ? '+' : ''; const icon = changePct > 0 ? '📈' : '📉'; const isHovered = hoveredConcept === `${rowIndex}-${idx}`; const fontSize = getFontSize(changePct); const isLargeFontSize = fontSize === 'xl' || fontSize === 'lg'; return ( { setIsPaused(true); setHoveredConcept(`${rowIndex}-${idx}`); }} onMouseLeave={() => { setIsPaused(false); setHoveredConcept(null); }} onClick={() => handleConceptClick(concept.name)} _hover={{ transform: 'scale(1.2)', zIndex: 100 }} > {/* 概念名称 */} {concept.name} {/* 悬停时显示涨跌幅 */} {isHovered && ( {icon} {concept.name} {sign}{changePct.toFixed(2)}% )} ); })} ); })} ); }; /** * 产品特性图标组件 */ const FeatureIcon = ({ icon, title, description }) => { return ( {title} {description} ); }; /** * 顶部说明面板主组件 */ const HeroPanel = () => { const gradientBg = 'linear-gradient(135deg, #0a0a0a 0%, #1a1a2e 25%, #16213e 50%, #1a1a2e 75%, #0a0a0a 100%)'; const cardBorder = '1px solid'; const borderColor = useColorModeValue('rgba(255, 215, 0, 0.4)', 'rgba(255, 215, 0, 0.3)'); return ( {/* 动态网格背景 */} {/* 扫描线效果 */} {/* 装饰性光晕 - 多层动态叠加 */} {/* 四角光点装饰 */} {/* 左侧:产品介绍 */} 价值前沿 实时捕捉市场动态,智能分析投资机会。
整合多维数据源,为您提供专业的投资决策支持。
{/* 中间:沪深指数K线图 */} {/* 右侧:热门概念词云图 */} 🔥 热门概念 实时更新 🌊 概念流动展示 🎨 颜色表示涨跌幅 👆 点击查看详情
); }; export default HeroPanel;