feat(MarketDashboard): 添加市场概况卡片(上证/深证/总市值/成交额)

新增组件:
- MarketSummaryCard: 紧凑型 2x2 网格布局
  - 上证指数:价格、涨跌额、涨跌幅
  - 深证指数:价格、涨跌额、涨跌幅
  - 总市值:万亿级格式化显示
  - 成交额:万亿级格式化显示

布局更新:
- MarketOverview: 从 3 列扩展为 4 列
- 市场概况卡片位于最左侧

Mock API:
- /api/market/summary: 返回实时市场概况数据
- 数据基于时间产生小波动,模拟真实行情

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
zdl
2025-12-23 15:28:48 +08:00
parent 7d859e18ca
commit 145b6575d8
4 changed files with 219 additions and 4 deletions

View File

@@ -1,20 +1,24 @@
// 市场概况组件 - 顶部横条(与事件中心头部保持一致)
// 布局:上证指数 | 深证成指 | 创业板指+涨跌分布
// 布局:市场概况 | 上证指数 | 深证成指 | 创业板指+涨跌分布
import React from 'react';
import { Box, SimpleGrid } from '@chakra-ui/react';
import {
IndexKLineCard,
GemIndexCard,
MarketSummaryCard,
} from './atoms';
const MarketOverview = ({ marketStats = {} }) => {
return (
<Box borderRadius="xl">
{/* 3列网格布局:上证指数 | 深证成指 | 创业板指+涨跌 */}
{/* 4列网格布局:市场概况 | 上证指数 | 深证成指 | 创业板指+涨跌 */}
<SimpleGrid
columns={{ base: 2, md: 3 }}
columns={{ base: 2, md: 4 }}
spacing={3}
>
{/* 市场概况 - 上证/深证/总市值/成交额 */}
<MarketSummaryCard />
{/* 上证指数 - K线卡片 */}
<IndexKLineCard
indexCode="sh000001"

View File

@@ -0,0 +1,160 @@
// 市场概况卡片 - 紧凑版(上证/深证/总市值/成交额)
import React, { useEffect, useState } from 'react';
import { Box, Text, VStack, HStack, SimpleGrid } from '@chakra-ui/react';
import { THEME } from '../../constants';
import { getApiBase } from '@/utils/apiConfig';
/**
* 格式化大数字(万亿/亿)
*/
const formatLargeNumber = (num) => {
if (!num && num !== 0) return '--';
const trillion = 1000000000000; // 万亿
const billion = 100000000; // 亿
if (num >= trillion) {
return `${(num / trillion).toFixed(1)}万亿`;
} else if (num >= billion) {
return `${(num / billion).toFixed(1)}亿`;
}
return num.toLocaleString();
};
/**
* 单个指数/统计项
*/
const SummaryItem = ({ label, value, change, changeAmount, subValue, isIndex = true }) => {
const isUp = change > 0;
const isFlat = change === 0;
const changeColor = isFlat
? 'rgba(255, 255, 255, 0.6)'
: isUp ? THEME.status.up : THEME.status.down;
return (
<VStack align="flex-start" spacing={0.5} py={2}>
{/* 标签 */}
<Text fontSize="xs" color="rgba(255, 255, 255, 0.5)">
{label}
</Text>
{/* 主数值 */}
<Text
fontSize="lg"
fontWeight="bold"
color="rgba(255, 255, 255, 0.95)"
lineHeight="1.2"
>
{typeof value === 'number' ? value.toFixed(2) : value}
</Text>
{/* 涨跌信息(仅指数显示) */}
{isIndex && change !== undefined ? (
<Text fontSize="xs" color={changeColor}>
{changeAmount !== undefined && (
<Text as="span" mr={1}>
{isUp ? '+' : ''}{typeof changeAmount === 'number' ? changeAmount.toFixed(2) : changeAmount}
</Text>
)}
{isUp ? '+' : ''}{change.toFixed(2)}%
</Text>
) : (
// 非指数项显示副标签
<Text fontSize="xs" color="rgba(255, 255, 255, 0.4)">
{subValue || label}
</Text>
)}
</VStack>
);
};
/**
* 市场概况卡片
*/
const MarketSummaryCard = () => {
const [marketData, setMarketData] = useState({
shanghai: { value: 3391.88, change: 0.52, changeAmount: 17.55 },
shenzhen: { value: 10723.49, change: 0.68, changeAmount: 72.38 },
totalMarketCap: 105.6 * 1000000000000, // 105.6万亿
turnover: 1.0 * 1000000000000, // 1.0万亿
});
// 获取实时数据
useEffect(() => {
const fetchMarketData = async () => {
try {
const base = getApiBase();
const response = await fetch(`${base}/api/market/summary`, {
credentials: 'include',
});
if (response.ok) {
const data = await response.json();
if (data.success && data.data) {
setMarketData(prev => ({
...prev,
...data.data,
}));
}
}
} catch (error) {
// 使用默认数据
console.debug('Using default market data');
}
};
fetchMarketData();
// 每分钟更新一次
const interval = setInterval(fetchMarketData, 60000);
return () => clearInterval(interval);
}, []);
return (
<Box
bg="rgba(26, 26, 46, 0.6)"
borderRadius="lg"
px={3}
py={2}
border="1px solid"
borderColor="rgba(212, 175, 55, 0.15)"
backdropFilter="blur(8px)"
minW="200px"
>
<SimpleGrid columns={2} spacing={0}>
{/* 上证 */}
<SummaryItem
label="上证"
value={marketData.shanghai.value}
change={marketData.shanghai.change}
changeAmount={marketData.shanghai.changeAmount}
isIndex={true}
/>
{/* 深证 */}
<SummaryItem
label="深证"
value={marketData.shenzhen.value}
change={marketData.shenzhen.change}
changeAmount={marketData.shenzhen.changeAmount}
isIndex={true}
/>
{/* 总市值 */}
<SummaryItem
label="总市值"
value={formatLargeNumber(marketData.totalMarketCap)}
subValue={formatLargeNumber(marketData.totalMarketCap)}
isIndex={false}
/>
{/* 成交额 */}
<SummaryItem
label="成交额"
value={formatLargeNumber(marketData.turnover)}
subValue="成交万亿"
isIndex={false}
/>
</SimpleGrid>
</Box>
);
};
export default MarketSummaryCard;

View File

@@ -12,3 +12,6 @@ export { default as HotSectorsRanking } from './HotSectorsRanking';
export { default as IndexKLineCard } from './IndexKLineCard';
export { default as RiseFallProgressBar } from './RiseFallProgressBar';
export { default as GemIndexCard } from './GemIndexCard';
// 市场概况卡片(上证/深证/总市值/成交额)
export { default as MarketSummaryCard } from './MarketSummaryCard';