feat: 添加股票行情卡片

This commit is contained in:
zdl
2025-12-09 17:26:58 +08:00
parent 701f96855e
commit d7759b1da3
4 changed files with 301 additions and 0 deletions

View File

@@ -0,0 +1,221 @@
/**
* StockQuoteCard - 股票行情卡片组件
*
* 展示股票的实时行情、关键指标和主力动态
*/
import React from 'react';
import {
Box,
Card,
CardBody,
Flex,
HStack,
VStack,
Text,
Badge,
Progress,
Skeleton,
useColorModeValue,
} from '@chakra-ui/react';
import type { StockQuoteCardProps } from './types';
import { mockStockQuoteData } from './mockData';
/**
* 格式化价格显示
*/
const formatPrice = (price: number): string => {
return price.toLocaleString('zh-CN', {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
});
};
/**
* 格式化涨跌幅显示
*/
const formatChangePercent = (percent: number): string => {
const sign = percent >= 0 ? '+' : '';
return `${sign}${percent.toFixed(2)}%`;
};
/**
* 格式化主力净流入显示
*/
const formatNetInflow = (value: number): string => {
const sign = value >= 0 ? '+' : '';
return `${sign}${value.toFixed(2)}亿`;
};
const StockQuoteCard: React.FC<StockQuoteCardProps> = ({
data = mockStockQuoteData,
isLoading = false,
}) => {
// 颜色配置
const cardBg = useColorModeValue('white', 'gray.800');
const borderColor = useColorModeValue('gray.200', 'gray.700');
const labelColor = useColorModeValue('gray.500', 'gray.400');
const valueColor = useColorModeValue('gray.800', 'gray.100');
const sectionTitleColor = useColorModeValue('gray.600', 'gray.300');
// 涨跌颜色
const priceColor = data.changePercent >= 0 ? 'green.500' : 'red.500';
const inflowColor = data.mainNetInflow >= 0 ? 'green.500' : 'red.500';
if (isLoading) {
return (
<Card bg={cardBg} shadow="sm" borderWidth="1px" borderColor={borderColor}>
<CardBody>
<Skeleton height="120px" />
</CardBody>
</Card>
);
}
return (
<Card bg={cardBg} shadow="sm" borderWidth="1px" borderColor={borderColor}>
<CardBody py={4} px={6}>
{/* 顶部:股票名称 + 更新时间 */}
<Flex justify="space-between" align="center" mb={4}>
<HStack spacing={3}>
<Text fontSize="xl" fontWeight="bold" color={valueColor}>
{data.name}
</Text>
<Text fontSize="md" color={labelColor}>
({data.code})
</Text>
{data.indexTags.map((tag) => (
<Badge
key={tag}
variant="outline"
colorScheme="gray"
fontSize="xs"
px={2}
>
{tag}
</Badge>
))}
</HStack>
<Text fontSize="sm" color={labelColor}>
{data.updateTime}
</Text>
</Flex>
{/* 三栏布局 */}
<Flex gap={8}>
{/* 左栏:价格信息 */}
<Box flex="1">
<HStack align="baseline" spacing={3} mb={3}>
<Text fontSize="3xl" fontWeight="bold" color={priceColor}>
{formatPrice(data.currentPrice)}
</Text>
<Badge
colorScheme={data.changePercent >= 0 ? 'green' : 'red'}
fontSize="md"
px={2}
py={0.5}
>
{formatChangePercent(data.changePercent)}
</Badge>
</HStack>
<HStack spacing={6} color={labelColor} fontSize="sm">
<Text>
<Text as="span" color={valueColor} fontWeight="medium">
{formatPrice(data.todayOpen)}
</Text>
</Text>
<Text>
<Text as="span" color={valueColor} fontWeight="medium">
{formatPrice(data.yesterdayClose)}
</Text>
</Text>
</HStack>
</Box>
{/* 中栏:关键指标 */}
<Box flex="1" borderLeftWidth="1px" borderColor={borderColor} pl={8}>
<Text
fontSize="sm"
fontWeight="medium"
color={sectionTitleColor}
mb={3}
>
</Text>
<VStack align="stretch" spacing={2} fontSize="sm">
<HStack justify="space-between">
<Text color={labelColor}>(PE)</Text>
<Text color={valueColor} fontWeight="medium">
{data.pe.toFixed(2)}
</Text>
</HStack>
<HStack justify="space-between">
<Text color={labelColor}>(PB)</Text>
<Text color={valueColor} fontWeight="medium">
{data.pb.toFixed(2)}
</Text>
</HStack>
<HStack justify="space-between">
<Text color={labelColor}></Text>
<Text color={valueColor} fontWeight="medium">
{data.marketCap}
</Text>
</HStack>
<HStack justify="space-between">
<Text color={labelColor}>52</Text>
<Text color={valueColor} fontWeight="medium">
{formatPrice(data.week52Low)}-{formatPrice(data.week52High)}
</Text>
</HStack>
</VStack>
</Box>
{/* 右栏:主力动态 */}
<Box flex="1" borderLeftWidth="1px" borderColor={borderColor} pl={8}>
<Text
fontSize="sm"
fontWeight="medium"
color={sectionTitleColor}
mb={3}
>
</Text>
<VStack align="stretch" spacing={2} fontSize="sm">
<HStack justify="space-between">
<Text color={labelColor}></Text>
<Text color={inflowColor} fontWeight="medium">
{formatNetInflow(data.mainNetInflow)}
</Text>
</HStack>
<HStack justify="space-between">
<Text color={labelColor}></Text>
<Text color={valueColor} fontWeight="medium">
{data.institutionHolding.toFixed(2)}%
</Text>
</HStack>
{/* 买卖比例条 */}
<Box mt={1}>
<Progress
value={data.buyRatio}
size="sm"
colorScheme="green"
bg="red.400"
borderRadius="full"
/>
<HStack justify="space-between" mt={1} fontSize="xs">
<Text color="green.500">{data.buyRatio}%</Text>
<Text color="red.500">{data.sellRatio}%</Text>
</HStack>
</Box>
</VStack>
</Box>
</Flex>
</CardBody>
</Card>
);
};
export default StockQuoteCard;

