heropanel修改

This commit is contained in:
2026-01-09 07:26:21 +08:00
parent 042dc0a62c
commit b9672bcef1

View File

@@ -17,6 +17,8 @@ import {
Tab, Tab,
TabPanels, TabPanels,
TabPanel, TabPanel,
Input,
Flex,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { import {
FireOutlined, FireOutlined,
@@ -24,6 +26,7 @@ import {
ThunderboltOutlined, ThunderboltOutlined,
TrophyOutlined, TrophyOutlined,
StockOutlined, StockOutlined,
CalendarOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { getApiBase } from '@utils/apiConfig'; import { getApiBase } from '@utils/apiConfig';
@@ -62,9 +65,8 @@ const getChgColor = (val) => {
*/ */
const WinRateGauge = ({ rate }) => { const WinRateGauge = ({ rate }) => {
const validRate = Math.min(100, Math.max(0, rate || 0)); const validRate = Math.min(100, Math.max(0, rate || 0));
const angle = (validRate / 100) * 180; // 0-180度 const angle = (validRate / 100) * 180;
// 根据胜率确定颜色
const getGaugeColor = (r) => { const getGaugeColor = (r) => {
if (r >= 70) return '#52C41A'; if (r >= 70) return '#52C41A';
if (r >= 50) return '#FFD700'; if (r >= 50) return '#FFD700';
@@ -74,23 +76,21 @@ const WinRateGauge = ({ rate }) => {
const gaugeColor = getGaugeColor(validRate); const gaugeColor = getGaugeColor(validRate);
return ( return (
<Box position="relative" w="100%" h="90px" overflow="hidden"> <Box position="relative" w="100%" h="80px" overflow="hidden">
{/* 仪表盘背景 */}
<Box <Box
position="absolute" position="absolute"
bottom="0" bottom="0"
left="50%" left="50%"
transform="translateX(-50%)" transform="translateX(-50%)"
w="140px" w="120px"
h="70px" h="60px"
borderTopLeftRadius="70px" borderTopLeftRadius="60px"
borderTopRightRadius="70px" borderTopRightRadius="60px"
bg="rgba(255,255,255,0.05)" bg="rgba(255,255,255,0.05)"
border="3px solid rgba(255,255,255,0.1)" border="3px solid rgba(255,255,255,0.1)"
borderBottom="none" borderBottom="none"
overflow="hidden" overflow="hidden"
> >
{/* 填充弧 */}
<Box <Box
position="absolute" position="absolute"
bottom="0" bottom="0"
@@ -103,13 +103,12 @@ const WinRateGauge = ({ rate }) => {
/> />
</Box> </Box>
{/* 指针 */}
<Box <Box
position="absolute" position="absolute"
bottom="3px" bottom="3px"
left="50%" left="50%"
w="3px" w="2px"
h="50px" h="42px"
bg={gaugeColor} bg={gaugeColor}
borderRadius="full" borderRadius="full"
transformOrigin="bottom center" transformOrigin="bottom center"
@@ -118,36 +117,33 @@ const WinRateGauge = ({ rate }) => {
transition="transform 0.5s ease-out" transition="transform 0.5s ease-out"
/> />
{/* 中心点 */}
<Box <Box
position="absolute" position="absolute"
bottom="0" bottom="0"
left="50%" left="50%"
transform="translateX(-50%)" transform="translateX(-50%)"
w="12px" w="10px"
h="12px" h="10px"
bg={gaugeColor} bg={gaugeColor}
borderRadius="full" borderRadius="full"
boxShadow={`0 0 8px ${gaugeColor}`} boxShadow={`0 0 8px ${gaugeColor}`}
/> />
{/* 数值显示 */}
<VStack <VStack
position="absolute" position="absolute"
bottom="15px" bottom="12px"
left="50%" left="50%"
transform="translateX(-50%)" transform="translateX(-50%)"
spacing={0} spacing={0}
> >
<Text fontSize="2xl" fontWeight="bold" color={gaugeColor} lineHeight="1"> <Text fontSize="xl" fontWeight="bold" color={gaugeColor} lineHeight="1">
{validRate.toFixed(0)}% {validRate.toFixed(0)}%
</Text> </Text>
<Text fontSize="xs" color="gray.500">胜率</Text> <Text fontSize="2xs" color="gray.500">胜率</Text>
</VStack> </VStack>
{/* 刻度标签 */} <Text position="absolute" bottom="0" left="12px" fontSize="2xs" color="gray.600">0</Text>
<Text position="absolute" bottom="0" left="15px" fontSize="2xs" color="gray.600">0</Text> <Text position="absolute" bottom="0" right="12px" fontSize="2xs" color="gray.600">100</Text>
<Text position="absolute" bottom="0" right="15px" fontSize="2xs" color="gray.600">100</Text>
</Box> </Box>
); );
}; };
@@ -159,21 +155,21 @@ const CompactStatCard = ({ label, value, icon, color = '#FFD700', subText }) =>
<Box <Box
bg="rgba(0,0,0,0.25)" bg="rgba(0,0,0,0.25)"
borderRadius="md" borderRadius="md"
p={2.5} p={2}
border="1px solid" border="1px solid"
borderColor="rgba(255,215,0,0.12)" borderColor="rgba(255,215,0,0.12)"
_hover={{ borderColor: 'rgba(255,215,0,0.25)' }} _hover={{ borderColor: 'rgba(255,215,0,0.25)' }}
transition="all 0.2s" transition="all 0.2s"
> >
<HStack spacing={1.5} mb={1}> <HStack spacing={1.5} mb={0.5}>
<Box color={color} fontSize="xs"> <Box color={color} fontSize="xs">
{icon} {icon}
</Box> </Box>
<Text fontSize="xs" color="gray.500" fontWeight="medium"> <Text fontSize="2xs" color="gray.500" fontWeight="medium">
{label} {label}
</Text> </Text>
</HStack> </HStack>
<Text fontSize="lg" fontWeight="bold" color={color} lineHeight="1.2"> <Text fontSize="md" fontWeight="bold" color={color} lineHeight="1.2">
{value} {value}
</Text> </Text>
{subText && ( {subText && (
@@ -197,7 +193,7 @@ const TopEventItem = ({ event, rank }) => {
return ( return (
<HStack <HStack
spacing={2} spacing={2}
py={1.5} py={1}
px={2} px={2}
bg="rgba(0,0,0,0.2)" bg="rgba(0,0,0,0.2)"
borderRadius="md" borderRadius="md"
@@ -240,7 +236,7 @@ const TopStockItem = ({ stock, rank }) => {
return ( return (
<HStack <HStack
spacing={2} spacing={2}
py={1.5} py={1}
px={2} px={2}
bg="rgba(0,0,0,0.2)" bg="rgba(0,0,0,0.2)"
borderRadius="md" borderRadius="md"
@@ -257,7 +253,7 @@ const TopStockItem = ({ stock, rank }) => {
> >
{rank} {rank}
</Badge> </Badge>
<Text fontSize="xs" color="gray.400" w="60px"> <Text fontSize="xs" color="gray.400" w="55px">
{stock.stockCode?.split('.')[0] || '-'} {stock.stockCode?.split('.')[0] || '-'}
</Text> </Text>
<Text fontSize="xs" color="gray.300" flex="1" noOfLines={1}> <Text fontSize="xs" color="gray.300" flex="1" noOfLines={1}>
@@ -275,13 +271,15 @@ const EventDailyStats = () => {
const [stats, setStats] = useState(null); const [stats, setStats] = useState(null);
const [error, setError] = useState(null); const [error, setError] = useState(null);
const [activeTab, setActiveTab] = useState(0); const [activeTab, setActiveTab] = useState(0);
const [selectedDate, setSelectedDate] = useState('');
const fetchStats = useCallback(async () => { const fetchStats = useCallback(async (dateStr = '') => {
setLoading(true); setLoading(true);
setError(null); setError(null);
try { try {
const apiBase = getApiBase(); const apiBase = getApiBase();
const response = await fetch(`${apiBase}/api/v1/events/effectiveness-stats?days=1`); const dateParam = dateStr ? `&date=${dateStr}` : '';
const response = await fetch(`${apiBase}/api/v1/events/effectiveness-stats?days=1${dateParam}`);
if (!response.ok) throw new Error('获取数据失败'); if (!response.ok) throw new Error('获取数据失败');
const data = await response.json(); const data = await response.json();
if (data.success || data.code === 200) { if (data.success || data.code === 200) {
@@ -290,7 +288,7 @@ const EventDailyStats = () => {
throw new Error(data.message || '数据格式错误'); throw new Error(data.message || '数据格式错误');
} }
} catch (err) { } catch (err) {
console.error('获取当日事件统计失败:', err); console.error('获取事件统计失败:', err);
setError(err.message); setError(err.message);
} finally { } finally {
setLoading(false); setLoading(false);
@@ -298,10 +296,22 @@ const EventDailyStats = () => {
}, []); }, []);
useEffect(() => { useEffect(() => {
fetchStats(); fetchStats(selectedDate);
const interval = setInterval(fetchStats, 5 * 60 * 1000); }, [fetchStats, selectedDate]);
// 自动刷新(仅当选择今天时)
useEffect(() => {
if (!selectedDate) {
const interval = setInterval(() => fetchStats(''), 5 * 60 * 1000);
return () => clearInterval(interval); return () => clearInterval(interval);
}, [fetchStats]); }
}, [selectedDate, fetchStats]);
const handleDateChange = (e) => {
setSelectedDate(e.target.value);
};
const isToday = !selectedDate;
if (loading) { if (loading) {
return ( return (
@@ -312,7 +322,6 @@ const EventDailyStats = () => {
border="1px solid" border="1px solid"
borderColor="rgba(255, 215, 0, 0.15)" borderColor="rgba(255, 215, 0, 0.15)"
h="100%" h="100%"
minH="380px"
> >
<Center h="100%"> <Center h="100%">
<Spinner size="md" color="yellow.400" thickness="3px" /> <Spinner size="md" color="yellow.400" thickness="3px" />
@@ -330,7 +339,6 @@ const EventDailyStats = () => {
border="1px solid" border="1px solid"
borderColor="rgba(255, 215, 0, 0.15)" borderColor="rgba(255, 215, 0, 0.15)"
h="100%" h="100%"
minH="380px"
> >
<Center h="100%"> <Center h="100%">
<VStack spacing={2}> <VStack spacing={2}>
@@ -354,6 +362,8 @@ const EventDailyStats = () => {
position="relative" position="relative"
overflow="hidden" overflow="hidden"
h="100%" h="100%"
display="flex"
flexDirection="column"
> >
{/* 背景装饰 */} {/* 背景装饰 */}
<Box <Box
@@ -366,8 +376,9 @@ const EventDailyStats = () => {
pointerEvents="none" pointerEvents="none"
/> />
{/* 标题 */} {/* 标题 */}
<HStack spacing={2} mb={2}> <Flex justify="space-between" align="center" mb={2}>
<HStack spacing={2}>
<Box <Box
w="3px" w="3px"
h="16px" h="16px"
@@ -375,14 +386,43 @@ const EventDailyStats = () => {
borderRadius="full" borderRadius="full"
/> />
<Text fontSize="sm" fontWeight="bold" color="white" letterSpacing="wide"> <Text fontSize="sm" fontWeight="bold" color="white" letterSpacing="wide">
今日统计 {isToday ? '今日统计' : '历史统计'}
</Text> </Text>
{isToday && (
<Badge colorScheme="yellow" variant="subtle" fontSize="2xs" px={1.5}> <Badge colorScheme="yellow" variant="subtle" fontSize="2xs" px={1.5}>
实时 实时
</Badge> </Badge>
)}
</HStack> </HStack>
<HStack spacing={1}>
<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>
</Flex>
<VStack spacing={2} align="stretch"> {/* 内容区域 - 使用 flex: 1 填充剩余空间 */}
<VStack spacing={2} align="stretch" flex="1">
{/* 胜率仪表盘 */} {/* 胜率仪表盘 */}
<WinRateGauge rate={summary?.positiveRate || 0} /> <WinRateGauge rate={summary?.positiveRate || 0} />
@@ -393,77 +433,78 @@ const EventDailyStats = () => {
value={summary?.totalEvents || 0} value={summary?.totalEvents || 0}
icon={<FireOutlined />} icon={<FireOutlined />}
color="#FFD700" color="#FFD700"
subText="今日追踪" subText="追踪"
/> />
<CompactStatCard <CompactStatCard
label="关联股票" label="关联股票"
value={summary?.totalStocks || 0} value={summary?.totalStocks || 0}
icon={<StockOutlined />} icon={<StockOutlined />}
color="#1890FF" color="#1890FF"
subText="去重统计" subText="去重"
/> />
<CompactStatCard <CompactStatCard
label="平均超额" label="平均超额"
value={formatChg(summary?.avgChg)} value={formatChg(summary?.avgChg)}
icon={<RiseOutlined />} icon={<RiseOutlined />}
color={getChgColor(summary?.avgChg)} color={getChgColor(summary?.avgChg)}
subText="关联股票"
/> />
<CompactStatCard <CompactStatCard
label="最大超额" label="最大超额"
value={formatChg(summary?.maxChg)} value={formatChg(summary?.maxChg)}
icon={<ThunderboltOutlined />} icon={<ThunderboltOutlined />}
color="#FF4D4F" color="#FF4D4F"
subText="单事件最佳"
/> />
</Box> </Box>
{/* 分割线 */} {/* 分割线 */}
<Box h="1px" bg="rgba(255,215,0,0.1)" /> <Box h="1px" bg="rgba(255,215,0,0.1)" flexShrink={0} />
{/* TOP 表现 - Tab 切换 */} {/* TOP 表现 - Tab 切换flex: 1 填充剩余空间 */}
<Box flex="1" overflow="hidden"> <Box flex="1" display="flex" flexDirection="column" minH={0}>
<Tabs <Tabs
variant="soft-rounded" variant="soft-rounded"
colorScheme="yellow" colorScheme="yellow"
size="sm" size="sm"
index={activeTab} index={activeTab}
onChange={setActiveTab} onChange={setActiveTab}
display="flex"
flexDirection="column"
flex="1"
> >
<TabList mb={2}> <TabList mb={1} flexShrink={0}>
<Tab <Tab
fontSize="xs" fontSize="xs"
py={1} py={1}
px={3} px={2}
_selected={{ bg: 'rgba(255,215,0,0.2)', color: '#FFD700' }} _selected={{ bg: 'rgba(255,215,0,0.2)', color: '#FFD700' }}
color="gray.500" color="gray.500"
> >
<HStack spacing={1}> <HStack spacing={1}>
<TrophyOutlined style={{ fontSize: '11px' }} /> <TrophyOutlined style={{ fontSize: '10px' }} />
<Text>事件TOP10</Text> <Text>事件TOP10</Text>
</HStack> </HStack>
</Tab> </Tab>
<Tab <Tab
fontSize="xs" fontSize="xs"
py={1} py={1}
px={3} px={2}
_selected={{ bg: 'rgba(255,215,0,0.2)', color: '#FFD700' }} _selected={{ bg: 'rgba(255,215,0,0.2)', color: '#FFD700' }}
color="gray.500" color="gray.500"
> >
<HStack spacing={1}> <HStack spacing={1}>
<StockOutlined style={{ fontSize: '11px' }} /> <StockOutlined style={{ fontSize: '10px' }} />
<Text>股票TOP10</Text> <Text>股票TOP10</Text>
</HStack> </HStack>
</Tab> </Tab>
</TabList> </TabList>
<TabPanels> <TabPanels flex="1" minH={0}>
{/* 事件 TOP10 */} {/* 事件 TOP10 */}
<TabPanel p={0}> <TabPanel p={0} h="100%">
<VStack <VStack
spacing={1} spacing={1}
align="stretch" align="stretch"
maxH="195px" h="100%"
overflowY="auto" overflowY="auto"
pr={1} pr={1}
css={{ css={{
@@ -485,11 +526,11 @@ const EventDailyStats = () => {
</TabPanel> </TabPanel>
{/* 股票 TOP10 */} {/* 股票 TOP10 */}
<TabPanel p={0}> <TabPanel p={0} h="100%">
<VStack <VStack
spacing={1} spacing={1}
align="stretch" align="stretch"
maxH="195px" h="100%"
overflowY="auto" overflowY="auto"
pr={1} pr={1}
css={{ css={{