diff --git a/src/views/StockOverview/components/HotspotOverview/components/AlertSummary.js b/src/views/StockOverview/components/HotspotOverview/components/AlertSummary.js index 7e1f77f3..2969dbbc 100644 --- a/src/views/StockOverview/components/HotspotOverview/components/AlertSummary.js +++ b/src/views/StockOverview/components/HotspotOverview/components/AlertSummary.js @@ -10,17 +10,11 @@ import { Text, Badge, Icon, - Stat, - StatLabel, - StatNumber, - StatHelpText, - StatArrow, SimpleGrid, useColorModeValue, Tooltip, Flex, } from '@chakra-ui/react'; -import { keyframes } from '@emotion/react'; import { TrendingUp, TrendingDown, @@ -37,17 +31,6 @@ import { } from 'lucide-react'; 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; } -`; - /** * 获取异动类型对应的图标 */ diff --git a/src/views/StockOverview/components/HotspotOverview/components/ConceptAlertList.js b/src/views/StockOverview/components/HotspotOverview/components/ConceptAlertList.js index e026f155..f0ec5140 100644 --- a/src/views/StockOverview/components/HotspotOverview/components/ConceptAlertList.js +++ b/src/views/StockOverview/components/HotspotOverview/components/ConceptAlertList.js @@ -27,7 +27,6 @@ import { PopoverContent, PopoverBody, Portal, - chakra, } from '@chakra-ui/react'; import { keyframes } from '@emotion/react'; import { @@ -54,25 +53,18 @@ import { TRIGGERED_RULES_CONFIG, getAlertTypeLabel, getAlertTypeDescription, - getAlertTypeColor, formatScore, getScoreColor, getScoreLevel, - formatMetric, } from '../utils/chartHelpers'; import MiniTimelineChart from '@components/Charts/Stock/MiniTimelineChart'; -// 动画效果 -const pulseGlow = keyframes` +// 动画效果 - 使用 emotion keyframes 需要配合 css prop +const pulseGlowKeyframes = keyframes` 0%, 100% { box-shadow: 0 0 5px 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 }) => { = 0.15 ? `${pulseGlow} 2s infinite` : undefined} + css={alert.limit_up_ratio >= 0.15 ? { animation: `${pulseGlowKeyframes} 2s infinite` } : undefined} /> {Math.round(alert.limit_up_ratio * 100)}% diff --git a/src/views/StockOverview/components/HotspotOverview/index.js b/src/views/StockOverview/components/HotspotOverview/index.js index 9a72db0c..327e87a2 100644 --- a/src/views/StockOverview/components/HotspotOverview/index.js +++ b/src/views/StockOverview/components/HotspotOverview/index.js @@ -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 { @@ -23,10 +21,9 @@ import { Spacer, Tooltip, useColorModeValue, - Grid, - GridItem, IconButton, Collapse, + SimpleGrid, } from '@chakra-ui/react'; import { keyframes } from '@emotion/react'; import { @@ -38,10 +35,13 @@ import { Info, Zap, AlertCircle, + TrendingUp, + TrendingDown, } from 'lucide-react'; import { useHotspotData } from './hooks'; import { IndexMinuteChart, ConceptAlertList, AlertSummary } from './components'; +import { ALERT_TYPE_CONFIG, getAlertTypeLabel } from './utils/chartHelpers'; // 动画效果 const gradientShift = keyframes` @@ -55,6 +55,103 @@ const pulseGlow = keyframes` 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 ( + onClick?.(alert)} + transition="all 0.2s" + _hover={{ + borderColor: config.color, + transform: 'translateY(-2px)', + boxShadow: `0 4px 15px ${config.color}25`, + }} + position="relative" + overflow="hidden" + > + {/* 顶部渐变条 */} + + + {/* 时间 + 类型 */} + + + {alert.time} + + + + + {getAlertTypeLabel(alert.alert_type)} + + + + + {/* 概念名称 */} + + {alert.concept_name} + + + {/* 分数 + Alpha */} + + + 评分 + + {Math.round(alert.final_score || 0)} + + + {alert.alpha != null && ( + = 0 ? '#ff4d4f' : '#52c41a'} + > + α {alert.alpha >= 0 ? '+' : ''}{alert.alpha.toFixed(1)}% + + )} + + + ); +}; + /** * 热点概览主组件 * @param {Object} props @@ -62,7 +159,7 @@ const pulseGlow = keyframes` */ const HotspotOverview = ({ selectedDate }) => { const [selectedAlert, setSelectedAlert] = useState(null); - const [showAlertList, setShowAlertList] = useState(true); + const [showDetailList, setShowDetailList] = useState(false); // 获取数据 const { loading, error, data } = useHotspotData(selectedDate); @@ -72,10 +169,8 @@ const HotspotOverview = ({ selectedDate }) => { const borderColor = useColorModeValue('gray.200', '#1f1f1f'); const textColor = useColorModeValue('gray.800', 'white'); const subTextColor = useColorModeValue('gray.600', 'gray.400'); - const headerGradient = useColorModeValue( - 'linear(to-r, orange.500, red.500)', - 'linear(to-r, orange.400, red.400)' - ); + const sectionBg = useColorModeValue('gray.50', '#0d0d0d'); + const scrollbarColor = useColorModeValue('#ddd', '#333'); // 点击异动标注 const handleAlertClick = useCallback((alert) => { @@ -92,38 +187,27 @@ const HotspotOverview = ({ selectedDate }) => { borderColor={borderColor} overflow="hidden" > - {/* 顶部装饰条 */} - -
+
- + - - 加载热点概览数据 - - - 正在获取市场异动信息... - + 加载热点概览数据 + 正在获取市场异动信息...
@@ -142,24 +226,14 @@ const HotspotOverview = ({ selectedDate }) => { overflow="hidden" > -
- + - - 数据加载失败 - - - {error} - + 数据加载失败 + {error}
@@ -167,10 +241,7 @@ const HotspotOverview = ({ selectedDate }) => { ); } - // 无数据 - if (!data) { - return null; - } + if (!data) return null; const { index, alerts, alert_summary } = data; @@ -182,24 +253,18 @@ const HotspotOverview = ({ selectedDate }) => { borderColor={borderColor} overflow="hidden" 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)' - ), - }} > {/* 顶部装饰条 */} {/* 头部 */} - + { - - 热点概览 - - - 实时概念异动监控 - + 热点概览 + 实时概念异动监控 - {/* 异动数量徽章 */} {alerts.length > 0 && ( - - - - {alerts.length} - + + + {alerts.length} )} - - {/* 切换按钮 */} - - } - size="sm" - variant="ghost" - borderRadius="lg" - onClick={() => setShowAlertList(!showAlertList)} - aria-label="切换异动列表" - _hover={{ - bg: useColorModeValue('gray.100', 'gray.800'), - }} - /> - - - {/* 信息提示 */} - + @@ -265,102 +295,184 @@ const HotspotOverview = ({ selectedDate }) => { - {/* 统计摘要 */} - - + {/* 统计摘要 - 简化版 */} + + {/* 指数信息 */} + + + + {index?.name || '上证指数'} + = 0 ? '#ff4d4f' : '#52c41a'} + > + {index?.latest_price?.toFixed(2) || '-'} + + + + = 0 ? 'red.50' : 'green.50'} + > + = 0 ? TrendingUp : TrendingDown} + boxSize={3} + color={(index?.change_pct || 0) >= 0 ? '#ff4d4f' : '#52c41a'} + /> + = 0 ? '#ff4d4f' : '#52c41a'} + > + {(index?.change_pct || 0) >= 0 ? '+' : ''}{(index?.change_pct || 0).toFixed(2)}% + + + + {index?.high?.toFixed(2)} + {index?.low?.toFixed(2)} + + + + + + {/* 异动统计 */} + + + 今日异动 + {alerts.length} 次 + + + {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 ( + + {config.label} + {count} + + ); + })} + + + + + {/* 大尺寸分时图 */} + + + + + + 大盘分时走势 + + + + + - {/* 主体内容:图表 + 异动列表 */} - - {/* 分时图 */} - - - - - + {/* 异动列表 - 横向滚动 */} + {alerts.length > 0 && ( + + + + + - - 大盘分时走势 - - - - + 异动记录 + (横向滚动查看更多) - - - + + } + size="sm" + variant="ghost" + borderRadius="lg" + onClick={() => setShowDetailList(!showDetailList)} + aria-label="切换详细列表" + /> + + - {/* 异动列表(可收起) */} - - + {/* 横向滚动卡片 */} + + + {[...alerts] + .sort((a, b) => (b.time || '').localeCompare(a.time || '')) + .map((alert, idx) => ( + + ))} + + + + {/* 详细列表(可展开) */} + - - - - - - 异动记录 - - - ({alerts.length}) - - - - - + + + )} {/* 无异动提示 */} {alerts.length === 0 && ( -
+
- - 当日暂无概念异动数据 - + 当日暂无概念异动数据
)}