refactor(StockSummaryCard): 黑金主题 4 列布局重构
- 布局从 1+3 改为 4 列横向排列(股票信息/交易热度/估值安全/情绪风险) - 新增 darkGoldTheme 黑金主题配置 - 采用原子设计模式拆分:5 个原子组件 + 2 个业务组件 - 原子组件:DarkGoldCard、CardTitle、MetricValue、PriceDisplay、StatusTag - 业务组件:StockHeaderCard、MetricCard - 提取状态计算工具到 utils.ts - types.ts: theme 参数改为可选 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,133 +0,0 @@
|
|||||||
// src/views/Company/components/MarketDataView/components/StockSummaryCard.tsx
|
|
||||||
// 股票概览卡片组件
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import {
|
|
||||||
CardBody,
|
|
||||||
Grid,
|
|
||||||
GridItem,
|
|
||||||
VStack,
|
|
||||||
HStack,
|
|
||||||
Heading,
|
|
||||||
Badge,
|
|
||||||
Stat,
|
|
||||||
StatLabel,
|
|
||||||
StatNumber,
|
|
||||||
StatHelpText,
|
|
||||||
StatArrow,
|
|
||||||
SimpleGrid,
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
import ThemedCard from './ThemedCard';
|
|
||||||
import { formatNumber, formatPercent } from '../utils/formatUtils';
|
|
||||||
import type { StockSummaryCardProps } from '../types';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 股票概览卡片组件
|
|
||||||
* 显示股票基本信息、最新交易数据和融资融券数据
|
|
||||||
*/
|
|
||||||
const StockSummaryCard: React.FC<StockSummaryCardProps> = ({ summary, theme }) => {
|
|
||||||
if (!summary) return null;
|
|
||||||
|
|
||||||
const { latest_trade, latest_funding, latest_pledge } = summary;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ThemedCard theme={theme}>
|
|
||||||
<CardBody>
|
|
||||||
<Grid templateColumns="repeat(12, 1fr)" gap={6}>
|
|
||||||
{/* 左侧:股票名称和涨跌 */}
|
|
||||||
<GridItem colSpan={{ base: 12, md: 4 }}>
|
|
||||||
<VStack align="start" spacing={2}>
|
|
||||||
<HStack>
|
|
||||||
<Heading size="xl" color={theme.textSecondary}>
|
|
||||||
{summary.stock_name}
|
|
||||||
</Heading>
|
|
||||||
<Badge colorScheme="blue" fontSize="lg">
|
|
||||||
{summary.stock_code}
|
|
||||||
</Badge>
|
|
||||||
</HStack>
|
|
||||||
{latest_trade && (
|
|
||||||
<HStack spacing={4}>
|
|
||||||
<Stat>
|
|
||||||
<StatNumber fontSize="4xl" color={theme.textPrimary}>
|
|
||||||
{latest_trade.close}
|
|
||||||
</StatNumber>
|
|
||||||
<StatHelpText fontSize="lg">
|
|
||||||
<StatArrow
|
|
||||||
type={latest_trade.change_percent >= 0 ? 'increase' : 'decrease'}
|
|
||||||
color={latest_trade.change_percent >= 0 ? theme.success : theme.danger}
|
|
||||||
/>
|
|
||||||
{Math.abs(latest_trade.change_percent).toFixed(2)}%
|
|
||||||
</StatHelpText>
|
|
||||||
</Stat>
|
|
||||||
</HStack>
|
|
||||||
)}
|
|
||||||
</VStack>
|
|
||||||
</GridItem>
|
|
||||||
|
|
||||||
{/* 右侧:详细指标 */}
|
|
||||||
<GridItem colSpan={{ base: 12, md: 8 }}>
|
|
||||||
{/* 交易指标 */}
|
|
||||||
<SimpleGrid columns={{ base: 2, md: 4 }} spacing={4}>
|
|
||||||
{latest_trade && (
|
|
||||||
<>
|
|
||||||
<Stat>
|
|
||||||
<StatLabel color={theme.textMuted}>成交量</StatLabel>
|
|
||||||
<StatNumber color={theme.textSecondary}>
|
|
||||||
{formatNumber(latest_trade.volume, 0)}
|
|
||||||
</StatNumber>
|
|
||||||
</Stat>
|
|
||||||
<Stat>
|
|
||||||
<StatLabel color={theme.textMuted}>成交额</StatLabel>
|
|
||||||
<StatNumber color={theme.textSecondary}>
|
|
||||||
{formatNumber(latest_trade.amount)}
|
|
||||||
</StatNumber>
|
|
||||||
</Stat>
|
|
||||||
<Stat>
|
|
||||||
<StatLabel color={theme.textMuted}>换手率</StatLabel>
|
|
||||||
<StatNumber color={theme.textSecondary}>
|
|
||||||
{formatPercent(latest_trade.turnover_rate)}
|
|
||||||
</StatNumber>
|
|
||||||
</Stat>
|
|
||||||
<Stat>
|
|
||||||
<StatLabel color={theme.textMuted}>市盈率</StatLabel>
|
|
||||||
<StatNumber color={theme.textSecondary}>
|
|
||||||
{latest_trade.pe_ratio || '-'}
|
|
||||||
</StatNumber>
|
|
||||||
</Stat>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</SimpleGrid>
|
|
||||||
|
|
||||||
{/* 融资融券和质押指标 */}
|
|
||||||
{latest_funding && (
|
|
||||||
<SimpleGrid columns={{ base: 2, md: 3 }} spacing={4} mt={4}>
|
|
||||||
<Stat>
|
|
||||||
<StatLabel color={theme.textMuted}>融资余额</StatLabel>
|
|
||||||
<StatNumber color={theme.success} fontSize="lg">
|
|
||||||
{formatNumber(latest_funding.financing_balance)}
|
|
||||||
</StatNumber>
|
|
||||||
</Stat>
|
|
||||||
<Stat>
|
|
||||||
<StatLabel color={theme.textMuted}>融券余额</StatLabel>
|
|
||||||
<StatNumber color={theme.danger} fontSize="lg">
|
|
||||||
{formatNumber(latest_funding.securities_balance)}
|
|
||||||
</StatNumber>
|
|
||||||
</Stat>
|
|
||||||
{latest_pledge && (
|
|
||||||
<Stat>
|
|
||||||
<StatLabel color={theme.textMuted}>质押比例</StatLabel>
|
|
||||||
<StatNumber color={theme.warning} fontSize="lg">
|
|
||||||
{formatPercent(latest_pledge.pledge_ratio)}
|
|
||||||
</StatNumber>
|
|
||||||
</Stat>
|
|
||||||
)}
|
|
||||||
</SimpleGrid>
|
|
||||||
)}
|
|
||||||
</GridItem>
|
|
||||||
</Grid>
|
|
||||||
</CardBody>
|
|
||||||
</ThemedCard>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default StockSummaryCard;
|
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
// 指标卡片组件
|
||||||
|
import React from 'react';
|
||||||
|
import { Box, VStack } from '@chakra-ui/react';
|
||||||
|
import { DarkGoldCard, CardTitle, MetricValue } from './atoms';
|
||||||
|
import { darkGoldTheme } from '../../constants';
|
||||||
|
|
||||||
|
export interface MetricCardProps {
|
||||||
|
title: string;
|
||||||
|
subtitle: string;
|
||||||
|
leftIcon: React.ReactNode;
|
||||||
|
rightIcon?: React.ReactNode;
|
||||||
|
mainLabel: string;
|
||||||
|
mainValue: string;
|
||||||
|
mainColor: string;
|
||||||
|
mainSuffix?: string;
|
||||||
|
subText: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指标卡片组件 - 用于展示单个指标数据
|
||||||
|
*/
|
||||||
|
const MetricCard: React.FC<MetricCardProps> = ({
|
||||||
|
title,
|
||||||
|
subtitle,
|
||||||
|
leftIcon,
|
||||||
|
rightIcon,
|
||||||
|
mainLabel,
|
||||||
|
mainValue,
|
||||||
|
mainColor,
|
||||||
|
mainSuffix,
|
||||||
|
subText,
|
||||||
|
}) => (
|
||||||
|
<DarkGoldCard>
|
||||||
|
<CardTitle
|
||||||
|
title={title}
|
||||||
|
subtitle={subtitle}
|
||||||
|
leftIcon={leftIcon}
|
||||||
|
rightIcon={rightIcon}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<VStack align="start" spacing={1} mb={3}>
|
||||||
|
<MetricValue
|
||||||
|
label={mainLabel}
|
||||||
|
value={mainValue}
|
||||||
|
color={mainColor}
|
||||||
|
suffix={mainSuffix}
|
||||||
|
/>
|
||||||
|
</VStack>
|
||||||
|
|
||||||
|
<Box color={darkGoldTheme.textMuted} fontSize="sm">
|
||||||
|
{subText}
|
||||||
|
</Box>
|
||||||
|
</DarkGoldCard>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default MetricCard;
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
// 股票信息卡片组件(4列布局版本)
|
||||||
|
import React from 'react';
|
||||||
|
import { Box, HStack, Text, Icon } from '@chakra-ui/react';
|
||||||
|
import { TrendingUp, TrendingDown } from 'lucide-react';
|
||||||
|
import { DarkGoldCard } from './atoms';
|
||||||
|
import { getTrendDescription, getPriceColor } from './utils';
|
||||||
|
import { darkGoldTheme } from '../../constants';
|
||||||
|
|
||||||
|
export interface StockHeaderCardProps {
|
||||||
|
stockName: string;
|
||||||
|
stockCode: string;
|
||||||
|
price: number;
|
||||||
|
changePercent: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 股票信息卡片 - 4 列布局中的第一个卡片
|
||||||
|
*/
|
||||||
|
const StockHeaderCard: React.FC<StockHeaderCardProps> = ({
|
||||||
|
stockName,
|
||||||
|
stockCode,
|
||||||
|
price,
|
||||||
|
changePercent,
|
||||||
|
}) => {
|
||||||
|
const isUp = changePercent >= 0;
|
||||||
|
const priceColor = getPriceColor(changePercent);
|
||||||
|
const trendDesc = getTrendDescription(changePercent);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DarkGoldCard position="relative" overflow="hidden">
|
||||||
|
{/* 背景装饰线 */}
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
right={0}
|
||||||
|
top={0}
|
||||||
|
width="60%"
|
||||||
|
height="100%"
|
||||||
|
opacity={0.12}
|
||||||
|
background={`linear-gradient(135deg, transparent 30%, ${priceColor})`}
|
||||||
|
clipPath="polygon(40% 0, 100% 0, 100% 100%, 20% 100%)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 股票名称和代码 */}
|
||||||
|
<HStack spacing={2} mb={3}>
|
||||||
|
<Text
|
||||||
|
color={darkGoldTheme.textPrimary}
|
||||||
|
fontSize="xl"
|
||||||
|
fontWeight="bold"
|
||||||
|
>
|
||||||
|
{stockName}
|
||||||
|
</Text>
|
||||||
|
<Text color={darkGoldTheme.textMuted} fontSize="md">
|
||||||
|
({stockCode})
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
|
||||||
|
{/* 价格和涨跌幅 */}
|
||||||
|
<HStack spacing={3} align="baseline" mb={2}>
|
||||||
|
<Text
|
||||||
|
color={priceColor}
|
||||||
|
fontSize="4xl"
|
||||||
|
fontWeight="bold"
|
||||||
|
lineHeight="1"
|
||||||
|
>
|
||||||
|
{price.toFixed(2)}
|
||||||
|
</Text>
|
||||||
|
<HStack spacing={1} align="center">
|
||||||
|
<Icon
|
||||||
|
as={isUp ? TrendingUp : TrendingDown}
|
||||||
|
color={priceColor}
|
||||||
|
boxSize={5}
|
||||||
|
/>
|
||||||
|
<Text color={priceColor} fontSize="lg" fontWeight="bold">
|
||||||
|
{isUp ? '+' : ''}{changePercent.toFixed(2)}%
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
</HStack>
|
||||||
|
|
||||||
|
{/* 走势简述 */}
|
||||||
|
<Text color={darkGoldTheme.textMuted} fontSize="sm">
|
||||||
|
走势简述:
|
||||||
|
<Text as="span" color={priceColor} fontWeight="medium">
|
||||||
|
{trendDesc}
|
||||||
|
</Text>
|
||||||
|
</Text>
|
||||||
|
</DarkGoldCard>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StockHeaderCard;
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
// 卡片标题原子组件
|
||||||
|
import React from 'react';
|
||||||
|
import { Flex, HStack, Box, Text } from '@chakra-ui/react';
|
||||||
|
import { darkGoldTheme } from '../../../constants';
|
||||||
|
|
||||||
|
interface CardTitleProps {
|
||||||
|
title: string;
|
||||||
|
subtitle: string;
|
||||||
|
leftIcon: React.ReactNode;
|
||||||
|
rightIcon?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 卡片标题组件 - 显示图标+标题+副标题
|
||||||
|
*/
|
||||||
|
const CardTitle: React.FC<CardTitleProps> = ({
|
||||||
|
title,
|
||||||
|
subtitle,
|
||||||
|
leftIcon,
|
||||||
|
rightIcon,
|
||||||
|
}) => (
|
||||||
|
<Flex justify="space-between" align="center" mb={4}>
|
||||||
|
<HStack spacing={2}>
|
||||||
|
<Box color={darkGoldTheme.gold}>{leftIcon}</Box>
|
||||||
|
<Text color={darkGoldTheme.gold} fontSize="lg" fontWeight="bold">
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
<Text color={darkGoldTheme.textMuted} fontSize="sm">
|
||||||
|
({subtitle})
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
{rightIcon && <Box color={darkGoldTheme.gold}>{rightIcon}</Box>}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default CardTitle;
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
// 黑金主题卡片容器原子组件
|
||||||
|
import React from 'react';
|
||||||
|
import { Box, BoxProps } from '@chakra-ui/react';
|
||||||
|
import { darkGoldTheme } from '../../../constants';
|
||||||
|
|
||||||
|
interface DarkGoldCardProps extends BoxProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
hoverable?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 黑金主题卡片容器
|
||||||
|
*/
|
||||||
|
const DarkGoldCard: React.FC<DarkGoldCardProps> = ({
|
||||||
|
children,
|
||||||
|
hoverable = true,
|
||||||
|
...props
|
||||||
|
}) => (
|
||||||
|
<Box
|
||||||
|
bg={darkGoldTheme.bgCard}
|
||||||
|
borderRadius="xl"
|
||||||
|
border="1px solid"
|
||||||
|
borderColor={darkGoldTheme.border}
|
||||||
|
p={5}
|
||||||
|
transition="all 0.3s ease"
|
||||||
|
_hover={
|
||||||
|
hoverable
|
||||||
|
? {
|
||||||
|
bg: darkGoldTheme.bgCardHover,
|
||||||
|
borderColor: darkGoldTheme.borderHover,
|
||||||
|
transform: 'translateY(-2px)',
|
||||||
|
boxShadow: '0 8px 24px rgba(0, 0, 0, 0.4)',
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default DarkGoldCard;
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
// 核心数值展示原子组件
|
||||||
|
import React from 'react';
|
||||||
|
import { HStack, Text } from '@chakra-ui/react';
|
||||||
|
import { darkGoldTheme } from '../../../constants';
|
||||||
|
|
||||||
|
interface MetricValueProps {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
color: string;
|
||||||
|
suffix?: string;
|
||||||
|
size?: 'sm' | 'md' | 'lg';
|
||||||
|
}
|
||||||
|
|
||||||
|
const sizeMap = {
|
||||||
|
sm: { label: 'sm', value: '2xl', suffix: 'md' },
|
||||||
|
md: { label: 'md', value: '3xl', suffix: 'lg' },
|
||||||
|
lg: { label: 'md', value: '4xl', suffix: 'xl' },
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 核心数值展示组件 - 显示标签+数值
|
||||||
|
*/
|
||||||
|
const MetricValue: React.FC<MetricValueProps> = ({
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
color,
|
||||||
|
suffix,
|
||||||
|
size = 'lg',
|
||||||
|
}) => {
|
||||||
|
const sizes = sizeMap[size];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HStack spacing={2} align="baseline">
|
||||||
|
<Text color={darkGoldTheme.textMuted} fontSize={sizes.label}>
|
||||||
|
{label}
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
color={color}
|
||||||
|
fontSize={sizes.value}
|
||||||
|
fontWeight="bold"
|
||||||
|
lineHeight="1"
|
||||||
|
>
|
||||||
|
{value}
|
||||||
|
</Text>
|
||||||
|
{suffix && (
|
||||||
|
<Text color={color} fontSize={sizes.suffix} fontWeight="bold">
|
||||||
|
{suffix}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</HStack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MetricValue;
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
// 价格显示原子组件
|
||||||
|
import React from 'react';
|
||||||
|
import { HStack, Text, Icon } from '@chakra-ui/react';
|
||||||
|
import { TrendingUp, TrendingDown } from 'lucide-react';
|
||||||
|
|
||||||
|
interface PriceDisplayProps {
|
||||||
|
price: number;
|
||||||
|
changePercent: number;
|
||||||
|
priceColor: string;
|
||||||
|
size?: 'sm' | 'md' | 'lg' | 'xl';
|
||||||
|
}
|
||||||
|
|
||||||
|
const sizeMap = {
|
||||||
|
sm: { price: '2xl', percent: 'md', icon: 4 },
|
||||||
|
md: { price: '3xl', percent: 'lg', icon: 5 },
|
||||||
|
lg: { price: '4xl', percent: 'xl', icon: 6 },
|
||||||
|
xl: { price: '5xl', percent: 'xl', icon: 6 },
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 价格显示组件 - 显示价格和涨跌幅
|
||||||
|
*/
|
||||||
|
const PriceDisplay: React.FC<PriceDisplayProps> = ({
|
||||||
|
price,
|
||||||
|
changePercent,
|
||||||
|
priceColor,
|
||||||
|
size = 'xl',
|
||||||
|
}) => {
|
||||||
|
const isUp = changePercent >= 0;
|
||||||
|
const sizes = sizeMap[size];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HStack spacing={4} align="baseline">
|
||||||
|
<Text
|
||||||
|
color={priceColor}
|
||||||
|
fontSize={sizes.price}
|
||||||
|
fontWeight="bold"
|
||||||
|
lineHeight="1"
|
||||||
|
>
|
||||||
|
{price.toFixed(2)}
|
||||||
|
</Text>
|
||||||
|
<HStack spacing={1} align="center">
|
||||||
|
<Icon
|
||||||
|
as={isUp ? TrendingUp : TrendingDown}
|
||||||
|
color={priceColor}
|
||||||
|
boxSize={sizes.icon}
|
||||||
|
/>
|
||||||
|
<Text color={priceColor} fontSize={sizes.percent} fontWeight="bold">
|
||||||
|
{isUp ? '+' : ''}{changePercent.toFixed(2)}%
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
</HStack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PriceDisplay;
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// 状态标签原子组件
|
||||||
|
import React from 'react';
|
||||||
|
import { Text } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
interface StatusTagProps {
|
||||||
|
text: string;
|
||||||
|
color: string;
|
||||||
|
showParentheses?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态标签 - 显示如"活跃"、"健康"等状态文字
|
||||||
|
*/
|
||||||
|
const StatusTag: React.FC<StatusTagProps> = ({
|
||||||
|
text,
|
||||||
|
color,
|
||||||
|
showParentheses = true,
|
||||||
|
}) => (
|
||||||
|
<Text color={color} fontWeight="medium" ml={1}>
|
||||||
|
{showParentheses ? `(${text})` : text}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default StatusTag;
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// 原子组件统一导出
|
||||||
|
export { default as StatusTag } from './StatusTag';
|
||||||
|
export { default as PriceDisplay } from './PriceDisplay';
|
||||||
|
export { default as MetricValue } from './MetricValue';
|
||||||
|
export { default as CardTitle } from './CardTitle';
|
||||||
|
export { default as DarkGoldCard } from './DarkGoldCard';
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
// StockSummaryCard 主组件
|
||||||
|
import React from 'react';
|
||||||
|
import { SimpleGrid, HStack, Text, VStack } from '@chakra-ui/react';
|
||||||
|
import { Flame, Coins, DollarSign, Shield } from 'lucide-react';
|
||||||
|
import StockHeaderCard from './StockHeaderCard';
|
||||||
|
import MetricCard from './MetricCard';
|
||||||
|
import { StatusTag } from './atoms';
|
||||||
|
import { getTurnoverStatus, getPEStatus, getPledgeStatus } from './utils';
|
||||||
|
import { formatNumber, formatPercent } from '../../utils/formatUtils';
|
||||||
|
import { darkGoldTheme } from '../../constants';
|
||||||
|
import type { StockSummaryCardProps } from '../../types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 股票概览卡片组件
|
||||||
|
* 4 列横向布局:股票信息 + 交易热度 + 估值安全 + 情绪风险
|
||||||
|
*/
|
||||||
|
const StockSummaryCard: React.FC<StockSummaryCardProps> = ({ summary }) => {
|
||||||
|
if (!summary) return null;
|
||||||
|
|
||||||
|
const { latest_trade, latest_funding, latest_pledge } = summary;
|
||||||
|
|
||||||
|
// 计算状态
|
||||||
|
const turnoverStatus = latest_trade
|
||||||
|
? getTurnoverStatus(latest_trade.turnover_rate)
|
||||||
|
: { text: '-', color: darkGoldTheme.textMuted };
|
||||||
|
|
||||||
|
const peStatus = getPEStatus(latest_trade?.pe_ratio);
|
||||||
|
|
||||||
|
const pledgeStatus = latest_pledge
|
||||||
|
? getPledgeStatus(latest_pledge.pledge_ratio)
|
||||||
|
: { text: '-', color: darkGoldTheme.textMuted };
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SimpleGrid columns={{ base: 1, md: 2, lg: 4 }} spacing={4}>
|
||||||
|
{/* 卡片1: 股票信息 */}
|
||||||
|
{latest_trade && (
|
||||||
|
<StockHeaderCard
|
||||||
|
stockName={summary.stock_name}
|
||||||
|
stockCode={summary.stock_code}
|
||||||
|
price={latest_trade.close}
|
||||||
|
changePercent={latest_trade.change_percent}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{/* 卡片1: 交易热度 */}
|
||||||
|
<MetricCard
|
||||||
|
title="交易热度"
|
||||||
|
subtitle="流动性"
|
||||||
|
leftIcon={<Flame size={22} />}
|
||||||
|
rightIcon={<Coins size={22} />}
|
||||||
|
mainLabel="成交额"
|
||||||
|
mainValue={latest_trade ? formatNumber(latest_trade.amount) : '-'}
|
||||||
|
mainColor={darkGoldTheme.orange}
|
||||||
|
subText={
|
||||||
|
<HStack spacing={1} flexWrap="wrap">
|
||||||
|
<Text>
|
||||||
|
成交量 {latest_trade ? formatNumber(latest_trade.volume, 0) : '-'}
|
||||||
|
</Text>
|
||||||
|
<Text>|</Text>
|
||||||
|
<Text>
|
||||||
|
换手率 {latest_trade ? formatPercent(latest_trade.turnover_rate) : '-'}
|
||||||
|
</Text>
|
||||||
|
<StatusTag text={turnoverStatus.text} color={turnoverStatus.color} />
|
||||||
|
</HStack>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 卡片2: 估值 VS 安全 */}
|
||||||
|
<MetricCard
|
||||||
|
title="估值 VS 安全"
|
||||||
|
subtitle="便宜否"
|
||||||
|
leftIcon={<DollarSign size={22} />}
|
||||||
|
rightIcon={<Shield size={22} />}
|
||||||
|
mainLabel="市盈率(PE)"
|
||||||
|
mainValue={latest_trade?.pe_ratio?.toFixed(2) || '-'}
|
||||||
|
mainColor={darkGoldTheme.orange}
|
||||||
|
subText={
|
||||||
|
<VStack align="start" spacing={1}>
|
||||||
|
<Text color={peStatus.color} fontWeight="medium">
|
||||||
|
{peStatus.text}
|
||||||
|
</Text>
|
||||||
|
<HStack spacing={1} flexWrap="wrap">
|
||||||
|
<Text>
|
||||||
|
质押率 {latest_pledge ? formatPercent(latest_pledge.pledge_ratio) : '-'}
|
||||||
|
</Text>
|
||||||
|
<StatusTag text={pledgeStatus.text} color={pledgeStatus.color} />
|
||||||
|
</HStack>
|
||||||
|
</VStack>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 卡片3: 情绪与风险 */}
|
||||||
|
<MetricCard
|
||||||
|
title="情绪与风险"
|
||||||
|
subtitle="资金面"
|
||||||
|
leftIcon={<Flame size={22} />}
|
||||||
|
mainLabel="融资余额"
|
||||||
|
mainValue={latest_funding ? formatNumber(latest_funding.financing_balance) : '-'}
|
||||||
|
mainColor={darkGoldTheme.green}
|
||||||
|
subText={
|
||||||
|
<VStack align="start" spacing={0}>
|
||||||
|
<Text color={darkGoldTheme.textMuted}>(强调做多力量)</Text>
|
||||||
|
<HStack spacing={1} flexWrap="wrap" mt={1}>
|
||||||
|
<Text>
|
||||||
|
融券 {latest_funding ? formatNumber(latest_funding.securities_balance) : '-'}
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
</VStack>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</SimpleGrid>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StockSummaryCard;
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
// 状态计算工具函数
|
||||||
|
import { darkGoldTheme } from '../../constants';
|
||||||
|
|
||||||
|
export interface StatusResult {
|
||||||
|
text: string;
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取走势简述
|
||||||
|
*/
|
||||||
|
export const getTrendDescription = (changePercent: number): string => {
|
||||||
|
if (changePercent >= 5) return '强势上涨';
|
||||||
|
if (changePercent >= 2) return '稳步上涨';
|
||||||
|
if (changePercent > 0) return '小幅上涨';
|
||||||
|
if (changePercent === 0) return '横盘整理';
|
||||||
|
if (changePercent > -2) return '小幅下跌';
|
||||||
|
if (changePercent > -5) return '震荡下跌';
|
||||||
|
return '大幅下跌';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取换手率状态标签
|
||||||
|
*/
|
||||||
|
export const getTurnoverStatus = (rate: number): StatusResult => {
|
||||||
|
if (rate >= 3) return { text: '活跃', color: darkGoldTheme.orange };
|
||||||
|
if (rate >= 1) return { text: '正常', color: darkGoldTheme.gold };
|
||||||
|
return { text: '冷清', color: darkGoldTheme.textMuted };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取市盈率估值标签
|
||||||
|
*/
|
||||||
|
export const getPEStatus = (pe: number | undefined): StatusResult => {
|
||||||
|
if (!pe || pe <= 0) return { text: '亏损', color: darkGoldTheme.red };
|
||||||
|
if (pe < 10) return { text: '极低估值 / 安全边际高', color: darkGoldTheme.green };
|
||||||
|
if (pe < 20) return { text: '合理估值', color: darkGoldTheme.gold };
|
||||||
|
if (pe < 40) return { text: '偏高估值', color: darkGoldTheme.orange };
|
||||||
|
return { text: '高估值 / 泡沫风险', color: darkGoldTheme.red };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取质押率健康状态
|
||||||
|
*/
|
||||||
|
export const getPledgeStatus = (ratio: number): StatusResult => {
|
||||||
|
if (ratio < 10) return { text: '健康', color: darkGoldTheme.green };
|
||||||
|
if (ratio < 30) return { text: '正常', color: darkGoldTheme.gold };
|
||||||
|
if (ratio < 50) return { text: '偏高', color: darkGoldTheme.orange };
|
||||||
|
return { text: '警惕', color: darkGoldTheme.red };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取价格颜色
|
||||||
|
*/
|
||||||
|
export const getPriceColor = (changePercent: number): string => {
|
||||||
|
return changePercent >= 0 ? darkGoldTheme.red : darkGoldTheme.green;
|
||||||
|
};
|
||||||
@@ -28,6 +28,35 @@ export const themes: Record<'light', Theme> = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 黑金主题配置 - 用于 StockSummaryCard
|
||||||
|
*/
|
||||||
|
export const darkGoldTheme = {
|
||||||
|
// 背景
|
||||||
|
bgCard: 'linear-gradient(135deg, #1a1a2e 0%, #0f0f1a 100%)',
|
||||||
|
bgCardHover: 'linear-gradient(135deg, #252540 0%, #1a1a2e 100%)',
|
||||||
|
|
||||||
|
// 边框
|
||||||
|
border: 'rgba(212, 175, 55, 0.3)',
|
||||||
|
borderHover: 'rgba(212, 175, 55, 0.6)',
|
||||||
|
|
||||||
|
// 文字
|
||||||
|
textPrimary: '#FFFFFF',
|
||||||
|
textSecondary: 'rgba(255, 255, 255, 0.85)',
|
||||||
|
textMuted: 'rgba(255, 255, 255, 0.6)',
|
||||||
|
|
||||||
|
// 强调色
|
||||||
|
gold: '#D4AF37',
|
||||||
|
goldLight: '#F4D03F',
|
||||||
|
orange: '#FF9500',
|
||||||
|
green: '#00C851',
|
||||||
|
red: '#FF4444',
|
||||||
|
|
||||||
|
// 标签背景
|
||||||
|
tagBg: 'rgba(212, 175, 55, 0.15)',
|
||||||
|
tagText: '#D4AF37',
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 默认股票代码
|
* 默认股票代码
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -270,7 +270,7 @@ export interface MarkdownRendererProps {
|
|||||||
*/
|
*/
|
||||||
export interface StockSummaryCardProps {
|
export interface StockSummaryCardProps {
|
||||||
summary: MarketSummary;
|
summary: MarketSummary;
|
||||||
theme: Theme;
|
theme?: Theme; // 可选,StockSummaryCard 使用内置黑金主题
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user