From 09ca7265d79093d436d75676b571e513a37cfefc Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Tue, 16 Dec 2025 14:01:42 +0800 Subject: [PATCH] =?UTF-8?q?refactor(StockSummaryCard):=20=E9=BB=91?= =?UTF-8?q?=E9=87=91=E4=B8=BB=E9=A2=98=204=20=E5=88=97=E5=B8=83=E5=B1=80?= =?UTF-8?q?=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 布局从 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 --- .../components/StockSummaryCard.tsx | 133 ------------------ .../StockSummaryCard/MetricCard.tsx | 56 ++++++++ .../StockSummaryCard/StockHeaderCard.tsx | 90 ++++++++++++ .../StockSummaryCard/atoms/CardTitle.tsx | 36 +++++ .../StockSummaryCard/atoms/DarkGoldCard.tsx | 42 ++++++ .../StockSummaryCard/atoms/MetricValue.tsx | 54 +++++++ .../StockSummaryCard/atoms/PriceDisplay.tsx | 56 ++++++++ .../StockSummaryCard/atoms/StatusTag.tsx | 24 ++++ .../StockSummaryCard/atoms/index.ts | 6 + .../components/StockSummaryCard/index.tsx | 114 +++++++++++++++ .../components/StockSummaryCard/utils.ts | 57 ++++++++ .../components/MarketDataView/constants.ts | 29 ++++ .../components/MarketDataView/types.ts | 2 +- 13 files changed, 565 insertions(+), 134 deletions(-) delete mode 100644 src/views/Company/components/MarketDataView/components/StockSummaryCard.tsx create mode 100644 src/views/Company/components/MarketDataView/components/StockSummaryCard/MetricCard.tsx create mode 100644 src/views/Company/components/MarketDataView/components/StockSummaryCard/StockHeaderCard.tsx create mode 100644 src/views/Company/components/MarketDataView/components/StockSummaryCard/atoms/CardTitle.tsx create mode 100644 src/views/Company/components/MarketDataView/components/StockSummaryCard/atoms/DarkGoldCard.tsx create mode 100644 src/views/Company/components/MarketDataView/components/StockSummaryCard/atoms/MetricValue.tsx create mode 100644 src/views/Company/components/MarketDataView/components/StockSummaryCard/atoms/PriceDisplay.tsx create mode 100644 src/views/Company/components/MarketDataView/components/StockSummaryCard/atoms/StatusTag.tsx create mode 100644 src/views/Company/components/MarketDataView/components/StockSummaryCard/atoms/index.ts create mode 100644 src/views/Company/components/MarketDataView/components/StockSummaryCard/index.tsx create mode 100644 src/views/Company/components/MarketDataView/components/StockSummaryCard/utils.ts diff --git a/src/views/Company/components/MarketDataView/components/StockSummaryCard.tsx b/src/views/Company/components/MarketDataView/components/StockSummaryCard.tsx deleted file mode 100644 index 4c8a2d3f..00000000 --- a/src/views/Company/components/MarketDataView/components/StockSummaryCard.tsx +++ /dev/null @@ -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 = ({ summary, theme }) => { - if (!summary) return null; - - const { latest_trade, latest_funding, latest_pledge } = summary; - - return ( - - - - {/* 左侧:股票名称和涨跌 */} - - - - - {summary.stock_name} - - - {summary.stock_code} - - - {latest_trade && ( - - - - {latest_trade.close} - - - = 0 ? 'increase' : 'decrease'} - color={latest_trade.change_percent >= 0 ? theme.success : theme.danger} - /> - {Math.abs(latest_trade.change_percent).toFixed(2)}% - - - - )} - - - - {/* 右侧:详细指标 */} - - {/* 交易指标 */} - - {latest_trade && ( - <> - - 成交量 - - {formatNumber(latest_trade.volume, 0)} - - - - 成交额 - - {formatNumber(latest_trade.amount)} - - - - 换手率 - - {formatPercent(latest_trade.turnover_rate)} - - - - 市盈率 - - {latest_trade.pe_ratio || '-'} - - - - )} - - - {/* 融资融券和质押指标 */} - {latest_funding && ( - - - 融资余额 - - {formatNumber(latest_funding.financing_balance)} - - - - 融券余额 - - {formatNumber(latest_funding.securities_balance)} - - - {latest_pledge && ( - - 质押比例 - - {formatPercent(latest_pledge.pledge_ratio)} - - - )} - - )} - - - - - ); -}; - -export default StockSummaryCard; diff --git a/src/views/Company/components/MarketDataView/components/StockSummaryCard/MetricCard.tsx b/src/views/Company/components/MarketDataView/components/StockSummaryCard/MetricCard.tsx new file mode 100644 index 00000000..3ffc1e88 --- /dev/null +++ b/src/views/Company/components/MarketDataView/components/StockSummaryCard/MetricCard.tsx @@ -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 = ({ + title, + subtitle, + leftIcon, + rightIcon, + mainLabel, + mainValue, + mainColor, + mainSuffix, + subText, +}) => ( + + + + + + + + + {subText} + + +); + +export default MetricCard; diff --git a/src/views/Company/components/MarketDataView/components/StockSummaryCard/StockHeaderCard.tsx b/src/views/Company/components/MarketDataView/components/StockSummaryCard/StockHeaderCard.tsx new file mode 100644 index 00000000..35e2f2e2 --- /dev/null +++ b/src/views/Company/components/MarketDataView/components/StockSummaryCard/StockHeaderCard.tsx @@ -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 = ({ + stockName, + stockCode, + price, + changePercent, +}) => { + const isUp = changePercent >= 0; + const priceColor = getPriceColor(changePercent); + const trendDesc = getTrendDescription(changePercent); + + return ( + + {/* 背景装饰线 */} + + + {/* 股票名称和代码 */} + + + {stockName} + + + ({stockCode}) + + + + {/* 价格和涨跌幅 */} + + + {price.toFixed(2)} + + + + + {isUp ? '+' : ''}{changePercent.toFixed(2)}% + + + + + {/* 走势简述 */} + + 走势简述: + + {trendDesc} + + + + ); +}; + +export default StockHeaderCard; diff --git a/src/views/Company/components/MarketDataView/components/StockSummaryCard/atoms/CardTitle.tsx b/src/views/Company/components/MarketDataView/components/StockSummaryCard/atoms/CardTitle.tsx new file mode 100644 index 00000000..f99da16c --- /dev/null +++ b/src/views/Company/components/MarketDataView/components/StockSummaryCard/atoms/CardTitle.tsx @@ -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 = ({ + title, + subtitle, + leftIcon, + rightIcon, +}) => ( + + + {leftIcon} + + {title} + + + ({subtitle}) + + + {rightIcon && {rightIcon}} + +); + +export default CardTitle; diff --git a/src/views/Company/components/MarketDataView/components/StockSummaryCard/atoms/DarkGoldCard.tsx b/src/views/Company/components/MarketDataView/components/StockSummaryCard/atoms/DarkGoldCard.tsx new file mode 100644 index 00000000..6d4493e9 --- /dev/null +++ b/src/views/Company/components/MarketDataView/components/StockSummaryCard/atoms/DarkGoldCard.tsx @@ -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 = ({ + children, + hoverable = true, + ...props +}) => ( + + {children} + +); + +export default DarkGoldCard; diff --git a/src/views/Company/components/MarketDataView/components/StockSummaryCard/atoms/MetricValue.tsx b/src/views/Company/components/MarketDataView/components/StockSummaryCard/atoms/MetricValue.tsx new file mode 100644 index 00000000..4a7a9855 --- /dev/null +++ b/src/views/Company/components/MarketDataView/components/StockSummaryCard/atoms/MetricValue.tsx @@ -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 = ({ + label, + value, + color, + suffix, + size = 'lg', +}) => { + const sizes = sizeMap[size]; + + return ( + + + {label} + + + {value} + + {suffix && ( + + {suffix} + + )} + + ); +}; + +export default MetricValue; diff --git a/src/views/Company/components/MarketDataView/components/StockSummaryCard/atoms/PriceDisplay.tsx b/src/views/Company/components/MarketDataView/components/StockSummaryCard/atoms/PriceDisplay.tsx new file mode 100644 index 00000000..d1b45399 --- /dev/null +++ b/src/views/Company/components/MarketDataView/components/StockSummaryCard/atoms/PriceDisplay.tsx @@ -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 = ({ + price, + changePercent, + priceColor, + size = 'xl', +}) => { + const isUp = changePercent >= 0; + const sizes = sizeMap[size]; + + return ( + + + {price.toFixed(2)} + + + + + {isUp ? '+' : ''}{changePercent.toFixed(2)}% + + + + ); +}; + +export default PriceDisplay; diff --git a/src/views/Company/components/MarketDataView/components/StockSummaryCard/atoms/StatusTag.tsx b/src/views/Company/components/MarketDataView/components/StockSummaryCard/atoms/StatusTag.tsx new file mode 100644 index 00000000..c9b8eec4 --- /dev/null +++ b/src/views/Company/components/MarketDataView/components/StockSummaryCard/atoms/StatusTag.tsx @@ -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 = ({ + text, + color, + showParentheses = true, +}) => ( + + {showParentheses ? `(${text})` : text} + +); + +export default StatusTag; diff --git a/src/views/Company/components/MarketDataView/components/StockSummaryCard/atoms/index.ts b/src/views/Company/components/MarketDataView/components/StockSummaryCard/atoms/index.ts new file mode 100644 index 00000000..afa16db9 --- /dev/null +++ b/src/views/Company/components/MarketDataView/components/StockSummaryCard/atoms/index.ts @@ -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'; diff --git a/src/views/Company/components/MarketDataView/components/StockSummaryCard/index.tsx b/src/views/Company/components/MarketDataView/components/StockSummaryCard/index.tsx new file mode 100644 index 00000000..4a5b7164 --- /dev/null +++ b/src/views/Company/components/MarketDataView/components/StockSummaryCard/index.tsx @@ -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 = ({ 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 ( + + {/* 卡片1: 股票信息 */} + {latest_trade && ( + + )} + {/* 卡片1: 交易热度 */} + } + rightIcon={} + mainLabel="成交额" + mainValue={latest_trade ? formatNumber(latest_trade.amount) : '-'} + mainColor={darkGoldTheme.orange} + subText={ + + + 成交量 {latest_trade ? formatNumber(latest_trade.volume, 0) : '-'} + + | + + 换手率 {latest_trade ? formatPercent(latest_trade.turnover_rate) : '-'} + + + + } + /> + + {/* 卡片2: 估值 VS 安全 */} + } + rightIcon={} + mainLabel="市盈率(PE)" + mainValue={latest_trade?.pe_ratio?.toFixed(2) || '-'} + mainColor={darkGoldTheme.orange} + subText={ + + + {peStatus.text} + + + + 质押率 {latest_pledge ? formatPercent(latest_pledge.pledge_ratio) : '-'} + + + + + } + /> + + {/* 卡片3: 情绪与风险 */} + } + mainLabel="融资余额" + mainValue={latest_funding ? formatNumber(latest_funding.financing_balance) : '-'} + mainColor={darkGoldTheme.green} + subText={ + + (强调做多力量) + + + 融券 {latest_funding ? formatNumber(latest_funding.securities_balance) : '-'} + + + + } + /> + + ); +}; + +export default StockSummaryCard; diff --git a/src/views/Company/components/MarketDataView/components/StockSummaryCard/utils.ts b/src/views/Company/components/MarketDataView/components/StockSummaryCard/utils.ts new file mode 100644 index 00000000..e599f657 --- /dev/null +++ b/src/views/Company/components/MarketDataView/components/StockSummaryCard/utils.ts @@ -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; +}; diff --git a/src/views/Company/components/MarketDataView/constants.ts b/src/views/Company/components/MarketDataView/constants.ts index beaf149f..7e333490 100644 --- a/src/views/Company/components/MarketDataView/constants.ts +++ b/src/views/Company/components/MarketDataView/constants.ts @@ -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', +}; + /** * 默认股票代码 */ diff --git a/src/views/Company/components/MarketDataView/types.ts b/src/views/Company/components/MarketDataView/types.ts index a6604cf4..bdaa6fdf 100644 --- a/src/views/Company/components/MarketDataView/types.ts +++ b/src/views/Company/components/MarketDataView/types.ts @@ -270,7 +270,7 @@ export interface MarkdownRendererProps { */ export interface StockSummaryCardProps { summary: MarketSummary; - theme: Theme; + theme?: Theme; // 可选,StockSummaryCard 使用内置黑金主题 } /**