heropanel修改

This commit is contained in:
2026-01-09 08:12:05 +08:00
parent f054987241
commit 907725165c
2 changed files with 348 additions and 225 deletions

View File

@@ -64,121 +64,113 @@ const getChgColor = (val) => {
* 获取胜率颜色(>50%红色,<50%绿色)
*/
const getRateColor = (rate) => {
if (rate >= 50) return '#FF4D4F'; // 红色(上涨多)
return '#52C41A'; // 绿色(下跌多)
if (rate >= 50) return '#F31260'; // HeroUI 红色
return '#17C964'; // HeroUI 绿色
};
/**
* 半圆仪表盘组件 - 使用 SVG 实现渐变弧线
* 渐变色:左侧绿色 -> 中间黄色 -> 右侧红色
* HeroUI 风格圆环仪表盘
*/
const SemiCircleGauge = ({ rate, label, size = 'normal' }) => {
const CircularGauge = ({ rate, label, icon }) => {
const validRate = Math.min(100, Math.max(0, rate || 0));
// 角度0% = -90deg (左), 50% = 0deg (上), 100% = 90deg (右)
const needleAngle = (validRate / 100) * 180 - 90;
const gaugeColor = getRateColor(validRate);
const isSmall = size === 'small';
const svgSize = isSmall ? 80 : 100;
const strokeWidth = isSmall ? 6 : 8;
const radius = (svgSize - strokeWidth) / 2;
const needleLength = isSmall ? 28 : 35;
const gradientId = `gauge-gradient-${label.replace(/\s/g, '-')}`;
// 计算半圆弧的路径(从左到右)
const arcPath = `M ${strokeWidth / 2} ${svgSize / 2} A ${radius} ${radius} 0 0 1 ${svgSize - strokeWidth / 2} ${svgSize / 2}`;
const circumference = 2 * Math.PI * 42; // 半径42
const strokeDashoffset = circumference - (validRate / 100) * circumference;
return (
<Box position="relative" w={`${svgSize}px`} h={`${svgSize / 2 + 18}px`}>
{/* SVG 半圆弧 */}
<svg
width={svgSize}
height={svgSize / 2 + 2}
style={{ position: 'absolute', top: 0, left: 0 }}
>
<defs>
<linearGradient id={gradientId} x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stopColor="#52C41A" />
<stop offset="50%" stopColor="#FADB14" />
<stop offset="100%" stopColor="#FF4D4F" />
</linearGradient>
</defs>
<path
d={arcPath}
fill="none"
stroke={`url(#${gradientId})`}
strokeWidth={strokeWidth}
strokeLinecap="round"
/>
</svg>
<Box
bg="rgba(255,255,255,0.03)"
backdropFilter="blur(20px)"
borderRadius="2xl"
p={4}
border="1px solid"
borderColor="rgba(255,255,255,0.08)"
position="relative"
overflow="hidden"
flex="1"
_before={{
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: `radial-gradient(circle at 30% 20%, ${gaugeColor}15 0%, transparent 50%)`,
pointerEvents: 'none',
}}
>
{/* 圆环仪表盘 */}
<Center>
<Box position="relative" w="100px" h="100px">
<svg width="100" height="100" style={{ transform: 'rotate(-90deg)' }}>
{/* 背景圆环 */}
<circle
cx="50"
cy="50"
r="42"
fill="none"
stroke="rgba(255,255,255,0.08)"
strokeWidth="8"
/>
{/* 渐变定义 */}
<defs>
<linearGradient id={`gauge-grad-${label}`} x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stopColor={gaugeColor} stopOpacity="0.6" />
<stop offset="100%" stopColor={gaugeColor} />
</linearGradient>
</defs>
{/* 进度圆环 */}
<circle
cx="50"
cy="50"
r="42"
fill="none"
stroke={`url(#gauge-grad-${label})`}
strokeWidth="8"
strokeLinecap="round"
strokeDasharray={circumference}
strokeDashoffset={strokeDashoffset}
style={{
transition: 'stroke-dashoffset 0.8s ease-out',
filter: `drop-shadow(0 0 8px ${gaugeColor}60)`,
}}
/>
</svg>
{/* 中心数值 */}
<VStack
position="absolute"
top="50%"
left="50%"
transform="translate(-50%, -50%)"
spacing={0}
>
<Text
fontSize="2xl"
fontWeight="bold"
color={gaugeColor}
lineHeight="1"
textShadow={`0 0 20px ${gaugeColor}40`}
>
{validRate.toFixed(1)}
</Text>
<Text fontSize="xs" color="whiteAlpha.600">%</Text>
</VStack>
</Box>
</Center>
{/* 指针 */}
<Box
position="absolute"
bottom="18px"
left="50%"
w="2px"
h={`${needleLength}px`}
bg={gaugeColor}
borderRadius="full"
transformOrigin="bottom center"
transform={`translateX(-50%) rotate(${needleAngle}deg)`}
boxShadow={`0 0 6px ${gaugeColor}`}
transition="transform 0.5s ease-out"
/>
{/* 指针中心点 */}
<Box
position="absolute"
bottom="15px"
left="50%"
transform="translateX(-50%)"
w="6px"
h="6px"
bg={gaugeColor}
borderRadius="full"
boxShadow={`0 0 4px ${gaugeColor}`}
/>
{/* 刻度标记 */}
<Text
position="absolute"
bottom="18px"
left="2px"
fontSize="2xs"
color="gray.600"
>
0
</Text>
<Text
position="absolute"
bottom="18px"
right="2px"
fontSize="2xs"
color="gray.600"
>
100
</Text>
{/* 数值和标签 */}
<VStack
position="absolute"
bottom="0"
left="50%"
transform="translateX(-50%)"
spacing={0}
>
<Text fontSize={isSmall ? 'sm' : 'md'} fontWeight="bold" color={gaugeColor} lineHeight="1">
{validRate.toFixed(1)}%
{/* 标签 */}
<HStack justify="center" mt={2} spacing={2}>
<Box color={gaugeColor} fontSize="sm">{icon}</Box>
<Text fontSize="sm" color="whiteAlpha.800" fontWeight="medium">
{label}
</Text>
<Text fontSize="2xs" color="gray.500">{label}</Text>
</VStack>
</HStack>
</Box>
);
};
/**
* 胜率对比组件 - 双仪表盘
* HeroUI 风格胜率对比面板
*/
const WinRateGauge = ({ eventRate, marketRate, marketStats }) => {
const eventRateVal = eventRate || 0;
@@ -186,60 +178,122 @@ const WinRateGauge = ({ eventRate, marketRate, marketStats }) => {
return (
<Box>
{/* 双仪表盘对比 */}
<HStack spacing={2} justify="center" mb={1}>
<VStack spacing={0}>
<SemiCircleGauge rate={eventRateVal} label="事件胜率" size="small" />
</VStack>
<Box w="1px" h="50px" bg="rgba(255,215,0,0.2)" />
<VStack spacing={0}>
<SemiCircleGauge rate={marketRateVal} label="大盘上涨率" size="small" />
</VStack>
{/* 双仪表盘对比 - HeroUI 毛玻璃卡片 */}
<HStack spacing={4} mb={4}>
<CircularGauge
rate={eventRateVal}
label="事件胜率"
icon={<TrophyOutlined />}
/>
<CircularGauge
rate={marketRateVal}
label="大盘上涨率"
icon={<RiseOutlined />}
/>
</HStack>
{/* 市场统计 */}
{marketStats && (
<HStack justify="center" spacing={3} mt={1}>
<HStack spacing={1}>
<Box w="6px" h="6px" borderRadius="full" bg="#FF4D4F" />
<Text fontSize="2xs" color="#FF4D4F">{marketStats.risingCount}</Text>
{/* 市场统计 - 毛玻璃条 */}
{marketStats && marketStats.totalCount > 0 && (
<Box
bg="rgba(255,255,255,0.03)"
backdropFilter="blur(10px)"
borderRadius="xl"
p={3}
border="1px solid rgba(255,255,255,0.06)"
>
<HStack justify="space-between" mb={2}>
<Text fontSize="xs" color="whiteAlpha.500">沪深两市实时</Text>
<Text fontSize="xs" color="whiteAlpha.400">{marketStats.totalCount} </Text>
</HStack>
<HStack spacing={1}>
<Box w="6px" h="6px" borderRadius="full" bg="gray.400" />
<Text fontSize="2xs" color="gray.400">{marketStats.flatCount}</Text>
{/* 进度条 */}
<Box position="relative" h="6px" borderRadius="full" overflow="hidden" bg="rgba(255,255,255,0.05)">
<Box
position="absolute"
left="0"
top="0"
h="100%"
w={`${(marketStats.risingCount / marketStats.totalCount) * 100}%`}
bg="linear-gradient(90deg, #F31260, #FF6B9D)"
borderRadius="full"
/>
<Box
position="absolute"
left={`${(marketStats.risingCount / marketStats.totalCount) * 100}%`}
top="0"
h="100%"
w={`${(marketStats.flatCount / marketStats.totalCount) * 100}%`}
bg="rgba(255,255,255,0.3)"
/>
</Box>
{/* 数字统计 */}
<HStack justify="space-between" mt={2}>
<HStack spacing={1}>
<Box w="8px" h="8px" borderRadius="full" bg="#F31260" boxShadow="0 0 8px #F3126060" />
<Text fontSize="sm" color="#FF6B9D" fontWeight="bold">{marketStats.risingCount}</Text>
<Text fontSize="xs" color="whiteAlpha.500"></Text>
</HStack>
<HStack spacing={1}>
<Box w="8px" h="8px" borderRadius="full" bg="whiteAlpha.400" />
<Text fontSize="sm" color="whiteAlpha.700" fontWeight="bold">{marketStats.flatCount}</Text>
<Text fontSize="xs" color="whiteAlpha.500"></Text>
</HStack>
<HStack spacing={1}>
<Box w="8px" h="8px" borderRadius="full" bg="#17C964" boxShadow="0 0 8px #17C96460" />
<Text fontSize="sm" color="#17C964" fontWeight="bold">{marketStats.fallingCount}</Text>
<Text fontSize="xs" color="whiteAlpha.500"></Text>
</HStack>
</HStack>
<HStack spacing={1}>
<Box w="6px" h="6px" borderRadius="full" bg="#52C41A" />
<Text fontSize="2xs" color="#52C41A">{marketStats.fallingCount}</Text>
</HStack>
</HStack>
</Box>
)}
</Box>
);
};
/**
* 紧凑数据卡片
* HeroUI 风格紧凑数据卡片
*/
const CompactStatCard = ({ label, value, icon, color = '#FFD700', subText }) => (
const CompactStatCard = ({ label, value, icon, color = '#7C3AED', subText }) => (
<Box
bg="rgba(0,0,0,0.25)"
borderRadius="md"
p={2}
bg="rgba(255,255,255,0.03)"
backdropFilter="blur(10px)"
borderRadius="xl"
p={3}
border="1px solid"
borderColor="rgba(255,215,0,0.12)"
_hover={{ borderColor: 'rgba(255,215,0,0.25)' }}
transition="all 0.2s"
borderColor="rgba(255,255,255,0.06)"
_hover={{
borderColor: 'rgba(255,255,255,0.12)',
bg: 'rgba(255,255,255,0.05)',
transform: 'translateY(-2px)',
}}
transition="all 0.3s ease"
position="relative"
overflow="hidden"
_before={{
content: '""',
position: 'absolute',
top: 0,
right: 0,
w: '60px',
h: '60px',
background: `radial-gradient(circle, ${color}15 0%, transparent 70%)`,
pointerEvents: 'none',
}}
>
<HStack spacing={1.5} mb={0.5}>
<Box color={color} fontSize="xs">
<HStack spacing={2} mb={1}>
<Box
color={color}
fontSize="sm"
p={1.5}
borderRadius="lg"
bg={`${color}15`}
>
{icon}
</Box>
<Text fontSize="2xs" color="gray.500" fontWeight="medium">
<Text fontSize="xs" color="whiteAlpha.600" fontWeight="medium">
{label}
</Text>
</HStack>
<Text fontSize="md" fontWeight="bold" color={color} lineHeight="1.2">
<Text fontSize="lg" fontWeight="bold" color={color} lineHeight="1.2" textShadow={`0 0 20px ${color}30`}>
{value}
</Text>
{subText && (
@@ -424,90 +478,137 @@ const EventDailyStats = () => {
return (
<Box
bg="linear-gradient(180deg, rgba(25, 32, 55, 0.95) 0%, rgba(17, 24, 39, 0.98) 100%)"
borderRadius="xl"
p={3}
bg="linear-gradient(135deg, rgba(10, 10, 20, 0.9) 0%, rgba(20, 20, 40, 0.95) 50%, rgba(15, 15, 30, 0.9) 100%)"
backdropFilter="blur(20px)"
borderRadius="2xl"
p={4}
border="1px solid"
borderColor="rgba(255, 215, 0, 0.15)"
borderColor="rgba(255, 255, 255, 0.08)"
position="relative"
overflow="hidden"
h="100%"
display="flex"
flexDirection="column"
boxShadow="0 8px 32px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255,255,255,0.05)"
>
{/* 背景装饰 */}
{/* 背景光效 */}
<Box
position="absolute"
top="-30%"
right="-20%"
w="150px"
h="150px"
bg="radial-gradient(circle, rgba(255,215,0,0.06) 0%, transparent 70%)"
top="-100px"
right="-100px"
w="300px"
h="300px"
bg="radial-gradient(circle, rgba(124, 58, 237, 0.15) 0%, transparent 60%)"
pointerEvents="none"
filter="blur(40px)"
/>
<Box
position="absolute"
bottom="-80px"
left="-80px"
w="250px"
h="250px"
bg="radial-gradient(circle, rgba(6, 182, 212, 0.1) 0%, transparent 60%)"
pointerEvents="none"
filter="blur(40px)"
/>
{/* 标题行 */}
<Flex justify="space-between" align="center" mb={2}>
<HStack spacing={2}>
<Flex justify="space-between" align="center" mb={4}>
<HStack spacing={3}>
<Box
w="3px"
h="16px"
bg="linear-gradient(180deg, #FFD700, #FFA500)"
w="4px"
h="20px"
bg="linear-gradient(180deg, #7C3AED, #06B6D4)"
borderRadius="full"
boxShadow="0 0 10px rgba(124, 58, 237, 0.5)"
/>
<Text fontSize="sm" fontWeight="bold" color="white" letterSpacing="wide">
<Text fontSize="md" fontWeight="bold" color="white" letterSpacing="wide">
{isToday ? '今日统计' : '历史统计'}
</Text>
{isToday && (
<Badge colorScheme="yellow" variant="subtle" fontSize="2xs" px={1.5}>
实时
</Badge>
)}
</HStack>
<HStack spacing={1}>
{/* 今天按钮 - 仅在查看历史时显示 */}
{!isToday && (
<Box
px={2}
py={0.5}
bg="rgba(255,215,0,0.15)"
border="1px solid rgba(255,215,0,0.3)"
borderRadius="md"
cursor="pointer"
_hover={{ bg: 'rgba(255,215,0,0.25)' }}
onClick={() => setSelectedDate('')}
bg="rgba(23, 201, 100, 0.15)"
border="1px solid rgba(23, 201, 100, 0.3)"
borderRadius="full"
>
<Text fontSize="xs" color="#FFD700" fontWeight="bold">今天</Text>
<HStack spacing={1}>
<Box
w="6px"
h="6px"
borderRadius="full"
bg="#17C964"
animation="pulse 2s infinite"
boxShadow="0 0 8px #17C964"
/>
<Text fontSize="xs" color="#17C964" fontWeight="medium">实时</Text>
</HStack>
</Box>
)}
<CalendarOutlined style={{ color: '#FFD700', fontSize: '12px' }} />
<Input
type="date"
size="xs"
value={selectedDate}
onChange={handleDateChange}
max={new Date().toISOString().split('T')[0]}
bg="rgba(0,0,0,0.3)"
border="1px solid rgba(255,215,0,0.2)"
borderRadius="md"
color="white"
fontSize="xs"
w="110px"
h="24px"
_hover={{ borderColor: 'rgba(255,215,0,0.4)' }}
_focus={{ borderColor: '#FFD700', boxShadow: 'none' }}
css={{
'&::-webkit-calendar-picker-indicator': {
filter: 'invert(1)',
cursor: 'pointer',
},
}}
/>
</HStack>
<HStack spacing={2}>
{/* 今天按钮 - 仅在查看历史时显示 */}
{!isToday && (
<Box
px={3}
py={1}
bg="rgba(124, 58, 237, 0.15)"
border="1px solid rgba(124, 58, 237, 0.3)"
borderRadius="lg"
cursor="pointer"
_hover={{ bg: 'rgba(124, 58, 237, 0.25)', transform: 'scale(1.02)' }}
transition="all 0.2s"
onClick={() => setSelectedDate('')}
>
<Text fontSize="xs" color="#A78BFA" fontWeight="bold">返回今天</Text>
</Box>
)}
<Box
as="label"
display="flex"
alignItems="center"
gap={2}
px={3}
py={1.5}
bg="rgba(255,255,255,0.03)"
border="1px solid rgba(255,255,255,0.08)"
borderRadius="lg"
cursor="pointer"
_hover={{ bg: 'rgba(255,255,255,0.06)', borderColor: 'rgba(255,255,255,0.12)' }}
transition="all 0.2s"
>
<CalendarOutlined style={{ color: 'rgba(255,255,255,0.6)', fontSize: '14px' }} />
<Input
type="date"
size="xs"
value={selectedDate}
onChange={handleDateChange}
max={new Date().toISOString().split('T')[0]}
bg="transparent"
border="none"
color="whiteAlpha.800"
fontSize="xs"
w="100px"
h="20px"
p={0}
_hover={{ border: 'none' }}
_focus={{ border: 'none', boxShadow: 'none' }}
css={{
'&::-webkit-calendar-picker-indicator': {
filter: 'invert(0.8)',
cursor: 'pointer',
opacity: 0.6,
},
}}
/>
</Box>
</HStack>
</Flex>
{/* 内容区域 - 使用 flex: 1 填充剩余空间 */}
<VStack spacing={2} align="stretch" flex="1">
<VStack spacing={4} align="stretch" flex="1">
{/* 胜率对比仪表盘 */}
<WinRateGauge
eventRate={summary?.positiveRate || 0}

View File

@@ -2537,41 +2537,77 @@ const CombinedCalendar = () => {
};
/**
* 右侧 Tab 面板 - 连板情绪监测 / 综合日历
* 右侧 Tab 面板 - HeroUI 风格毛玻璃
*/
const RightPanelTabs = () => {
const [activeTab, setActiveTab] = useState('comet');
// 默认显示日历
const [activeTab, setActiveTab] = useState('calendar');
return (
<Box
bg="linear-gradient(180deg, rgba(25, 32, 55, 0.95) 0%, rgba(17, 24, 39, 0.98) 100%)"
borderRadius="xl"
bg="linear-gradient(135deg, rgba(10, 10, 20, 0.9) 0%, rgba(20, 20, 40, 0.95) 50%, rgba(15, 15, 30, 0.9) 100%)"
backdropFilter="blur(20px)"
borderRadius="2xl"
border="1px solid"
borderColor="rgba(255, 215, 0, 0.15)"
borderColor="rgba(255, 255, 255, 0.08)"
h="100%"
display="flex"
flexDirection="column"
overflow="hidden"
boxShadow="0 8px 32px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255,255,255,0.05)"
position="relative"
>
{/* 背景光效 */}
<Box
position="absolute"
top="-50px"
left="-50px"
w="200px"
h="200px"
bg="radial-gradient(circle, rgba(6, 182, 212, 0.1) 0%, transparent 60%)"
pointerEvents="none"
filter="blur(40px)"
/>
{/* Tab 切换头 */}
<HStack
px={4}
py={2}
borderBottom="1px solid rgba(255,215,0,0.1)"
py={3}
borderBottom="1px solid rgba(255,255,255,0.06)"
bg="rgba(0,0,0,0.2)"
spacing={0}
spacing={2}
>
<Box
px={4}
py={2}
cursor="pointer"
borderRadius="md"
bg={activeTab === 'comet' ? 'rgba(255,215,0,0.15)' : 'transparent'}
color={activeTab === 'comet' ? '#FFD700' : 'gray.400'}
borderRadius="xl"
bg={activeTab === 'calendar' ? 'rgba(124, 58, 237, 0.2)' : 'transparent'}
color={activeTab === 'calendar' ? '#A78BFA' : 'whiteAlpha.500'}
fontWeight={activeTab === 'calendar' ? 'bold' : 'normal'}
fontSize="sm"
transition="all 0.3s ease"
border={activeTab === 'calendar' ? '1px solid rgba(124, 58, 237, 0.3)' : '1px solid transparent'}
_hover={{ color: '#A78BFA', bg: 'rgba(124, 58, 237, 0.1)' }}
onClick={() => setActiveTab('calendar')}
>
<HStack spacing={2}>
<CalendarOutlined />
<Text>涨停与未来日历</Text>
</HStack>
</Box>
<Box
px={4}
py={2}
cursor="pointer"
borderRadius="xl"
bg={activeTab === 'comet' ? 'rgba(6, 182, 212, 0.2)' : 'transparent'}
color={activeTab === 'comet' ? '#22D3EE' : 'whiteAlpha.500'}
fontWeight={activeTab === 'comet' ? 'bold' : 'normal'}
fontSize="sm"
transition="all 0.2s"
_hover={{ color: '#FFD700', bg: 'rgba(255,215,0,0.08)' }}
transition="all 0.3s ease"
border={activeTab === 'comet' ? '1px solid rgba(6, 182, 212, 0.3)' : '1px solid transparent'}
_hover={{ color: '#22D3EE', bg: 'rgba(6, 182, 212, 0.1)' }}
onClick={() => setActiveTab('comet')}
>
<HStack spacing={2}>
@@ -2579,32 +2615,18 @@ const RightPanelTabs = () => {
<Text>连板情绪监测</Text>
</HStack>
</Box>
<Box
px={4}
py={2}
cursor="pointer"
borderRadius="md"
bg={activeTab === 'calendar' ? 'rgba(255,215,0,0.15)' : 'transparent'}
color={activeTab === 'calendar' ? '#FFD700' : 'gray.400'}
fontWeight={activeTab === 'calendar' ? 'bold' : 'normal'}
fontSize="sm"
transition="all 0.2s"
_hover={{ color: '#FFD700', bg: 'rgba(255,215,0,0.08)' }}
onClick={() => setActiveTab('calendar')}
>
<HStack spacing={2}>
<CalendarOutlined />
<Text>事件日历</Text>
</HStack>
</Box>
</HStack>
{/* Tab 内容区域 */}
<Box flex="1" minH={0} p={3}>
{/* Tab 内容区域 - 固定高度确保一致 */}
<Box flex="1" minH="650px" p={3} overflow="hidden">
{activeTab === 'comet' ? (
<ThemeCometChart />
<Box h="100%">
<ThemeCometChart />
</Box>
) : (
<CombinedCalendar />
<Box h="100%">
<CombinedCalendar />
</Box>
)}
</Box>
</Box>