refactor(StockQuoteCard): 子组件提取与 DEEP_SPACE_THEME 统一
- PriceDisplay: 更新为 DEEP_SPACE_THEME,添加发光效果 - StockHeader: 更新为 DEEP_SPACE_THEME,金色边框和发光 - MetricRow: 新建指标行组件,支持普通和高亮模式 - 主组件从 321 行精简到 180 行(-44%) - 统一使用提取的子组件,移除内联代码 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* MetricRow - 指标行原子组件
|
||||
*
|
||||
* 用于数据区块内的单行指标展示
|
||||
* 支持普通和高亮两种模式
|
||||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import { HStack, Text } from '@chakra-ui/react';
|
||||
import { DEEP_SPACE_THEME as T } from './theme';
|
||||
|
||||
export interface MetricRowProps {
|
||||
/** 指标标签 */
|
||||
label: string;
|
||||
/** 指标值 */
|
||||
value: string | number;
|
||||
/** 值的颜色(默认白色) */
|
||||
valueColor?: string;
|
||||
/** 是否高亮显示(加粗 + 发光) */
|
||||
highlight?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指标行组件
|
||||
*
|
||||
* 布局:左侧标签 + 右侧值
|
||||
* 高亮模式:更大字号 + 发光效果
|
||||
*/
|
||||
export const MetricRow: React.FC<MetricRowProps> = memo(({
|
||||
label,
|
||||
value,
|
||||
valueColor = T.textWhite,
|
||||
highlight = false,
|
||||
}) => (
|
||||
<HStack justify="space-between" fontSize="13px">
|
||||
<Text color={T.textMuted}>{label}</Text>
|
||||
<Text
|
||||
color={valueColor}
|
||||
fontWeight={highlight ? '700' : '600'}
|
||||
fontSize={highlight ? '15px' : '13px'}
|
||||
textShadow={highlight ? `0 0 10px ${valueColor}40` : undefined}
|
||||
>
|
||||
{value}
|
||||
</Text>
|
||||
</HStack>
|
||||
));
|
||||
|
||||
MetricRow.displayName = 'MetricRow';
|
||||
@@ -1,38 +1,67 @@
|
||||
/**
|
||||
* PriceDisplay - 价格显示原子组件
|
||||
* 显示当前价格和涨跌幅 Badge
|
||||
*
|
||||
* 深空 FUI 设计风格:
|
||||
* - 大号价格数字,带涨跌色发光
|
||||
* - Badge 使用半透明背景 + 边框发光
|
||||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import { HStack, Text, Badge } from '@chakra-ui/react';
|
||||
import { formatPrice, formatChangePercent } from './formatters';
|
||||
import { STOCK_CARD_THEME } from './theme';
|
||||
import { DEEP_SPACE_THEME as T } from './theme';
|
||||
|
||||
export interface PriceDisplayProps {
|
||||
/** 当前价格 */
|
||||
currentPrice: number;
|
||||
/** 涨跌幅(百分比) */
|
||||
changePercent: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 价格显示组件
|
||||
*
|
||||
* 使用发光效果突出涨跌状态:
|
||||
* - 涨:红色 (#FF4757) + 红色光晕
|
||||
* - 跌:绿色 (#00D984) + 绿色光晕
|
||||
*/
|
||||
export const PriceDisplay: React.FC<PriceDisplayProps> = memo(({
|
||||
currentPrice,
|
||||
changePercent,
|
||||
}) => {
|
||||
const { upColor, downColor } = STOCK_CARD_THEME;
|
||||
const priceColor = changePercent >= 0 ? upColor : downColor;
|
||||
// 根据涨跌计算颜色和发光效果
|
||||
const { priceColor, priceGlow, priceBg } = useMemo(() => {
|
||||
const isUp = changePercent >= 0;
|
||||
return {
|
||||
priceColor: isUp ? T.upColor : T.downColor,
|
||||
priceGlow: isUp ? T.upGlow : T.downGlow,
|
||||
priceBg: isUp ? T.upColorMuted : T.downColorMuted,
|
||||
};
|
||||
}, [changePercent]);
|
||||
|
||||
return (
|
||||
<HStack align="baseline" spacing={3} mb={3}>
|
||||
<Text fontSize="48px" fontWeight="bold" color={priceColor}>
|
||||
<HStack align="baseline" spacing={3}>
|
||||
{/* 主价格 - 大号字体 + 发光 */}
|
||||
<Text
|
||||
fontSize="42px"
|
||||
fontWeight="bold"
|
||||
color={priceColor}
|
||||
textShadow={priceGlow}
|
||||
lineHeight="1"
|
||||
>
|
||||
{formatPrice(currentPrice)}
|
||||
</Text>
|
||||
|
||||
{/* 涨跌幅 Badge - 半透明背景 + 边框发光 */}
|
||||
<Badge
|
||||
bg={changePercent >= 0 ? upColor : downColor}
|
||||
color="#FFFFFF"
|
||||
fontSize="20px"
|
||||
bg={priceBg}
|
||||
color={priceColor}
|
||||
fontSize="18px"
|
||||
fontWeight="bold"
|
||||
px={3}
|
||||
py={1}
|
||||
borderRadius="md"
|
||||
py={1.5}
|
||||
borderRadius={T.radiusMD}
|
||||
boxShadow={priceGlow}
|
||||
>
|
||||
{formatChangePercent(changePercent)}
|
||||
</Badge>
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
/**
|
||||
* StockHeader - 股票头部原子组件
|
||||
* 显示股票名称、代码、行业标签、指数标签、操作按钮
|
||||
*
|
||||
* 深空 FUI 设计风格:
|
||||
* - 股票名称带金色发光
|
||||
* - 行业标签使用金色边框
|
||||
* - 操作按钮悬停态有玻璃效果
|
||||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
@@ -8,14 +12,20 @@ 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';
|
||||
import { DEEP_SPACE_THEME as T } from './theme';
|
||||
|
||||
export interface StockHeaderProps {
|
||||
/** 股票名称 */
|
||||
name: string;
|
||||
/** 股票代码 */
|
||||
code: string;
|
||||
/** 一级行业 */
|
||||
industryL1?: string;
|
||||
/** 二级行业 */
|
||||
industry?: string;
|
||||
/** 指数标签(沪深300、中证500等) */
|
||||
indexTags?: string[];
|
||||
/** 更新时间 */
|
||||
updateTime?: string;
|
||||
// 关注相关
|
||||
isInWatchlist?: boolean;
|
||||
@@ -28,6 +38,13 @@ export interface StockHeaderProps {
|
||||
onCompare?: (stockCode: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 股票头部组件
|
||||
*
|
||||
* 包含:
|
||||
* - 左侧:股票名称、代码、行业标签、指数标签
|
||||
* - 右侧:对比输入、关注按钮、分享按钮、更新时间
|
||||
*/
|
||||
export const StockHeader: React.FC<StockHeaderProps> = memo(({
|
||||
name,
|
||||
code,
|
||||
@@ -42,32 +59,35 @@ export const StockHeader: React.FC<StockHeaderProps> = memo(({
|
||||
isCompareLoading = false,
|
||||
onCompare,
|
||||
}) => {
|
||||
const { labelColor, valueColor, borderColor } = STOCK_CARD_THEME;
|
||||
|
||||
return (
|
||||
<Flex justify="space-between" align="center" mb={4}>
|
||||
<Flex justify="space-between" align="center">
|
||||
{/* 左侧:股票名称 + 行业标签 + 指数标签 */}
|
||||
<HStack spacing={3} align="center">
|
||||
{/* 股票名称 - 突出显示 */}
|
||||
<Text fontSize="26px" fontWeight="800" color={valueColor}>
|
||||
<HStack spacing={4} align="center">
|
||||
{/* 股票名称 - 金色发光效果 */}
|
||||
<Text
|
||||
fontSize="24px"
|
||||
fontWeight="800"
|
||||
color={T.textPrimary}
|
||||
textShadow={`0 0 20px ${T.gold}40`}
|
||||
>
|
||||
{name}
|
||||
</Text>
|
||||
<Text fontSize="18px" fontWeight="normal" color={labelColor}>
|
||||
<Text fontSize="16px" color={T.textMuted} fontWeight="normal">
|
||||
({code})
|
||||
</Text>
|
||||
|
||||
{/* 行业标签 */}
|
||||
{/* 行业标签 - 金色边框 */}
|
||||
{(industryL1 || industry) && (
|
||||
<Badge
|
||||
bg="transparent"
|
||||
color={labelColor}
|
||||
fontSize="14px"
|
||||
fontWeight="medium"
|
||||
color={T.textSecondary}
|
||||
fontSize="13px"
|
||||
fontWeight="500"
|
||||
border="1px solid"
|
||||
borderColor={borderColor}
|
||||
px={2}
|
||||
py={0.5}
|
||||
borderRadius="md"
|
||||
borderColor={T.borderGold}
|
||||
px={3}
|
||||
py={1}
|
||||
borderRadius={T.radiusMD}
|
||||
>
|
||||
{industryL1 && industry
|
||||
? `${industryL1} · ${industry}`
|
||||
@@ -77,7 +97,7 @@ export const StockHeader: React.FC<StockHeaderProps> = memo(({
|
||||
|
||||
{/* 指数标签 */}
|
||||
{indexTags && indexTags.length > 0 && (
|
||||
<Text fontSize="14px" color={labelColor}>
|
||||
<Text fontSize="13px" color={T.textMuted}>
|
||||
{indexTags.join('、')}
|
||||
</Text>
|
||||
)}
|
||||
@@ -103,13 +123,14 @@ export const StockHeader: React.FC<StockHeaderProps> = memo(({
|
||||
aria-label="分享"
|
||||
icon={<Share2 size={18} />}
|
||||
variant="ghost"
|
||||
color={labelColor}
|
||||
color={T.textSecondary}
|
||||
size="sm"
|
||||
borderRadius={T.radiusSM}
|
||||
onClick={onShare}
|
||||
_hover={{ bg: 'whiteAlpha.100' }}
|
||||
_hover={{ bg: T.borderGlass, color: T.textPrimary }}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Text fontSize="14px" color={labelColor}>
|
||||
<Text fontSize="13px" color={T.textMuted}>
|
||||
{updateTime?.split(' ')[1] || '--:--'}
|
||||
</Text>
|
||||
</HStack>
|
||||
|
||||
@@ -17,6 +17,7 @@ export { KeyMetrics } from './KeyMetrics';
|
||||
export { MainForceInfo } from './MainForceInfo';
|
||||
export { CompanyInfo } from './CompanyInfo';
|
||||
export { StockHeader } from './StockHeader';
|
||||
export { MetricRow } from './MetricRow';
|
||||
|
||||
// ============================================
|
||||
// 容器组件 - 布局
|
||||
@@ -50,4 +51,5 @@ export type { KeyMetricsProps } from './KeyMetrics';
|
||||
export type { MainForceInfoProps } from './MainForceInfo';
|
||||
export type { CompanyInfoProps, CompanyBasicInfo } from './CompanyInfo';
|
||||
export type { StockHeaderProps } from './StockHeader';
|
||||
export type { MetricRowProps } from './MetricRow';
|
||||
export type { GlassSectionProps } from './GlassSection';
|
||||
|
||||
@@ -6,97 +6,44 @@
|
||||
* - 光影深度,弥散背景光
|
||||
* - 极致圆角,科幻数据终端感
|
||||
*
|
||||
* 功能模块:
|
||||
* - 股票头部:名称、代码、行业标签、操作按钮(对比、关注、分享)
|
||||
* - 价格展示:当前价格、涨跌幅 Badge
|
||||
* - 次要行情:今开、昨收、最高、最低(SecondaryQuote 组件)
|
||||
* - 数据区块:估值指标、市值股本、主力动态(三列 GlassSection 布局)
|
||||
*
|
||||
* 组件结构:
|
||||
* - GlowDecorations:装饰性光效(背景层)
|
||||
* - LoadingSkeleton:加载骨架屏
|
||||
* - GlassSection:玻璃容器(包装数据区块)
|
||||
* - SecondaryQuote:次要行情展示
|
||||
* - KeyMetrics:关键指标(估值 + 市值)
|
||||
* - StockHeader:股票名称、代码、行业标签、操作按钮
|
||||
* - PriceDisplay:当前价格、涨跌幅 Badge
|
||||
* - SecondaryQuote:今开、昨收、最高、最低
|
||||
* - GlassSection + MetricRow:估值指标、市值股本
|
||||
* - MainForceInfo:主力动态
|
||||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
HStack,
|
||||
VStack,
|
||||
Text,
|
||||
Badge,
|
||||
IconButton,
|
||||
Tooltip,
|
||||
useDisclosure,
|
||||
} from '@chakra-ui/react';
|
||||
import { Share2 } from 'lucide-react';
|
||||
import FavoriteButton from '@components/FavoriteButton';
|
||||
import { Box, Flex, VStack, useDisclosure } from '@chakra-ui/react';
|
||||
import { CardGlow } from '@components/FUI';
|
||||
|
||||
// 子组件导入
|
||||
import {
|
||||
StockCompareModal,
|
||||
CompareStockInput,
|
||||
LoadingSkeleton,
|
||||
GlassSection,
|
||||
StockHeader,
|
||||
PriceDisplay,
|
||||
SecondaryQuote,
|
||||
MetricRow,
|
||||
MainForceInfo,
|
||||
DEEP_SPACE_THEME as T,
|
||||
formatPrice,
|
||||
} from './components';
|
||||
import { glassCardStyle } from './components/theme';
|
||||
|
||||
// Hooks 和工具
|
||||
// Hooks
|
||||
import { useStockQuoteData, useStockCompare } from './hooks';
|
||||
import { DEEP_SPACE_THEME, glassCardStyle } from './components/theme';
|
||||
import { formatPrice, formatChangePercent } from './components/formatters';
|
||||
import type { StockQuoteCardProps } from './types';
|
||||
|
||||
/** 主题常量简写 */
|
||||
const T = DEEP_SPACE_THEME;
|
||||
|
||||
/**
|
||||
* 指标行组件 - 用于数据区块内的单行指标展示
|
||||
*
|
||||
* @param label - 指标标签
|
||||
* @param value - 指标值
|
||||
* @param valueColor - 值的颜色(默认白色)
|
||||
* @param highlight - 是否高亮显示(加粗 + 发光)
|
||||
*/
|
||||
interface MetricRowProps {
|
||||
label: string;
|
||||
value: string | number;
|
||||
valueColor?: string;
|
||||
highlight?: boolean;
|
||||
}
|
||||
|
||||
const MetricRow: React.FC<MetricRowProps> = ({
|
||||
label,
|
||||
value,
|
||||
valueColor = T.textWhite,
|
||||
highlight = false,
|
||||
}) => (
|
||||
<HStack justify="space-between" fontSize="13px">
|
||||
<Text color={T.textMuted}>{label}</Text>
|
||||
<Text
|
||||
color={valueColor}
|
||||
fontWeight={highlight ? '700' : '600'}
|
||||
fontSize={highlight ? '15px' : '13px'}
|
||||
textShadow={highlight ? `0 0 10px ${valueColor}40` : undefined}
|
||||
>
|
||||
{value}
|
||||
</Text>
|
||||
</HStack>
|
||||
);
|
||||
|
||||
const StockQuoteCard: React.FC<StockQuoteCardProps> = ({
|
||||
stockCode,
|
||||
isInWatchlist = false,
|
||||
isWatchlistLoading = false,
|
||||
onWatchlistToggle,
|
||||
}) => {
|
||||
const { quoteData, basicInfo, isLoading } = useStockQuoteData(stockCode);
|
||||
const { quoteData, isLoading } = useStockQuoteData(stockCode);
|
||||
const {
|
||||
currentStockInfo,
|
||||
compareStockInfo,
|
||||
@@ -122,121 +69,33 @@ const StockQuoteCard: React.FC<StockQuoteCardProps> = ({
|
||||
return <LoadingSkeleton />;
|
||||
}
|
||||
|
||||
// 涨跌判断(用于价格颜色)
|
||||
const isUp = quoteData.changePercent >= 0;
|
||||
const priceColor = isUp ? T.upColor : T.downColor;
|
||||
const priceGlow = isUp ? T.upGlow : T.downGlow;
|
||||
const priceBg = isUp ? T.upColorMuted : T.downColorMuted;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
{...glassCardStyle.containerGold}
|
||||
p={6}
|
||||
>
|
||||
<Box {...glassCardStyle.containerGold} p={6}>
|
||||
<CardGlow variant="gold" />
|
||||
|
||||
{/* 内容区域(在装饰层之上)*/}
|
||||
<VStack align="stretch" spacing={4} position="relative" zIndex={1}>
|
||||
|
||||
{/* ========== 头部区域 ========== */}
|
||||
<Flex justify="space-between" align="center">
|
||||
{/* 左侧:股票名称 + 代码 + 行业 */}
|
||||
<HStack spacing={4} align="center">
|
||||
<Text
|
||||
fontSize="24px"
|
||||
fontWeight="800"
|
||||
color={T.textPrimary}
|
||||
textShadow={`0 0 20px ${T.gold}40`}
|
||||
>
|
||||
{quoteData.name}
|
||||
</Text>
|
||||
<Text fontSize="16px" color={T.textMuted} fontWeight="normal">
|
||||
({quoteData.code})
|
||||
</Text>
|
||||
|
||||
{/* 行业标签 */}
|
||||
{(quoteData.industryL1 || quoteData.industry) && (
|
||||
<Badge
|
||||
bg="transparent"
|
||||
color={T.textSecondary}
|
||||
fontSize="13px"
|
||||
fontWeight="500"
|
||||
border="1px solid"
|
||||
borderColor={T.borderGold}
|
||||
px={3}
|
||||
py={1}
|
||||
borderRadius={T.radiusMD}
|
||||
>
|
||||
{quoteData.industryL1 && quoteData.industry
|
||||
? `${quoteData.industryL1} · ${quoteData.industry}`
|
||||
: quoteData.industry || quoteData.industryL1}
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
{/* 指数标签 */}
|
||||
{quoteData.indexTags && quoteData.indexTags.length > 0 && (
|
||||
<Text fontSize="13px" color={T.textMuted}>
|
||||
{quoteData.indexTags.join('、')}
|
||||
</Text>
|
||||
)}
|
||||
</HStack>
|
||||
|
||||
{/* 右侧:操作按钮 */}
|
||||
<HStack spacing={3}>
|
||||
<CompareStockInput
|
||||
<StockHeader
|
||||
name={quoteData.name}
|
||||
code={quoteData.code}
|
||||
industryL1={quoteData.industryL1}
|
||||
industry={quoteData.industry}
|
||||
indexTags={quoteData.indexTags}
|
||||
updateTime={quoteData.updateTime}
|
||||
isInWatchlist={isInWatchlist}
|
||||
isWatchlistLoading={isWatchlistLoading}
|
||||
onWatchlistToggle={onWatchlistToggle}
|
||||
isCompareLoading={isCompareLoading}
|
||||
onCompare={handleCompare}
|
||||
isLoading={isCompareLoading}
|
||||
currentStockCode={quoteData.code}
|
||||
/>
|
||||
<FavoriteButton
|
||||
isFavorite={isInWatchlist}
|
||||
isLoading={isWatchlistLoading}
|
||||
onClick={onWatchlistToggle || (() => {})}
|
||||
colorScheme="gold"
|
||||
size="sm"
|
||||
/>
|
||||
<Tooltip label="分享" placement="top">
|
||||
<IconButton
|
||||
aria-label="分享"
|
||||
icon={<Share2 size={18} />}
|
||||
variant="ghost"
|
||||
color={T.textSecondary}
|
||||
size="sm"
|
||||
borderRadius={T.radiusSM}
|
||||
_hover={{ bg: T.borderGlass, color: T.textPrimary }}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Text fontSize="13px" color={T.textMuted}>
|
||||
{quoteData.updateTime?.split(' ')[1] || '--:--'}
|
||||
</Text>
|
||||
</HStack>
|
||||
</Flex>
|
||||
|
||||
{/* ========== 价格区域 ========== */}
|
||||
<HStack align="baseline" spacing={3}>
|
||||
<Text
|
||||
fontSize="42px"
|
||||
fontWeight="bold"
|
||||
color={priceColor}
|
||||
textShadow={priceGlow}
|
||||
lineHeight="1"
|
||||
>
|
||||
{formatPrice(quoteData.currentPrice)}
|
||||
</Text>
|
||||
<Badge
|
||||
bg={priceBg}
|
||||
color={priceColor}
|
||||
fontSize="18px"
|
||||
fontWeight="bold"
|
||||
px={3}
|
||||
py={1.5}
|
||||
borderRadius={T.radiusMD}
|
||||
boxShadow={priceGlow}
|
||||
>
|
||||
{formatChangePercent(quoteData.changePercent)}
|
||||
</Badge>
|
||||
</HStack>
|
||||
<PriceDisplay
|
||||
currentPrice={quoteData.currentPrice}
|
||||
changePercent={quoteData.changePercent}
|
||||
/>
|
||||
|
||||
{/* ========== 次要行情 ========== */}
|
||||
<SecondaryQuote
|
||||
|
||||
Reference in New Issue
Block a user