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%绿色) * 获取胜率颜色(>50%红色,<50%绿色)
*/ */
const getRateColor = (rate) => { const getRateColor = (rate) => {
if (rate >= 50) return '#FF4D4F'; // 红色(上涨多) if (rate >= 50) return '#F31260'; // HeroUI 红色
return '#52C41A'; // 绿色(下跌多) 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)); 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 gaugeColor = getRateColor(validRate);
const isSmall = size === 'small'; const circumference = 2 * Math.PI * 42; // 半径42
const svgSize = isSmall ? 80 : 100; const strokeDashoffset = circumference - (validRate / 100) * circumference;
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}`;
return ( return (
<Box position="relative" w={`${svgSize}px`} h={`${svgSize / 2 + 18}px`}> <Box
{/* SVG 半圆弧 */} bg="rgba(255,255,255,0.03)"
<svg backdropFilter="blur(20px)"
width={svgSize} borderRadius="2xl"
height={svgSize / 2 + 2} p={4}
style={{ position: 'absolute', top: 0, left: 0 }} border="1px solid"
> borderColor="rgba(255,255,255,0.08)"
<defs> position="relative"
<linearGradient id={gradientId} x1="0%" y1="0%" x2="100%" y2="0%"> overflow="hidden"
<stop offset="0%" stopColor="#52C41A" /> flex="1"
<stop offset="50%" stopColor="#FADB14" /> _before={{
<stop offset="100%" stopColor="#FF4D4F" /> content: '""',
</linearGradient> position: 'absolute',
</defs> top: 0,
<path left: 0,
d={arcPath} right: 0,
fill="none" bottom: 0,
stroke={`url(#${gradientId})`} background: `radial-gradient(circle at 30% 20%, ${gaugeColor}15 0%, transparent 50%)`,
strokeWidth={strokeWidth} pointerEvents: 'none',
strokeLinecap="round" }}
/> >
</svg> {/* 圆环仪表盘 */}
<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 <HStack justify="center" mt={2} spacing={2}>
position="absolute" <Box color={gaugeColor} fontSize="sm">{icon}</Box>
bottom="18px" <Text fontSize="sm" color="whiteAlpha.800" fontWeight="medium">
left="50%" {label}
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)}%
</Text> </Text>
<Text fontSize="2xs" color="gray.500">{label}</Text> </HStack>
</VStack>
</Box> </Box>
); );
}; };
/** /**
* 胜率对比组件 - 双仪表盘 * HeroUI 风格胜率对比面板
*/ */
const WinRateGauge = ({ eventRate, marketRate, marketStats }) => { const WinRateGauge = ({ eventRate, marketRate, marketStats }) => {
const eventRateVal = eventRate || 0; const eventRateVal = eventRate || 0;
@@ -186,60 +178,122 @@ const WinRateGauge = ({ eventRate, marketRate, marketStats }) => {
return ( return (
<Box> <Box>
{/* 双仪表盘对比 */} {/* 双仪表盘对比 - HeroUI 毛玻璃卡片 */}
<HStack spacing={2} justify="center" mb={1}> <HStack spacing={4} mb={4}>
<VStack spacing={0}> <CircularGauge
<SemiCircleGauge rate={eventRateVal} label="事件胜率" size="small" /> rate={eventRateVal}
</VStack> label="事件胜率"
<Box w="1px" h="50px" bg="rgba(255,215,0,0.2)" /> icon={<TrophyOutlined />}
<VStack spacing={0}> />
<SemiCircleGauge rate={marketRateVal} label="大盘上涨率" size="small" /> <CircularGauge
</VStack> rate={marketRateVal}
label="大盘上涨率"
icon={<RiseOutlined />}
/>
</HStack> </HStack>
{/* 市场统计 */} {/* 市场统计 - 毛玻璃条 */}
{marketStats && ( {marketStats && marketStats.totalCount > 0 && (
<HStack justify="center" spacing={3} mt={1}> <Box
<HStack spacing={1}> bg="rgba(255,255,255,0.03)"
<Box w="6px" h="6px" borderRadius="full" bg="#FF4D4F" /> backdropFilter="blur(10px)"
<Text fontSize="2xs" color="#FF4D4F">{marketStats.risingCount}</Text> 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>
<HStack spacing={1}> {/* 进度条 */}
<Box w="6px" h="6px" borderRadius="full" bg="gray.400" /> <Box position="relative" h="6px" borderRadius="full" overflow="hidden" bg="rgba(255,255,255,0.05)">
<Text fontSize="2xs" color="gray.400">{marketStats.flatCount}</Text> <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>
<HStack spacing={1}> </Box>
<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 <Box
bg="rgba(0,0,0,0.25)" bg="rgba(255,255,255,0.03)"
borderRadius="md" backdropFilter="blur(10px)"
p={2} borderRadius="xl"
p={3}
border="1px solid" border="1px solid"
borderColor="rgba(255,215,0,0.12)" borderColor="rgba(255,255,255,0.06)"
_hover={{ borderColor: 'rgba(255,215,0,0.25)' }} _hover={{
transition="all 0.2s" 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}> <HStack spacing={2} mb={1}>
<Box color={color} fontSize="xs"> <Box
color={color}
fontSize="sm"
p={1.5}
borderRadius="lg"
bg={`${color}15`}
>
{icon} {icon}
</Box> </Box>
<Text fontSize="2xs" color="gray.500" fontWeight="medium"> <Text fontSize="xs" color="whiteAlpha.600" fontWeight="medium">
{label} {label}
</Text> </Text>
</HStack> </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} {value}
</Text> </Text>
{subText && ( {subText && (
@@ -424,90 +478,137 @@ const EventDailyStats = () => {
return ( return (
<Box <Box
bg="linear-gradient(180deg, rgba(25, 32, 55, 0.95) 0%, rgba(17, 24, 39, 0.98) 100%)" 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%)"
borderRadius="xl" backdropFilter="blur(20px)"
p={3} borderRadius="2xl"
p={4}
border="1px solid" border="1px solid"
borderColor="rgba(255, 215, 0, 0.15)" borderColor="rgba(255, 255, 255, 0.08)"
position="relative" position="relative"
overflow="hidden" overflow="hidden"
h="100%" h="100%"
display="flex" display="flex"
flexDirection="column" flexDirection="column"
boxShadow="0 8px 32px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255,255,255,0.05)"
> >
{/* 背景装饰 */} {/* 背景光效 */}
<Box <Box
position="absolute" position="absolute"
top="-30%" top="-100px"
right="-20%" right="-100px"
w="150px" w="300px"
h="150px" h="300px"
bg="radial-gradient(circle, rgba(255,215,0,0.06) 0%, transparent 70%)" bg="radial-gradient(circle, rgba(124, 58, 237, 0.15) 0%, transparent 60%)"
pointerEvents="none" 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}> <Flex justify="space-between" align="center" mb={4}>
<HStack spacing={2}> <HStack spacing={3}>
<Box <Box
w="3px" w="4px"
h="16px" h="20px"
bg="linear-gradient(180deg, #FFD700, #FFA500)" bg="linear-gradient(180deg, #7C3AED, #06B6D4)"
borderRadius="full" 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 ? '今日统计' : '历史统计'} {isToday ? '今日统计' : '历史统计'}
</Text> </Text>
{isToday && ( {isToday && (
<Badge colorScheme="yellow" variant="subtle" fontSize="2xs" px={1.5}>
实时
</Badge>
)}
</HStack>
<HStack spacing={1}>
{/* 今天按钮 - 仅在查看历史时显示 */}
{!isToday && (
<Box <Box
px={2} px={2}
py={0.5} py={0.5}
bg="rgba(255,215,0,0.15)" bg="rgba(23, 201, 100, 0.15)"
border="1px solid rgba(255,215,0,0.3)" border="1px solid rgba(23, 201, 100, 0.3)"
borderRadius="md" borderRadius="full"
cursor="pointer"
_hover={{ bg: 'rgba(255,215,0,0.25)' }}
onClick={() => setSelectedDate('')}
> >
<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> </Box>
)} )}
<CalendarOutlined style={{ color: '#FFD700', fontSize: '12px' }} /> </HStack>
<Input <HStack spacing={2}>
type="date" {/* 今天按钮 - 仅在查看历史时显示 */}
size="xs" {!isToday && (
value={selectedDate} <Box
onChange={handleDateChange} px={3}
max={new Date().toISOString().split('T')[0]} py={1}
bg="rgba(0,0,0,0.3)" bg="rgba(124, 58, 237, 0.15)"
border="1px solid rgba(255,215,0,0.2)" border="1px solid rgba(124, 58, 237, 0.3)"
borderRadius="md" borderRadius="lg"
color="white" cursor="pointer"
fontSize="xs" _hover={{ bg: 'rgba(124, 58, 237, 0.25)', transform: 'scale(1.02)' }}
w="110px" transition="all 0.2s"
h="24px" onClick={() => setSelectedDate('')}
_hover={{ borderColor: 'rgba(255,215,0,0.4)' }} >
_focus={{ borderColor: '#FFD700', boxShadow: 'none' }} <Text fontSize="xs" color="#A78BFA" fontWeight="bold">返回今天</Text>
css={{ </Box>
'&::-webkit-calendar-picker-indicator': { )}
filter: 'invert(1)', <Box
cursor: 'pointer', 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> </HStack>
</Flex> </Flex>
{/* 内容区域 - 使用 flex: 1 填充剩余空间 */} {/* 内容区域 - 使用 flex: 1 填充剩余空间 */}
<VStack spacing={2} align="stretch" flex="1"> <VStack spacing={4} align="stretch" flex="1">
{/* 胜率对比仪表盘 */} {/* 胜率对比仪表盘 */}
<WinRateGauge <WinRateGauge
eventRate={summary?.positiveRate || 0} eventRate={summary?.positiveRate || 0}

View File

@@ -2537,41 +2537,77 @@ const CombinedCalendar = () => {
}; };
/** /**
* 右侧 Tab 面板 - 连板情绪监测 / 综合日历 * 右侧 Tab 面板 - HeroUI 风格毛玻璃
*/ */
const RightPanelTabs = () => { const RightPanelTabs = () => {
const [activeTab, setActiveTab] = useState('comet'); // 默认显示日历
const [activeTab, setActiveTab] = useState('calendar');
return ( return (
<Box <Box
bg="linear-gradient(180deg, rgba(25, 32, 55, 0.95) 0%, rgba(17, 24, 39, 0.98) 100%)" 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%)"
borderRadius="xl" backdropFilter="blur(20px)"
borderRadius="2xl"
border="1px solid" border="1px solid"
borderColor="rgba(255, 215, 0, 0.15)" borderColor="rgba(255, 255, 255, 0.08)"
h="100%" h="100%"
display="flex" display="flex"
flexDirection="column" flexDirection="column"
overflow="hidden" 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 切换头 */} {/* Tab 切换头 */}
<HStack <HStack
px={4} px={4}
py={2} py={3}
borderBottom="1px solid rgba(255,215,0,0.1)" borderBottom="1px solid rgba(255,255,255,0.06)"
bg="rgba(0,0,0,0.2)" bg="rgba(0,0,0,0.2)"
spacing={0} spacing={2}
> >
<Box <Box
px={4} px={4}
py={2} py={2}
cursor="pointer" cursor="pointer"
borderRadius="md" borderRadius="xl"
bg={activeTab === 'comet' ? 'rgba(255,215,0,0.15)' : 'transparent'} bg={activeTab === 'calendar' ? 'rgba(124, 58, 237, 0.2)' : 'transparent'}
color={activeTab === 'comet' ? '#FFD700' : 'gray.400'} 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'} fontWeight={activeTab === 'comet' ? 'bold' : 'normal'}
fontSize="sm" fontSize="sm"
transition="all 0.2s" transition="all 0.3s ease"
_hover={{ color: '#FFD700', bg: 'rgba(255,215,0,0.08)' }} 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')} onClick={() => setActiveTab('comet')}
> >
<HStack spacing={2}> <HStack spacing={2}>
@@ -2579,32 +2615,18 @@ const RightPanelTabs = () => {
<Text>连板情绪监测</Text> <Text>连板情绪监测</Text>
</HStack> </HStack>
</Box> </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> </HStack>
{/* Tab 内容区域 */} {/* Tab 内容区域 - 固定高度确保一致 */}
<Box flex="1" minH={0} p={3}> <Box flex="1" minH="650px" p={3} overflow="hidden">
{activeTab === 'comet' ? ( {activeTab === 'comet' ? (
<ThemeCometChart /> <Box h="100%">
<ThemeCometChart />
</Box>
) : ( ) : (
<CombinedCalendar /> <Box h="100%">
<CombinedCalendar />
</Box>
)} )}
</Box> </Box>
</Box> </Box>