1179 lines
38 KiB
JavaScript
1179 lines
38 KiB
JavaScript
// 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 `
|
||
<div style="font-weight: bold; border-bottom: 1px solid #FFD700; padding-bottom: 6px; margin-bottom: 8px; font-size: 12px;">
|
||
📅 ${rawDataItem.time}
|
||
</div>
|
||
<div style="display: grid; grid-template-columns: auto 1fr; gap: 6px 16px; line-height: 1.8;">
|
||
<span style="color: #999;">开盘</span><span style="font-weight: bold; font-size: 13px;">${open.toFixed(2)}</span>
|
||
<span style="color: #999;">最高</span><span style="font-weight: bold; font-size: 13px; color: #ec0000;">${high.toFixed(2)}</span>
|
||
<span style="color: #999;">最低</span><span style="font-weight: bold; font-size: 13px; color: #00da3c;">${low.toFixed(2)}</span>
|
||
<span style="color: #999;">收盘</span><span style="font-weight: bold; font-size: 13px;">${close.toFixed(2)}</span>
|
||
<span style="color: #999;">涨跌</span><span style="font-weight: bold; font-size: 13px; color: ${changeColor};">${changeSign}${change.toFixed(2)}</span>
|
||
<span style="color: #999;">涨跌幅</span><span style="font-weight: bold; font-size: 14px; color: ${changeColor};">${changeSign}${changePct}%</span>
|
||
</div>
|
||
`;
|
||
}
|
||
},
|
||
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 (
|
||
<Center h="120px">
|
||
<Spinner size="sm" color="gold" />
|
||
</Center>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<VStack spacing={2} align="stretch" h="140px">
|
||
<HStack justify="space-between">
|
||
<VStack align="start" spacing={0}>
|
||
<Text fontSize="xs" color="whiteAlpha.700" fontWeight="medium">{indexName}</Text>
|
||
<Text
|
||
fontSize="2xl"
|
||
fontWeight="extrabold"
|
||
color="white"
|
||
textShadow="0 2px 4px rgba(0,0,0,0.3)"
|
||
>
|
||
{latestData?.close.toFixed(2)}
|
||
</Text>
|
||
<Text fontSize="xs" color="whiteAlpha.500">
|
||
📅 {currentDate}
|
||
</Text>
|
||
</VStack>
|
||
<VStack align="end" spacing={1}>
|
||
<Text
|
||
fontSize="lg"
|
||
fontWeight="extrabold"
|
||
color={latestData?.isPositive ? '#ec0000' : '#00da3c'}
|
||
textShadow={latestData?.isPositive ? '0 2px 8px rgba(236, 0, 0, 0.4)' : '0 2px 8px rgba(0, 218, 60, 0.4)'}
|
||
>
|
||
{latestData?.isPositive ? '↗' : '↘'} {latestData?.isPositive ? '+' : ''}{latestData?.change}%
|
||
</Text>
|
||
{isInTradingTime() && (
|
||
<HStack spacing={1}>
|
||
<Box
|
||
w="6px"
|
||
h="6px"
|
||
borderRadius="full"
|
||
bg="green.400"
|
||
animation="pulse 2s infinite"
|
||
boxShadow="0 0 6px rgba(72, 187, 120, 0.8)"
|
||
/>
|
||
<Text fontSize="xs" color="green.400" fontWeight="medium">
|
||
实时更新
|
||
</Text>
|
||
</HStack>
|
||
)}
|
||
</VStack>
|
||
</HStack>
|
||
<Box flex="1" position="relative">
|
||
<ReactECharts
|
||
option={chartOption}
|
||
style={{ height: '90px', width: '100%' }}
|
||
opts={{ renderer: 'canvas' }}
|
||
/>
|
||
</Box>
|
||
</VStack>
|
||
);
|
||
};
|
||
|
||
/**
|
||
* 概念流动动画组件(替代词云)
|
||
*/
|
||
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 (
|
||
<Center h="200px">
|
||
<Spinner size="md" color="gold" />
|
||
</Center>
|
||
);
|
||
}
|
||
|
||
if (concepts.length === 0) {
|
||
return (
|
||
<Center h="200px">
|
||
<Text fontSize="sm" color="whiteAlpha.600">
|
||
暂无热门概念数据
|
||
</Text>
|
||
</Center>
|
||
);
|
||
}
|
||
|
||
// 将 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 (
|
||
<VStack spacing={3} h="200px" justify="center" overflow="hidden" position="relative" w="100%">
|
||
{rows.map((row, rowIndex) => {
|
||
// 每行不同的速度:第一行50秒,第二行60秒,第三行70秒(更慢)
|
||
const duration = 50 + rowIndex * 10;
|
||
|
||
return (
|
||
<Box
|
||
key={rowIndex}
|
||
position="relative"
|
||
width="100%"
|
||
height="58px"
|
||
overflow="hidden"
|
||
>
|
||
<HStack
|
||
spacing={3}
|
||
position="absolute"
|
||
left={0}
|
||
h="100%"
|
||
align="center"
|
||
animation={!isPaused ? `flowRight${rowIndex} ${duration}s linear infinite` : 'none'}
|
||
_hover={{ animationPlayState: 'paused' }}
|
||
sx={{
|
||
[`@keyframes flowRight${rowIndex}`]: {
|
||
'0%': { transform: 'translateX(0)' },
|
||
'100%': { transform: 'translateX(-50%)' }
|
||
}
|
||
}}
|
||
>
|
||
{/* 渲染两次以实现无缝循环 */}
|
||
{[...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 (
|
||
<Box
|
||
key={`${rowIndex}-${idx}`}
|
||
px={2}
|
||
cursor="pointer"
|
||
position="relative"
|
||
transition="all 0.3s ease"
|
||
onMouseEnter={() => {
|
||
setIsPaused(true);
|
||
setHoveredConcept(`${rowIndex}-${idx}`);
|
||
}}
|
||
onMouseLeave={() => {
|
||
setIsPaused(false);
|
||
setHoveredConcept(null);
|
||
}}
|
||
onClick={() => handleConceptClick(concept.name)}
|
||
_hover={{
|
||
transform: 'scale(1.2)',
|
||
zIndex: 100
|
||
}}
|
||
>
|
||
{/* 概念名称 */}
|
||
<Text
|
||
as="span"
|
||
color={color}
|
||
fontSize={fontSize}
|
||
fontWeight={isLargeFontSize ? 'extrabold' : 'bold'}
|
||
textShadow={`0 0 ${isLargeFontSize ? '12px' : '8px'} ${color}80, 0 2px 4px rgba(0,0,0,0.5)`}
|
||
whiteSpace="nowrap"
|
||
transition="all 0.3s ease"
|
||
_hover={{
|
||
textShadow: `0 0 20px ${color}, 0 0 30px ${color}60, 0 2px 6px rgba(0,0,0,0.7)`
|
||
}}
|
||
>
|
||
{concept.name}
|
||
</Text>
|
||
|
||
{/* 悬停时显示涨跌幅 */}
|
||
{isHovered && (
|
||
<Box
|
||
position="absolute"
|
||
top="-45px"
|
||
left="50%"
|
||
transform="translateX(-50%)"
|
||
bg="rgba(20, 20, 20, 0.95)"
|
||
borderWidth="1px"
|
||
borderColor="#FFD700"
|
||
borderRadius="md"
|
||
px={3}
|
||
py={2}
|
||
whiteSpace="nowrap"
|
||
boxShadow="0 4px 12px rgba(0,0,0,0.3)"
|
||
zIndex={200}
|
||
_before={{
|
||
content: '""',
|
||
position: 'absolute',
|
||
bottom: '-6px',
|
||
left: '50%',
|
||
transform: 'translateX(-50%)',
|
||
width: 0,
|
||
height: 0,
|
||
borderLeft: '6px solid transparent',
|
||
borderRight: '6px solid transparent',
|
||
borderTop: '6px solid #FFD700'
|
||
}}
|
||
>
|
||
<VStack spacing={1}>
|
||
<Text fontSize="xs" color="white" fontWeight="bold">
|
||
{icon} {concept.name}
|
||
</Text>
|
||
<Text fontSize="sm" color={color} fontWeight="bold">
|
||
{sign}{changePct.toFixed(2)}%
|
||
</Text>
|
||
</VStack>
|
||
</Box>
|
||
)}
|
||
</Box>
|
||
);
|
||
})}
|
||
</HStack>
|
||
</Box>
|
||
);
|
||
})}
|
||
</VStack>
|
||
);
|
||
};
|
||
|
||
/**
|
||
* 产品特性图标组件
|
||
*/
|
||
const FeatureIcon = ({ icon, title, description }) => {
|
||
return (
|
||
<HStack
|
||
spacing={3}
|
||
align="start"
|
||
transition="all 0.3s ease"
|
||
cursor="pointer"
|
||
_hover={{
|
||
transform: 'translateX(8px)'
|
||
}}
|
||
>
|
||
<Box
|
||
p={2}
|
||
borderRadius="lg"
|
||
bg="whiteAlpha.200"
|
||
color="gold"
|
||
position="relative"
|
||
overflow="hidden"
|
||
transition="all 0.3s ease"
|
||
_hover={{
|
||
bg: "whiteAlpha.300",
|
||
boxShadow: "0 0 20px rgba(255, 215, 0, 0.4)"
|
||
}}
|
||
_before={{
|
||
content: '""',
|
||
position: 'absolute',
|
||
top: '-50%',
|
||
left: '-50%',
|
||
width: '200%',
|
||
height: '200%',
|
||
background: 'linear-gradient(45deg, transparent, rgba(255, 215, 0, 0.3), transparent)',
|
||
transform: 'rotate(45deg)',
|
||
animation: 'iconShine 3s ease-in-out infinite'
|
||
}}
|
||
sx={{
|
||
'@keyframes iconShine': {
|
||
'0%': { transform: 'translateX(-100%) translateY(-100%) rotate(45deg)' },
|
||
'100%': { transform: 'translateX(100%) translateY(100%) rotate(45deg)' }
|
||
}
|
||
}}
|
||
>
|
||
<Icon as={icon} boxSize={5} position="relative" zIndex={1} />
|
||
</Box>
|
||
<VStack align="start" spacing={0}>
|
||
<Text fontSize="sm" fontWeight="bold" color="white" letterSpacing="wide">
|
||
{title}
|
||
</Text>
|
||
<Text fontSize="xs" color="whiteAlpha.800">
|
||
{description}
|
||
</Text>
|
||
</VStack>
|
||
</HStack>
|
||
);
|
||
};
|
||
|
||
/**
|
||
* 顶部说明面板主组件
|
||
*/
|
||
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 (
|
||
<Card
|
||
bg={gradientBg}
|
||
borderColor={borderColor}
|
||
borderWidth={cardBorder}
|
||
boxShadow="0 20px 60px rgba(0, 0, 0, 0.7), 0 0 0 1px rgba(255, 215, 0, 0.15) inset, 0 0 80px rgba(255, 215, 0, 0.05)"
|
||
mb={6}
|
||
overflow="hidden"
|
||
position="relative"
|
||
transition="all 0.5s cubic-bezier(0.4, 0, 0.2, 1)"
|
||
_hover={{
|
||
boxShadow: "0 24px 80px rgba(0, 0, 0, 0.8), 0 0 0 2px rgba(255, 215, 0, 0.3) inset, 0 0 120px rgba(255, 215, 0, 0.1)",
|
||
transform: "translateY(-4px) scale(1.005)",
|
||
borderColor: "rgba(255, 215, 0, 0.5)"
|
||
}}
|
||
>
|
||
{/* 动态网格背景 */}
|
||
<Box
|
||
position="absolute"
|
||
top={0}
|
||
left={0}
|
||
right={0}
|
||
bottom={0}
|
||
opacity={0.03}
|
||
pointerEvents="none"
|
||
bgImage="linear-gradient(rgba(255, 215, 0, 0.3) 1px, transparent 1px), linear-gradient(90deg, rgba(255, 215, 0, 0.3) 1px, transparent 1px)"
|
||
bgSize="50px 50px"
|
||
animation="gridMove 20s linear infinite"
|
||
sx={{
|
||
'@keyframes gridMove': {
|
||
'0%': { backgroundPosition: '0 0' },
|
||
'100%': { backgroundPosition: '50px 50px' }
|
||
}
|
||
}}
|
||
/>
|
||
|
||
{/* 扫描线效果 */}
|
||
<Box
|
||
position="absolute"
|
||
top={0}
|
||
left={0}
|
||
right={0}
|
||
height="2px"
|
||
bgGradient="linear(to-r, transparent, rgba(255, 215, 0, 0.8), transparent)"
|
||
pointerEvents="none"
|
||
animation="scanLine 3s ease-in-out infinite"
|
||
sx={{
|
||
'@keyframes scanLine': {
|
||
'0%': { transform: 'translateY(0)', opacity: 0 },
|
||
'50%': { opacity: 1 },
|
||
'100%': { transform: 'translateY(400px)', opacity: 0 }
|
||
}
|
||
}}
|
||
/>
|
||
|
||
{/* 装饰性光晕 - 多层动态叠加 */}
|
||
<Box
|
||
position="absolute"
|
||
top="-40%"
|
||
right="-20%"
|
||
width="700px"
|
||
height="700px"
|
||
borderRadius="full"
|
||
bg="radial-gradient(circle, rgba(255, 215, 0, 0.2) 0%, rgba(255, 165, 0, 0.12) 30%, transparent 70%)"
|
||
pointerEvents="none"
|
||
filter="blur(50px)"
|
||
animation="floatGlow1 8s ease-in-out infinite"
|
||
sx={{
|
||
'@keyframes floatGlow1': {
|
||
'0%, 100%': { transform: 'translate(0, 0) scale(1)' },
|
||
'50%': { transform: 'translate(-30px, 20px) scale(1.1)' }
|
||
}
|
||
}}
|
||
/>
|
||
<Box
|
||
position="absolute"
|
||
bottom="-30%"
|
||
left="-15%"
|
||
width="500px"
|
||
height="500px"
|
||
borderRadius="full"
|
||
bg="radial-gradient(circle, rgba(236, 0, 0, 0.15) 0%, rgba(255, 77, 79, 0.08) 40%, transparent 70%)"
|
||
pointerEvents="none"
|
||
filter="blur(60px)"
|
||
animation="floatGlow2 10s ease-in-out infinite"
|
||
sx={{
|
||
'@keyframes floatGlow2': {
|
||
'0%, 100%': { transform: 'translate(0, 0) scale(1)' },
|
||
'50%': { transform: 'translate(40px, -30px) scale(1.15)' }
|
||
}
|
||
}}
|
||
/>
|
||
<Box
|
||
position="absolute"
|
||
top="40%"
|
||
left="30%"
|
||
width="600px"
|
||
height="600px"
|
||
borderRadius="full"
|
||
bg="radial-gradient(circle, rgba(0, 218, 60, 0.08) 0%, transparent 60%)"
|
||
pointerEvents="none"
|
||
filter="blur(70px)"
|
||
animation="floatGlow3 12s ease-in-out infinite"
|
||
sx={{
|
||
'@keyframes floatGlow3': {
|
||
'0%, 100%': { transform: 'translate(0, 0) scale(1)' },
|
||
'50%': { transform: 'translate(-50px, 40px) scale(1.2)' }
|
||
}
|
||
}}
|
||
/>
|
||
|
||
{/* 四角光点装饰 */}
|
||
<Box
|
||
position="absolute"
|
||
top="20px"
|
||
left="20px"
|
||
width="4px"
|
||
height="4px"
|
||
borderRadius="full"
|
||
bg="#FFD700"
|
||
boxShadow="0 0 20px #FFD700"
|
||
animation="cornerPulse 2s ease-in-out infinite"
|
||
sx={{
|
||
'@keyframes cornerPulse': {
|
||
'0%, 100%': { opacity: 0.3, transform: 'scale(1)' },
|
||
'50%': { opacity: 1, transform: 'scale(1.5)' }
|
||
}
|
||
}}
|
||
/>
|
||
<Box
|
||
position="absolute"
|
||
top="20px"
|
||
right="20px"
|
||
width="4px"
|
||
height="4px"
|
||
borderRadius="full"
|
||
bg="#FFD700"
|
||
boxShadow="0 0 20px #FFD700"
|
||
animation="cornerPulse 2s ease-in-out infinite 0.5s"
|
||
sx={{
|
||
'@keyframes cornerPulse': {
|
||
'0%, 100%': { opacity: 0.3, transform: 'scale(1)' },
|
||
'50%': { opacity: 1, transform: 'scale(1.5)' }
|
||
}
|
||
}}
|
||
/>
|
||
<Box
|
||
position="absolute"
|
||
bottom="20px"
|
||
left="20px"
|
||
width="4px"
|
||
height="4px"
|
||
borderRadius="full"
|
||
bg="#FFD700"
|
||
boxShadow="0 0 20px #FFD700"
|
||
animation="cornerPulse 2s ease-in-out infinite 1s"
|
||
sx={{
|
||
'@keyframes cornerPulse': {
|
||
'0%, 100%': { opacity: 0.3, transform: 'scale(1)' },
|
||
'50%': { opacity: 1, transform: 'scale(1.5)' }
|
||
}
|
||
}}
|
||
/>
|
||
<Box
|
||
position="absolute"
|
||
bottom="20px"
|
||
right="20px"
|
||
width="4px"
|
||
height="4px"
|
||
borderRadius="full"
|
||
bg="#FFD700"
|
||
boxShadow="0 0 20px #FFD700"
|
||
animation="cornerPulse 2s ease-in-out infinite 1.5s"
|
||
sx={{
|
||
'@keyframes cornerPulse': {
|
||
'0%, 100%': { opacity: 0.3, transform: 'scale(1)' },
|
||
'50%': { opacity: 1, transform: 'scale(1.5)' }
|
||
}
|
||
}}
|
||
/>
|
||
|
||
<CardBody p={6}>
|
||
<SimpleGrid columns={{ base: 1, lg: 3 }} spacing={6}>
|
||
{/* 左侧:产品介绍 */}
|
||
<Box
|
||
animation="fadeInLeft 0.8s ease-out"
|
||
sx={{
|
||
'@keyframes fadeInLeft': {
|
||
'0%': { opacity: 0, transform: 'translateX(-30px)' },
|
||
'100%': { opacity: 1, transform: 'translateX(0)' }
|
||
}
|
||
}}
|
||
>
|
||
<VStack align="start" spacing={4}>
|
||
<Heading size="lg" color="white" fontWeight="extrabold">
|
||
<Text
|
||
bgGradient="linear(to-r, #FFD700, #FFA500, #FFD700)"
|
||
bgClip="text"
|
||
backgroundSize="200% 100%"
|
||
animation="shimmer 3s linear infinite"
|
||
sx={{
|
||
'@keyframes shimmer': {
|
||
'0%': { backgroundPosition: '-200% 0' },
|
||
'100%': { backgroundPosition: '200% 0' }
|
||
}
|
||
}}
|
||
>
|
||
价值前沿
|
||
</Text>
|
||
</Heading>
|
||
<Text fontSize="sm" color="whiteAlpha.900" lineHeight="1.8" fontWeight="medium">
|
||
实时捕捉市场动态,智能分析投资机会。
|
||
<br />
|
||
整合多维数据源,为您提供专业的投资决策支持。
|
||
</Text>
|
||
|
||
<VStack spacing={3} align="stretch" w="100%" mt={2}>
|
||
<Box
|
||
animation="fadeInUp 0.8s ease-out 0.2s both"
|
||
sx={{
|
||
'@keyframes fadeInUp': {
|
||
'0%': { opacity: 0, transform: 'translateY(20px)' },
|
||
'100%': { opacity: 1, transform: 'translateY(0)' }
|
||
}
|
||
}}
|
||
>
|
||
<FeatureIcon
|
||
icon={Activity}
|
||
title="实时监控"
|
||
description="7×24小时追踪市场动态"
|
||
/>
|
||
</Box>
|
||
<Box
|
||
animation="fadeInUp 0.8s ease-out 0.4s both"
|
||
sx={{
|
||
'@keyframes fadeInUp': {
|
||
'0%': { opacity: 0, transform: 'translateY(20px)' },
|
||
'100%': { opacity: 1, transform: 'translateY(0)' }
|
||
}
|
||
}}
|
||
>
|
||
<FeatureIcon
|
||
icon={TrendingUp}
|
||
title="智能分析"
|
||
description="AI驱动的概念板块分析"
|
||
/>
|
||
</Box>
|
||
<Box
|
||
animation="fadeInUp 0.8s ease-out 0.6s both"
|
||
sx={{
|
||
'@keyframes fadeInUp': {
|
||
'0%': { opacity: 0, transform: 'translateY(20px)' },
|
||
'100%': { opacity: 1, transform: 'translateY(0)' }
|
||
}
|
||
}}
|
||
>
|
||
<FeatureIcon
|
||
icon={Globe}
|
||
title="全面覆盖"
|
||
description="A股全市场深度数据"
|
||
/>
|
||
</Box>
|
||
<Box
|
||
animation="fadeInUp 0.8s ease-out 0.8s both"
|
||
sx={{
|
||
'@keyframes fadeInUp': {
|
||
'0%': { opacity: 0, transform: 'translateY(20px)' },
|
||
'100%': { opacity: 1, transform: 'translateY(0)' }
|
||
}
|
||
}}
|
||
>
|
||
<FeatureIcon
|
||
icon={Zap}
|
||
title="极速响应"
|
||
description="毫秒级数据更新推送"
|
||
/>
|
||
</Box>
|
||
</VStack>
|
||
</VStack>
|
||
</Box>
|
||
|
||
{/* 中间:沪深指数K线图 */}
|
||
<Box
|
||
animation="fadeInScale 0.8s ease-out 0.3s both"
|
||
sx={{
|
||
'@keyframes fadeInScale': {
|
||
'0%': { opacity: 0, transform: 'scale(0.95)' },
|
||
'100%': { opacity: 1, transform: 'scale(1)' }
|
||
}
|
||
}}
|
||
>
|
||
<VStack spacing={4} h="100%">
|
||
<Box
|
||
w="100%"
|
||
p={4}
|
||
bg="whiteAlpha.100"
|
||
borderRadius="xl"
|
||
borderWidth="1px"
|
||
borderColor="whiteAlpha.200"
|
||
backdropFilter="blur(10px)"
|
||
boxShadow="0 4px 16px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.05)"
|
||
transition="all 0.4s cubic-bezier(0.4, 0, 0.2, 1)"
|
||
position="relative"
|
||
overflow="hidden"
|
||
_hover={{
|
||
bg: "whiteAlpha.150",
|
||
borderColor: "rgba(236, 0, 0, 0.4)",
|
||
boxShadow: "0 8px 32px rgba(236, 0, 0, 0.3), 0 0 0 1px rgba(236, 0, 0, 0.2) inset, inset 0 1px 0 rgba(255, 255, 255, 0.1)",
|
||
transform: "translateY(-4px) scale(1.02)"
|
||
}}
|
||
_before={{
|
||
content: '""',
|
||
position: 'absolute',
|
||
top: 0,
|
||
left: '-100%',
|
||
width: '100%',
|
||
height: '100%',
|
||
background: 'linear-gradient(90deg, transparent, rgba(236, 0, 0, 0.1), transparent)',
|
||
transition: 'left 0.5s ease'
|
||
}}
|
||
_hover_before={{
|
||
left: '100%'
|
||
}}
|
||
>
|
||
<MiniIndexChart indexCode="000001" indexName="上证指数" />
|
||
</Box>
|
||
<Box
|
||
w="100%"
|
||
p={4}
|
||
bg="whiteAlpha.100"
|
||
borderRadius="xl"
|
||
borderWidth="1px"
|
||
borderColor="whiteAlpha.200"
|
||
backdropFilter="blur(10px)"
|
||
boxShadow="0 4px 16px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.05)"
|
||
transition="all 0.4s cubic-bezier(0.4, 0, 0.2, 1)"
|
||
position="relative"
|
||
overflow="hidden"
|
||
_hover={{
|
||
bg: "whiteAlpha.150",
|
||
borderColor: "rgba(0, 218, 60, 0.4)",
|
||
boxShadow: "0 8px 32px rgba(0, 218, 60, 0.3), 0 0 0 1px rgba(0, 218, 60, 0.2) inset, inset 0 1px 0 rgba(255, 255, 255, 0.1)",
|
||
transform: "translateY(-4px) scale(1.02)"
|
||
}}
|
||
_before={{
|
||
content: '""',
|
||
position: 'absolute',
|
||
top: 0,
|
||
left: '-100%',
|
||
width: '100%',
|
||
height: '100%',
|
||
background: 'linear-gradient(90deg, transparent, rgba(0, 218, 60, 0.1), transparent)',
|
||
transition: 'left 0.5s ease'
|
||
}}
|
||
_hover_before={{
|
||
left: '100%'
|
||
}}
|
||
>
|
||
<MiniIndexChart indexCode="399001" indexName="深证成指" />
|
||
</Box>
|
||
</VStack>
|
||
</Box>
|
||
|
||
{/* 右侧:热门概念词云图 */}
|
||
<Box
|
||
animation="fadeInRight 0.8s ease-out 0.5s both"
|
||
sx={{
|
||
'@keyframes fadeInRight': {
|
||
'0%': { opacity: 0, transform: 'translateX(30px)' },
|
||
'100%': { opacity: 1, transform: 'translateX(0)' }
|
||
}
|
||
}}
|
||
>
|
||
<VStack align="start" spacing={3} h="100%">
|
||
<HStack
|
||
spacing={2}
|
||
p={2}
|
||
borderRadius="md"
|
||
bg="whiteAlpha.50"
|
||
w="full"
|
||
justify="space-between"
|
||
position="relative"
|
||
overflow="hidden"
|
||
transition="all 0.3s ease"
|
||
_hover={{
|
||
bg: "whiteAlpha.100",
|
||
boxShadow: "0 0 20px rgba(255, 165, 0, 0.3)"
|
||
}}
|
||
_before={{
|
||
content: '""',
|
||
position: 'absolute',
|
||
top: 0,
|
||
left: 0,
|
||
right: 0,
|
||
bottom: 0,
|
||
background: 'linear-gradient(90deg, transparent, rgba(255, 165, 0, 0.1), transparent)',
|
||
animation: 'headerShimmer 3s ease-in-out infinite',
|
||
pointerEvents: 'none'
|
||
}}
|
||
sx={{
|
||
'@keyframes headerShimmer': {
|
||
'0%': { transform: 'translateX(-100%)' },
|
||
'100%': { transform: 'translateX(100%)' }
|
||
}
|
||
}}
|
||
>
|
||
<HStack spacing={2}>
|
||
<Text
|
||
fontSize="lg"
|
||
fontWeight="extrabold"
|
||
bgGradient="linear(to-r, #FFD700, #FFA500, #FF4500)"
|
||
bgClip="text"
|
||
backgroundSize="200% 100%"
|
||
animation="gradientFlow 3s ease infinite"
|
||
sx={{
|
||
'@keyframes gradientFlow': {
|
||
'0%, 100%': { backgroundPosition: '0% 50%' },
|
||
'50%': { backgroundPosition: '100% 50%' }
|
||
}
|
||
}}
|
||
>
|
||
🔥 热门概念
|
||
</Text>
|
||
</HStack>
|
||
<HStack spacing={1}>
|
||
<Box
|
||
w="6px"
|
||
h="6px"
|
||
borderRadius="full"
|
||
bg="orange.400"
|
||
animation="liveIndicator 1.5s ease-in-out infinite"
|
||
boxShadow="0 0 10px rgba(251, 146, 60, 0.8)"
|
||
position="relative"
|
||
_after={{
|
||
content: '""',
|
||
position: 'absolute',
|
||
top: '-3px',
|
||
left: '-3px',
|
||
right: '-3px',
|
||
bottom: '-3px',
|
||
borderRadius: 'full',
|
||
border: '2px solid',
|
||
borderColor: 'orange.400',
|
||
animation: 'ripple 1.5s ease-out infinite'
|
||
}}
|
||
sx={{
|
||
'@keyframes liveIndicator': {
|
||
'0%, 100%': { opacity: 1, transform: 'scale(1)' },
|
||
'50%': { opacity: 0.6, transform: 'scale(1.2)' }
|
||
},
|
||
'@keyframes ripple': {
|
||
'0%': { transform: 'scale(1)', opacity: 0.8 },
|
||
'100%': { transform: 'scale(2.5)', opacity: 0 }
|
||
}
|
||
}}
|
||
/>
|
||
<Text fontSize="xs" color="orange.400" fontWeight="medium">
|
||
实时更新
|
||
</Text>
|
||
</HStack>
|
||
</HStack>
|
||
<Box
|
||
w="100%"
|
||
flex="1"
|
||
position="relative"
|
||
overflow="hidden"
|
||
borderRadius="lg"
|
||
bg="whiteAlpha.50"
|
||
p={2}
|
||
transition="all 0.3s ease"
|
||
_hover={{
|
||
bg: "whiteAlpha.100",
|
||
boxShadow: "0 0 30px rgba(255, 215, 0, 0.2)"
|
||
}}
|
||
>
|
||
<ConceptFlowAnimation />
|
||
</Box>
|
||
<HStack
|
||
spacing={2}
|
||
fontSize="xs"
|
||
color="whiteAlpha.600"
|
||
w="100%"
|
||
justify="center"
|
||
animation="fadeIn 1s ease-out 1s both"
|
||
sx={{
|
||
'@keyframes fadeIn': {
|
||
'0%': { opacity: 0 },
|
||
'100%': { opacity: 1 }
|
||
}
|
||
}}
|
||
>
|
||
<Text>🌊 概念流动展示</Text>
|
||
<Text>•</Text>
|
||
<Text>🎨 颜色表示涨跌幅</Text>
|
||
<Text>•</Text>
|
||
<Text>👆 点击查看详情</Text>
|
||
</HStack>
|
||
</VStack>
|
||
</Box>
|
||
</SimpleGrid>
|
||
</CardBody>
|
||
</Card>
|
||
);
|
||
};
|
||
|
||
export default HeroPanel;
|