update pay ui

This commit is contained in:
2025-12-11 07:48:19 +08:00
parent 8748e81a7b
commit f23b859f77
3 changed files with 285 additions and 198 deletions

View File

@@ -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; }
`;
/** /**
* 获取异动类型对应的图标 * 获取异动类型对应的图标
*/ */

View File

@@ -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)}%

View File

@@ -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>
)} )}