Files
vf_react/src/views/Community/components/HeroPanel.js
2025-11-13 23:24:54 +08:00

1179 lines
38 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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