From 3bd48e1ddd792625974f3b0a77fce2baccbfe1ed Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Tue, 16 Dec 2025 20:24:01 +0800 Subject: [PATCH] =?UTF-8?q?refactor(StockQuoteCard):=20=E6=8B=86=E5=88=86?= =?UTF-8?q?=E4=B8=BA=E5=8E=9F=E5=AD=90=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 theme.ts 黑金主题常量 - 新增 formatters.ts 格式化工具函数 - 拆分 PriceDisplay/SecondaryQuote/KeyMetrics/MainForceInfo/CompanyInfo/StockHeader - 主组件从 414 行简化为 150 行 - 提高可维护性和复用性 --- .../StockQuoteCard/components/CompanyInfo.tsx | 87 +++++ .../StockQuoteCard/components/KeyMetrics.tsx | 76 ++++ .../components/MainForceInfo.tsx | 71 ++++ .../components/PriceDisplay.tsx | 43 ++ .../components/SecondaryQuote.tsx | 59 +++ .../StockQuoteCard/components/StockHeader.tsx | 120 ++++++ .../StockQuoteCard/components/formatters.ts | 29 ++ .../StockQuoteCard/components/index.ts | 23 +- .../StockQuoteCard/components/theme.ts | 20 + .../components/StockQuoteCard/index.tsx | 367 +++--------------- 10 files changed, 579 insertions(+), 316 deletions(-) create mode 100644 src/views/Company/components/StockQuoteCard/components/CompanyInfo.tsx create mode 100644 src/views/Company/components/StockQuoteCard/components/KeyMetrics.tsx create mode 100644 src/views/Company/components/StockQuoteCard/components/MainForceInfo.tsx create mode 100644 src/views/Company/components/StockQuoteCard/components/PriceDisplay.tsx create mode 100644 src/views/Company/components/StockQuoteCard/components/SecondaryQuote.tsx create mode 100644 src/views/Company/components/StockQuoteCard/components/StockHeader.tsx create mode 100644 src/views/Company/components/StockQuoteCard/components/formatters.ts create mode 100644 src/views/Company/components/StockQuoteCard/components/theme.ts diff --git a/src/views/Company/components/StockQuoteCard/components/CompanyInfo.tsx b/src/views/Company/components/StockQuoteCard/components/CompanyInfo.tsx new file mode 100644 index 00000000..e9f4faf3 --- /dev/null +++ b/src/views/Company/components/StockQuoteCard/components/CompanyInfo.tsx @@ -0,0 +1,87 @@ +/** + * CompanyInfo - 公司信息原子组件 + * 显示公司基本信息(成立日期、注册资本、所在地、官网、简介) + */ + +import React, { memo } from 'react'; +import { Box, Flex, HStack, Text, Link, Icon, Divider } from '@chakra-ui/react'; +import { Calendar, Coins, MapPin, Globe } from 'lucide-react'; +import { formatRegisteredCapital, formatDate } from '../../CompanyOverview/utils'; +import { STOCK_CARD_THEME } from './theme'; + +export interface CompanyBasicInfo { + establish_date?: string; + reg_capital?: number; + province?: string; + city?: string; + website?: string; + company_intro?: string; +} + +export interface CompanyInfoProps { + basicInfo: CompanyBasicInfo; +} + +export const CompanyInfo: React.FC = memo(({ basicInfo }) => { + const { labelColor, valueColor, borderColor } = STOCK_CARD_THEME; + + return ( + <> + + + {/* 左侧:公司关键属性 (flex=1) */} + + + + + 成立: + + {formatDate(basicInfo.establish_date)} + + + + + 注册资本: + + {formatRegisteredCapital(basicInfo.reg_capital)} + + + + + 所在地: + + {basicInfo.province} {basicInfo.city} + + + + + {basicInfo.website ? ( + + 访问官网 + + ) : ( + 暂无官网 + )} + + + + + {/* 右侧:公司简介 (flex=2) */} + + + 公司简介: + {basicInfo.company_intro || '暂无'} + + + + + ); +}); + +CompanyInfo.displayName = 'CompanyInfo'; diff --git a/src/views/Company/components/StockQuoteCard/components/KeyMetrics.tsx b/src/views/Company/components/StockQuoteCard/components/KeyMetrics.tsx new file mode 100644 index 00000000..1e17390a --- /dev/null +++ b/src/views/Company/components/StockQuoteCard/components/KeyMetrics.tsx @@ -0,0 +1,76 @@ +/** + * KeyMetrics - 关键指标原子组件 + * 显示 PE、EPS、PB、流通市值、52周波动 + */ + +import React, { memo } from 'react'; +import { Box, VStack, HStack, Text } from '@chakra-ui/react'; +import { formatPrice } from './formatters'; +import { STOCK_CARD_THEME } from './theme'; + +export interface KeyMetricsProps { + pe: number; + eps?: number; + pb: number; + marketCap: string; + week52Low: number; + week52High: number; +} + +export const KeyMetrics: React.FC = memo(({ + pe, + eps, + pb, + marketCap, + week52Low, + week52High, +}) => { + const { labelColor, valueColor, sectionTitleColor } = STOCK_CARD_THEME; + + return ( + + + 关键指标 + + + + 市盈率(PE): + + {pe.toFixed(2)} + + + + 每股收益(EPS): + + {eps?.toFixed(3) || '-'} + + + + 市净率(PB): + + {pb.toFixed(2)} + + + + 流通市值: + + {marketCap} + + + + 52周波动: + + {formatPrice(week52Low)}-{formatPrice(week52High)} + + + + + ); +}); + +KeyMetrics.displayName = 'KeyMetrics'; diff --git a/src/views/Company/components/StockQuoteCard/components/MainForceInfo.tsx b/src/views/Company/components/StockQuoteCard/components/MainForceInfo.tsx new file mode 100644 index 00000000..607d1a73 --- /dev/null +++ b/src/views/Company/components/StockQuoteCard/components/MainForceInfo.tsx @@ -0,0 +1,71 @@ +/** + * MainForceInfo - 主力动态原子组件 + * 显示主力净流入、机构持仓、买卖比例 + */ + +import React, { memo } from 'react'; +import { Box, VStack, HStack, Text, Progress } from '@chakra-ui/react'; +import { formatNetInflow } from './formatters'; +import { STOCK_CARD_THEME } from './theme'; + +export interface MainForceInfoProps { + mainNetInflow: number; + institutionHolding: number; + buyRatio: number; + sellRatio: number; +} + +export const MainForceInfo: React.FC = memo(({ + mainNetInflow, + institutionHolding, + buyRatio, + sellRatio, +}) => { + const { labelColor, valueColor, sectionTitleColor, borderColor, upColor, downColor } = STOCK_CARD_THEME; + const inflowColor = mainNetInflow >= 0 ? upColor : downColor; + + return ( + + + 主力动态 + + + + 主力净流入: + + {formatNetInflow(mainNetInflow)} + + + + 机构持仓: + + {institutionHolding.toFixed(2)}% + + + {/* 买卖比例条 */} + + div': { bg: upColor }, + }} + bg={downColor} + borderRadius="full" + /> + + 买入{buyRatio}% + 卖出{sellRatio}% + + + + + ); +}); + +MainForceInfo.displayName = 'MainForceInfo'; diff --git a/src/views/Company/components/StockQuoteCard/components/PriceDisplay.tsx b/src/views/Company/components/StockQuoteCard/components/PriceDisplay.tsx new file mode 100644 index 00000000..23baf6d7 --- /dev/null +++ b/src/views/Company/components/StockQuoteCard/components/PriceDisplay.tsx @@ -0,0 +1,43 @@ +/** + * PriceDisplay - 价格显示原子组件 + * 显示当前价格和涨跌幅 Badge + */ + +import React, { memo } from 'react'; +import { HStack, Text, Badge } from '@chakra-ui/react'; +import { formatPrice, formatChangePercent } from './formatters'; +import { STOCK_CARD_THEME } from './theme'; + +export interface PriceDisplayProps { + currentPrice: number; + changePercent: number; +} + +export const PriceDisplay: React.FC = memo(({ + currentPrice, + changePercent, +}) => { + const { upColor, downColor } = STOCK_CARD_THEME; + const priceColor = changePercent >= 0 ? upColor : downColor; + + return ( + + + {formatPrice(currentPrice)} + + = 0 ? upColor : downColor} + color="#FFFFFF" + fontSize="20px" + fontWeight="bold" + px={3} + py={1} + borderRadius="md" + > + {formatChangePercent(changePercent)} + + + ); +}); + +PriceDisplay.displayName = 'PriceDisplay'; diff --git a/src/views/Company/components/StockQuoteCard/components/SecondaryQuote.tsx b/src/views/Company/components/StockQuoteCard/components/SecondaryQuote.tsx new file mode 100644 index 00000000..d6e7a685 --- /dev/null +++ b/src/views/Company/components/StockQuoteCard/components/SecondaryQuote.tsx @@ -0,0 +1,59 @@ +/** + * SecondaryQuote - 次要行情原子组件 + * 显示今开、昨收、最高、最低 + */ + +import React, { memo } from 'react'; +import { HStack, Text } from '@chakra-ui/react'; +import { formatPrice } from './formatters'; +import { STOCK_CARD_THEME } from './theme'; + +export interface SecondaryQuoteProps { + todayOpen: number; + yesterdayClose: number; + todayHigh: number; + todayLow: number; +} + +export const SecondaryQuote: React.FC = memo(({ + todayOpen, + yesterdayClose, + todayHigh, + todayLow, +}) => { + const { labelColor, valueColor, borderColor, upColor, downColor } = STOCK_CARD_THEME; + + return ( + + + 今开: + + {formatPrice(todayOpen)} + + + | + + 昨收: + + {formatPrice(yesterdayClose)} + + + | + + 最高: + + {formatPrice(todayHigh)} + + + | + + 最低: + + {formatPrice(todayLow)} + + + + ); +}); + +SecondaryQuote.displayName = 'SecondaryQuote'; diff --git a/src/views/Company/components/StockQuoteCard/components/StockHeader.tsx b/src/views/Company/components/StockQuoteCard/components/StockHeader.tsx new file mode 100644 index 00000000..779c4866 --- /dev/null +++ b/src/views/Company/components/StockQuoteCard/components/StockHeader.tsx @@ -0,0 +1,120 @@ +/** + * StockHeader - 股票头部原子组件 + * 显示股票名称、代码、行业标签、指数标签、操作按钮 + */ + +import React, { memo } from 'react'; +import { Flex, HStack, Text, Badge, IconButton, Tooltip } from '@chakra-ui/react'; +import { Share2 } from 'lucide-react'; +import FavoriteButton from '@components/FavoriteButton'; +import CompareStockInput from './CompareStockInput'; +import { STOCK_CARD_THEME } from './theme'; + +export interface StockHeaderProps { + name: string; + code: string; + industryL1?: string; + industry?: string; + indexTags?: string[]; + updateTime?: string; + // 关注相关 + isInWatchlist?: boolean; + isWatchlistLoading?: boolean; + onWatchlistToggle?: () => void; + // 分享 + onShare?: () => void; + // 对比相关 + isCompareLoading?: boolean; + onCompare?: (stockCode: string) => void; +} + +export const StockHeader: React.FC = memo(({ + name, + code, + industryL1, + industry, + indexTags, + updateTime, + isInWatchlist = false, + isWatchlistLoading = false, + onWatchlistToggle, + onShare, + isCompareLoading = false, + onCompare, +}) => { + const { labelColor, valueColor, borderColor } = STOCK_CARD_THEME; + + return ( + + {/* 左侧:股票名称 + 行业标签 + 指数标签 */} + + {/* 股票名称 - 突出显示 */} + + {name} + + + ({code}) + + + {/* 行业标签 */} + {(industryL1 || industry) && ( + + {industryL1 && industry + ? `${industryL1} · ${industry}` + : industry || industryL1} + + )} + + {/* 指数标签 */} + {indexTags && indexTags.length > 0 && ( + + {indexTags.join('、')} + + )} + + + {/* 右侧:对比 + 关注 + 分享 + 时间 */} + + {/* 股票对比输入 */} + {})} + isLoading={isCompareLoading} + currentStockCode={code} + /> + {})} + colorScheme="gold" + size="sm" + /> + + } + variant="ghost" + color={labelColor} + size="sm" + onClick={onShare} + _hover={{ bg: 'whiteAlpha.100' }} + /> + + + {updateTime?.split(' ')[1] || '--:--'} + + + + ); +}); + +StockHeader.displayName = 'StockHeader'; diff --git a/src/views/Company/components/StockQuoteCard/components/formatters.ts b/src/views/Company/components/StockQuoteCard/components/formatters.ts new file mode 100644 index 00000000..1cdf05e9 --- /dev/null +++ b/src/views/Company/components/StockQuoteCard/components/formatters.ts @@ -0,0 +1,29 @@ +/** + * StockQuoteCard 格式化工具函数 + */ + +/** + * 格式化价格显示 + */ +export const formatPrice = (price: number): string => { + return price.toLocaleString('zh-CN', { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }); +}; + +/** + * 格式化涨跌幅显示 + */ +export const formatChangePercent = (percent: number): string => { + const sign = percent >= 0 ? '+' : ''; + return `${sign}${percent.toFixed(2)}%`; +}; + +/** + * 格式化主力净流入显示 + */ +export const formatNetInflow = (value: number): string => { + const sign = value >= 0 ? '+' : ''; + return `${sign}${value.toFixed(2)}亿`; +}; diff --git a/src/views/Company/components/StockQuoteCard/components/index.ts b/src/views/Company/components/StockQuoteCard/components/index.ts index da8d66e2..8fa88fd0 100644 --- a/src/views/Company/components/StockQuoteCard/components/index.ts +++ b/src/views/Company/components/StockQuoteCard/components/index.ts @@ -1,6 +1,27 @@ /** - * StockQuoteCard 子组件导出 + * StockQuoteCard 组件统一导出 */ +// 原子组件 +export { PriceDisplay } from './PriceDisplay'; +export { SecondaryQuote } from './SecondaryQuote'; +export { KeyMetrics } from './KeyMetrics'; +export { MainForceInfo } from './MainForceInfo'; +export { CompanyInfo } from './CompanyInfo'; +export { StockHeader } from './StockHeader'; + +// 复合组件 export { default as CompareStockInput } from './CompareStockInput'; export { default as StockCompareModal } from './StockCompareModal'; + +// 工具和主题 +export { STOCK_CARD_THEME } from './theme'; +export * from './formatters'; + +// 类型导出 +export type { PriceDisplayProps } from './PriceDisplay'; +export type { SecondaryQuoteProps } from './SecondaryQuote'; +export type { KeyMetricsProps } from './KeyMetrics'; +export type { MainForceInfoProps } from './MainForceInfo'; +export type { CompanyInfoProps, CompanyBasicInfo } from './CompanyInfo'; +export type { StockHeaderProps } from './StockHeader'; diff --git a/src/views/Company/components/StockQuoteCard/components/theme.ts b/src/views/Company/components/StockQuoteCard/components/theme.ts new file mode 100644 index 00000000..5358a125 --- /dev/null +++ b/src/views/Company/components/StockQuoteCard/components/theme.ts @@ -0,0 +1,20 @@ +/** + * StockQuoteCard 黑金主题配置 + */ + +export const STOCK_CARD_THEME = { + // 背景和边框 + cardBg: '#1A202C', + borderColor: '#C9A961', + + // 文字颜色 + labelColor: '#C9A961', + valueColor: '#F4D03F', + sectionTitleColor: '#F4D03F', + + // 涨跌颜色(红涨绿跌) + upColor: '#F44336', + downColor: '#4CAF50', +} as const; + +export type StockCardTheme = typeof STOCK_CARD_THEME; diff --git a/src/views/Company/components/StockQuoteCard/index.tsx b/src/views/Company/components/StockQuoteCard/index.tsx index 9da9e96f..a3560de8 100644 --- a/src/views/Company/components/StockQuoteCard/index.tsx +++ b/src/views/Company/components/StockQuoteCard/index.tsx @@ -2,6 +2,7 @@ * StockQuoteCard - 股票行情卡片组件 * * 展示股票的实时行情、关键指标和主力动态 + * 采用原子组件拆分,提高可维护性和复用性 */ import React from 'react'; @@ -10,52 +11,23 @@ import { Card, CardBody, Flex, - HStack, VStack, - Text, - Badge, - Progress, Skeleton, - IconButton, - Tooltip, - Divider, - Link, - Icon, useDisclosure, } from '@chakra-ui/react'; -import { Share2, Calendar, Coins, MapPin, Globe } from 'lucide-react'; -import { formatRegisteredCapital, formatDate } from '../CompanyOverview/utils'; -import FavoriteButton from '@components/FavoriteButton'; -import { CompareStockInput, StockCompareModal } from './components'; +import { + StockHeader, + PriceDisplay, + SecondaryQuote, + KeyMetrics, + MainForceInfo, + CompanyInfo, + StockCompareModal, + STOCK_CARD_THEME, +} from './components'; import type { StockQuoteCardProps } from './types'; -/** - * 格式化价格显示 - */ -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 = ({ data, isLoading = false, @@ -74,11 +46,6 @@ const StockQuoteCard: React.FC = ({ // 对比弹窗控制 const { isOpen: isCompareModalOpen, onOpen: openCompareModal, onClose: closeCompareModal } = useDisclosure(); - // 处理分享点击 - const handleShare = () => { - onShare?.(); - }; - // 处理对比按钮点击 const handleCompare = (stockCode: string) => { onCompare?.(stockCode); @@ -91,16 +58,7 @@ const StockQuoteCard: React.FC = ({ onCloseCompare?.(); }; - // 黑金主题颜色配置 - const cardBg = '#1A202C'; - const borderColor = '#C9A961'; - const labelColor = '#C9A961'; - const valueColor = '#F4D03F'; - const sectionTitleColor = '#F4D03F'; - - // 涨跌颜色(红涨绿跌) - const upColor = '#F44336'; // 涨 - 红色 - const downColor = '#4CAF50'; // 跌 - 绿色 + const { cardBg, borderColor } = STOCK_CARD_THEME; // 加载中或无数据时显示骨架屏 if (isLoading || !data) { @@ -117,82 +75,24 @@ const StockQuoteCard: React.FC = ({ ); } - const priceColor = data.changePercent >= 0 ? upColor : downColor; - const inflowColor = data.mainNetInflow >= 0 ? upColor : downColor; - return ( {/* 顶部:股票名称 + 关注/分享按钮 + 更新时间 */} - - {/* 左侧:股票名称 + 行业标签 + 指数标签 */} - - {/* 股票名称 - 突出显示 */} - - {data.name} - - - ({data.code}) - - - {/* 行业标签 */} - {(data.industryL1 || data.industry) && ( - - {data.industryL1 && data.industry - ? `${data.industryL1} · ${data.industry}` - : data.industry || data.industryL1} - - )} - - {/* 指数标签 */} - {data.indexTags?.length > 0 && ( - - {data.indexTags.join('、')} - - )} - - - {/* 右侧:对比 + 关注 + 分享 + 时间 */} - - {/* 股票对比输入 */} - - {})} - colorScheme="gold" - size="sm" - /> - - } - variant="ghost" - color={labelColor} - size="sm" - onClick={handleShare} - _hover={{ bg: 'whiteAlpha.100' }} - /> - - - {data.updateTime?.split(' ')[1] || '--:--'} - - - + {/* 股票对比弹窗 */} = ({ {/* 左栏:价格信息 (flex=1) */} - - - {formatPrice(data.currentPrice)} - - = 0 ? upColor : downColor} - color="#FFFFFF" - fontSize="20px" - fontWeight="bold" - px={3} - py={1} - borderRadius="md" - > - {formatChangePercent(data.changePercent)} - - - {/* 次要行情:今开 | 昨收 | 最高 | 最低 */} - - - 今开: - - {formatPrice(data.todayOpen)} - - - | - - 昨收: - - {formatPrice(data.yesterdayClose)} - - - | - - 最高: - - {formatPrice(data.todayHigh)} - - - | - - 最低: - - {formatPrice(data.todayLow)} - - - + + {/* 右栏:关键指标 + 主力动态 (flex=2) */} - {/* 关键指标 */} - - - 关键指标 - - - - 市盈率(PE): - - {data.pe.toFixed(2)} - - - - 每股收益(EPS): - - {data.eps?.toFixed(3) || '-'} - - - - 市净率(PB): - - {data.pb.toFixed(2)} - - - - 流通市值: - - {data.marketCap} - - - - 52周波动: - - {formatPrice(data.week52Low)}-{formatPrice(data.week52High)} - - - - - - {/* 主力动态 */} - - - 主力动态 - - - - 主力净流入: - - {formatNetInflow(data.mainNetInflow)} - - - - 机构持仓: - - {data.institutionHolding.toFixed(2)}% - - - {/* 买卖比例条 */} - - div': { bg: upColor }, - }} - bg={downColor} - borderRadius="full" - /> - - 买入{data.buyRatio}% - 卖出{data.sellRatio}% - - - - + + - {/* 公司信息区块 - 1:2 布局 */} - {basicInfo && ( - <> - - - {/* 左侧:公司关键属性 (flex=1) */} - - - - - 成立: - - {formatDate(basicInfo.establish_date)} - - - - - 注册资本: - - {formatRegisteredCapital(basicInfo.reg_capital)} - - - - - 所在地: - - {basicInfo.province} {basicInfo.city} - - - - - {basicInfo.website ? ( - - 访问官网 - - ) : ( - 暂无官网 - )} - - - - - {/* 右侧:公司简介 (flex=2) */} - - - 公司简介: - {basicInfo.company_intro || '暂无'} - - - - - )} + {/* 公司信息区块 */} + {basicInfo && } );