refactor(StockOverview): 优化布局与数据展示
- 头部统计卡片从 4 列精简为 3 列,移除冗余下跌家数 - 涨跌家数改为"多空对比"卡片,双色数值 + 进度条 - 各卡片新增环比趋势指示(放量/缩量等) - 日期选择器移至 HotspotOverview 头部右侧 - 大盘分时图调整至统计卡片上方 - 异动标签支持点击筛选 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -3,8 +3,9 @@
|
|||||||
* 展示大盘分时走势 + 概念异动标注
|
* 展示大盘分时走势 + 概念异动标注
|
||||||
*
|
*
|
||||||
* 布局设计:
|
* 布局设计:
|
||||||
* - 顶部:统计摘要(指数信息 + 异动统计)
|
* - 顶部:标题 + 日期选择器 + 异动数量
|
||||||
* - 中部:大尺寸分时图(主要展示区域)
|
* - 中部:大尺寸分时图(主要展示区域)
|
||||||
|
* - 下方:统计卡片(指数信息 + 异动统计)
|
||||||
* - 底部:异动列表(横向滚动卡片)
|
* - 底部:异动列表(横向滚动卡片)
|
||||||
*/
|
*/
|
||||||
import React, { useState, useCallback } from 'react';
|
import React, { useState, useCallback } from 'react';
|
||||||
@@ -39,6 +40,7 @@ import {
|
|||||||
import { useHotspotData } from './hooks';
|
import { useHotspotData } from './hooks';
|
||||||
import { IndexMinuteChart, AlertDetailDrawer } from './components';
|
import { IndexMinuteChart, AlertDetailDrawer } 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,
|
||||||
@@ -192,10 +194,15 @@ const CompactAlertCard = ({ alert, onClick, isSelected }) => {
|
|||||||
* 热点概览主组件
|
* 热点概览主组件
|
||||||
* @param {Object} props
|
* @param {Object} props
|
||||||
* @param {Date|null} props.selectedDate - 选中的交易日期
|
* @param {Date|null} props.selectedDate - 选中的交易日期
|
||||||
|
* @param {Function} props.onDateChange - 日期变更回调
|
||||||
|
* @param {Date} props.minDate - 最小可选日期
|
||||||
|
* @param {Date} props.maxDate - 最大可选日期
|
||||||
*/
|
*/
|
||||||
const HotspotOverview = ({ selectedDate }) => {
|
const HotspotOverview = ({ selectedDate, onDateChange, minDate, maxDate }) => {
|
||||||
const [selectedAlert, setSelectedAlert] = useState(null);
|
const [selectedAlert, setSelectedAlert] = useState(null);
|
||||||
const [drawerAlertData, setDrawerAlertData] = useState(null);
|
const [drawerAlertData, setDrawerAlertData] = useState(null);
|
||||||
|
// 选中的异动类型过滤器(null 表示全部)
|
||||||
|
const [selectedAlertType, setSelectedAlertType] = useState(null);
|
||||||
|
|
||||||
// 右边栏抽屉控制
|
// 右边栏抽屉控制
|
||||||
const { isOpen: isDrawerOpen, onOpen: onDrawerOpen, onClose: onDrawerClose } = useDisclosure();
|
const { isOpen: isDrawerOpen, onOpen: onDrawerOpen, onClose: onDrawerClose } = useDisclosure();
|
||||||
@@ -231,6 +238,11 @@ const HotspotOverview = ({ selectedDate }) => {
|
|||||||
onDrawerOpen();
|
onDrawerOpen();
|
||||||
}, [onDrawerOpen]);
|
}, [onDrawerOpen]);
|
||||||
|
|
||||||
|
// 点击异动类型标签 - 切换过滤器
|
||||||
|
const handleAlertTypeClick = useCallback((type) => {
|
||||||
|
setSelectedAlertType(prevType => prevType === type ? null : type);
|
||||||
|
}, []);
|
||||||
|
|
||||||
// 渲染加载状态 - Glassmorphism 风格
|
// 渲染加载状态 - Glassmorphism 风格
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
@@ -335,6 +347,11 @@ const HotspotOverview = ({ selectedDate }) => {
|
|||||||
|
|
||||||
const { index, alerts, alert_summary } = data;
|
const { index, alerts, alert_summary } = data;
|
||||||
|
|
||||||
|
// 根据选中的类型过滤异动列表
|
||||||
|
const filteredAlerts = selectedAlertType
|
||||||
|
? alerts.filter(alert => alert.alert_type === selectedAlertType)
|
||||||
|
: alerts;
|
||||||
|
|
||||||
// 计算市场颜色
|
// 计算市场颜色
|
||||||
const marketColor = getMarketColor(index?.change_pct || 0);
|
const marketColor = getMarketColor(index?.change_pct || 0);
|
||||||
const marketGlow = getMarketGlow(index?.change_pct || 0);
|
const marketGlow = getMarketGlow(index?.change_pct || 0);
|
||||||
@@ -415,6 +432,18 @@ const HotspotOverview = ({ selectedDate }) => {
|
|||||||
</HStack>
|
</HStack>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<HStack spacing={3}>
|
<HStack spacing={3}>
|
||||||
|
{/* 日期选择器 */}
|
||||||
|
{onDateChange && (
|
||||||
|
<TradeDatePicker
|
||||||
|
value={selectedDate}
|
||||||
|
onChange={onDateChange}
|
||||||
|
latestTradeDate={null}
|
||||||
|
minDate={minDate}
|
||||||
|
maxDate={maxDate}
|
||||||
|
isDarkMode={true}
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{alerts.length > 0 && (
|
{alerts.length > 0 && (
|
||||||
<HStack
|
<HStack
|
||||||
spacing={2}
|
spacing={2}
|
||||||
@@ -441,7 +470,7 @@ const HotspotOverview = ({ selectedDate }) => {
|
|||||||
</Text>
|
</Text>
|
||||||
</HStack>
|
</HStack>
|
||||||
)}
|
)}
|
||||||
<Tooltip label="展示大盘走势与概念异动的关联" hasArrow maxW="200px">
|
<Tooltip label="大盘分时走势与概念异动关联分析" hasArrow maxW="200px">
|
||||||
<Box cursor="help" p={2} borderRadius="full" _hover={{ bg: 'rgba(255,255,255,0.05)' }}>
|
<Box cursor="help" p={2} borderRadius="full" _hover={{ bg: 'rgba(255,255,255,0.05)' }}>
|
||||||
<Icon as={Info} color={subTextColor} boxSize={4} />
|
<Icon as={Info} color={subTextColor} boxSize={4} />
|
||||||
</Box>
|
</Box>
|
||||||
@@ -449,7 +478,65 @@ const HotspotOverview = ({ selectedDate }) => {
|
|||||||
</HStack>
|
</HStack>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
{/* 统计摘要 - Glassmorphism Bento Grid */}
|
{/* 大尺寸分时图 - Glassmorphism(移到统计卡片前面) */}
|
||||||
|
<Box
|
||||||
|
bg={sectionBg}
|
||||||
|
backdropFilter={glassEffect.light.backdropFilter}
|
||||||
|
borderRadius="20px"
|
||||||
|
border={glassEffect.light.border}
|
||||||
|
p={5}
|
||||||
|
mb={5}
|
||||||
|
position="relative"
|
||||||
|
overflow="hidden"
|
||||||
|
>
|
||||||
|
{/* 图表区域背景光晕 */}
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
bottom="-50px"
|
||||||
|
left="50%"
|
||||||
|
transform="translateX(-50%)"
|
||||||
|
w="60%"
|
||||||
|
h="100px"
|
||||||
|
borderRadius="full"
|
||||||
|
bg="rgba(139, 92, 246, 0.08)"
|
||||||
|
filter="blur(40px)"
|
||||||
|
pointerEvents="none"
|
||||||
|
/>
|
||||||
|
<HStack spacing={3} mb={4}>
|
||||||
|
<Box
|
||||||
|
p={2}
|
||||||
|
borderRadius="12px"
|
||||||
|
bg="rgba(139, 92, 246, 0.15)"
|
||||||
|
border="1px solid rgba(139, 92, 246, 0.25)"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
as={LineChart}
|
||||||
|
boxSize={5}
|
||||||
|
color={colors.accent.purple}
|
||||||
|
css={css`filter: drop-shadow(0 0 6px #8b5cf6);`}
|
||||||
|
/>
|
||||||
|
</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}>
|
<SimpleGrid columns={{ base: 1, md: 2 }} spacing={4} mb={5}>
|
||||||
{/* 指数信息卡片 */}
|
{/* 指数信息卡片 */}
|
||||||
<Box
|
<Box
|
||||||
@@ -546,16 +633,32 @@ const HotspotOverview = ({ selectedDate }) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<HStack justify="space-between" mb={3}>
|
<HStack justify="space-between" mb={3}>
|
||||||
|
<HStack spacing={2}>
|
||||||
<Text fontSize="sm" fontWeight="bold" color={textColor}>今日异动</Text>
|
<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
|
<Text
|
||||||
fontSize="sm"
|
fontSize="sm"
|
||||||
color={colors.accent.purple}
|
color={colors.accent.purple}
|
||||||
fontWeight="bold"
|
fontWeight="bold"
|
||||||
css={css`text-shadow: 0 0 10px rgba(139, 92, 246, 0.5);`}
|
css={css`text-shadow: 0 0 10px rgba(139, 92, 246, 0.5);`}
|
||||||
>
|
>
|
||||||
{alerts.length} 次
|
{selectedAlertType ? `${filteredAlerts.length}/${alerts.length}` : alerts.length} 次
|
||||||
</Text>
|
</Text>
|
||||||
</HStack>
|
</HStack>
|
||||||
|
</HStack>
|
||||||
<Flex gap={2} flexWrap="wrap">
|
<Flex gap={2} flexWrap="wrap">
|
||||||
{Object.entries(alert_summary || {})
|
{Object.entries(alert_summary || {})
|
||||||
.filter(([_, count]) => count > 0)
|
.filter(([_, count]) => count > 0)
|
||||||
@@ -563,6 +666,7 @@ const HotspotOverview = ({ selectedDate }) => {
|
|||||||
.map(([type, count]) => {
|
.map(([type, count]) => {
|
||||||
const config = ALERT_TYPE_CONFIG[type];
|
const config = ALERT_TYPE_CONFIG[type];
|
||||||
if (!config) return null;
|
if (!config) return null;
|
||||||
|
const isSelected = selectedAlertType === type;
|
||||||
return (
|
return (
|
||||||
<HStack
|
<HStack
|
||||||
key={type}
|
key={type}
|
||||||
@@ -570,15 +674,20 @@ const HotspotOverview = ({ selectedDate }) => {
|
|||||||
px={3}
|
px={3}
|
||||||
py={1.5}
|
py={1.5}
|
||||||
borderRadius="full"
|
borderRadius="full"
|
||||||
bg={`${config.color}15`}
|
bg={isSelected ? `${config.color}35` : `${config.color}15`}
|
||||||
border={`1px solid ${config.color}25`}
|
border={isSelected ? `2px solid ${config.color}` : `1px solid ${config.color}25`}
|
||||||
|
cursor="pointer"
|
||||||
transition="all 0.2s"
|
transition="all 0.2s"
|
||||||
|
transform={isSelected ? 'scale(1.05)' : 'scale(1)'}
|
||||||
|
boxShadow={isSelected ? `0 0 20px ${config.color}40` : 'none'}
|
||||||
|
onClick={() => handleAlertTypeClick(type)}
|
||||||
_hover={{
|
_hover={{
|
||||||
bg: `${config.color}25`,
|
bg: `${config.color}25`,
|
||||||
boxShadow: `0 0 15px ${config.color}30`,
|
boxShadow: `0 0 15px ${config.color}30`,
|
||||||
|
transform: 'scale(1.02)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text fontSize="xs" color={config.color}>{config.label}</Text>
|
<Text fontSize="xs" color={config.color} fontWeight={isSelected ? 'bold' : 'medium'}>{config.label}</Text>
|
||||||
<Text
|
<Text
|
||||||
fontSize="xs"
|
fontSize="xs"
|
||||||
fontWeight="bold"
|
fontWeight="bold"
|
||||||
@@ -594,64 +703,6 @@ const HotspotOverview = ({ selectedDate }) => {
|
|||||||
</Box>
|
</Box>
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
|
|
||||||
{/* 大尺寸分时图 - Glassmorphism */}
|
|
||||||
<Box
|
|
||||||
bg={sectionBg}
|
|
||||||
backdropFilter={glassEffect.light.backdropFilter}
|
|
||||||
borderRadius="20px"
|
|
||||||
border={glassEffect.light.border}
|
|
||||||
p={5}
|
|
||||||
mb={5}
|
|
||||||
position="relative"
|
|
||||||
overflow="hidden"
|
|
||||||
>
|
|
||||||
{/* 图表区域背景光晕 */}
|
|
||||||
<Box
|
|
||||||
position="absolute"
|
|
||||||
bottom="-50px"
|
|
||||||
left="50%"
|
|
||||||
transform="translateX(-50%)"
|
|
||||||
w="60%"
|
|
||||||
h="100px"
|
|
||||||
borderRadius="full"
|
|
||||||
bg="rgba(139, 92, 246, 0.08)"
|
|
||||||
filter="blur(40px)"
|
|
||||||
pointerEvents="none"
|
|
||||||
/>
|
|
||||||
<HStack spacing={3} mb={4}>
|
|
||||||
<Box
|
|
||||||
p={2}
|
|
||||||
borderRadius="12px"
|
|
||||||
bg="rgba(139, 92, 246, 0.15)"
|
|
||||||
border="1px solid rgba(139, 92, 246, 0.25)"
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
as={LineChart}
|
|
||||||
boxSize={5}
|
|
||||||
color={colors.accent.purple}
|
|
||||||
css={css`filter: drop-shadow(0 0 6px #8b5cf6);`}
|
|
||||||
/>
|
|
||||||
</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 横向滚动 */}
|
{/* 异动列表 - Glassmorphism 横向滚动 */}
|
||||||
{alerts.length > 0 && (
|
{alerts.length > 0 && (
|
||||||
<Box>
|
<Box>
|
||||||
@@ -670,7 +721,11 @@ const HotspotOverview = ({ selectedDate }) => {
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Text fontSize="sm" fontWeight="bold" color={textColor}>异动记录</Text>
|
<Text fontSize="sm" fontWeight="bold" color={textColor}>异动记录</Text>
|
||||||
<Text fontSize="xs" color={colors.text.muted}>(点击卡片查看详情)</Text>
|
<Text fontSize="xs" color={colors.text.muted}>
|
||||||
|
{selectedAlertType
|
||||||
|
? `(已筛选 ${ALERT_TYPE_CONFIG[selectedAlertType]?.label || selectedAlertType},共 ${filteredAlerts.length} 条)`
|
||||||
|
: '(点击卡片查看详情)'}
|
||||||
|
</Text>
|
||||||
</HStack>
|
</HStack>
|
||||||
|
|
||||||
{/* 横向滚动卡片 */}
|
{/* 横向滚动卡片 */}
|
||||||
@@ -688,7 +743,7 @@ const HotspotOverview = ({ selectedDate }) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<HStack spacing={3} pb={1}>
|
<HStack spacing={3} pb={1}>
|
||||||
{[...alerts]
|
{[...filteredAlerts]
|
||||||
.sort((a, b) => (b.time || '').localeCompare(a.time || ''))
|
.sort((a, b) => (b.time || '').localeCompare(a.time || ''))
|
||||||
.map((alert, idx) => (
|
.map((alert, idx) => (
|
||||||
<CompactAlertCard
|
<CompactAlertCard
|
||||||
@@ -704,7 +759,7 @@ const HotspotOverview = ({ selectedDate }) => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 无异动提示 - Glassmorphism */}
|
{/* 无异动提示 - Glassmorphism */}
|
||||||
{alerts.length === 0 && (
|
{filteredAlerts.length === 0 && (
|
||||||
<Center
|
<Center
|
||||||
py={12}
|
py={12}
|
||||||
bg={sectionBg}
|
bg={sectionBg}
|
||||||
@@ -742,7 +797,22 @@ const HotspotOverview = ({ selectedDate }) => {
|
|||||||
css={css`filter: drop-shadow(0 0 10px rgba(139, 92, 246, 0.3));`}
|
css={css`filter: drop-shadow(0 0 10px rgba(139, 92, 246, 0.3));`}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Text color={colors.text.tertiary} fontSize="sm">当日暂无概念异动数据</Text>
|
<Text color={colors.text.tertiary} fontSize="sm">
|
||||||
|
{selectedAlertType
|
||||||
|
? `未找到「${ALERT_TYPE_CONFIG[selectedAlertType]?.label || selectedAlertType}」类型的异动`
|
||||||
|
: '当日暂无概念异动数据'}
|
||||||
|
</Text>
|
||||||
|
{selectedAlertType && (
|
||||||
|
<Text
|
||||||
|
fontSize="xs"
|
||||||
|
color={colors.accent.purple}
|
||||||
|
cursor="pointer"
|
||||||
|
onClick={() => setSelectedAlertType(null)}
|
||||||
|
_hover={{ textDecoration: 'underline' }}
|
||||||
|
>
|
||||||
|
查看全部异动
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
</VStack>
|
</VStack>
|
||||||
</Center>
|
</Center>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ import {
|
|||||||
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 } from 'lucide-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 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';
|
||||||
@@ -265,12 +265,14 @@ const StockOverview = () => {
|
|||||||
const newStats = {
|
const newStats = {
|
||||||
...(prevStats || {}), // 先保留所有现有字段(包括 rising_count/falling_count)
|
...(prevStats || {}), // 先保留所有现有字段(包括 rising_count/falling_count)
|
||||||
...data.summary, // 然后覆盖 summary 字段
|
...data.summary, // 然后覆盖 summary 字段
|
||||||
|
yesterday: data.yesterday, // 保存昨日对比数据
|
||||||
date: data.trade_date
|
date: data.trade_date
|
||||||
};
|
};
|
||||||
return newStats;
|
return newStats;
|
||||||
});
|
});
|
||||||
const newStats = {
|
const newStats = {
|
||||||
...data.summary,
|
...data.summary,
|
||||||
|
yesterday: data.yesterday,
|
||||||
date: data.trade_date
|
date: data.trade_date
|
||||||
};
|
};
|
||||||
// 日期和可选日期列表由 fetchTopConcepts 统一设置,这里不再设置
|
// 日期和可选日期列表由 fetchTopConcepts 统一设置,这里不再设置
|
||||||
@@ -674,42 +676,69 @@ const StockOverview = () => {
|
|||||||
onResultSelect: (item, index) => handleSelectStock(item.raw, index),
|
onResultSelect: (item, index) => handleSelectStock(item.raw, index),
|
||||||
}}
|
}}
|
||||||
stats={{
|
stats={{
|
||||||
columns: { base: 2, md: 4 },
|
columns: { base: 1, sm: 3, md: 3 },
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
key: 'marketCap',
|
key: 'marketCap',
|
||||||
label: 'A股总市值',
|
label: 'A股总市值',
|
||||||
value: marketStats ? `${(marketStats.total_market_cap / 10000).toFixed(1)}万亿` : null,
|
value: marketStats ? `${(marketStats.total_market_cap / 10000).toFixed(1)}万亿` : null,
|
||||||
|
// 市值趋势对比
|
||||||
|
trend: marketStats?.yesterday?.total_market_cap ? (() => {
|
||||||
|
const change = ((marketStats.total_market_cap - marketStats.yesterday.total_market_cap) / marketStats.yesterday.total_market_cap) * 100;
|
||||||
|
return {
|
||||||
|
direction: change > 0.01 ? 'up' : change < -0.01 ? 'down' : 'flat',
|
||||||
|
percent: Math.abs(change),
|
||||||
|
};
|
||||||
|
})() : undefined,
|
||||||
|
// 水印背景图标 - 钱袋图标
|
||||||
|
watermark: { icon: Wallet, 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 ? (() => {
|
||||||
|
const change = ((marketStats.total_amount - marketStats.yesterday.total_amount) / marketStats.yesterday.total_amount) * 100;
|
||||||
|
return {
|
||||||
|
direction: change > 0.01 ? 'up' : change < -0.01 ? 'down' : 'flat',
|
||||||
|
percent: Math.abs(change),
|
||||||
|
label: change > 5 ? '放量' : change < -5 ? '缩量' : undefined,
|
||||||
|
};
|
||||||
|
})() : undefined,
|
||||||
|
// 水印背景图标 - 钞票图标
|
||||||
|
watermark: { icon: Banknote, color: goldColor, opacity: 0.1 },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'rising',
|
key: 'rising',
|
||||||
label: '上涨家数',
|
label: '多空对比',
|
||||||
value: marketStats?.rising_count,
|
// 显示为 "上涨/下跌" 格式,使用自定义渲染
|
||||||
valueColor: '#ff4d4d',
|
value: (marketStats?.rising_count && marketStats?.falling_count)
|
||||||
},
|
? `${marketStats.rising_count}/${marketStats.falling_count}`
|
||||||
{
|
: marketStats?.rising_count,
|
||||||
key: 'falling',
|
// 涨跌进度条(不显示底部标签)
|
||||||
label: '下跌家数',
|
progressBar: (marketStats?.rising_count && marketStats?.falling_count) ? {
|
||||||
value: marketStats?.falling_count,
|
value: marketStats.rising_count,
|
||||||
valueColor: 'green.400',
|
total: marketStats.falling_count,
|
||||||
|
positiveColor: '#ff4d4d',
|
||||||
|
negativeColor: '#22c55e',
|
||||||
|
} : undefined,
|
||||||
|
// 水印背景图标 - 天平图标
|
||||||
|
watermark: { icon: Scale, color: goldColor, opacity: 0.1 },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 主内容区 */}
|
{/* 主内容区 - 负 margin 使卡片向上浮动,与 Hero 产生重叠纵深感 */}
|
||||||
<Box py={10} px={6} position="relative" zIndex={1}>
|
<Box pt={6} pb={10} px={6} mt={-6} position="relative" zIndex={2}>
|
||||||
{/* 日期选择器 */}
|
{/* 热点概览 - 大盘走势 + 概念异动 */}
|
||||||
<Box mb={6}>
|
{/* 只在 selectedDate 确定后渲染,避免 null → 日期 的双重请求 */}
|
||||||
<Flex align="center" gap={4} flexWrap="wrap">
|
<Box mb={10}>
|
||||||
<TradeDatePicker
|
{selectedDate ? (
|
||||||
value={selectedDate}
|
<HotspotOverview
|
||||||
onChange={(date) => {
|
selectedDate={selectedDate}
|
||||||
|
onDateChange={(date) => {
|
||||||
const dateStr = date.toISOString().split('T')[0];
|
const dateStr = date.toISOString().split('T')[0];
|
||||||
const previousDateStr = selectedDate ? selectedDate.toISOString().split('T')[0] : null;
|
const previousDateStr = selectedDate ? selectedDate.toISOString().split('T')[0] : null;
|
||||||
trackDateChanged(dateStr, previousDateStr);
|
trackDateChanged(dateStr, previousDateStr);
|
||||||
@@ -718,25 +747,9 @@ const StockOverview = () => {
|
|||||||
fetchMarketStats(dateStr);
|
fetchMarketStats(dateStr);
|
||||||
fetchTopConcepts(dateStr);
|
fetchTopConcepts(dateStr);
|
||||||
}}
|
}}
|
||||||
latestTradeDate={null}
|
|
||||||
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}
|
||||||
label="交易日期"
|
|
||||||
isDarkMode={true}
|
|
||||||
/>
|
/>
|
||||||
</Flex>
|
|
||||||
{selectedDate && (
|
|
||||||
<Text fontSize="sm" color={subTextColor} mt={2}>
|
|
||||||
当前显示 {selectedDate.toISOString().split('T')[0]} 的市场数据
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* 热点概览 - 大盘走势 + 概念异动 */}
|
|
||||||
{/* 只在 selectedDate 确定后渲染,避免 null → 日期 的双重请求 */}
|
|
||||||
<Box mb={10}>
|
|
||||||
{selectedDate ? (
|
|
||||||
<HotspotOverview selectedDate={selectedDate} />
|
|
||||||
) : (
|
) : (
|
||||||
<Card
|
<Card
|
||||||
bg={cardBg}
|
bg={cardBg}
|
||||||
|
|||||||
Reference in New Issue
Block a user