// 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;