View File

@@ -0,0 +1,33 @@
import type { StockQuoteCardData } from './types';
/**
* 贵州茅台 Mock 数据
*/
export const mockStockQuoteData: StockQuoteCardData = {
// 基础信息
name: '贵州茅台',
code: '600519.SH',
indexTags: ['沪深300'],
// 价格信息
currentPrice: 2178.5,
changePercent: 3.65,
todayOpen: 2156.0,
yesterdayClose: 2101.0,
// 关键指标
pe: 38.62,
pb: 14.82,
marketCap: '2.73万亿',
week52Low: 1980,
week52High: 2350,
// 主力动态
mainNetInflow: 1.28,
institutionHolding: 72.35,
buyRatio: 85,
sellRatio: 15,
// 更新时间
updateTime: '2025-12-03 14:30:25',
};

View File

@@ -0,0 +1,43 @@
/**
* StockQuoteCard 组件类型定义
*/
/**
* 股票行情卡片数据
*/
export interface StockQuoteCardData {
// 基础信息
name: string; // 股票名称
code: string; // 股票代码
indexTags: string[]; // 指数标签(如 沪深300、上证50
// 价格信息
currentPrice: number; // 当前价格
changePercent: number; // 涨跌幅(百分比,如 3.65 表示 +3.65%
todayOpen: number; // 今开
yesterdayClose: number; // 昨收
// 关键指标
pe: number; // 市盈率
pb: number; // 市净率
marketCap: string; // 流通市值(已格式化,如 "2.73万亿"
week52Low: number; // 52周最低
week52High: number; // 52周最高
// 主力动态
mainNetInflow: number; // 主力净流入(亿)
institutionHolding: number; // 机构持仓比例(百分比)
buyRatio: number; // 买入比例(百分比)
sellRatio: number; // 卖出比例(百分比)
// 更新时间
updateTime: string; // 格式YYYY-MM-DD HH:mm:ss
}
/**
* StockQuoteCard 组件 Props
*/
export interface StockQuoteCardProps {
data?: StockQuoteCardData;
isLoading?: boolean;
}

View File

@@ -11,6 +11,7 @@ import { useCompanyEvents } from './hooks/useCompanyEvents';
// 页面组件 // 页面组件
import CompanyHeader from './components/CompanyHeader'; import CompanyHeader from './components/CompanyHeader';
import StockQuoteCard from './components/StockQuoteCard';
import CompanyTabs from './components/CompanyTabs'; import CompanyTabs from './components/CompanyTabs';
/** /**
@@ -80,6 +81,9 @@ const CompanyIndex = () => {
bgColor={bgColor} bgColor={bgColor}
/> />
{/* 股票行情卡片:价格、关键指标、主力动态 */}
<StockQuoteCard />
{/* Tab 切换区域:概览、行情、财务、预测 */} {/* Tab 切换区域:概览、行情、财务、预测 */}
<CompanyTabs <CompanyTabs
stockCode={stockCode} stockCode={stockCode}