update pay ui
This commit is contained in:
@@ -10,17 +10,11 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
Badge,
|
Badge,
|
||||||
Icon,
|
Icon,
|
||||||
Stat,
|
|
||||||
StatLabel,
|
|
||||||
StatNumber,
|
|
||||||
StatHelpText,
|
|
||||||
StatArrow,
|
|
||||||
SimpleGrid,
|
SimpleGrid,
|
||||||
useColorModeValue,
|
useColorModeValue,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Flex,
|
Flex,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { keyframes } from '@emotion/react';
|
|
||||||
import {
|
import {
|
||||||
TrendingUp,
|
TrendingUp,
|
||||||
TrendingDown,
|
TrendingDown,
|
||||||
@@ -37,17 +31,6 @@ import {
|
|||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { ALERT_TYPE_CONFIG, getAlertTypeLabel } from '../utils/chartHelpers';
|
import { ALERT_TYPE_CONFIG, getAlertTypeLabel } from '../utils/chartHelpers';
|
||||||
|
|
||||||
// 动画效果
|
|
||||||
const pulseAnimation = keyframes`
|
|
||||||
0%, 100% { opacity: 1; }
|
|
||||||
50% { opacity: 0.6; }
|
|
||||||
`;
|
|
||||||
|
|
||||||
const glowAnimation = keyframes`
|
|
||||||
0%, 100% { box-shadow: 0 0 5px currentColor; }
|
|
||||||
50% { box-shadow: 0 0 15px currentColor; }
|
|
||||||
`;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取异动类型对应的图标
|
* 获取异动类型对应的图标
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import {
|
|||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverBody,
|
PopoverBody,
|
||||||
Portal,
|
Portal,
|
||||||
chakra,
|
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { keyframes } from '@emotion/react';
|
import { keyframes } from '@emotion/react';
|
||||||
import {
|
import {
|
||||||
@@ -54,25 +53,18 @@ import {
|
|||||||
TRIGGERED_RULES_CONFIG,
|
TRIGGERED_RULES_CONFIG,
|
||||||
getAlertTypeLabel,
|
getAlertTypeLabel,
|
||||||
getAlertTypeDescription,
|
getAlertTypeDescription,
|
||||||
getAlertTypeColor,
|
|
||||||
formatScore,
|
formatScore,
|
||||||
getScoreColor,
|
getScoreColor,
|
||||||
getScoreLevel,
|
getScoreLevel,
|
||||||
formatMetric,
|
|
||||||
} from '../utils/chartHelpers';
|
} from '../utils/chartHelpers';
|
||||||
import MiniTimelineChart from '@components/Charts/Stock/MiniTimelineChart';
|
import MiniTimelineChart from '@components/Charts/Stock/MiniTimelineChart';
|
||||||
|
|
||||||
// 动画效果
|
// 动画效果 - 使用 emotion keyframes 需要配合 css prop
|
||||||
const pulseGlow = keyframes`
|
const pulseGlowKeyframes = keyframes`
|
||||||
0%, 100% { box-shadow: 0 0 5px currentColor; }
|
0%, 100% { box-shadow: 0 0 5px currentColor; }
|
||||||
50% { box-shadow: 0 0 15px currentColor, 0 0 25px currentColor; }
|
50% { box-shadow: 0 0 15px currentColor, 0 0 25px currentColor; }
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const shimmer = keyframes`
|
|
||||||
0% { background-position: -200% 0; }
|
|
||||||
100% { background-position: 200% 0; }
|
|
||||||
`;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取异动类型对应的图标
|
* 获取异动类型对应的图标
|
||||||
*/
|
*/
|
||||||
@@ -481,7 +473,7 @@ const AlertCard = ({ alert, isExpanded, onToggle, stocks, loadingStocks }) => {
|
|||||||
<Icon
|
<Icon
|
||||||
as={Flame}
|
as={Flame}
|
||||||
boxSize={3}
|
boxSize={3}
|
||||||
animation={alert.limit_up_ratio >= 0.15 ? `${pulseGlow} 2s infinite` : undefined}
|
css={alert.limit_up_ratio >= 0.15 ? { animation: `${pulseGlowKeyframes} 2s infinite` } : undefined}
|
||||||
/>
|
/>
|
||||||
<Text fontSize="xs" fontWeight="bold">
|
<Text fontSize="xs" fontWeight="bold">
|
||||||
{Math.round(alert.limit_up_ratio * 100)}%
|
{Math.round(alert.limit_up_ratio * 100)}%
|
||||||
|
|||||||
@@ -2,12 +2,10 @@
|
|||||||
* 热点概览组件 - 科技感设计
|
* 热点概览组件 - 科技感设计
|
||||||
* 展示大盘分时走势 + 概念异动标注
|
* 展示大盘分时走势 + 概念异动标注
|
||||||
*
|
*
|
||||||
* 模块化结构:
|
* 布局设计:
|
||||||
* - hooks/useHotspotData.js - 数据获取
|
* - 顶部:统计摘要(指数信息 + 异动统计)
|
||||||
* - components/IndexMinuteChart.js - 分时图
|
* - 中部:大尺寸分时图(主要展示区域)
|
||||||
* - components/ConceptAlertList.js - 异动列表
|
* - 底部:异动列表(横向滚动卡片)
|
||||||
* - components/AlertSummary.js - 统计摘要
|
|
||||||
* - utils/chartHelpers.js - 图表辅助函数
|
|
||||||
*/
|
*/
|
||||||
import React, { useState, useCallback } from 'react';
|
import React, { useState, useCallback } from 'react';
|
||||||
import {
|
import {
|
||||||
@@ -23,10 +21,9 @@ import {
|
|||||||
Spacer,
|
Spacer,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
useColorModeValue,
|
useColorModeValue,
|
||||||
Grid,
|
|
||||||
GridItem,
|
|
||||||
IconButton,
|
IconButton,
|
||||||
Collapse,
|
Collapse,
|
||||||
|
SimpleGrid,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { keyframes } from '@emotion/react';
|
import { keyframes } from '@emotion/react';
|
||||||
import {
|
import {
|
||||||
@@ -38,10 +35,13 @@ import {
|
|||||||
Info,
|
Info,
|
||||||
Zap,
|
Zap,
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
|
TrendingUp,
|
||||||
|
TrendingDown,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
import { useHotspotData } from './hooks';
|
import { useHotspotData } from './hooks';
|
||||||
import { IndexMinuteChart, ConceptAlertList, AlertSummary } from './components';
|
import { IndexMinuteChart, ConceptAlertList, AlertSummary } from './components';
|
||||||
|
import { ALERT_TYPE_CONFIG, getAlertTypeLabel } from './utils/chartHelpers';
|
||||||
|
|
||||||
// 动画效果
|
// 动画效果
|
||||||
const gradientShift = keyframes`
|
const gradientShift = keyframes`
|
||||||
@@ -55,6 +55,103 @@ const pulseGlow = keyframes`
|
|||||||
50% { opacity: 1; }
|
50% { opacity: 1; }
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 紧凑型异动卡片(用于横向滚动)
|
||||||
|
*/
|
||||||
|
const CompactAlertCard = ({ alert, onClick, isSelected }) => {
|
||||||
|
const cardBg = useColorModeValue('white', '#0d0d0d');
|
||||||
|
const borderColor = useColorModeValue('gray.200', '#2d2d2d');
|
||||||
|
const textColor = useColorModeValue('gray.800', 'white');
|
||||||
|
const subTextColor = useColorModeValue('gray.500', 'gray.400');
|
||||||
|
|
||||||
|
const config = ALERT_TYPE_CONFIG[alert.alert_type] || ALERT_TYPE_CONFIG.surge;
|
||||||
|
const isUp = alert.alert_type !== 'surge_down';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
bg={cardBg}
|
||||||
|
borderRadius="xl"
|
||||||
|
borderWidth="2px"
|
||||||
|
borderColor={isSelected ? config.color : borderColor}
|
||||||
|
p={3}
|
||||||
|
minW="180px"
|
||||||
|
maxW="200px"
|
||||||
|
cursor="pointer"
|
||||||
|
onClick={() => onClick?.(alert)}
|
||||||
|
transition="all 0.2s"
|
||||||
|
_hover={{
|
||||||
|
borderColor: config.color,
|
||||||
|
transform: 'translateY(-2px)',
|
||||||
|
boxShadow: `0 4px 15px ${config.color}25`,
|
||||||
|
}}
|
||||||
|
position="relative"
|
||||||
|
overflow="hidden"
|
||||||
|
>
|
||||||
|
{/* 顶部渐变条 */}
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
top={0}
|
||||||
|
left={0}
|
||||||
|
right={0}
|
||||||
|
h="3px"
|
||||||
|
bgGradient={`linear(to-r, ${config.gradient[0]}, ${config.gradient[1]})`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 时间 + 类型 */}
|
||||||
|
<HStack justify="space-between" mb={1}>
|
||||||
|
<Text fontSize="xs" color={subTextColor} fontFamily="mono">
|
||||||
|
{alert.time}
|
||||||
|
</Text>
|
||||||
|
<HStack
|
||||||
|
spacing={1}
|
||||||
|
px={1.5}
|
||||||
|
py={0.5}
|
||||||
|
borderRadius="md"
|
||||||
|
bg={`${config.color}15`}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
as={isUp ? TrendingUp : TrendingDown}
|
||||||
|
boxSize={3}
|
||||||
|
color={config.color}
|
||||||
|
/>
|
||||||
|
<Text fontSize="10px" fontWeight="bold" color={config.color}>
|
||||||
|
{getAlertTypeLabel(alert.alert_type)}
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
</HStack>
|
||||||
|
|
||||||
|
{/* 概念名称 */}
|
||||||
|
<Text
|
||||||
|
fontWeight="bold"
|
||||||
|
fontSize="sm"
|
||||||
|
color={textColor}
|
||||||
|
noOfLines={1}
|
||||||
|
mb={1}
|
||||||
|
>
|
||||||
|
{alert.concept_name}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{/* 分数 + Alpha */}
|
||||||
|
<HStack justify="space-between" fontSize="xs">
|
||||||
|
<HStack spacing={1}>
|
||||||
|
<Text color={subTextColor}>评分</Text>
|
||||||
|
<Text fontWeight="bold" color={config.color}>
|
||||||
|
{Math.round(alert.final_score || 0)}
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
{alert.alpha != null && (
|
||||||
|
<Text
|
||||||
|
fontWeight="bold"
|
||||||
|
color={alert.alpha >= 0 ? '#ff4d4f' : '#52c41a'}
|
||||||
|
>
|
||||||
|
α {alert.alpha >= 0 ? '+' : ''}{alert.alpha.toFixed(1)}%
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</HStack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 热点概览主组件
|
* 热点概览主组件
|
||||||
* @param {Object} props
|
* @param {Object} props
|
||||||
@@ -62,7 +159,7 @@ const pulseGlow = keyframes`
|
|||||||
*/
|
*/
|
||||||
const HotspotOverview = ({ selectedDate }) => {
|
const HotspotOverview = ({ selectedDate }) => {
|
||||||
const [selectedAlert, setSelectedAlert] = useState(null);
|
const [selectedAlert, setSelectedAlert] = useState(null);
|
||||||
const [showAlertList, setShowAlertList] = useState(true);
|
const [showDetailList, setShowDetailList] = useState(false);
|
||||||
|
|
||||||
// 获取数据
|
// 获取数据
|
||||||
const { loading, error, data } = useHotspotData(selectedDate);
|
const { loading, error, data } = useHotspotData(selectedDate);
|
||||||
@@ -72,10 +169,8 @@ const HotspotOverview = ({ selectedDate }) => {
|
|||||||
const borderColor = useColorModeValue('gray.200', '#1f1f1f');
|
const borderColor = useColorModeValue('gray.200', '#1f1f1f');
|
||||||
const textColor = useColorModeValue('gray.800', 'white');
|
const textColor = useColorModeValue('gray.800', 'white');
|
||||||
const subTextColor = useColorModeValue('gray.600', 'gray.400');
|
const subTextColor = useColorModeValue('gray.600', 'gray.400');
|
||||||
const headerGradient = useColorModeValue(
|
const sectionBg = useColorModeValue('gray.50', '#0d0d0d');
|
||||||
'linear(to-r, orange.500, red.500)',
|
const scrollbarColor = useColorModeValue('#ddd', '#333');
|
||||||
'linear(to-r, orange.400, red.400)'
|
|
||||||
);
|
|
||||||
|
|
||||||
// 点击异动标注
|
// 点击异动标注
|
||||||
const handleAlertClick = useCallback((alert) => {
|
const handleAlertClick = useCallback((alert) => {
|
||||||
@@ -92,38 +187,27 @@ const HotspotOverview = ({ selectedDate }) => {
|
|||||||
borderColor={borderColor}
|
borderColor={borderColor}
|
||||||
overflow="hidden"
|
overflow="hidden"
|
||||||
>
|
>
|
||||||
{/* 顶部装饰条 */}
|
|
||||||
<Box
|
<Box
|
||||||
h="4px"
|
h="4px"
|
||||||
bgGradient="linear(to-r, orange.400, red.500, pink.500)"
|
bgGradient="linear(to-r, orange.400, red.500, pink.500)"
|
||||||
backgroundSize="200% 200%"
|
backgroundSize="200% 200%"
|
||||||
animation={`${gradientShift} 3s ease infinite`}
|
css={{ animation: `${gradientShift} 3s ease infinite` }}
|
||||||
/>
|
/>
|
||||||
|
<Center h="500px" p={6}>
|
||||||
<Center h="400px" p={6}>
|
|
||||||
<VStack spacing={4}>
|
<VStack spacing={4}>
|
||||||
<Box position="relative">
|
<Box position="relative">
|
||||||
<Spinner
|
<Spinner size="xl" color="orange.400" thickness="3px" speed="0.8s" />
|
||||||
size="xl"
|
|
||||||
color="orange.400"
|
|
||||||
thickness="3px"
|
|
||||||
speed="0.8s"
|
|
||||||
/>
|
|
||||||
<Box
|
<Box
|
||||||
position="absolute"
|
position="absolute"
|
||||||
inset={0}
|
inset={0}
|
||||||
borderRadius="full"
|
borderRadius="full"
|
||||||
animation={`${pulseGlow} 2s ease-in-out infinite`}
|
css={{ animation: `${pulseGlow} 2s ease-in-out infinite` }}
|
||||||
boxShadow="0 0 30px rgba(251, 146, 60, 0.3)"
|
boxShadow="0 0 30px rgba(251, 146, 60, 0.3)"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<VStack spacing={1}>
|
<VStack spacing={1}>
|
||||||
<Text color={textColor} fontWeight="medium">
|
<Text color={textColor} fontWeight="medium">加载热点概览数据</Text>
|
||||||
加载热点概览数据
|
<Text color={subTextColor} fontSize="sm">正在获取市场异动信息...</Text>
|
||||||
</Text>
|
|
||||||
<Text color={subTextColor} fontSize="sm">
|
|
||||||
正在获取市场异动信息...
|
|
||||||
</Text>
|
|
||||||
</VStack>
|
</VStack>
|
||||||
</VStack>
|
</VStack>
|
||||||
</Center>
|
</Center>
|
||||||
@@ -142,24 +226,14 @@ const HotspotOverview = ({ selectedDate }) => {
|
|||||||
overflow="hidden"
|
overflow="hidden"
|
||||||
>
|
>
|
||||||
<Box h="4px" bg="red.500" />
|
<Box h="4px" bg="red.500" />
|
||||||
|
|
||||||
<Center h="400px" p={6}>
|
<Center h="400px" p={6}>
|
||||||
<VStack spacing={4}>
|
<VStack spacing={4}>
|
||||||
<Box
|
<Box p={4} borderRadius="full" bg="red.50">
|
||||||
p={4}
|
|
||||||
borderRadius="full"
|
|
||||||
bg="red.500"
|
|
||||||
bgOpacity={0.1}
|
|
||||||
>
|
|
||||||
<Icon as={AlertCircle} boxSize={10} color="red.400" />
|
<Icon as={AlertCircle} boxSize={10} color="red.400" />
|
||||||
</Box>
|
</Box>
|
||||||
<VStack spacing={1}>
|
<VStack spacing={1}>
|
||||||
<Text color="red.400" fontWeight="medium">
|
<Text color="red.400" fontWeight="medium">数据加载失败</Text>
|
||||||
数据加载失败
|
<Text color={subTextColor} fontSize="sm" textAlign="center">{error}</Text>
|
||||||
</Text>
|
|
||||||
<Text color={subTextColor} fontSize="sm" textAlign="center">
|
|
||||||
{error}
|
|
||||||
</Text>
|
|
||||||
</VStack>
|
</VStack>
|
||||||
</VStack>
|
</VStack>
|
||||||
</Center>
|
</Center>
|
||||||
@@ -167,10 +241,7 @@ const HotspotOverview = ({ selectedDate }) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 无数据
|
if (!data) return null;
|
||||||
if (!data) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { index, alerts, alert_summary } = data;
|
const { index, alerts, alert_summary } = data;
|
||||||
|
|
||||||
@@ -182,24 +253,18 @@ const HotspotOverview = ({ selectedDate }) => {
|
|||||||
borderColor={borderColor}
|
borderColor={borderColor}
|
||||||
overflow="hidden"
|
overflow="hidden"
|
||||||
transition="all 0.3s"
|
transition="all 0.3s"
|
||||||
_hover={{
|
|
||||||
boxShadow: useColorModeValue(
|
|
||||||
'0 4px 20px rgba(0,0,0,0.08)',
|
|
||||||
'0 4px 20px rgba(0,0,0,0.4)'
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{/* 顶部装饰条 */}
|
{/* 顶部装饰条 */}
|
||||||
<Box
|
<Box
|
||||||
h="4px"
|
h="4px"
|
||||||
bgGradient="linear(to-r, orange.400, red.500, pink.500)"
|
bgGradient="linear(to-r, orange.400, red.500, pink.500)"
|
||||||
backgroundSize="200% 200%"
|
backgroundSize="200% 200%"
|
||||||
animation={`${gradientShift} 3s ease infinite`}
|
css={{ animation: `${gradientShift} 3s ease infinite` }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box p={5}>
|
<Box p={5}>
|
||||||
{/* 头部 */}
|
{/* 头部 */}
|
||||||
<Flex align="center" mb={5}>
|
<Flex align="center" mb={4}>
|
||||||
<HStack spacing={3}>
|
<HStack spacing={3}>
|
||||||
<Box
|
<Box
|
||||||
p={2}
|
p={2}
|
||||||
@@ -210,54 +275,19 @@ const HotspotOverview = ({ selectedDate }) => {
|
|||||||
<Icon as={Flame} boxSize={5} color="white" />
|
<Icon as={Flame} boxSize={5} color="white" />
|
||||||
</Box>
|
</Box>
|
||||||
<VStack align="flex-start" spacing={0}>
|
<VStack align="flex-start" spacing={0}>
|
||||||
<Heading size="md" color={textColor} fontWeight="bold">
|
<Heading size="md" color={textColor} fontWeight="bold">热点概览</Heading>
|
||||||
热点概览
|
<Text fontSize="xs" color={subTextColor}>实时概念异动监控</Text>
|
||||||
</Heading>
|
|
||||||
<Text fontSize="xs" color={subTextColor}>
|
|
||||||
实时概念异动监控
|
|
||||||
</Text>
|
|
||||||
</VStack>
|
</VStack>
|
||||||
</HStack>
|
</HStack>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<HStack spacing={2}>
|
<HStack spacing={2}>
|
||||||
{/* 异动数量徽章 */}
|
|
||||||
{alerts.length > 0 && (
|
{alerts.length > 0 && (
|
||||||
<HStack
|
<HStack spacing={1} px={3} py={1.5} borderRadius="full" bg="orange.50">
|
||||||
spacing={1}
|
<Icon as={Zap} boxSize={3.5} color="orange.500" />
|
||||||
px={3}
|
<Text fontSize="sm" fontWeight="bold" color="orange.500">{alerts.length}</Text>
|
||||||
py={1.5}
|
|
||||||
borderRadius="full"
|
|
||||||
bg="orange.500"
|
|
||||||
bgOpacity={0.15}
|
|
||||||
>
|
|
||||||
<Icon as={Zap} boxSize={3.5} color="orange.400" />
|
|
||||||
<Text fontSize="sm" fontWeight="bold" color="orange.400">
|
|
||||||
{alerts.length}
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
</HStack>
|
||||||
)}
|
)}
|
||||||
|
<Tooltip label="展示大盘走势与概念异动的关联" hasArrow maxW="200px">
|
||||||
{/* 切换按钮 */}
|
|
||||||
<Tooltip label={showAlertList ? '收起异动列表' : '展开异动列表'} hasArrow>
|
|
||||||
<IconButton
|
|
||||||
icon={<Icon as={showAlertList ? ChevronUp : List} boxSize={4} />}
|
|
||||||
size="sm"
|
|
||||||
variant="ghost"
|
|
||||||
borderRadius="lg"
|
|
||||||
onClick={() => setShowAlertList(!showAlertList)}
|
|
||||||
aria-label="切换异动列表"
|
|
||||||
_hover={{
|
|
||||||
bg: useColorModeValue('gray.100', 'gray.800'),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
{/* 信息提示 */}
|
|
||||||
<Tooltip
|
|
||||||
label="展示大盘走势与概念异动的关联,帮助发现市场热点"
|
|
||||||
hasArrow
|
|
||||||
maxW="200px"
|
|
||||||
>
|
|
||||||
<Box cursor="help">
|
<Box cursor="help">
|
||||||
<Icon as={Info} color={subTextColor} boxSize={4} />
|
<Icon as={Info} color={subTextColor} boxSize={4} />
|
||||||
</Box>
|
</Box>
|
||||||
@@ -265,102 +295,184 @@ const HotspotOverview = ({ selectedDate }) => {
|
|||||||
</HStack>
|
</HStack>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
{/* 统计摘要 */}
|
{/* 统计摘要 - 简化版 */}
|
||||||
<Box mb={5}>
|
<SimpleGrid columns={{ base: 1, md: 2 }} spacing={4} mb={4}>
|
||||||
<AlertSummary indexData={index} alerts={alerts} alertSummary={alert_summary} />
|
{/* 指数信息 */}
|
||||||
|
<Box bg={sectionBg} borderRadius="xl" p={4} borderWidth="1px" borderColor={borderColor}>
|
||||||
|
<HStack justify="space-between" align="flex-start">
|
||||||
|
<VStack align="flex-start" spacing={0}>
|
||||||
|
<Text fontSize="xs" color={subTextColor}>{index?.name || '上证指数'}</Text>
|
||||||
|
<Text
|
||||||
|
fontSize="2xl"
|
||||||
|
fontWeight="bold"
|
||||||
|
color={(index?.change_pct || 0) >= 0 ? '#ff4d4f' : '#52c41a'}
|
||||||
|
>
|
||||||
|
{index?.latest_price?.toFixed(2) || '-'}
|
||||||
|
</Text>
|
||||||
|
</VStack>
|
||||||
|
<VStack align="flex-end" spacing={0}>
|
||||||
|
<HStack
|
||||||
|
spacing={1}
|
||||||
|
px={2}
|
||||||
|
py={1}
|
||||||
|
borderRadius="full"
|
||||||
|
bg={(index?.change_pct || 0) >= 0 ? 'red.50' : 'green.50'}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
as={(index?.change_pct || 0) >= 0 ? TrendingUp : TrendingDown}
|
||||||
|
boxSize={3}
|
||||||
|
color={(index?.change_pct || 0) >= 0 ? '#ff4d4f' : '#52c41a'}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
fontSize="sm"
|
||||||
|
fontWeight="bold"
|
||||||
|
color={(index?.change_pct || 0) >= 0 ? '#ff4d4f' : '#52c41a'}
|
||||||
|
>
|
||||||
|
{(index?.change_pct || 0) >= 0 ? '+' : ''}{(index?.change_pct || 0).toFixed(2)}%
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
<HStack spacing={3} mt={1} fontSize="xs" color={subTextColor}>
|
||||||
|
<Text>高 <Text as="span" color="#ff4d4f" fontWeight="bold">{index?.high?.toFixed(2)}</Text></Text>
|
||||||
|
<Text>低 <Text as="span" color="#52c41a" fontWeight="bold">{index?.low?.toFixed(2)}</Text></Text>
|
||||||
|
</HStack>
|
||||||
|
</VStack>
|
||||||
|
</HStack>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* 异动统计 */}
|
||||||
|
<Box bg={sectionBg} borderRadius="xl" p={4} borderWidth="1px" borderColor={borderColor}>
|
||||||
|
<HStack justify="space-between" mb={2}>
|
||||||
|
<Text fontSize="sm" fontWeight="medium" color={textColor}>今日异动</Text>
|
||||||
|
<Text fontSize="xs" color="orange.500" fontWeight="bold">{alerts.length} 次</Text>
|
||||||
|
</HStack>
|
||||||
|
<Flex gap={2} flexWrap="wrap">
|
||||||
|
{Object.entries(alert_summary || {})
|
||||||
|
.filter(([_, count]) => count > 0)
|
||||||
|
.slice(0, 4)
|
||||||
|
.map(([type, count]) => {
|
||||||
|
const config = ALERT_TYPE_CONFIG[type];
|
||||||
|
if (!config) return null;
|
||||||
|
return (
|
||||||
|
<HStack
|
||||||
|
key={type}
|
||||||
|
spacing={1}
|
||||||
|
px={2}
|
||||||
|
py={1}
|
||||||
|
borderRadius="md"
|
||||||
|
bg={`${config.color}10`}
|
||||||
|
>
|
||||||
|
<Text fontSize="xs" color={config.color}>{config.label}</Text>
|
||||||
|
<Text fontSize="xs" fontWeight="bold" color={config.color}>{count}</Text>
|
||||||
|
</HStack>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
</SimpleGrid>
|
||||||
|
|
||||||
|
{/* 大尺寸分时图 */}
|
||||||
|
<Box
|
||||||
|
bg={sectionBg}
|
||||||
|
borderRadius="xl"
|
||||||
|
borderWidth="1px"
|
||||||
|
borderColor={borderColor}
|
||||||
|
p={4}
|
||||||
|
mb={4}
|
||||||
|
>
|
||||||
|
<HStack spacing={2} mb={3}>
|
||||||
|
<Box p={1.5} borderRadius="lg" bg="purple.50">
|
||||||
|
<Icon as={LineChart} boxSize={4} color="purple.500" />
|
||||||
|
</Box>
|
||||||
|
<Text fontSize="sm" fontWeight="bold" color={textColor}>大盘分时走势</Text>
|
||||||
|
<Tooltip label="图表上的标记点表示概念异动时刻,点击可查看详情" hasArrow>
|
||||||
|
<Icon as={Info} boxSize={3} color={subTextColor} cursor="help" />
|
||||||
|
</Tooltip>
|
||||||
|
</HStack>
|
||||||
|
<IndexMinuteChart
|
||||||
|
indexData={index}
|
||||||
|
alerts={alerts}
|
||||||
|
onAlertClick={handleAlertClick}
|
||||||
|
height="420px"
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* 主体内容:图表 + 异动列表 */}
|
{/* 异动列表 - 横向滚动 */}
|
||||||
<Grid
|
{alerts.length > 0 && (
|
||||||
templateColumns={{ base: '1fr', lg: showAlertList ? '1fr 320px' : '1fr' }}
|
<Box>
|
||||||
gap={5}
|
<Flex justify="space-between" align="center" mb={3}>
|
||||||
>
|
<HStack spacing={2}>
|
||||||
{/* 分时图 */}
|
<Box p={1.5} borderRadius="lg" bg="orange.50">
|
||||||
<GridItem>
|
<Icon as={List} boxSize={4} color="orange.500" />
|
||||||
<Box
|
|
||||||
bg={useColorModeValue('gray.50', '#0d0d0d')}
|
|
||||||
borderRadius="xl"
|
|
||||||
borderWidth="1px"
|
|
||||||
borderColor={borderColor}
|
|
||||||
p={4}
|
|
||||||
transition="all 0.2s"
|
|
||||||
>
|
|
||||||
<HStack spacing={2} mb={3}>
|
|
||||||
<Box
|
|
||||||
p={1.5}
|
|
||||||
borderRadius="lg"
|
|
||||||
bg="purple.500"
|
|
||||||
bgOpacity={0.15}
|
|
||||||
>
|
|
||||||
<Icon as={LineChart} boxSize={4} color="purple.400" />
|
|
||||||
</Box>
|
</Box>
|
||||||
<Text fontSize="sm" fontWeight="bold" color={textColor}>
|
<Text fontSize="sm" fontWeight="bold" color={textColor}>异动记录</Text>
|
||||||
大盘分时走势
|
<Text fontSize="xs" color={subTextColor}>(横向滚动查看更多)</Text>
|
||||||
</Text>
|
|
||||||
<Tooltip label="图表上的标记点表示概念异动时刻" hasArrow>
|
|
||||||
<Icon as={Info} boxSize={3} color={subTextColor} cursor="help" />
|
|
||||||
</Tooltip>
|
|
||||||
</HStack>
|
</HStack>
|
||||||
<IndexMinuteChart
|
<Tooltip label={showDetailList ? '收起详细列表' : '展开详细列表'} hasArrow>
|
||||||
indexData={index}
|
<IconButton
|
||||||
alerts={alerts}
|
icon={<Icon as={showDetailList ? ChevronUp : ChevronDown} boxSize={4} />}
|
||||||
onAlertClick={handleAlertClick}
|
size="sm"
|
||||||
height="350px"
|
variant="ghost"
|
||||||
/>
|
borderRadius="lg"
|
||||||
</Box>
|
onClick={() => setShowDetailList(!showDetailList)}
|
||||||
</GridItem>
|
aria-label="切换详细列表"
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
{/* 异动列表(可收起) */}
|
{/* 横向滚动卡片 */}
|
||||||
<Collapse in={showAlertList} animateOpacity style={{ overflow: 'visible' }}>
|
<Box
|
||||||
<GridItem>
|
overflowX="auto"
|
||||||
|
pb={2}
|
||||||
|
sx={{
|
||||||
|
'&::-webkit-scrollbar': { height: '6px' },
|
||||||
|
'&::-webkit-scrollbar-track': { background: 'transparent' },
|
||||||
|
'&::-webkit-scrollbar-thumb': {
|
||||||
|
background: scrollbarColor,
|
||||||
|
borderRadius: '3px',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<HStack spacing={3} pb={1}>
|
||||||
|
{[...alerts]
|
||||||
|
.sort((a, b) => (b.time || '').localeCompare(a.time || ''))
|
||||||
|
.map((alert, idx) => (
|
||||||
|
<CompactAlertCard
|
||||||
|
key={`${alert.concept_id}-${alert.time}-${idx}`}
|
||||||
|
alert={alert}
|
||||||
|
onClick={handleAlertClick}
|
||||||
|
isSelected={selectedAlert?.concept_id === alert.concept_id && selectedAlert?.time === alert.time}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</HStack>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* 详细列表(可展开) */}
|
||||||
|
<Collapse in={showDetailList} animateOpacity>
|
||||||
<Box
|
<Box
|
||||||
bg={useColorModeValue('gray.50', '#0d0d0d')}
|
mt={4}
|
||||||
|
bg={sectionBg}
|
||||||
borderRadius="xl"
|
borderRadius="xl"
|
||||||
borderWidth="1px"
|
borderWidth="1px"
|
||||||
borderColor={borderColor}
|
borderColor={borderColor}
|
||||||
p={4}
|
p={4}
|
||||||
h="100%"
|
|
||||||
>
|
>
|
||||||
<HStack spacing={2} mb={3}>
|
|
||||||
<Box
|
|
||||||
p={1.5}
|
|
||||||
borderRadius="lg"
|
|
||||||
bg="orange.500"
|
|
||||||
bgOpacity={0.15}
|
|
||||||
>
|
|
||||||
<Icon as={List} boxSize={4} color="orange.400" />
|
|
||||||
</Box>
|
|
||||||
<Text fontSize="sm" fontWeight="bold" color={textColor}>
|
|
||||||
异动记录
|
|
||||||
</Text>
|
|
||||||
<Text fontSize="xs" color={subTextColor}>
|
|
||||||
({alerts.length})
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
|
||||||
<ConceptAlertList
|
<ConceptAlertList
|
||||||
alerts={alerts}
|
alerts={alerts}
|
||||||
onAlertClick={handleAlertClick}
|
onAlertClick={handleAlertClick}
|
||||||
selectedAlert={selectedAlert}
|
selectedAlert={selectedAlert}
|
||||||
maxHeight="380px"
|
maxHeight="400px"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</GridItem>
|
</Collapse>
|
||||||
</Collapse>
|
</Box>
|
||||||
</Grid>
|
)}
|
||||||
|
|
||||||
{/* 无异动提示 */}
|
{/* 无异动提示 */}
|
||||||
{alerts.length === 0 && (
|
{alerts.length === 0 && (
|
||||||
<Center
|
<Center py={8} bg={sectionBg} borderRadius="xl">
|
||||||
py={8}
|
|
||||||
mt={4}
|
|
||||||
bg={useColorModeValue('gray.50', '#0d0d0d')}
|
|
||||||
borderRadius="xl"
|
|
||||||
>
|
|
||||||
<VStack spacing={2}>
|
<VStack spacing={2}>
|
||||||
<Icon as={Zap} boxSize={8} color={subTextColor} opacity={0.5} />
|
<Icon as={Zap} boxSize={8} color={subTextColor} opacity={0.5} />
|
||||||
<Text color={subTextColor} fontSize="sm">
|
<Text color={subTextColor} fontSize="sm">当日暂无概念异动数据</Text>
|
||||||
当日暂无概念异动数据
|
|
||||||
</Text>
|
|
||||||
</VStack>
|
</VStack>
|
||||||
</Center>
|
</Center>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user