refactor(StockQuoteCard): 组件拆分与 FUI 光效统一
- 新增 CardGlow 组件到 @components/FUI,支持多种颜色主题 (gold/cyan/purple) - 拆分 StockQuoteCard 子组件:GlassSection、LoadingSkeleton - 更新 KeyMetrics、MainForceInfo、SecondaryQuote 使用 DEEP_SPACE_THEME - 主组件从 540 行精简到 321 行(减少 40%) - 删除重复的 GlowDecorations,统一使用 FUI/CardGlow 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
140
src/components/FUI/CardGlow.tsx
Normal file
140
src/components/FUI/CardGlow.tsx
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
/**
|
||||||
|
* CardGlow - 卡片级装饰光效组件
|
||||||
|
*
|
||||||
|
* 为卡片提供 FUI 风格的装饰元素:
|
||||||
|
* - 顶部光条(Ash Thorp 风格)
|
||||||
|
* - 角落发光效果(James Turrell 风格)
|
||||||
|
* - 可选背景网格
|
||||||
|
*
|
||||||
|
* 与 AmbientGlow 的区别:
|
||||||
|
* - AmbientGlow: 页面级环境光,position: fixed
|
||||||
|
* - CardGlow: 卡片级装饰光,相对于父容器定位
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* <Box position="relative" overflow="hidden">
|
||||||
|
* <CardGlow variant="gold" />
|
||||||
|
* {children}
|
||||||
|
* </Box>
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { memo } from 'react';
|
||||||
|
import { Box } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
export interface CardGlowProps {
|
||||||
|
/** 预设主题 */
|
||||||
|
variant?: 'gold' | 'cyan' | 'purple' | 'default';
|
||||||
|
/** 是否显示背景网格 */
|
||||||
|
showGrid?: boolean;
|
||||||
|
/** 自定义主色(覆盖 variant) */
|
||||||
|
primaryColor?: string;
|
||||||
|
/** 自定义次色(覆盖 variant) */
|
||||||
|
secondaryColor?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 预设颜色配置
|
||||||
|
const COLOR_PRESETS = {
|
||||||
|
gold: {
|
||||||
|
primary: 'rgba(212, 175, 55, 1)',
|
||||||
|
secondary: 'rgba(0, 212, 255, 0.1)',
|
||||||
|
grid: 'rgba(212, 175, 55, 0.03)',
|
||||||
|
},
|
||||||
|
cyan: {
|
||||||
|
primary: 'rgba(0, 212, 255, 1)',
|
||||||
|
secondary: 'rgba(212, 175, 55, 0.1)',
|
||||||
|
grid: 'rgba(0, 212, 255, 0.03)',
|
||||||
|
},
|
||||||
|
purple: {
|
||||||
|
primary: 'rgba(168, 85, 247, 1)',
|
||||||
|
secondary: 'rgba(0, 212, 255, 0.1)',
|
||||||
|
grid: 'rgba(168, 85, 247, 0.03)',
|
||||||
|
},
|
||||||
|
default: {
|
||||||
|
primary: 'rgba(255, 255, 255, 0.6)',
|
||||||
|
secondary: 'rgba(255, 255, 255, 0.1)',
|
||||||
|
grid: 'rgba(255, 255, 255, 0.02)',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 卡片装饰光效组件
|
||||||
|
*
|
||||||
|
* 纯展示组件,需要父容器设置 position: relative 和 overflow: hidden
|
||||||
|
*/
|
||||||
|
const CardGlow = memo<CardGlowProps>(({
|
||||||
|
variant = 'gold',
|
||||||
|
showGrid = true,
|
||||||
|
primaryColor,
|
||||||
|
secondaryColor,
|
||||||
|
}) => {
|
||||||
|
const preset = COLOR_PRESETS[variant];
|
||||||
|
const primary = primaryColor || preset.primary;
|
||||||
|
const secondary = secondaryColor || preset.secondary;
|
||||||
|
const gridColor = preset.grid;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* 顶部光条 - Ash Thorp 风格数据终端效果 */}
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
top={0}
|
||||||
|
left="50%"
|
||||||
|
transform="translateX(-50%)"
|
||||||
|
width="60%"
|
||||||
|
height="1px"
|
||||||
|
background={`linear-gradient(90deg, transparent, ${primary}, transparent)`}
|
||||||
|
opacity={0.6}
|
||||||
|
pointerEvents="none"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 左上角光晕 - James Turrell 风格光影效果 */}
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
top="-40px"
|
||||||
|
left="-40px"
|
||||||
|
width="80px"
|
||||||
|
height="80px"
|
||||||
|
borderRadius="50%"
|
||||||
|
background={`radial-gradient(circle, ${primary.replace('1)', '0.15)')} 0%, transparent 70%)`}
|
||||||
|
filter="blur(20px)"
|
||||||
|
pointerEvents="none"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 右下角光晕 - 补充色,增加层次感 */}
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
bottom="-40px"
|
||||||
|
right="-40px"
|
||||||
|
width="80px"
|
||||||
|
height="80px"
|
||||||
|
borderRadius="50%"
|
||||||
|
background={`radial-gradient(circle, ${secondary} 0%, transparent 70%)`}
|
||||||
|
filter="blur(20px)"
|
||||||
|
pointerEvents="none"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 背景网格 - 微妙的科技感纹理 */}
|
||||||
|
{showGrid && (
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
top={0}
|
||||||
|
left={0}
|
||||||
|
right={0}
|
||||||
|
bottom={0}
|
||||||
|
backgroundImage={`
|
||||||
|
linear-gradient(${gridColor} 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, ${gridColor} 1px, transparent 1px)
|
||||||
|
`}
|
||||||
|
backgroundSize="40px 40px"
|
||||||
|
pointerEvents="none"
|
||||||
|
opacity={0.5}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
CardGlow.displayName = 'CardGlow';
|
||||||
|
|
||||||
|
export default CardGlow;
|
||||||
@@ -1,12 +1,20 @@
|
|||||||
/**
|
/**
|
||||||
* FUI (Futuristic UI) 组件集合
|
* FUI (Futuristic UI) 组件集合
|
||||||
* Ash Thorp 风格的科幻 UI 组件
|
* Ash Thorp 风格的科幻 UI 组件
|
||||||
|
*
|
||||||
|
* 组件说明:
|
||||||
|
* - FuiCorners: 科幻角落装饰
|
||||||
|
* - FuiContainer: FUI 风格容器
|
||||||
|
* - AmbientGlow: 页面级环境光效果(position: fixed)
|
||||||
|
* - CardGlow: 卡片级装饰光效(相对定位,用于卡片内部)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { default as FuiCorners } from './FuiCorners';
|
export { default as FuiCorners } from './FuiCorners';
|
||||||
export { default as FuiContainer } from './FuiContainer';
|
export { default as FuiContainer } from './FuiContainer';
|
||||||
export { default as AmbientGlow } from './AmbientGlow';
|
export { default as AmbientGlow } from './AmbientGlow';
|
||||||
|
export { default as CardGlow } from './CardGlow';
|
||||||
|
|
||||||
export type { FuiCornersProps } from './FuiCorners';
|
export type { FuiCornersProps } from './FuiCorners';
|
||||||
export type { FuiContainerProps } from './FuiContainer';
|
export type { FuiContainerProps } from './FuiContainer';
|
||||||
export type { AmbientGlowProps } from './AmbientGlow';
|
export type { AmbientGlowProps } from './AmbientGlow';
|
||||||
|
export type { CardGlowProps } from './CardGlow';
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
/**
|
||||||
|
* GlassSection - 玻璃态内嵌区块组件
|
||||||
|
*
|
||||||
|
* 用于包装数据区块(如估值指标、市值股本、主力动态)
|
||||||
|
* 提供统一的 FUI 风格容器样式:
|
||||||
|
* - 半透明背景
|
||||||
|
* - 金色边框高亮
|
||||||
|
* - 顶部光条装饰
|
||||||
|
* - 悬停效果
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { memo } from 'react';
|
||||||
|
import { Box, Text } from '@chakra-ui/react';
|
||||||
|
import { DEEP_SPACE_THEME as T } from './theme';
|
||||||
|
|
||||||
|
export interface GlassSectionProps {
|
||||||
|
/** 区块标题 */
|
||||||
|
title: string;
|
||||||
|
/** 区块内容 */
|
||||||
|
children: React.ReactNode;
|
||||||
|
/** flex 布局属性,默认 1 */
|
||||||
|
flex?: number | string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 玻璃态内嵌区块
|
||||||
|
*
|
||||||
|
* 提供统一的数据区块容器样式
|
||||||
|
* 用于包装 KeyMetrics、MainForceInfo 等内容组件
|
||||||
|
*/
|
||||||
|
export const GlassSection: React.FC<GlassSectionProps> = memo(({
|
||||||
|
title,
|
||||||
|
children,
|
||||||
|
flex = 1,
|
||||||
|
}) => (
|
||||||
|
<Box
|
||||||
|
flex={flex}
|
||||||
|
bg={T.bgInset}
|
||||||
|
borderRadius={T.radiusLG}
|
||||||
|
border={`1px solid ${T.borderGlass}`}
|
||||||
|
p={4}
|
||||||
|
position="relative"
|
||||||
|
transition={T.transitionFast}
|
||||||
|
_hover={{
|
||||||
|
borderColor: T.borderGoldHover,
|
||||||
|
bg: 'rgba(15, 18, 35, 0.6)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* 区块顶部金色光条装饰 */}
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
top={0}
|
||||||
|
left="20px"
|
||||||
|
right="20px"
|
||||||
|
height="1px"
|
||||||
|
background={`linear-gradient(90deg, transparent, ${T.gold}40, transparent)`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 区块标题 */}
|
||||||
|
<Text
|
||||||
|
fontSize="14px"
|
||||||
|
fontWeight="700"
|
||||||
|
color={T.gold}
|
||||||
|
mb={3}
|
||||||
|
textTransform="uppercase"
|
||||||
|
letterSpacing="0.1em"
|
||||||
|
textShadow={`0 0 12px ${T.gold}60`}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{/* 区块内容 */}
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
));
|
||||||
|
|
||||||
|
GlassSection.displayName = 'GlassSection';
|
||||||
|
|
||||||
|
export default GlassSection;
|
||||||
@@ -1,23 +1,74 @@
|
|||||||
/**
|
/**
|
||||||
* KeyMetrics - 关键指标原子组件
|
* KeyMetrics - 关键指标原子组件
|
||||||
* 显示 PE、EPS、PB、流通市值、52周波动
|
*
|
||||||
|
* 显示估值和市值相关指标:
|
||||||
|
* - 市盈率 (PE)
|
||||||
|
* - 流通市值
|
||||||
|
* - 发行总股本
|
||||||
|
* - 流通股本
|
||||||
|
* - 换手率
|
||||||
|
* - 52周波动
|
||||||
|
*
|
||||||
|
* 注意:标题由外层 GlassSection 提供
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { memo } from 'react';
|
import React, { memo } from 'react';
|
||||||
import { Box, VStack, HStack, Text } from '@chakra-ui/react';
|
import { VStack, HStack, Text } from '@chakra-ui/react';
|
||||||
import { formatPrice } from './formatters';
|
import { formatPrice } from './formatters';
|
||||||
import { STOCK_CARD_THEME } from './theme';
|
import { DEEP_SPACE_THEME as T } from './theme';
|
||||||
|
|
||||||
export interface KeyMetricsProps {
|
export interface KeyMetricsProps {
|
||||||
|
/** 市盈率 */
|
||||||
pe: number;
|
pe: number;
|
||||||
|
/** 流通市值(已格式化字符串) */
|
||||||
marketCap: string;
|
marketCap: string;
|
||||||
totalShares?: number; // 发行总股本(亿股)
|
/** 发行总股本(亿股) */
|
||||||
floatShares?: number; // 流通股本(亿股)
|
totalShares?: number;
|
||||||
turnoverRate?: number; // 换手率(%)
|
/** 流通股本(亿股) */
|
||||||
|
floatShares?: number;
|
||||||
|
/** 换手率(%) */
|
||||||
|
turnoverRate?: number;
|
||||||
|
/** 52周最低价 */
|
||||||
week52Low: number;
|
week52Low: number;
|
||||||
|
/** 52周最高价 */
|
||||||
week52High: number;
|
week52High: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指标行组件 - 内部使用
|
||||||
|
*/
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关键指标展示组件
|
||||||
|
*
|
||||||
|
* 纯展示组件,不包含标题
|
||||||
|
* 应由 GlassSection 包装以提供标题
|
||||||
|
*/
|
||||||
export const KeyMetrics: React.FC<KeyMetricsProps> = memo(({
|
export const KeyMetrics: React.FC<KeyMetricsProps> = memo(({
|
||||||
pe,
|
pe,
|
||||||
marketCap,
|
marketCap,
|
||||||
@@ -26,59 +77,38 @@ export const KeyMetrics: React.FC<KeyMetricsProps> = memo(({
|
|||||||
turnoverRate,
|
turnoverRate,
|
||||||
week52Low,
|
week52Low,
|
||||||
week52High,
|
week52High,
|
||||||
}) => {
|
}) => (
|
||||||
const { labelColor, valueColor, sectionTitleColor } = STOCK_CARD_THEME;
|
<VStack align="stretch" spacing={2}>
|
||||||
|
<MetricRow
|
||||||
return (
|
label="市盈率 (PE)"
|
||||||
<Box flex="1">
|
value={pe ? pe.toFixed(2) : '-'}
|
||||||
<Text
|
valueColor={T.cyan}
|
||||||
fontSize="14px"
|
highlight
|
||||||
fontWeight="bold"
|
/>
|
||||||
color={sectionTitleColor}
|
<MetricRow
|
||||||
mb={3}
|
label="流通市值"
|
||||||
>
|
value={marketCap || '-'}
|
||||||
关键指标
|
valueColor={T.textPrimary}
|
||||||
</Text>
|
highlight
|
||||||
<VStack align="stretch" spacing={2} fontSize="14px">
|
/>
|
||||||
<HStack justify="space-between">
|
<MetricRow
|
||||||
<Text color={labelColor}>市盈率(PE):</Text>
|
label="发行总股本"
|
||||||
<Text color={valueColor} fontWeight="bold" fontSize="16px">
|
value={totalShares ? `${totalShares}亿股` : '-'}
|
||||||
{pe ? pe.toFixed(2) : '-'}
|
/>
|
||||||
</Text>
|
<MetricRow
|
||||||
</HStack>
|
label="流通股本"
|
||||||
<HStack justify="space-between">
|
value={floatShares ? `${floatShares}亿股` : '-'}
|
||||||
<Text color={labelColor}>流通市值:</Text>
|
/>
|
||||||
<Text color={valueColor} fontWeight="bold" fontSize="16px">
|
<MetricRow
|
||||||
{marketCap || '-'}
|
label="换手率"
|
||||||
</Text>
|
value={turnoverRate !== undefined ? `${turnoverRate.toFixed(2)}%` : '-'}
|
||||||
</HStack>
|
valueColor={turnoverRate && turnoverRate > 5 ? T.orange : T.textWhite}
|
||||||
<HStack justify="space-between">
|
/>
|
||||||
<Text color={labelColor}>发行总股本:</Text>
|
<MetricRow
|
||||||
<Text color={valueColor} fontWeight="bold" fontSize="16px">
|
label="52周波动"
|
||||||
{totalShares ? `${totalShares}亿股` : '-'}
|
value={`${formatPrice(week52Low)} - ${formatPrice(week52High)}`}
|
||||||
</Text>
|
/>
|
||||||
</HStack>
|
</VStack>
|
||||||
<HStack justify="space-between">
|
));
|
||||||
<Text color={labelColor}>流通股本:</Text>
|
|
||||||
<Text color={valueColor} fontWeight="bold" fontSize="16px">
|
|
||||||
{floatShares ? `${floatShares}亿股` : '-'}
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
|
||||||
<HStack justify="space-between">
|
|
||||||
<Text color={labelColor}>换手率:</Text>
|
|
||||||
<Text color={valueColor} fontWeight="bold" fontSize="16px">
|
|
||||||
{turnoverRate !== undefined ? `${turnoverRate.toFixed(2)}%` : '-'}
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
|
||||||
<HStack justify="space-between">
|
|
||||||
<Text color={labelColor}>52周波动:</Text>
|
|
||||||
<Text color={valueColor} fontWeight="bold" fontSize="16px">
|
|
||||||
{formatPrice(week52Low)}-{formatPrice(week52High)}
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
|
||||||
</VStack>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
KeyMetrics.displayName = 'KeyMetrics';
|
KeyMetrics.displayName = 'KeyMetrics';
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
/**
|
||||||
|
* LoadingSkeleton - 股票行情卡片加载骨架屏
|
||||||
|
*
|
||||||
|
* 在数据加载期间展示的骨架屏组件
|
||||||
|
* 保持与实际内容相同的布局结构,提供良好的加载体验
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { memo } from 'react';
|
||||||
|
import { Box, Flex, HStack, VStack, Skeleton } from '@chakra-ui/react';
|
||||||
|
import { CardGlow } from '@components/FUI';
|
||||||
|
import { glassCardStyle, DEEP_SPACE_THEME as T } from './theme';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 股票行情卡片加载骨架屏
|
||||||
|
*
|
||||||
|
* 布局结构对应实际卡片:
|
||||||
|
* - 头部:股票名称 + 操作按钮
|
||||||
|
* - 价格:当前价格 + 涨跌幅
|
||||||
|
* - 内容:多列数据区块
|
||||||
|
*/
|
||||||
|
export const LoadingSkeleton: React.FC = memo(() => (
|
||||||
|
<Box
|
||||||
|
{...glassCardStyle.containerGold}
|
||||||
|
p={8}
|
||||||
|
>
|
||||||
|
{/* 装饰性光效 */}
|
||||||
|
<CardGlow variant="gold" />
|
||||||
|
|
||||||
|
<VStack align="stretch" spacing={6} position="relative" zIndex={1}>
|
||||||
|
{/* 头部骨架:股票名称 + 代码 + 操作按钮 */}
|
||||||
|
<Flex justify="space-between">
|
||||||
|
<HStack spacing={3}>
|
||||||
|
<Skeleton
|
||||||
|
height="32px"
|
||||||
|
width="120px"
|
||||||
|
startColor={T.bgInset}
|
||||||
|
endColor={T.borderGlass}
|
||||||
|
borderRadius={T.radiusSM}
|
||||||
|
/>
|
||||||
|
<Skeleton
|
||||||
|
height="24px"
|
||||||
|
width="80px"
|
||||||
|
startColor={T.bgInset}
|
||||||
|
endColor={T.borderGlass}
|
||||||
|
borderRadius={T.radiusSM}
|
||||||
|
/>
|
||||||
|
</HStack>
|
||||||
|
<HStack spacing={2}>
|
||||||
|
<Skeleton
|
||||||
|
height="32px"
|
||||||
|
width="32px"
|
||||||
|
startColor={T.bgInset}
|
||||||
|
endColor={T.borderGlass}
|
||||||
|
borderRadius={T.radiusSM}
|
||||||
|
/>
|
||||||
|
<Skeleton
|
||||||
|
height="32px"
|
||||||
|
width="32px"
|
||||||
|
startColor={T.bgInset}
|
||||||
|
endColor={T.borderGlass}
|
||||||
|
borderRadius={T.radiusSM}
|
||||||
|
/>
|
||||||
|
</HStack>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
{/* 价格骨架:当前价格 + 涨跌幅 Badge */}
|
||||||
|
<HStack>
|
||||||
|
<Skeleton
|
||||||
|
height="56px"
|
||||||
|
width="160px"
|
||||||
|
startColor={T.bgInset}
|
||||||
|
endColor={T.borderGlass}
|
||||||
|
borderRadius={T.radiusMD}
|
||||||
|
/>
|
||||||
|
<Skeleton
|
||||||
|
height="36px"
|
||||||
|
width="100px"
|
||||||
|
startColor={T.bgInset}
|
||||||
|
endColor={T.borderGlass}
|
||||||
|
borderRadius={T.radiusMD}
|
||||||
|
/>
|
||||||
|
</HStack>
|
||||||
|
|
||||||
|
{/* 内容骨架:三列数据区块 */}
|
||||||
|
<Flex gap={6}>
|
||||||
|
<Box flex={1}>
|
||||||
|
<Skeleton
|
||||||
|
height="120px"
|
||||||
|
startColor={T.bgInset}
|
||||||
|
endColor={T.borderGlass}
|
||||||
|
borderRadius={T.radiusLG}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box flex={1}>
|
||||||
|
<Skeleton
|
||||||
|
height="120px"
|
||||||
|
startColor={T.bgInset}
|
||||||
|
endColor={T.borderGlass}
|
||||||
|
borderRadius={T.radiusLG}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box flex={1}>
|
||||||
|
<Skeleton
|
||||||
|
height="120px"
|
||||||
|
startColor={T.bgInset}
|
||||||
|
endColor={T.borderGlass}
|
||||||
|
borderRadius={T.radiusLG}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
</VStack>
|
||||||
|
</Box>
|
||||||
|
));
|
||||||
|
|
||||||
|
LoadingSkeleton.displayName = 'LoadingSkeleton';
|
||||||
|
|
||||||
|
export default LoadingSkeleton;
|
||||||
@@ -1,70 +1,113 @@
|
|||||||
/**
|
/**
|
||||||
* MainForceInfo - 主力动态原子组件
|
* MainForceInfo - 主力动态原子组件
|
||||||
* 显示主力净流入、机构持仓、买卖比例
|
*
|
||||||
|
* 显示主力资金和机构相关指标:
|
||||||
|
* - 主力净流入(带正负颜色)
|
||||||
|
* - 机构持仓比例
|
||||||
|
* - 买卖比例进度条
|
||||||
|
*
|
||||||
|
* 注意:标题由外层 GlassSection 提供
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { memo } from 'react';
|
import React, { memo } from 'react';
|
||||||
import { Box, VStack, HStack, Text, Progress } from '@chakra-ui/react';
|
import { Box, VStack, HStack, Text, Progress } from '@chakra-ui/react';
|
||||||
import { formatNetInflow } from './formatters';
|
import { formatNetInflow } from './formatters';
|
||||||
import { STOCK_CARD_THEME } from './theme';
|
import { DEEP_SPACE_THEME as T } from './theme';
|
||||||
|
|
||||||
export interface MainForceInfoProps {
|
export interface MainForceInfoProps {
|
||||||
|
/** 主力净流入(亿) */
|
||||||
mainNetInflow: number;
|
mainNetInflow: number;
|
||||||
|
/** 机构持仓比例(%) */
|
||||||
institutionHolding: number;
|
institutionHolding: number;
|
||||||
|
/** 买入比例(%) */
|
||||||
buyRatio: number;
|
buyRatio: number;
|
||||||
|
/** 卖出比例(%) */
|
||||||
sellRatio: number;
|
sellRatio: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指标行组件 - 内部使用
|
||||||
|
*/
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主力动态展示组件
|
||||||
|
*
|
||||||
|
* 纯展示组件,不包含标题
|
||||||
|
* 应由 GlassSection 包装以提供标题
|
||||||
|
*/
|
||||||
export const MainForceInfo: React.FC<MainForceInfoProps> = memo(({
|
export const MainForceInfo: React.FC<MainForceInfoProps> = memo(({
|
||||||
mainNetInflow,
|
mainNetInflow,
|
||||||
institutionHolding,
|
institutionHolding,
|
||||||
buyRatio,
|
buyRatio,
|
||||||
sellRatio,
|
sellRatio,
|
||||||
}) => {
|
}) => {
|
||||||
const { labelColor, valueColor, sectionTitleColor, borderColor, upColor, downColor } = STOCK_CARD_THEME;
|
const inflowColor = mainNetInflow >= 0 ? T.upColor : T.downColor;
|
||||||
const inflowColor = mainNetInflow >= 0 ? upColor : downColor;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box flex="1" borderLeftWidth="1px" borderColor={borderColor} pl={8}>
|
<VStack align="stretch" spacing={2}>
|
||||||
<Text
|
<MetricRow
|
||||||
fontSize="14px"
|
label="主力净流入"
|
||||||
fontWeight="bold"
|
value={formatNetInflow(mainNetInflow)}
|
||||||
color={sectionTitleColor}
|
valueColor={inflowColor}
|
||||||
mb={3}
|
highlight
|
||||||
>
|
/>
|
||||||
主力动态
|
<MetricRow
|
||||||
</Text>
|
label="机构持仓"
|
||||||
<VStack align="stretch" spacing={2} fontSize="14px">
|
value={`${institutionHolding.toFixed(2)}%`}
|
||||||
<HStack justify="space-between">
|
valueColor={T.purple}
|
||||||
<Text color={labelColor}>主力净流入:</Text>
|
highlight
|
||||||
<Text color={inflowColor} fontWeight="bold" fontSize="16px">
|
/>
|
||||||
{formatNetInflow(mainNetInflow)}
|
|
||||||
|
{/* 买卖比例进度条 */}
|
||||||
|
<Box mt={2}>
|
||||||
|
<Progress
|
||||||
|
value={buyRatio}
|
||||||
|
size="sm"
|
||||||
|
sx={{
|
||||||
|
'& > div': {
|
||||||
|
bg: T.upColor,
|
||||||
|
boxShadow: T.upGlow,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
bg={T.downColor}
|
||||||
|
borderRadius="full"
|
||||||
|
h="8px"
|
||||||
|
/>
|
||||||
|
<HStack justify="space-between" mt={2} fontSize="13px">
|
||||||
|
<Text color={T.upColor} fontWeight="600">
|
||||||
|
买入 {buyRatio}%
|
||||||
|
</Text>
|
||||||
|
<Text color={T.downColor} fontWeight="600">
|
||||||
|
卖出 {sellRatio}%
|
||||||
</Text>
|
</Text>
|
||||||
</HStack>
|
</HStack>
|
||||||
<HStack justify="space-between">
|
</Box>
|
||||||
<Text color={labelColor}>机构持仓:</Text>
|
</VStack>
|
||||||
<Text color={valueColor} fontWeight="bold" fontSize="16px">
|
|
||||||
{institutionHolding.toFixed(2)}%
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
|
||||||
{/* 买卖比例条 */}
|
|
||||||
<Box mt={1}>
|
|
||||||
<Progress
|
|
||||||
value={buyRatio}
|
|
||||||
size="sm"
|
|
||||||
sx={{
|
|
||||||
'& > div': { bg: upColor },
|
|
||||||
}}
|
|
||||||
bg={downColor}
|
|
||||||
borderRadius="full"
|
|
||||||
/>
|
|
||||||
<HStack justify="space-between" mt={1} fontSize="14px">
|
|
||||||
<Text color={upColor}>买入{buyRatio}%</Text>
|
|
||||||
<Text color={downColor}>卖出{sellRatio}%</Text>
|
|
||||||
</HStack>
|
|
||||||
</Box>
|
|
||||||
</VStack>
|
|
||||||
</Box>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,59 +1,74 @@
|
|||||||
/**
|
/**
|
||||||
* SecondaryQuote - 次要行情原子组件
|
* SecondaryQuote - 次要行情原子组件
|
||||||
* 显示今开、昨收、最高、最低
|
*
|
||||||
|
* 显示今开、昨收、最高、最低等次要行情数据
|
||||||
|
* 使用水平布局,通过竖线分隔符分隔各项
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { memo } from 'react';
|
import React, { memo } from 'react';
|
||||||
import { HStack, Text } from '@chakra-ui/react';
|
import { Box, HStack, Text } from '@chakra-ui/react';
|
||||||
import { formatPrice } from './formatters';
|
import { formatPrice } from './formatters';
|
||||||
import { STOCK_CARD_THEME } from './theme';
|
import { DEEP_SPACE_THEME as T } from './theme';
|
||||||
|
|
||||||
export interface SecondaryQuoteProps {
|
export interface SecondaryQuoteProps {
|
||||||
|
/** 今日开盘价 */
|
||||||
todayOpen: number;
|
todayOpen: number;
|
||||||
|
/** 昨日收盘价 */
|
||||||
yesterdayClose: number;
|
yesterdayClose: number;
|
||||||
|
/** 今日最高价 */
|
||||||
todayHigh: number;
|
todayHigh: number;
|
||||||
|
/** 今日最低价 */
|
||||||
todayLow: number;
|
todayLow: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 竖线分隔符组件
|
||||||
|
*/
|
||||||
|
const Divider: React.FC = () => (
|
||||||
|
<Box w="1px" h="14px" bg={T.divider} />
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 次要行情展示组件
|
||||||
|
*
|
||||||
|
* 水平排列展示今开、昨收、最高、最低
|
||||||
|
* 最高使用上涨颜色,最低使用下跌颜色
|
||||||
|
*/
|
||||||
export const SecondaryQuote: React.FC<SecondaryQuoteProps> = memo(({
|
export const SecondaryQuote: React.FC<SecondaryQuoteProps> = memo(({
|
||||||
todayOpen,
|
todayOpen,
|
||||||
yesterdayClose,
|
yesterdayClose,
|
||||||
todayHigh,
|
todayHigh,
|
||||||
todayLow,
|
todayLow,
|
||||||
}) => {
|
}) => (
|
||||||
const { labelColor, valueColor, borderColor, upColor, downColor } = STOCK_CARD_THEME;
|
<HStack spacing={6} fontSize="14px" flexWrap="wrap">
|
||||||
|
<Text color={T.textMuted}>
|
||||||
return (
|
今开:
|
||||||
<HStack spacing={4} fontSize="14px" flexWrap="wrap">
|
<Text as="span" color={T.textWhite} fontWeight="600" ml={1}>
|
||||||
<Text color={labelColor}>
|
{formatPrice(todayOpen)}
|
||||||
今开:
|
|
||||||
<Text as="span" color={valueColor} fontWeight="bold">
|
|
||||||
{formatPrice(todayOpen)}
|
|
||||||
</Text>
|
|
||||||
</Text>
|
</Text>
|
||||||
<Text color={borderColor}>|</Text>
|
</Text>
|
||||||
<Text color={labelColor}>
|
<Divider />
|
||||||
昨收:
|
<Text color={T.textMuted}>
|
||||||
<Text as="span" color={valueColor} fontWeight="bold">
|
昨收:
|
||||||
{formatPrice(yesterdayClose)}
|
<Text as="span" color={T.textWhite} fontWeight="600" ml={1}>
|
||||||
</Text>
|
{formatPrice(yesterdayClose)}
|
||||||
</Text>
|
</Text>
|
||||||
<Text color={borderColor}>|</Text>
|
</Text>
|
||||||
<Text color={labelColor}>
|
<Divider />
|
||||||
最高:
|
<Text color={T.textMuted}>
|
||||||
<Text as="span" color={upColor} fontWeight="bold">
|
最高:
|
||||||
{formatPrice(todayHigh)}
|
<Text as="span" color={T.upColor} fontWeight="600" ml={1}>
|
||||||
</Text>
|
{formatPrice(todayHigh)}
|
||||||
</Text>
|
</Text>
|
||||||
<Text color={borderColor}>|</Text>
|
</Text>
|
||||||
<Text color={labelColor}>
|
<Divider />
|
||||||
最低:
|
<Text color={T.textMuted}>
|
||||||
<Text as="span" color={downColor} fontWeight="bold">
|
最低:
|
||||||
{formatPrice(todayLow)}
|
<Text as="span" color={T.downColor} fontWeight="600" ml={1}>
|
||||||
</Text>
|
{formatPrice(todayLow)}
|
||||||
</Text>
|
</Text>
|
||||||
</HStack>
|
</Text>
|
||||||
);
|
</HStack>
|
||||||
});
|
));
|
||||||
|
|
||||||
SecondaryQuote.displayName = 'SecondaryQuote';
|
SecondaryQuote.displayName = 'SecondaryQuote';
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
/**
|
/**
|
||||||
* StockQuoteCard 组件统一导出
|
* StockQuoteCard 组件统一导出
|
||||||
|
*
|
||||||
|
* 组件分类:
|
||||||
|
* - 原子组件:基础展示组件(价格、指标、信息)
|
||||||
|
* - 容器组件:布局和装饰组件(玻璃容器、光效)
|
||||||
|
* - 复合组件:功能组件(对比、搜索)
|
||||||
|
* - 状态组件:加载、错误状态
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// 原子组件
|
// ============================================
|
||||||
|
// 原子组件 - 数据展示
|
||||||
|
// ============================================
|
||||||
export { PriceDisplay } from './PriceDisplay';
|
export { PriceDisplay } from './PriceDisplay';
|
||||||
export { SecondaryQuote } from './SecondaryQuote';
|
export { SecondaryQuote } from './SecondaryQuote';
|
||||||
export { KeyMetrics } from './KeyMetrics';
|
export { KeyMetrics } from './KeyMetrics';
|
||||||
@@ -10,18 +18,36 @@ export { MainForceInfo } from './MainForceInfo';
|
|||||||
export { CompanyInfo } from './CompanyInfo';
|
export { CompanyInfo } from './CompanyInfo';
|
||||||
export { StockHeader } from './StockHeader';
|
export { StockHeader } from './StockHeader';
|
||||||
|
|
||||||
// 复合组件
|
// ============================================
|
||||||
|
// 容器组件 - 布局
|
||||||
|
// ============================================
|
||||||
|
// 注意: 装饰光效组件已移至 @components/FUI/CardGlow
|
||||||
|
export { GlassSection } from './GlassSection';
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// 状态组件 - 加载/错误
|
||||||
|
// ============================================
|
||||||
|
export { LoadingSkeleton } from './LoadingSkeleton';
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// 复合组件 - 功能性
|
||||||
|
// ============================================
|
||||||
export { default as CompareStockInput } from './CompareStockInput';
|
export { default as CompareStockInput } from './CompareStockInput';
|
||||||
export { default as StockCompareModal } from './StockCompareModal';
|
export { default as StockCompareModal } from './StockCompareModal';
|
||||||
|
|
||||||
|
// ============================================
|
||||||
// 工具和主题
|
// 工具和主题
|
||||||
export { STOCK_CARD_THEME } from './theme';
|
// ============================================
|
||||||
|
export { STOCK_CARD_THEME, DEEP_SPACE_THEME, glassCardStyle } from './theme';
|
||||||
export * from './formatters';
|
export * from './formatters';
|
||||||
|
|
||||||
|
// ============================================
|
||||||
// 类型导出
|
// 类型导出
|
||||||
|
// ============================================
|
||||||
export type { PriceDisplayProps } from './PriceDisplay';
|
export type { PriceDisplayProps } from './PriceDisplay';
|
||||||
export type { SecondaryQuoteProps } from './SecondaryQuote';
|
export type { SecondaryQuoteProps } from './SecondaryQuote';
|
||||||
export type { KeyMetricsProps } from './KeyMetrics';
|
export type { KeyMetricsProps } from './KeyMetrics';
|
||||||
export type { MainForceInfoProps } from './MainForceInfo';
|
export type { MainForceInfoProps } from './MainForceInfo';
|
||||||
export type { CompanyInfoProps, CompanyBasicInfo } from './CompanyInfo';
|
export type { CompanyInfoProps, CompanyBasicInfo } from './CompanyInfo';
|
||||||
export type { StockHeaderProps } from './StockHeader';
|
export type { StockHeaderProps } from './StockHeader';
|
||||||
|
export type { GlassSectionProps } from './GlassSection';
|
||||||
|
|||||||
@@ -6,13 +6,19 @@
|
|||||||
* - 光影深度,弥散背景光
|
* - 光影深度,弥散背景光
|
||||||
* - 极致圆角,科幻数据终端感
|
* - 极致圆角,科幻数据终端感
|
||||||
*
|
*
|
||||||
* 保留原有所有功能:
|
* 功能模块:
|
||||||
* - 股票头部(名称、代码、行业、对比、关注、分享)
|
* - 股票头部:名称、代码、行业标签、操作按钮(对比、关注、分享)
|
||||||
* - 价格显示(当前价、涨跌幅)
|
* - 价格展示:当前价格、涨跌幅 Badge
|
||||||
* - 次要行情(今开、昨收、最高、最低)
|
* - 次要行情:今开、昨收、最高、最低(SecondaryQuote 组件)
|
||||||
* - 关键指标(PE、市值、股本、换手率、52周)
|
* - 数据区块:估值指标、市值股本、主力动态(三列 GlassSection 布局)
|
||||||
* - 主力动态(净流入、机构持仓、买卖比)
|
*
|
||||||
* - 公司信息(成立、注册资本、所在地、官网、简介)
|
* 组件结构:
|
||||||
|
* - GlowDecorations:装饰性光效(背景层)
|
||||||
|
* - LoadingSkeleton:加载骨架屏
|
||||||
|
* - GlassSection:玻璃容器(包装数据区块)
|
||||||
|
* - SecondaryQuote:次要行情展示
|
||||||
|
* - KeyMetrics:关键指标(估值 + 市值)
|
||||||
|
* - MainForceInfo:主力动态
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { memo } from 'react';
|
import React, { memo } from 'react';
|
||||||
@@ -25,144 +31,38 @@ import {
|
|||||||
Badge,
|
Badge,
|
||||||
IconButton,
|
IconButton,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Skeleton,
|
|
||||||
Progress,
|
|
||||||
Link,
|
|
||||||
Icon,
|
|
||||||
useDisclosure,
|
useDisclosure,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { Share2, Calendar, Coins, MapPin, Globe } from 'lucide-react';
|
import { Share2 } from 'lucide-react';
|
||||||
import FavoriteButton from '@components/FavoriteButton';
|
import FavoriteButton from '@components/FavoriteButton';
|
||||||
|
import { CardGlow } from '@components/FUI';
|
||||||
|
|
||||||
import { StockCompareModal, CompareStockInput } from './components';
|
// 子组件导入
|
||||||
|
import {
|
||||||
|
StockCompareModal,
|
||||||
|
CompareStockInput,
|
||||||
|
LoadingSkeleton,
|
||||||
|
GlassSection,
|
||||||
|
SecondaryQuote,
|
||||||
|
MainForceInfo,
|
||||||
|
} from './components';
|
||||||
|
|
||||||
|
// Hooks 和工具
|
||||||
import { useStockQuoteData, useStockCompare } from './hooks';
|
import { useStockQuoteData, useStockCompare } from './hooks';
|
||||||
import { DEEP_SPACE_THEME, glassCardStyle, decorativeElements } from './components/theme';
|
import { DEEP_SPACE_THEME, glassCardStyle } from './components/theme';
|
||||||
import { formatPrice, formatChangePercent, formatNetInflow } from './components/formatters';
|
import { formatPrice, formatChangePercent } from './components/formatters';
|
||||||
import { formatRegisteredCapital, formatDate } from '../CompanyOverview/utils';
|
|
||||||
import type { StockQuoteCardProps } from './types';
|
import type { StockQuoteCardProps } from './types';
|
||||||
|
|
||||||
|
/** 主题常量简写 */
|
||||||
const T = DEEP_SPACE_THEME;
|
const T = DEEP_SPACE_THEME;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 装饰性光效组件
|
* 指标行组件 - 用于数据区块内的单行指标展示
|
||||||
*/
|
*
|
||||||
const GlowDecorations: React.FC = () => (
|
* @param label - 指标标签
|
||||||
<>
|
* @param value - 指标值
|
||||||
{/* 顶部金色光条 */}
|
* @param valueColor - 值的颜色(默认白色)
|
||||||
<Box {...decorativeElements.topGlowBar} />
|
* @param highlight - 是否高亮显示(加粗 + 发光)
|
||||||
|
|
||||||
{/* 左上角光晕 */}
|
|
||||||
<Box
|
|
||||||
{...decorativeElements.cornerGlow}
|
|
||||||
top="-40px"
|
|
||||||
left="-40px"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* 右下角光晕 */}
|
|
||||||
<Box
|
|
||||||
{...decorativeElements.cornerGlow}
|
|
||||||
bottom="-40px"
|
|
||||||
right="-40px"
|
|
||||||
background={`radial-gradient(circle, rgba(0, 212, 255, 0.1) 0%, transparent 70%)`}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* 背景网格 */}
|
|
||||||
<Box {...decorativeElements.gridOverlay} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 加载骨架屏
|
|
||||||
*/
|
|
||||||
const LoadingSkeleton: React.FC = () => (
|
|
||||||
<Box
|
|
||||||
{...glassCardStyle.containerGold}
|
|
||||||
p={8}
|
|
||||||
>
|
|
||||||
<GlowDecorations />
|
|
||||||
|
|
||||||
<VStack align="stretch" spacing={6} position="relative" zIndex={1}>
|
|
||||||
{/* 头部骨架 */}
|
|
||||||
<Flex justify="space-between">
|
|
||||||
<HStack spacing={3}>
|
|
||||||
<Skeleton height="32px" width="120px" startColor={T.bgInset} endColor={T.borderGlass} borderRadius={T.radiusSM} />
|
|
||||||
<Skeleton height="24px" width="80px" startColor={T.bgInset} endColor={T.borderGlass} borderRadius={T.radiusSM} />
|
|
||||||
</HStack>
|
|
||||||
<HStack spacing={2}>
|
|
||||||
<Skeleton height="32px" width="32px" startColor={T.bgInset} endColor={T.borderGlass} borderRadius={T.radiusSM} />
|
|
||||||
<Skeleton height="32px" width="32px" startColor={T.bgInset} endColor={T.borderGlass} borderRadius={T.radiusSM} />
|
|
||||||
</HStack>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
{/* 价格骨架 */}
|
|
||||||
<HStack>
|
|
||||||
<Skeleton height="56px" width="160px" startColor={T.bgInset} endColor={T.borderGlass} borderRadius={T.radiusMD} />
|
|
||||||
<Skeleton height="36px" width="100px" startColor={T.bgInset} endColor={T.borderGlass} borderRadius={T.radiusMD} />
|
|
||||||
</HStack>
|
|
||||||
|
|
||||||
{/* 内容骨架 */}
|
|
||||||
<Flex gap={6}>
|
|
||||||
<Box flex={1}>
|
|
||||||
<Skeleton height="120px" startColor={T.bgInset} endColor={T.borderGlass} borderRadius={T.radiusLG} />
|
|
||||||
</Box>
|
|
||||||
<Box flex={1}>
|
|
||||||
<Skeleton height="120px" startColor={T.bgInset} endColor={T.borderGlass} borderRadius={T.radiusLG} />
|
|
||||||
</Box>
|
|
||||||
</Flex>
|
|
||||||
</VStack>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 玻璃态内嵌区块
|
|
||||||
*/
|
|
||||||
interface GlassSectionProps {
|
|
||||||
title: string;
|
|
||||||
children: React.ReactNode;
|
|
||||||
flex?: number | string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const GlassSection: React.FC<GlassSectionProps> = ({ title, children, flex = 1 }) => (
|
|
||||||
<Box
|
|
||||||
flex={flex}
|
|
||||||
bg={T.bgInset}
|
|
||||||
borderRadius={T.radiusLG}
|
|
||||||
border={`1px solid ${T.borderGlass}`}
|
|
||||||
p={4}
|
|
||||||
position="relative"
|
|
||||||
transition={T.transitionFast}
|
|
||||||
_hover={{
|
|
||||||
borderColor: T.borderGoldHover,
|
|
||||||
bg: 'rgba(15, 18, 35, 0.6)',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* 区块顶部光条 */}
|
|
||||||
<Box
|
|
||||||
position="absolute"
|
|
||||||
top={0}
|
|
||||||
left="20px"
|
|
||||||
right="20px"
|
|
||||||
height="1px"
|
|
||||||
background={`linear-gradient(90deg, transparent, ${T.gold}40, transparent)`}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Text
|
|
||||||
fontSize="14px"
|
|
||||||
fontWeight="700"
|
|
||||||
color={T.gold}
|
|
||||||
mb={3}
|
|
||||||
textTransform="uppercase"
|
|
||||||
letterSpacing="0.1em"
|
|
||||||
textShadow={`0 0 12px ${T.gold}60`}
|
|
||||||
>
|
|
||||||
{title}
|
|
||||||
</Text>
|
|
||||||
{children}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 指标行组件
|
|
||||||
*/
|
*/
|
||||||
interface MetricRowProps {
|
interface MetricRowProps {
|
||||||
label: string;
|
label: string;
|
||||||
@@ -222,12 +122,11 @@ const StockQuoteCard: React.FC<StockQuoteCardProps> = ({
|
|||||||
return <LoadingSkeleton />;
|
return <LoadingSkeleton />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 涨跌判断
|
// 涨跌判断(用于价格颜色)
|
||||||
const isUp = quoteData.changePercent >= 0;
|
const isUp = quoteData.changePercent >= 0;
|
||||||
const priceColor = isUp ? T.upColor : T.downColor;
|
const priceColor = isUp ? T.upColor : T.downColor;
|
||||||
const priceGlow = isUp ? T.upGlow : T.downGlow;
|
const priceGlow = isUp ? T.upGlow : T.downGlow;
|
||||||
const priceBg = isUp ? T.upColorMuted : T.downColorMuted;
|
const priceBg = isUp ? T.upColorMuted : T.downColorMuted;
|
||||||
const inflowColor = (quoteData.mainNetInflow || 0) >= 0 ? T.upColor : T.downColor;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -235,7 +134,7 @@ const StockQuoteCard: React.FC<StockQuoteCardProps> = ({
|
|||||||
{...glassCardStyle.containerGold}
|
{...glassCardStyle.containerGold}
|
||||||
p={6}
|
p={6}
|
||||||
>
|
>
|
||||||
<GlowDecorations />
|
<CardGlow variant="gold" />
|
||||||
|
|
||||||
{/* 内容区域(在装饰层之上)*/}
|
{/* 内容区域(在装饰层之上)*/}
|
||||||
<VStack align="stretch" spacing={4} position="relative" zIndex={1}>
|
<VStack align="stretch" spacing={4} position="relative" zIndex={1}>
|
||||||
@@ -340,39 +239,16 @@ const StockQuoteCard: React.FC<StockQuoteCardProps> = ({
|
|||||||
</HStack>
|
</HStack>
|
||||||
|
|
||||||
{/* ========== 次要行情 ========== */}
|
{/* ========== 次要行情 ========== */}
|
||||||
<HStack spacing={6} fontSize="14px" flexWrap="wrap">
|
<SecondaryQuote
|
||||||
<Text color={T.textMuted}>
|
todayOpen={quoteData.todayOpen}
|
||||||
今开:
|
yesterdayClose={quoteData.yesterdayClose}
|
||||||
<Text as="span" color={T.textWhite} fontWeight="600" ml={1}>
|
todayHigh={quoteData.todayHigh}
|
||||||
{formatPrice(quoteData.todayOpen)}
|
todayLow={quoteData.todayLow}
|
||||||
</Text>
|
/>
|
||||||
</Text>
|
|
||||||
<Box w="1px" h="14px" bg={T.divider} />
|
|
||||||
<Text color={T.textMuted}>
|
|
||||||
昨收:
|
|
||||||
<Text as="span" color={T.textWhite} fontWeight="600" ml={1}>
|
|
||||||
{formatPrice(quoteData.yesterdayClose)}
|
|
||||||
</Text>
|
|
||||||
</Text>
|
|
||||||
<Box w="1px" h="14px" bg={T.divider} />
|
|
||||||
<Text color={T.textMuted}>
|
|
||||||
最高:
|
|
||||||
<Text as="span" color={T.upColor} fontWeight="600" ml={1}>
|
|
||||||
{formatPrice(quoteData.todayHigh)}
|
|
||||||
</Text>
|
|
||||||
</Text>
|
|
||||||
<Box w="1px" h="14px" bg={T.divider} />
|
|
||||||
<Text color={T.textMuted}>
|
|
||||||
最低:
|
|
||||||
<Text as="span" color={T.downColor} fontWeight="600" ml={1}>
|
|
||||||
{formatPrice(quoteData.todayLow)}
|
|
||||||
</Text>
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
|
||||||
|
|
||||||
{/* ========== 数据区块(三列布局)========== */}
|
{/* ========== 数据区块(三列布局)========== */}
|
||||||
<Flex gap={4} flexWrap={{ base: 'wrap', lg: 'nowrap' }}>
|
<Flex gap={4} flexWrap={{ base: 'wrap', lg: 'nowrap' }}>
|
||||||
{/* 第一列:估值指标 */}
|
{/* 第一列:估值指标 - PE、流通股本、换手率 */}
|
||||||
<GlassSection title="估值指标" flex={1}>
|
<GlassSection title="估值指标" flex={1}>
|
||||||
<VStack align="stretch" spacing={2}>
|
<VStack align="stretch" spacing={2}>
|
||||||
<MetricRow
|
<MetricRow
|
||||||
@@ -393,7 +269,7 @@ const StockQuoteCard: React.FC<StockQuoteCardProps> = ({
|
|||||||
</VStack>
|
</VStack>
|
||||||
</GlassSection>
|
</GlassSection>
|
||||||
|
|
||||||
{/* 第二列:市值股本 */}
|
{/* 第二列:市值股本 - 流通市值、发行总股本、52周波动 */}
|
||||||
<GlassSection title="市值股本" flex={1}>
|
<GlassSection title="市值股本" flex={1}>
|
||||||
<VStack align="stretch" spacing={2}>
|
<VStack align="stretch" spacing={2}>
|
||||||
<MetricRow
|
<MetricRow
|
||||||
@@ -415,114 +291,16 @@ const StockQuoteCard: React.FC<StockQuoteCardProps> = ({
|
|||||||
|
|
||||||
{/* 第三列:主力动态 */}
|
{/* 第三列:主力动态 */}
|
||||||
<GlassSection title="主力动态" flex={1}>
|
<GlassSection title="主力动态" flex={1}>
|
||||||
<VStack align="stretch" spacing={2}>
|
<MainForceInfo
|
||||||
<MetricRow
|
mainNetInflow={quoteData.mainNetInflow || 0}
|
||||||
label="主力净流入"
|
institutionHolding={quoteData.institutionHolding}
|
||||||
value={formatNetInflow(quoteData.mainNetInflow)}
|
buyRatio={quoteData.buyRatio}
|
||||||
valueColor={inflowColor}
|
sellRatio={quoteData.sellRatio}
|
||||||
highlight
|
/>
|
||||||
/>
|
|
||||||
<MetricRow
|
|
||||||
label="机构持仓"
|
|
||||||
value={`${quoteData.institutionHolding.toFixed(2)}%`}
|
|
||||||
valueColor={T.purple}
|
|
||||||
highlight
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* 买卖比例条 */}
|
|
||||||
<Box mt={2}>
|
|
||||||
<Progress
|
|
||||||
value={quoteData.buyRatio}
|
|
||||||
size="sm"
|
|
||||||
sx={{
|
|
||||||
'& > div': {
|
|
||||||
bg: T.upColor,
|
|
||||||
boxShadow: T.upGlow,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
bg={T.downColor}
|
|
||||||
borderRadius="full"
|
|
||||||
h="8px"
|
|
||||||
/>
|
|
||||||
<HStack justify="space-between" mt={2} fontSize="13px">
|
|
||||||
<Text color={T.upColor} fontWeight="600">
|
|
||||||
买入 {quoteData.buyRatio}%
|
|
||||||
</Text>
|
|
||||||
<Text color={T.downColor} fontWeight="600">
|
|
||||||
卖出 {quoteData.sellRatio}%
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
|
||||||
</Box>
|
|
||||||
</VStack>
|
|
||||||
</GlassSection>
|
</GlassSection>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
{/* ========== 公司信息(已注释)========== */}
|
{/* 公司信息区块已移至 CompanyOverview 模块 */}
|
||||||
{/* {basicInfo && (
|
|
||||||
<>
|
|
||||||
<Box
|
|
||||||
h="1px"
|
|
||||||
bg={`linear-gradient(90deg, transparent, ${T.gold}30, transparent)`}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Flex gap={8} flexWrap={{ base: 'wrap', lg: 'nowrap' }}>
|
|
||||||
<HStack spacing={6} flex={1} flexWrap="wrap" fontSize="14px">
|
|
||||||
<HStack spacing={2}>
|
|
||||||
<Icon as={Calendar} color={T.textMuted} boxSize={4} />
|
|
||||||
<Text color={T.textMuted}>成立:</Text>
|
|
||||||
<Text color={T.textWhite} fontWeight="600">
|
|
||||||
{formatDate(basicInfo.establish_date)}
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
|
||||||
<HStack spacing={2}>
|
|
||||||
<Icon as={Coins} color={T.textMuted} boxSize={4} />
|
|
||||||
<Text color={T.textMuted}>注册资本:</Text>
|
|
||||||
<Text color={T.textWhite} fontWeight="600">
|
|
||||||
{formatRegisteredCapital(basicInfo.reg_capital)}
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
|
||||||
<HStack spacing={2}>
|
|
||||||
<Icon as={MapPin} color={T.textMuted} boxSize={4} />
|
|
||||||
<Text color={T.textMuted}>所在地:</Text>
|
|
||||||
<Text color={T.textWhite} fontWeight="600">
|
|
||||||
{basicInfo.province} {basicInfo.city}
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
|
||||||
<HStack spacing={2}>
|
|
||||||
<Icon as={Globe} color={T.textMuted} boxSize={4} />
|
|
||||||
{basicInfo.website ? (
|
|
||||||
<Link
|
|
||||||
href={basicInfo.website}
|
|
||||||
isExternal
|
|
||||||
color={T.cyan}
|
|
||||||
fontWeight="600"
|
|
||||||
_hover={{ color: T.textPrimary, textDecoration: 'underline' }}
|
|
||||||
>
|
|
||||||
访问官网
|
|
||||||
</Link>
|
|
||||||
) : (
|
|
||||||
<Text color={T.textWhiteMuted}>暂无官网</Text>
|
|
||||||
)}
|
|
||||||
</HStack>
|
|
||||||
</HStack>
|
|
||||||
|
|
||||||
<Box
|
|
||||||
flex={2}
|
|
||||||
borderLeftWidth="1px"
|
|
||||||
borderColor={T.divider}
|
|
||||||
pl={8}
|
|
||||||
minW="0"
|
|
||||||
>
|
|
||||||
<Text fontSize="14px" color={T.textMuted} noOfLines={2}>
|
|
||||||
<Text as="span" fontWeight="700" color={T.textSecondary}>
|
|
||||||
公司简介:
|
|
||||||
</Text>
|
|
||||||
{basicInfo.company_intro || '暂无'}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
</Flex>
|
|
||||||
</>
|
|
||||||
)} */}
|
|
||||||
</VStack>
|
</VStack>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user