feat(HotspotOverview): 重构筛选区布局,与分时图标题同行显示
- 新增 AlertFilterSection 组件,支持内联显示 - 筛选标签(类型+数量)、异动总数徽章、日期选择器整合到标题行 - 移除与灵活屏重复的三指数卡片组件 - 简化热点概览整体布局结构 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,162 @@
|
|||||||
|
/**
|
||||||
|
* AlertFilterSection - 异动筛选区组件(内联版本)
|
||||||
|
* 包含:筛选标签(带数量)+ 异动总数徽章 + 日期选择器
|
||||||
|
* 设计为与标题行内联使用
|
||||||
|
*/
|
||||||
|
import React, { memo } from 'react';
|
||||||
|
import { Flex, HStack, Text, Icon } from '@chakra-ui/react';
|
||||||
|
import { css } from '@emotion/react';
|
||||||
|
import { Zap } from 'lucide-react';
|
||||||
|
|
||||||
|
import { ALERT_TYPE_CONFIG } from '../utils/chartHelpers';
|
||||||
|
import TradeDatePicker from '@components/TradeDatePicker';
|
||||||
|
import { colors } from '../../../theme/glassTheme';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 筛选标签组件
|
||||||
|
*/
|
||||||
|
const FilterTag = memo(({ type, count, isSelected, onClick, config }) => (
|
||||||
|
<HStack
|
||||||
|
spacing={1}
|
||||||
|
px={2}
|
||||||
|
py={1}
|
||||||
|
borderRadius="full"
|
||||||
|
bg={isSelected ? `${config.color}35` : `${config.color}15`}
|
||||||
|
border={isSelected ? `2px solid ${config.color}` : `1px solid ${config.color}25`}
|
||||||
|
cursor="pointer"
|
||||||
|
transition="all 0.2s"
|
||||||
|
transform={isSelected ? 'scale(1.05)' : 'scale(1)'}
|
||||||
|
boxShadow={isSelected ? `0 0 15px ${config.color}40` : 'none'}
|
||||||
|
onClick={() => onClick(type)}
|
||||||
|
_hover={{
|
||||||
|
bg: `${config.color}25`,
|
||||||
|
boxShadow: `0 0 10px ${config.color}30`,
|
||||||
|
transform: 'scale(1.02)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text fontSize="xs" color={config.color} fontWeight={isSelected ? 'bold' : 'medium'}>
|
||||||
|
{config.label}
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
fontSize="xs"
|
||||||
|
fontWeight="bold"
|
||||||
|
color={config.color}
|
||||||
|
css={css`text-shadow: 0 0 8px ${config.color}50;`}
|
||||||
|
>
|
||||||
|
{count}
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
));
|
||||||
|
FilterTag.displayName = 'FilterTag';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异动总数徽章
|
||||||
|
*/
|
||||||
|
const AlertCountBadge = memo(({ totalCount, filteredCount, selectedAlertType }) => (
|
||||||
|
<HStack
|
||||||
|
spacing={1.5}
|
||||||
|
px={3}
|
||||||
|
py={1}
|
||||||
|
borderRadius="full"
|
||||||
|
bg="rgba(139, 92, 246, 0.15)"
|
||||||
|
border="1px solid rgba(139, 92, 246, 0.3)"
|
||||||
|
boxShadow="0 0 10px rgba(139, 92, 246, 0.2)"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
as={Zap}
|
||||||
|
boxSize={3.5}
|
||||||
|
color={colors.accent.purple}
|
||||||
|
css={css`filter: drop-shadow(0 0 4px #8b5cf6);`}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
fontSize="xs"
|
||||||
|
fontWeight="bold"
|
||||||
|
color={colors.accent.purple}
|
||||||
|
css={css`text-shadow: 0 0 8px rgba(139, 92, 246, 0.5);`}
|
||||||
|
>
|
||||||
|
{selectedAlertType ? `${filteredCount}/${totalCount}` : totalCount}
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
));
|
||||||
|
AlertCountBadge.displayName = 'AlertCountBadge';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AlertFilterSection 主组件 - 内联版本,无外层包裹
|
||||||
|
*/
|
||||||
|
const AlertFilterSection = ({
|
||||||
|
alertSummary,
|
||||||
|
selectedAlertType,
|
||||||
|
onAlertTypeClick,
|
||||||
|
onClearFilter,
|
||||||
|
totalCount,
|
||||||
|
filteredCount,
|
||||||
|
// 日期选择器相关
|
||||||
|
selectedDate,
|
||||||
|
onDateChange,
|
||||||
|
minDate,
|
||||||
|
maxDate,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
align="center"
|
||||||
|
gap={2}
|
||||||
|
flexWrap="wrap"
|
||||||
|
>
|
||||||
|
{/* 筛选标签 */}
|
||||||
|
{Object.entries(alertSummary || {})
|
||||||
|
.filter(([_, count]) => count > 0)
|
||||||
|
.slice(0, 5)
|
||||||
|
.map(([type, count]) => {
|
||||||
|
const config = ALERT_TYPE_CONFIG[type];
|
||||||
|
if (!config) return null;
|
||||||
|
return (
|
||||||
|
<FilterTag
|
||||||
|
key={type}
|
||||||
|
type={type}
|
||||||
|
count={count}
|
||||||
|
isSelected={selectedAlertType === type}
|
||||||
|
onClick={onAlertTypeClick}
|
||||||
|
config={config}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{/* 清除筛选按钮 */}
|
||||||
|
{selectedAlertType && (
|
||||||
|
<Text
|
||||||
|
fontSize="xs"
|
||||||
|
color={colors.accent.purple}
|
||||||
|
cursor="pointer"
|
||||||
|
onClick={onClearFilter}
|
||||||
|
_hover={{ textDecoration: 'underline' }}
|
||||||
|
>
|
||||||
|
清除筛选
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 异动总数徽章 */}
|
||||||
|
<AlertCountBadge
|
||||||
|
totalCount={totalCount}
|
||||||
|
filteredCount={filteredCount}
|
||||||
|
selectedAlertType={selectedAlertType}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 日期选择器 */}
|
||||||
|
{onDateChange && (
|
||||||
|
<TradeDatePicker
|
||||||
|
value={selectedDate}
|
||||||
|
onChange={onDateChange}
|
||||||
|
latestTradeDate={null}
|
||||||
|
minDate={minDate}
|
||||||
|
maxDate={maxDate}
|
||||||
|
size="sm"
|
||||||
|
showIcon={false}
|
||||||
|
showLatestTradeDateTip={false}
|
||||||
|
label=""
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(AlertFilterSection);
|
||||||
@@ -2,3 +2,4 @@ export { default as IndexMinuteChart } from './IndexMinuteChart';
|
|||||||
export { default as ConceptAlertList } from './ConceptAlertList';
|
export { default as ConceptAlertList } from './ConceptAlertList';
|
||||||
export { default as AlertSummary } from './AlertSummary';
|
export { default as AlertSummary } from './AlertSummary';
|
||||||
export { default as AlertDetailDrawer } from './AlertDetailDrawer';
|
export { default as AlertDetailDrawer } from './AlertDetailDrawer';
|
||||||
|
export { default as AlertFilterSection } from './AlertFilterSection';
|
||||||
|
|||||||
@@ -17,11 +17,10 @@ import {
|
|||||||
VStack,
|
VStack,
|
||||||
Spinner,
|
Spinner,
|
||||||
Center,
|
Center,
|
||||||
|
Spacer,
|
||||||
Icon,
|
Icon,
|
||||||
Flex,
|
Flex,
|
||||||
Spacer,
|
|
||||||
Tooltip,
|
Tooltip,
|
||||||
SimpleGrid,
|
|
||||||
useDisclosure,
|
useDisclosure,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { keyframes, css } from '@emotion/react';
|
import { keyframes, css } from '@emotion/react';
|
||||||
@@ -38,15 +37,12 @@ import {
|
|||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
import { useHotspotData } from './hooks';
|
import { useHotspotData } from './hooks';
|
||||||
import { IndexMinuteChart, AlertDetailDrawer } from './components';
|
import { IndexMinuteChart, AlertDetailDrawer, AlertFilterSection } from './components';
|
||||||
import { ALERT_TYPE_CONFIG, getAlertTypeLabel } from './utils/chartHelpers';
|
import { ALERT_TYPE_CONFIG, getAlertTypeLabel } from './utils/chartHelpers';
|
||||||
import TradeDatePicker from '@components/TradeDatePicker';
|
|
||||||
import {
|
import {
|
||||||
glassEffect,
|
glassEffect,
|
||||||
colors,
|
colors,
|
||||||
glowEffects,
|
|
||||||
getMarketColor,
|
getMarketColor,
|
||||||
getMarketGlow,
|
|
||||||
} from '../../theme/glassTheme';
|
} from '../../theme/glassTheme';
|
||||||
|
|
||||||
// 动画效果
|
// 动画效果
|
||||||
@@ -198,7 +194,7 @@ const CompactAlertCard = ({ alert, onClick, isSelected }) => {
|
|||||||
* @param {Date} props.minDate - 最小可选日期
|
* @param {Date} props.minDate - 最小可选日期
|
||||||
* @param {Date} props.maxDate - 最大可选日期
|
* @param {Date} props.maxDate - 最大可选日期
|
||||||
*/
|
*/
|
||||||
const HotspotOverview = ({ selectedDate, onDateChange, minDate, maxDate }) => {
|
const HotspotOverview = ({ selectedDate, onDateChange, minDate, maxDate, onDataLoaded }) => {
|
||||||
const [selectedAlert, setSelectedAlert] = useState(null);
|
const [selectedAlert, setSelectedAlert] = useState(null);
|
||||||
const [drawerAlertData, setDrawerAlertData] = useState(null);
|
const [drawerAlertData, setDrawerAlertData] = useState(null);
|
||||||
// 选中的异动类型过滤器(null 表示全部)
|
// 选中的异动类型过滤器(null 表示全部)
|
||||||
@@ -208,11 +204,17 @@ const HotspotOverview = ({ selectedDate, onDateChange, minDate, maxDate }) => {
|
|||||||
const { isOpen: isDrawerOpen, onOpen: onDrawerOpen, onClose: onDrawerClose } = useDisclosure();
|
const { isOpen: isDrawerOpen, onOpen: onDrawerOpen, onClose: onDrawerClose } = useDisclosure();
|
||||||
|
|
||||||
// 获取数据
|
// 获取数据
|
||||||
const { loading, error, data } = useHotspotData(selectedDate);
|
const { loading, refreshing, error, data } = useHotspotData(selectedDate);
|
||||||
|
|
||||||
|
// 当数据加载完成时,通知父组件
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (data && onDataLoaded) {
|
||||||
|
onDataLoaded(data);
|
||||||
|
}
|
||||||
|
}, [data, onDataLoaded]);
|
||||||
|
|
||||||
// Glassmorphism 颜色主题
|
// Glassmorphism 颜色主题
|
||||||
const cardBg = glassEffect.card.bg;
|
const cardBg = glassEffect.card.bg;
|
||||||
const borderColor = colors.border.primary;
|
|
||||||
const textColor = colors.text.primary;
|
const textColor = colors.text.primary;
|
||||||
const subTextColor = colors.text.secondary;
|
const subTextColor = colors.text.secondary;
|
||||||
const sectionBg = glassEffect.light.bg;
|
const sectionBg = glassEffect.light.bg;
|
||||||
@@ -352,9 +354,6 @@ const HotspotOverview = ({ selectedDate, onDateChange, minDate, maxDate }) => {
|
|||||||
? alerts.filter(alert => alert.alert_type === selectedAlertType)
|
? alerts.filter(alert => alert.alert_type === selectedAlertType)
|
||||||
: alerts;
|
: alerts;
|
||||||
|
|
||||||
// 计算市场颜色
|
|
||||||
const marketColor = getMarketColor(index?.change_pct || 0);
|
|
||||||
const marketGlow = getMarketGlow(index?.change_pct || 0);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@@ -430,54 +429,9 @@ const HotspotOverview = ({ selectedDate, onDateChange, minDate, maxDate }) => {
|
|||||||
</HStack>
|
</HStack>
|
||||||
</VStack>
|
</VStack>
|
||||||
</HStack>
|
</HStack>
|
||||||
<Spacer />
|
|
||||||
<HStack spacing={3}>
|
|
||||||
{/* 日期选择器 */}
|
|
||||||
{onDateChange && (
|
|
||||||
<TradeDatePicker
|
|
||||||
value={selectedDate}
|
|
||||||
onChange={onDateChange}
|
|
||||||
latestTradeDate={null}
|
|
||||||
minDate={minDate}
|
|
||||||
maxDate={maxDate}
|
|
||||||
size="sm"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{alerts.length > 0 && (
|
|
||||||
<HStack
|
|
||||||
spacing={2}
|
|
||||||
px={4}
|
|
||||||
py={2}
|
|
||||||
borderRadius="full"
|
|
||||||
bg="rgba(139, 92, 246, 0.15)"
|
|
||||||
border="1px solid rgba(139, 92, 246, 0.3)"
|
|
||||||
boxShadow="0 0 15px rgba(139, 92, 246, 0.2)"
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
as={Zap}
|
|
||||||
boxSize={4}
|
|
||||||
color={colors.accent.purple}
|
|
||||||
css={css`filter: drop-shadow(0 0 6px #8b5cf6);`}
|
|
||||||
/>
|
|
||||||
<Text
|
|
||||||
fontSize="sm"
|
|
||||||
fontWeight="bold"
|
|
||||||
color={colors.accent.purple}
|
|
||||||
css={css`text-shadow: 0 0 10px rgba(139, 92, 246, 0.5);`}
|
|
||||||
>
|
|
||||||
{alerts.length}
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
|
||||||
)}
|
|
||||||
<Tooltip label="大盘分时走势与概念异动关联分析" hasArrow maxW="200px">
|
|
||||||
<Box cursor="help" p={2} borderRadius="full" _hover={{ bg: 'rgba(255,255,255,0.05)' }}>
|
|
||||||
<Icon as={Info} color={subTextColor} boxSize={4} />
|
|
||||||
</Box>
|
|
||||||
</Tooltip>
|
|
||||||
</HStack>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
{/* 大尺寸分时图 - Glassmorphism(移到统计卡片前面) */}
|
{/* 大尺寸分时图 - Glassmorphism */}
|
||||||
<Box
|
<Box
|
||||||
bg={sectionBg}
|
bg={sectionBg}
|
||||||
backdropFilter={glassEffect.light.backdropFilter}
|
backdropFilter={glassEffect.light.backdropFilter}
|
||||||
@@ -501,206 +455,73 @@ const HotspotOverview = ({ selectedDate, onDateChange, minDate, maxDate }) => {
|
|||||||
filter="blur(40px)"
|
filter="blur(40px)"
|
||||||
pointerEvents="none"
|
pointerEvents="none"
|
||||||
/>
|
/>
|
||||||
<HStack spacing={3} mb={4}>
|
{/* 标题行:大盘分时走势 + 筛选区 */}
|
||||||
<Box
|
<Flex align="center" mb={4} flexWrap="wrap" gap={3}>
|
||||||
p={2}
|
<HStack spacing={3} flexShrink={0}>
|
||||||
borderRadius="12px"
|
<Box
|
||||||
bg="rgba(139, 92, 246, 0.15)"
|
p={2}
|
||||||
border="1px solid rgba(139, 92, 246, 0.25)"
|
borderRadius="12px"
|
||||||
>
|
bg="rgba(139, 92, 246, 0.15)"
|
||||||
<Icon
|
border="1px solid rgba(139, 92, 246, 0.25)"
|
||||||
as={LineChart}
|
>
|
||||||
boxSize={5}
|
<Icon
|
||||||
color={colors.accent.purple}
|
as={LineChart}
|
||||||
css={css`filter: drop-shadow(0 0 6px #8b5cf6);`}
|
boxSize={5}
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Text
|
|
||||||
fontSize="sm"
|
|
||||||
fontWeight="bold"
|
|
||||||
color={textColor}
|
|
||||||
css={css`text-shadow: 0 0 20px rgba(139, 92, 246, 0.3);`}
|
|
||||||
>
|
|
||||||
大盘分时走势
|
|
||||||
</Text>
|
|
||||||
<Tooltip label="图表上的标记点表示概念异动时刻,点击可查看详情" hasArrow>
|
|
||||||
<Icon as={Info} boxSize={3.5} color={colors.text.muted} cursor="help" />
|
|
||||||
</Tooltip>
|
|
||||||
</HStack>
|
|
||||||
<IndexMinuteChart
|
|
||||||
indexData={index}
|
|
||||||
alerts={alerts}
|
|
||||||
onAlertClick={handleChartAlertClick}
|
|
||||||
height="420px"
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* 统计摘要 - Glassmorphism Bento Grid(移到分时图后面) */}
|
|
||||||
<SimpleGrid columns={{ base: 1, md: 2 }} spacing={4} mb={5}>
|
|
||||||
{/* 指数信息卡片 */}
|
|
||||||
<Box
|
|
||||||
bg={sectionBg}
|
|
||||||
backdropFilter={glassEffect.light.backdropFilter}
|
|
||||||
borderRadius="20px"
|
|
||||||
border={glassEffect.light.border}
|
|
||||||
p={5}
|
|
||||||
position="relative"
|
|
||||||
overflow="hidden"
|
|
||||||
transition="all 0.3s"
|
|
||||||
_hover={{
|
|
||||||
border: `1px solid ${marketColor}30`,
|
|
||||||
boxShadow: `0 8px 30px ${marketColor}15`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* 背景光晕 */}
|
|
||||||
<Box
|
|
||||||
position="absolute"
|
|
||||||
top="-20px"
|
|
||||||
right="-20px"
|
|
||||||
w="100px"
|
|
||||||
h="100px"
|
|
||||||
borderRadius="full"
|
|
||||||
bg={`${marketColor}10`}
|
|
||||||
filter="blur(30px)"
|
|
||||||
pointerEvents="none"
|
|
||||||
/>
|
|
||||||
<HStack justify="space-between" align="flex-start">
|
|
||||||
<VStack align="flex-start" spacing={1}>
|
|
||||||
<Text fontSize="xs" color={colors.text.muted} letterSpacing="1px" textTransform="uppercase">
|
|
||||||
{index?.name || '上证指数'}
|
|
||||||
</Text>
|
|
||||||
<Text
|
|
||||||
fontSize="3xl"
|
|
||||||
fontWeight="bold"
|
|
||||||
color={marketColor}
|
|
||||||
css={css`text-shadow: 0 0 30px ${marketColor}60;`}
|
|
||||||
>
|
|
||||||
{index?.latest_price?.toFixed(2) || '-'}
|
|
||||||
</Text>
|
|
||||||
</VStack>
|
|
||||||
<VStack align="flex-end" spacing={2}>
|
|
||||||
<HStack
|
|
||||||
spacing={2}
|
|
||||||
px={3}
|
|
||||||
py={1.5}
|
|
||||||
borderRadius="full"
|
|
||||||
bg={`${marketColor}15`}
|
|
||||||
border={`1px solid ${marketColor}25`}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
as={(index?.change_pct || 0) >= 0 ? TrendingUp : TrendingDown}
|
|
||||||
boxSize={4}
|
|
||||||
color={marketColor}
|
|
||||||
css={css`filter: drop-shadow(0 0 4px ${marketColor});`}
|
|
||||||
/>
|
|
||||||
<Text
|
|
||||||
fontSize="sm"
|
|
||||||
fontWeight="bold"
|
|
||||||
color={marketColor}
|
|
||||||
css={css`text-shadow: 0 0 10px ${marketColor}50;`}
|
|
||||||
>
|
|
||||||
{(index?.change_pct || 0) >= 0 ? '+' : ''}{(index?.change_pct || 0).toFixed(2)}%
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
|
||||||
<HStack spacing={4} fontSize="xs" color={colors.text.tertiary}>
|
|
||||||
<HStack spacing={1}>
|
|
||||||
<Text>高</Text>
|
|
||||||
<Text color={colors.market.up} fontWeight="bold">{index?.high?.toFixed(2)}</Text>
|
|
||||||
</HStack>
|
|
||||||
<HStack spacing={1}>
|
|
||||||
<Text>低</Text>
|
|
||||||
<Text color={colors.market.down} fontWeight="bold">{index?.low?.toFixed(2)}</Text>
|
|
||||||
</HStack>
|
|
||||||
</HStack>
|
|
||||||
</VStack>
|
|
||||||
</HStack>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* 异动统计卡片 */}
|
|
||||||
<Box
|
|
||||||
bg={sectionBg}
|
|
||||||
backdropFilter={glassEffect.light.backdropFilter}
|
|
||||||
borderRadius="20px"
|
|
||||||
border={glassEffect.light.border}
|
|
||||||
p={5}
|
|
||||||
position="relative"
|
|
||||||
overflow="hidden"
|
|
||||||
transition="all 0.3s"
|
|
||||||
_hover={{
|
|
||||||
border: '1px solid rgba(139, 92, 246, 0.3)',
|
|
||||||
boxShadow: '0 8px 30px rgba(139, 92, 246, 0.1)',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<HStack justify="space-between" mb={3}>
|
|
||||||
<HStack spacing={2}>
|
|
||||||
<Text fontSize="sm" fontWeight="bold" color={textColor}>今日异动</Text>
|
|
||||||
<Text fontSize="xs" color={colors.text.muted}>(点击筛选)</Text>
|
|
||||||
</HStack>
|
|
||||||
<HStack spacing={2}>
|
|
||||||
{selectedAlertType && (
|
|
||||||
<Text
|
|
||||||
fontSize="xs"
|
|
||||||
color={colors.accent.purple}
|
|
||||||
cursor="pointer"
|
|
||||||
onClick={() => setSelectedAlertType(null)}
|
|
||||||
_hover={{ textDecoration: 'underline' }}
|
|
||||||
>
|
|
||||||
清除筛选
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
<Text
|
|
||||||
fontSize="sm"
|
|
||||||
color={colors.accent.purple}
|
color={colors.accent.purple}
|
||||||
fontWeight="bold"
|
css={css`filter: drop-shadow(0 0 6px #8b5cf6);`}
|
||||||
css={css`text-shadow: 0 0 10px rgba(139, 92, 246, 0.5);`}
|
/>
|
||||||
>
|
</Box>
|
||||||
{selectedAlertType ? `${filteredAlerts.length}/${alerts.length}` : alerts.length} 次
|
<Text
|
||||||
</Text>
|
fontSize="sm"
|
||||||
</HStack>
|
fontWeight="bold"
|
||||||
|
color={textColor}
|
||||||
|
css={css`text-shadow: 0 0 20px rgba(139, 92, 246, 0.3);`}
|
||||||
|
>
|
||||||
|
大盘分时走势
|
||||||
|
</Text>
|
||||||
|
<Tooltip label="图表上的标记点表示概念异动时刻,点击可查看详情" hasArrow>
|
||||||
|
<Icon as={Info} boxSize={3.5} color={colors.text.muted} cursor="help" />
|
||||||
|
</Tooltip>
|
||||||
</HStack>
|
</HStack>
|
||||||
<Flex gap={2} flexWrap="wrap">
|
<Spacer />
|
||||||
{Object.entries(alert_summary || {})
|
{/* 筛选区内联 */}
|
||||||
.filter(([_, count]) => count > 0)
|
<AlertFilterSection
|
||||||
.slice(0, 5)
|
alertSummary={alert_summary}
|
||||||
.map(([type, count]) => {
|
selectedAlertType={selectedAlertType}
|
||||||
const config = ALERT_TYPE_CONFIG[type];
|
onAlertTypeClick={handleAlertTypeClick}
|
||||||
if (!config) return null;
|
onClearFilter={() => setSelectedAlertType(null)}
|
||||||
const isSelected = selectedAlertType === type;
|
totalCount={alerts.length}
|
||||||
return (
|
filteredCount={filteredAlerts.length}
|
||||||
<HStack
|
selectedDate={selectedDate}
|
||||||
key={type}
|
onDateChange={onDateChange}
|
||||||
spacing={1.5}
|
minDate={minDate}
|
||||||
px={3}
|
maxDate={maxDate}
|
||||||
py={1.5}
|
/>
|
||||||
borderRadius="full"
|
</Flex>
|
||||||
bg={isSelected ? `${config.color}35` : `${config.color}15`}
|
<Box position="relative">
|
||||||
border={isSelected ? `2px solid ${config.color}` : `1px solid ${config.color}25`}
|
<IndexMinuteChart
|
||||||
cursor="pointer"
|
indexData={index}
|
||||||
transition="all 0.2s"
|
alerts={alerts}
|
||||||
transform={isSelected ? 'scale(1.05)' : 'scale(1)'}
|
onAlertClick={handleChartAlertClick}
|
||||||
boxShadow={isSelected ? `0 0 20px ${config.color}40` : 'none'}
|
height="420px"
|
||||||
onClick={() => handleAlertTypeClick(type)}
|
/>
|
||||||
_hover={{
|
{/* 刷新时的轻量级加载指示器 */}
|
||||||
bg: `${config.color}25`,
|
{refreshing && (
|
||||||
boxShadow: `0 0 15px ${config.color}30`,
|
<Center
|
||||||
transform: 'scale(1.02)',
|
position="absolute"
|
||||||
}}
|
top={0}
|
||||||
>
|
left={0}
|
||||||
<Text fontSize="xs" color={config.color} fontWeight={isSelected ? 'bold' : 'medium'}>{config.label}</Text>
|
right={0}
|
||||||
<Text
|
bottom={0}
|
||||||
fontSize="xs"
|
bg="rgba(0, 0, 0, 0.3)"
|
||||||
fontWeight="bold"
|
borderRadius="12px"
|
||||||
color={config.color}
|
zIndex={10}
|
||||||
css={css`text-shadow: 0 0 8px ${config.color}50;`}
|
>
|
||||||
>
|
<Spinner size="lg" color="#8b5cf6" thickness="3px" />
|
||||||
{count}
|
</Center>
|
||||||
</Text>
|
)}
|
||||||
</HStack>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Flex>
|
|
||||||
</Box>
|
</Box>
|
||||||
</SimpleGrid>
|
</Box>
|
||||||
|
|
||||||
{/* 异动列表 - Glassmorphism 横向滚动 */}
|
{/* 异动列表 - Glassmorphism 横向滚动 */}
|
||||||
{alerts.length > 0 && (
|
{alerts.length > 0 && (
|
||||||
|
|||||||
@@ -5,24 +5,13 @@ import {
|
|||||||
Box,
|
Box,
|
||||||
Heading,
|
Heading,
|
||||||
Text,
|
Text,
|
||||||
Input,
|
|
||||||
InputGroup,
|
|
||||||
InputLeftElement,
|
|
||||||
InputRightElement,
|
|
||||||
IconButton,
|
|
||||||
Button,
|
Button,
|
||||||
SimpleGrid,
|
SimpleGrid,
|
||||||
Card,
|
Card,
|
||||||
CardBody,
|
CardBody,
|
||||||
CardHeader,
|
|
||||||
VStack,
|
VStack,
|
||||||
HStack,
|
HStack,
|
||||||
Badge,
|
Badge,
|
||||||
Stat,
|
|
||||||
StatLabel,
|
|
||||||
StatNumber,
|
|
||||||
StatHelpText,
|
|
||||||
StatArrow,
|
|
||||||
Flex,
|
Flex,
|
||||||
Spacer,
|
Spacer,
|
||||||
Icon,
|
Icon,
|
||||||
@@ -30,25 +19,13 @@ import {
|
|||||||
Spinner,
|
Spinner,
|
||||||
Center,
|
Center,
|
||||||
Divider,
|
Divider,
|
||||||
List,
|
|
||||||
ListItem,
|
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Menu,
|
|
||||||
MenuButton,
|
|
||||||
MenuList,
|
|
||||||
MenuItem,
|
|
||||||
useDisclosure,
|
|
||||||
Image,
|
|
||||||
Fade,
|
|
||||||
Collapse,
|
|
||||||
Stack,
|
|
||||||
Progress,
|
|
||||||
Tag,
|
Tag,
|
||||||
TagLabel,
|
TagLabel,
|
||||||
Skeleton,
|
Skeleton,
|
||||||
SkeletonText,
|
SkeletonText,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { Search, X, ArrowRight, TrendingUp, Info, ChevronRight, Calendar, LineChart, Flame, Rocket, Brain, ArrowUp, ArrowDown, BarChart2, Tag as TagIcon, Layers, Zap, Wallet, Banknote, Scale } from 'lucide-react';
|
import { TrendingUp, Info, ChevronRight, Flame, Rocket, ArrowUp, ArrowDown, BarChart2, Layers, Zap, Wallet, Banknote, Scale } from 'lucide-react';
|
||||||
import ConceptStocksModal from '@components/ConceptStocksModal';
|
import ConceptStocksModal from '@components/ConceptStocksModal';
|
||||||
import TradeDatePicker from '@components/TradeDatePicker';
|
import TradeDatePicker from '@components/TradeDatePicker';
|
||||||
import HotspotOverview from './components/HotspotOverview';
|
import HotspotOverview from './components/HotspotOverview';
|
||||||
@@ -76,22 +53,12 @@ const StockOverview = () => {
|
|||||||
// 🎯 事件追踪 Hook
|
// 🎯 事件追踪 Hook
|
||||||
const {
|
const {
|
||||||
trackMarketStatsViewed,
|
trackMarketStatsViewed,
|
||||||
trackSearchInitiated,
|
|
||||||
trackStockSearched,
|
|
||||||
trackSearchResultClicked,
|
|
||||||
trackConceptClicked,
|
trackConceptClicked,
|
||||||
trackConceptStockClicked,
|
|
||||||
trackHeatmapStockClicked,
|
trackHeatmapStockClicked,
|
||||||
trackStockDetailViewed,
|
|
||||||
trackConceptDetailViewed,
|
|
||||||
trackDateChanged,
|
trackDateChanged,
|
||||||
} = useStockOverviewEvents({ navigate });
|
} = useStockOverviewEvents({ navigate });
|
||||||
|
|
||||||
// 状态管理
|
// 状态管理
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
|
||||||
const [searchResults, setSearchResults] = useState([]);
|
|
||||||
const [isSearching, setIsSearching] = useState(false);
|
|
||||||
const [showResults, setShowResults] = useState(false);
|
|
||||||
const [topConcepts, setTopConcepts] = useState([]);
|
const [topConcepts, setTopConcepts] = useState([]);
|
||||||
const [heatmapData, setHeatmapData] = useState([]);
|
const [heatmapData, setHeatmapData] = useState([]);
|
||||||
const [loadingConcepts, setLoadingConcepts] = useState(true);
|
const [loadingConcepts, setLoadingConcepts] = useState(true);
|
||||||
@@ -104,6 +71,17 @@ const StockOverview = () => {
|
|||||||
const [isStockModalOpen, setIsStockModalOpen] = useState(false);
|
const [isStockModalOpen, setIsStockModalOpen] = useState(false);
|
||||||
const [selectedConcept, setSelectedConcept] = useState(null);
|
const [selectedConcept, setSelectedConcept] = useState(null);
|
||||||
|
|
||||||
|
// 涨停/跌停统计状态
|
||||||
|
const [limitStats, setLimitStats] = useState({
|
||||||
|
limitUpCount: 0,
|
||||||
|
limitDownCount: 0,
|
||||||
|
continuousLimitCount: 0,
|
||||||
|
maxContinuousDays: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
// HotspotOverview 数据(用于 Hero 区展示大盘涨跌幅和异动数量)
|
||||||
|
const [hotspotData, setHotspotData] = useState(null);
|
||||||
|
|
||||||
// 深色主题配色 - 参考概念中心
|
// 深色主题配色 - 参考概念中心
|
||||||
const bgColor = '#0a0a0f'; // 深色背景
|
const bgColor = '#0a0a0f'; // 深色背景
|
||||||
const cardBg = 'rgba(255, 255, 255, 0.03)'; // 玻璃态卡片背景
|
const cardBg = 'rgba(255, 255, 255, 0.03)'; // 玻璃态卡片背景
|
||||||
@@ -123,59 +101,6 @@ const StockOverview = () => {
|
|||||||
setIsStockModalOpen(true);
|
setIsStockModalOpen(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 防抖搜索
|
|
||||||
const debounceSearch = useCallback(
|
|
||||||
(() => {
|
|
||||||
let timeoutId;
|
|
||||||
return (query) => {
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
if (!query.trim()) {
|
|
||||||
setSearchResults([]);
|
|
||||||
setShowResults(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
timeoutId = setTimeout(() => {
|
|
||||||
searchStocks(query);
|
|
||||||
}, 300);
|
|
||||||
};
|
|
||||||
})(),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
// 搜索股票
|
|
||||||
const searchStocks = async (query) => {
|
|
||||||
setIsSearching(true);
|
|
||||||
try {
|
|
||||||
logger.debug('StockOverview', '开始搜索股票', { query });
|
|
||||||
const response = await fetch(`${getApiBase()}/api/stocks/search?q=${encodeURIComponent(query)}&limit=10`);
|
|
||||||
const data = await response.json();
|
|
||||||
logger.debug('StockOverview', 'API返回数据', {
|
|
||||||
status: response.status,
|
|
||||||
resultCount: data.data?.length || 0
|
|
||||||
});
|
|
||||||
|
|
||||||
if (data.success) {
|
|
||||||
const results = data.data || [];
|
|
||||||
setSearchResults(results);
|
|
||||||
setShowResults(true);
|
|
||||||
|
|
||||||
// 🎯 追踪搜索查询
|
|
||||||
trackStockSearched(query, results.length);
|
|
||||||
} else {
|
|
||||||
logger.warn('StockOverview', '搜索失败', data.error || '请稍后重试', { query });
|
|
||||||
// ❌ 移除搜索失败 toast(非关键操作)
|
|
||||||
|
|
||||||
// 🎯 追踪搜索无结果
|
|
||||||
trackStockSearched(query, 0);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('StockOverview', 'searchStocks', error, { query });
|
|
||||||
// ❌ 移除搜索失败 toast(非关键操作)
|
|
||||||
} finally {
|
|
||||||
setIsSearching(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 获取每日涨幅靠前的概念
|
// 获取每日涨幅靠前的概念
|
||||||
const fetchTopConcepts = async (date = null) => {
|
const fetchTopConcepts = async (date = null) => {
|
||||||
setLoadingConcepts(true);
|
setLoadingConcepts(true);
|
||||||
@@ -237,10 +162,30 @@ const StockOverview = () => {
|
|||||||
falling_count: data.statistics.falling_count
|
falling_count: data.statistics.falling_count
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 计算涨停/跌停家数
|
||||||
|
const stocks = data.data || [];
|
||||||
|
const limitUpCount = stocks.filter(s => s.change_percent >= 9.9).length;
|
||||||
|
const limitDownCount = stocks.filter(s => s.change_percent <= -9.9).length;
|
||||||
|
// 计算连板股(简化版:统计涨幅超过 19% 的作为 2 连板估算)
|
||||||
|
const continuousLimitCount = stocks.filter(s => s.change_percent >= 19.8).length;
|
||||||
|
setLimitStats(prev => ({
|
||||||
|
...prev,
|
||||||
|
limitUpCount,
|
||||||
|
limitDownCount,
|
||||||
|
continuousLimitCount,
|
||||||
|
// 最高连板天数暂时用估算值(涨幅除以10取整)
|
||||||
|
maxContinuousDays: stocks.length > 0
|
||||||
|
? Math.max(...stocks.map(s => Math.floor((s.change_percent || 0) / 9.9)))
|
||||||
|
: 0
|
||||||
|
}));
|
||||||
|
|
||||||
// 日期由 fetchTopConcepts 统一设置,这里不再设置
|
// 日期由 fetchTopConcepts 统一设置,这里不再设置
|
||||||
logger.debug('StockOverview', '热力图数据加载成功', {
|
logger.debug('StockOverview', '热力图数据加载成功', {
|
||||||
count: data.data?.length || 0,
|
count: data.data?.length || 0,
|
||||||
date: data.trade_date
|
date: data.trade_date,
|
||||||
|
limitUpCount,
|
||||||
|
limitDownCount
|
||||||
});
|
});
|
||||||
// 延迟渲染热力图,确保DOM已经准备好
|
// 延迟渲染热力图,确保DOM已经准备好
|
||||||
setTimeout(() => renderHeatmap(data.data), 100);
|
setTimeout(() => renderHeatmap(data.data), 100);
|
||||||
@@ -504,35 +449,6 @@ const StockOverview = () => {
|
|||||||
return '微盘股(<50亿)';
|
return '微盘股(<50亿)';
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理搜索输入
|
|
||||||
const handleSearchChange = (e) => {
|
|
||||||
const value = e.target.value;
|
|
||||||
setSearchQuery(value);
|
|
||||||
|
|
||||||
// 🎯 追踪搜索开始(首次输入时)
|
|
||||||
if (value && !searchQuery) {
|
|
||||||
trackSearchInitiated();
|
|
||||||
}
|
|
||||||
|
|
||||||
debounceSearch(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 清空搜索
|
|
||||||
const handleClearSearch = () => {
|
|
||||||
setSearchQuery('');
|
|
||||||
setSearchResults([]);
|
|
||||||
setShowResults(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 选择股票
|
|
||||||
const handleSelectStock = (stock, index = 0) => {
|
|
||||||
// 🎯 追踪搜索结果点击
|
|
||||||
trackSearchResultClicked(stock, index);
|
|
||||||
|
|
||||||
navigate(`/company?scode=${stock.stock_code}`);
|
|
||||||
handleClearSearch();
|
|
||||||
};
|
|
||||||
|
|
||||||
// 查看概念详情(模仿概念中心:打开对应HTML页)
|
// 查看概念详情(模仿概念中心:打开对应HTML页)
|
||||||
const handleConceptClick = (concept, rank = 0) => {
|
const handleConceptClick = (concept, rank = 0) => {
|
||||||
// 🎯 追踪概念点击
|
// 🎯 追踪概念点击
|
||||||
@@ -647,57 +563,52 @@ const StockOverview = () => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
search={{
|
|
||||||
placeholder: '搜索股票代码、名称或拼音首字母...',
|
|
||||||
showSearchButton: true,
|
|
||||||
searchButtonText: '搜索',
|
|
||||||
maxWidth: '2xl',
|
|
||||||
// 受控模式
|
|
||||||
value: searchQuery,
|
|
||||||
onChange: (value) => {
|
|
||||||
setSearchQuery(value);
|
|
||||||
if (value && !searchQuery) {
|
|
||||||
trackSearchInitiated();
|
|
||||||
}
|
|
||||||
debounceSearch(value);
|
|
||||||
},
|
|
||||||
onClear: handleClearSearch,
|
|
||||||
results: searchResults.map((stock) => ({
|
|
||||||
id: stock.stock_code,
|
|
||||||
label: stock.stock_name,
|
|
||||||
subLabel: stock.stock_code,
|
|
||||||
extra: stock.pinyin_abbr?.toUpperCase(),
|
|
||||||
tags: stock.exchange ? [{ text: stock.exchange }] : [],
|
|
||||||
raw: stock,
|
|
||||||
})),
|
|
||||||
isSearching: isSearching,
|
|
||||||
showDropdown: showResults,
|
|
||||||
onSearch: async () => [],
|
|
||||||
onResultSelect: (item, index) => handleSelectStock(item.raw, index),
|
|
||||||
}}
|
|
||||||
stats={{
|
stats={{
|
||||||
columns: { base: 1, sm: 3, md: 3 },
|
columns: { base: 1, sm: 2, md: 3 },
|
||||||
items: [
|
items: [
|
||||||
|
// 第一行:核心指标
|
||||||
{
|
{
|
||||||
key: 'marketCap',
|
key: 'indexChange',
|
||||||
label: 'A股总市值',
|
label: '大盘涨跌幅',
|
||||||
value: marketStats ? `${(marketStats.total_market_cap / 10000).toFixed(1)}万亿` : null,
|
value: hotspotData?.index?.change_pct != null
|
||||||
// 市值趋势对比
|
? `${hotspotData.index.change_pct >= 0 ? '+' : ''}${hotspotData.index.change_pct.toFixed(2)}%`
|
||||||
trend: marketStats?.yesterday?.total_market_cap ? (() => {
|
: null,
|
||||||
const change = ((marketStats.total_market_cap - marketStats.yesterday.total_market_cap) / marketStats.yesterday.total_market_cap) * 100;
|
valueColor: (hotspotData?.index?.change_pct ?? 0) >= 0 ? '#ff4d4d' : '#22c55e',
|
||||||
return {
|
watermark: { icon: TrendingUp, color: goldColor, opacity: 0.1 },
|
||||||
direction: change > 0.01 ? 'up' : change < -0.01 ? 'down' : 'flat',
|
|
||||||
percent: Math.abs(change),
|
|
||||||
};
|
|
||||||
})() : undefined,
|
|
||||||
// 水印背景图标 - 钱袋图标
|
|
||||||
watermark: { icon: Wallet, color: goldColor, opacity: 0.1 },
|
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'limitUpDown',
|
||||||
|
label: '涨停/跌停',
|
||||||
|
value: (limitStats.limitUpCount > 0 || limitStats.limitDownCount > 0)
|
||||||
|
? `${limitStats.limitUpCount}/${limitStats.limitDownCount}`
|
||||||
|
: null,
|
||||||
|
progressBar: (limitStats.limitUpCount > 0 || limitStats.limitDownCount > 0) ? {
|
||||||
|
value: limitStats.limitUpCount,
|
||||||
|
total: limitStats.limitDownCount || 1, // 避免除零
|
||||||
|
positiveColor: '#ff4d4d',
|
||||||
|
negativeColor: '#22c55e',
|
||||||
|
} : undefined,
|
||||||
|
watermark: { icon: Zap, color: '#ff4d4d', opacity: 0.1 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'rising',
|
||||||
|
label: '多空对比',
|
||||||
|
value: (marketStats?.rising_count && marketStats?.falling_count)
|
||||||
|
? `${marketStats.rising_count}/${marketStats.falling_count}`
|
||||||
|
: null,
|
||||||
|
progressBar: (marketStats?.rising_count && marketStats?.falling_count) ? {
|
||||||
|
value: marketStats.rising_count,
|
||||||
|
total: marketStats.falling_count,
|
||||||
|
positiveColor: '#ff4d4d',
|
||||||
|
negativeColor: '#22c55e',
|
||||||
|
} : undefined,
|
||||||
|
watermark: { icon: Scale, color: goldColor, opacity: 0.1 },
|
||||||
|
},
|
||||||
|
// 第二行:辅助指标
|
||||||
{
|
{
|
||||||
key: 'amount',
|
key: 'amount',
|
||||||
label: '今日成交额',
|
label: '今日成交额',
|
||||||
value: marketStats ? `${(marketStats.total_amount / 10000).toFixed(1)}万亿` : null,
|
value: marketStats ? `${(marketStats.total_amount / 10000).toFixed(1)}万亿` : null,
|
||||||
// 成交额趋势对比(放量/缩量)
|
|
||||||
trend: marketStats?.yesterday?.total_amount ? (() => {
|
trend: marketStats?.yesterday?.total_amount ? (() => {
|
||||||
const change = ((marketStats.total_amount - marketStats.yesterday.total_amount) / marketStats.yesterday.total_amount) * 100;
|
const change = ((marketStats.total_amount - marketStats.yesterday.total_amount) / marketStats.yesterday.total_amount) * 100;
|
||||||
return {
|
return {
|
||||||
@@ -706,25 +617,32 @@ const StockOverview = () => {
|
|||||||
label: change > 5 ? '放量' : change < -5 ? '缩量' : undefined,
|
label: change > 5 ? '放量' : change < -5 ? '缩量' : undefined,
|
||||||
};
|
};
|
||||||
})() : undefined,
|
})() : undefined,
|
||||||
// 水印背景图标 - 钞票图标
|
|
||||||
watermark: { icon: Banknote, color: goldColor, opacity: 0.1 },
|
watermark: { icon: Banknote, color: goldColor, opacity: 0.1 },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'rising',
|
key: 'marketCap',
|
||||||
label: '多空对比',
|
label: 'A股总市值',
|
||||||
// 显示为 "上涨/下跌" 格式,使用自定义渲染
|
value: marketStats ? `${(marketStats.total_market_cap / 10000).toFixed(1)}万亿` : null,
|
||||||
value: (marketStats?.rising_count && marketStats?.falling_count)
|
trend: marketStats?.yesterday?.total_market_cap ? (() => {
|
||||||
? `${marketStats.rising_count}/${marketStats.falling_count}`
|
const change = ((marketStats.total_market_cap - marketStats.yesterday.total_market_cap) / marketStats.yesterday.total_market_cap) * 100;
|
||||||
: marketStats?.rising_count,
|
return {
|
||||||
// 涨跌进度条(不显示底部标签)
|
direction: change > 0.01 ? 'up' : change < -0.01 ? 'down' : 'flat',
|
||||||
progressBar: (marketStats?.rising_count && marketStats?.falling_count) ? {
|
percent: Math.abs(change),
|
||||||
value: marketStats.rising_count,
|
};
|
||||||
total: marketStats.falling_count,
|
})() : undefined,
|
||||||
positiveColor: '#ff4d4d',
|
watermark: { icon: Wallet, color: goldColor, opacity: 0.1 },
|
||||||
negativeColor: '#22c55e',
|
},
|
||||||
} : undefined,
|
{
|
||||||
// 水印背景图标 - 天平图标
|
key: 'continuousLimit',
|
||||||
watermark: { icon: Scale, color: goldColor, opacity: 0.1 },
|
label: '连板龙头',
|
||||||
|
value: limitStats.limitUpCount > 0
|
||||||
|
? `${limitStats.limitUpCount}只`
|
||||||
|
: '暂无',
|
||||||
|
helpText: limitStats.maxContinuousDays > 1
|
||||||
|
? `最高${limitStats.maxContinuousDays}天`
|
||||||
|
: undefined,
|
||||||
|
valueColor: '#f59e0b',
|
||||||
|
watermark: { icon: Rocket, color: '#f59e0b', opacity: 0.1 },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}}
|
}}
|
||||||
@@ -749,6 +667,7 @@ const StockOverview = () => {
|
|||||||
}}
|
}}
|
||||||
minDate={tradingDays.length > 0 ? new Date(tradingDays[0]) : undefined}
|
minDate={tradingDays.length > 0 ? new Date(tradingDays[0]) : undefined}
|
||||||
maxDate={tradingDays.length > 0 ? new Date(tradingDays[tradingDays.length - 1]) : undefined}
|
maxDate={tradingDays.length > 0 ? new Date(tradingDays[tradingDays.length - 1]) : undefined}
|
||||||
|
onDataLoaded={setHotspotData}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Card
|
<Card
|
||||||
|
|||||||
Reference in New Issue
Block a user