更新Company页面的UI为FUI风格
This commit is contained in:
@@ -1,8 +1,10 @@
|
|||||||
/**
|
/**
|
||||||
* SubTabContainer - 二级导航容器组件
|
* SubTabContainer - 二级导航容器组件
|
||||||
*
|
*
|
||||||
* 用于模块内的子功能切换(如公司档案下的股权结构、管理团队等)
|
* 深空 FUI 设计风格(Glassmorphism + Ash Thorp + James Turrell)
|
||||||
* 与 TabContainer(一级导航)区分:无 Card 包裹,直接融入父容器
|
* - 玻璃态导航栏,漂浮感
|
||||||
|
* - 选中态发光效果,科幻数据终端感
|
||||||
|
* - 流畅的过渡动画
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* ```tsx
|
* ```tsx
|
||||||
@@ -43,6 +45,40 @@ export interface SubTabConfig {
|
|||||||
component?: ComponentType<any>;
|
component?: ComponentType<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 深空 FUI 主题配置
|
||||||
|
*/
|
||||||
|
const DEEP_SPACE = {
|
||||||
|
// 背景
|
||||||
|
bgGlass: 'rgba(12, 14, 28, 0.6)',
|
||||||
|
bgGlassHover: 'rgba(18, 22, 42, 0.7)',
|
||||||
|
|
||||||
|
// 边框
|
||||||
|
borderGold: 'rgba(212, 175, 55, 0.2)',
|
||||||
|
borderGoldHover: 'rgba(212, 175, 55, 0.5)',
|
||||||
|
borderGlass: 'rgba(255, 255, 255, 0.06)',
|
||||||
|
|
||||||
|
// 发光
|
||||||
|
glowGold: '0 0 30px rgba(212, 175, 55, 0.25), 0 4px 20px rgba(0, 0, 0, 0.3)',
|
||||||
|
innerGlow: 'inset 0 1px 0 rgba(255, 255, 255, 0.08)',
|
||||||
|
|
||||||
|
// 文字
|
||||||
|
textWhite: 'rgba(255, 255, 255, 0.95)',
|
||||||
|
textMuted: 'rgba(255, 255, 255, 0.6)',
|
||||||
|
textGold: '#F4D03F',
|
||||||
|
textDark: '#0A0A14',
|
||||||
|
|
||||||
|
// 选中态
|
||||||
|
selectedBg: 'linear-gradient(135deg, rgba(212, 175, 55, 0.95) 0%, rgba(184, 150, 12, 0.95) 100%)',
|
||||||
|
|
||||||
|
// 圆角
|
||||||
|
radius: '12px',
|
||||||
|
radiusLG: '16px',
|
||||||
|
|
||||||
|
// 动画
|
||||||
|
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 主题配置
|
* 主题配置
|
||||||
*/
|
*/
|
||||||
@@ -56,16 +92,16 @@ export interface SubTabTheme {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 预设主题 - FUI 风格优化
|
* 预设主题 - 深空 FUI 风格
|
||||||
*/
|
*/
|
||||||
const THEME_PRESETS: Record<string, SubTabTheme> = {
|
const THEME_PRESETS: Record<string, SubTabTheme> = {
|
||||||
blackGold: {
|
blackGold: {
|
||||||
bg: 'transparent',
|
bg: DEEP_SPACE.bgGlass,
|
||||||
borderColor: 'rgba(212, 175, 55, 0.15)',
|
borderColor: DEEP_SPACE.borderGold,
|
||||||
tabSelectedBg: 'linear-gradient(135deg, rgba(212, 175, 55, 0.95) 0%, rgba(184, 150, 12, 0.95) 100%)',
|
tabSelectedBg: DEEP_SPACE.selectedBg,
|
||||||
tabSelectedColor: '#0A0A14',
|
tabSelectedColor: DEEP_SPACE.textDark,
|
||||||
tabUnselectedColor: 'rgba(255, 255, 255, 0.85)', // 调亮:白色更清晰
|
tabUnselectedColor: DEEP_SPACE.textWhite,
|
||||||
tabHoverBg: 'rgba(212, 175, 55, 0.15)',
|
tabHoverBg: DEEP_SPACE.bgGlassHover,
|
||||||
},
|
},
|
||||||
default: {
|
default: {
|
||||||
bg: 'white',
|
bg: 'white',
|
||||||
@@ -158,74 +194,102 @@ const SubTabContainer: React.FC<SubTabContainerProps> = memo(({
|
|||||||
index={currentIndex}
|
index={currentIndex}
|
||||||
onChange={handleTabChange}
|
onChange={handleTabChange}
|
||||||
>
|
>
|
||||||
|
{/* TabList - 玻璃态导航栏 */}
|
||||||
<TabList
|
<TabList
|
||||||
bg={theme.bg}
|
bg={theme.bg}
|
||||||
|
backdropFilter="blur(20px)"
|
||||||
|
WebkitBackdropFilter="blur(20px)"
|
||||||
borderBottom="1px solid"
|
borderBottom="1px solid"
|
||||||
borderColor={theme.borderColor}
|
borderColor={theme.borderColor}
|
||||||
pl={2}
|
borderRadius={DEEP_SPACE.radiusLG}
|
||||||
pr={4}
|
mx={2}
|
||||||
|
mb={2}
|
||||||
|
px={3}
|
||||||
py={3}
|
py={3}
|
||||||
flexWrap="nowrap"
|
flexWrap="nowrap"
|
||||||
gap={2}
|
gap={2}
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
overflowX="auto"
|
overflowX="auto"
|
||||||
|
position="relative"
|
||||||
|
boxShadow={DEEP_SPACE.innerGlow}
|
||||||
css={{
|
css={{
|
||||||
'&::-webkit-scrollbar': { display: 'none' },
|
'&::-webkit-scrollbar': { display: 'none' },
|
||||||
scrollbarWidth: 'none',
|
scrollbarWidth: 'none',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{tabs.map((tab) => (
|
{/* 顶部金色光条 */}
|
||||||
<Tab
|
<Box
|
||||||
key={tab.key}
|
position="absolute"
|
||||||
color={theme.tabUnselectedColor}
|
top={0}
|
||||||
borderRadius="md"
|
left="50%"
|
||||||
px={5}
|
transform="translateX(-50%)"
|
||||||
py={2.5}
|
width="50%"
|
||||||
fontSize="sm"
|
height="1px"
|
||||||
fontWeight="500"
|
background={`linear-gradient(90deg, transparent, rgba(212, 175, 55, 0.4), transparent)`}
|
||||||
whiteSpace="nowrap"
|
/>
|
||||||
flexShrink={0}
|
|
||||||
border="1px solid transparent"
|
{tabs.map((tab, idx) => {
|
||||||
position="relative"
|
const isSelected = idx === currentIndex;
|
||||||
letterSpacing="0.05em"
|
|
||||||
transition="all 0.25s cubic-bezier(0.4, 0, 0.2, 1)"
|
return (
|
||||||
_before={{
|
<Tab
|
||||||
content: '""',
|
key={tab.key}
|
||||||
position: 'absolute',
|
color={theme.tabUnselectedColor}
|
||||||
bottom: '-1px',
|
borderRadius={DEEP_SPACE.radius}
|
||||||
left: '50%',
|
px={6}
|
||||||
transform: 'translateX(-50%)',
|
py={3}
|
||||||
width: '0%',
|
fontSize="15px"
|
||||||
height: '2px',
|
fontWeight="500"
|
||||||
bg: '#D4AF37',
|
whiteSpace="nowrap"
|
||||||
transition: 'width 0.25s ease',
|
flexShrink={0}
|
||||||
}}
|
border="1px solid transparent"
|
||||||
_selected={{
|
position="relative"
|
||||||
bg: theme.tabSelectedBg,
|
letterSpacing="0.03em"
|
||||||
color: theme.tabSelectedColor,
|
transition={DEEP_SPACE.transition}
|
||||||
fontWeight: '700',
|
_before={{
|
||||||
boxShadow: '0 4px 16px rgba(212, 175, 55, 0.35), 0 0 20px rgba(212, 175, 55, 0.15)',
|
content: '""',
|
||||||
border: '1px solid rgba(212, 175, 55, 0.6)',
|
position: 'absolute',
|
||||||
transform: 'translateY(-1px)',
|
bottom: '-1px',
|
||||||
_before: {
|
left: '50%',
|
||||||
width: '80%',
|
transform: 'translateX(-50%)',
|
||||||
},
|
width: isSelected ? '70%' : '0%',
|
||||||
}}
|
height: '2px',
|
||||||
_hover={{
|
bg: '#D4AF37',
|
||||||
bg: theme.tabHoverBg,
|
borderRadius: 'full',
|
||||||
border: '1px solid rgba(212, 175, 55, 0.35)',
|
transition: 'width 0.3s ease',
|
||||||
transform: 'translateY(-1px)',
|
boxShadow: isSelected ? '0 0 10px rgba(212, 175, 55, 0.5)' : 'none',
|
||||||
_before: {
|
}}
|
||||||
width: '60%',
|
_selected={{
|
||||||
},
|
bg: theme.tabSelectedBg,
|
||||||
}}
|
color: theme.tabSelectedColor,
|
||||||
>
|
fontWeight: '700',
|
||||||
<HStack spacing={2}>
|
boxShadow: DEEP_SPACE.glowGold,
|
||||||
{tab.icon && <Icon as={tab.icon} boxSize={4} />}
|
border: `1px solid ${DEEP_SPACE.borderGoldHover}`,
|
||||||
<Text>{tab.name}</Text>
|
transform: 'translateY(-2px)',
|
||||||
</HStack>
|
}}
|
||||||
</Tab>
|
_hover={{
|
||||||
))}
|
bg: isSelected ? undefined : theme.tabHoverBg,
|
||||||
|
border: isSelected ? undefined : `1px solid ${DEEP_SPACE.borderGold}`,
|
||||||
|
transform: 'translateY(-1px)',
|
||||||
|
}}
|
||||||
|
_active={{
|
||||||
|
transform: 'translateY(0)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<HStack spacing={2}>
|
||||||
|
{tab.icon && (
|
||||||
|
<Icon
|
||||||
|
as={tab.icon}
|
||||||
|
boxSize={4}
|
||||||
|
opacity={isSelected ? 1 : 0.7}
|
||||||
|
transition="opacity 0.2s"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Text>{tab.name}</Text>
|
||||||
|
</HStack>
|
||||||
|
</Tab>
|
||||||
|
);
|
||||||
|
})}
|
||||||
{rightElement && (
|
{rightElement && (
|
||||||
<>
|
<>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
|
|||||||
@@ -1,20 +1,193 @@
|
|||||||
/**
|
/**
|
||||||
* StockQuoteCard 黑金主题配置
|
* StockQuoteCard 深空 FUI 主题配置
|
||||||
|
*
|
||||||
|
* 设计灵感:
|
||||||
|
* - Ash Thorp / Linear.app 科幻 FUI 风格
|
||||||
|
* - James Turrell 光影艺术
|
||||||
|
* - Glassmorphism 玻璃态设计
|
||||||
|
* - 深空漂浮的数据终端
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const STOCK_CARD_THEME = {
|
// ============================================
|
||||||
// 背景和边框
|
// 深空 FUI 主题系统
|
||||||
cardBg: '#1A202C',
|
// ============================================
|
||||||
borderColor: '#C9A961',
|
export const DEEP_SPACE_THEME = {
|
||||||
|
// === 深空背景层 ===
|
||||||
|
// 最深层背景(宇宙深邃)
|
||||||
|
bgDeep: 'rgba(8, 8, 18, 0.98)',
|
||||||
|
// 玻璃卡片背景(半透明漂浮)
|
||||||
|
bgGlass: 'rgba(15, 18, 35, 0.65)',
|
||||||
|
bgGlassHover: 'rgba(20, 25, 50, 0.75)',
|
||||||
|
// 内嵌区块背景
|
||||||
|
bgInset: 'rgba(10, 12, 25, 0.5)',
|
||||||
|
|
||||||
// 文字颜色
|
// === Glassmorphism 效果 ===
|
||||||
labelColor: '#C9A961',
|
blur: '24px',
|
||||||
valueColor: '#F4D03F',
|
blurLight: '12px',
|
||||||
sectionTitleColor: '#F4D03F',
|
|
||||||
|
|
||||||
// 涨跌颜色(红涨绿跌)
|
// === 边框系统 ===
|
||||||
upColor: '#F44336',
|
// 主边框(发光金边)
|
||||||
downColor: '#4CAF50',
|
borderGold: 'rgba(212, 175, 55, 0.4)',
|
||||||
|
borderGoldHover: 'rgba(212, 175, 55, 0.7)',
|
||||||
|
// 玻璃边框(微妙白边)
|
||||||
|
borderGlass: 'rgba(255, 255, 255, 0.08)',
|
||||||
|
borderGlassHover: 'rgba(255, 255, 255, 0.15)',
|
||||||
|
// 分隔线
|
||||||
|
divider: 'rgba(212, 175, 55, 0.15)',
|
||||||
|
|
||||||
|
// === 发光系统(James Turrell 风格)===
|
||||||
|
// 金色光晕
|
||||||
|
glowGold: '0 0 40px rgba(212, 175, 55, 0.15), 0 0 80px rgba(212, 175, 55, 0.08)',
|
||||||
|
glowGoldHover: '0 0 60px rgba(212, 175, 55, 0.25), 0 0 120px rgba(212, 175, 55, 0.12)',
|
||||||
|
// 青色光晕(科技感)
|
||||||
|
glowCyan: '0 0 30px rgba(0, 212, 255, 0.2), 0 0 60px rgba(0, 212, 255, 0.1)',
|
||||||
|
// 漂浮阴影(深度感)
|
||||||
|
floatShadow: '0 20px 60px rgba(0, 0, 0, 0.4), 0 8px 20px rgba(0, 0, 0, 0.3)',
|
||||||
|
// 内发光(Turrell 光隧道效果)
|
||||||
|
innerGlow: 'inset 0 1px 0 rgba(255, 255, 255, 0.05), inset 0 -1px 0 rgba(0, 0, 0, 0.2)',
|
||||||
|
|
||||||
|
// === 圆角系统(极致圆角)===
|
||||||
|
radiusXL: '24px',
|
||||||
|
radiusLG: '16px',
|
||||||
|
radiusMD: '12px',
|
||||||
|
radiusSM: '8px',
|
||||||
|
|
||||||
|
// === 文字颜色 ===
|
||||||
|
// 主文字(高亮金色)
|
||||||
|
textPrimary: '#F4D03F',
|
||||||
|
// 次要文字(柔和金色)
|
||||||
|
textSecondary: 'rgba(212, 175, 55, 0.85)',
|
||||||
|
// 标签文字(低对比度)
|
||||||
|
textMuted: 'rgba(180, 160, 120, 0.7)',
|
||||||
|
// 纯白文字
|
||||||
|
textWhite: 'rgba(255, 255, 255, 0.95)',
|
||||||
|
textWhiteMuted: 'rgba(255, 255, 255, 0.6)',
|
||||||
|
|
||||||
|
// === 涨跌颜色(红涨绿跌)===
|
||||||
|
upColor: '#FF4757',
|
||||||
|
upColorMuted: 'rgba(255, 71, 87, 0.15)',
|
||||||
|
upGlow: '0 0 20px rgba(255, 71, 87, 0.3)',
|
||||||
|
downColor: '#00D984',
|
||||||
|
downColorMuted: 'rgba(0, 217, 132, 0.15)',
|
||||||
|
downGlow: '0 0 20px rgba(0, 217, 132, 0.3)',
|
||||||
|
|
||||||
|
// === 强调色 ===
|
||||||
|
gold: '#D4AF37',
|
||||||
|
goldBright: '#F4D03F',
|
||||||
|
cyan: '#00D4FF',
|
||||||
|
purple: '#A855F7',
|
||||||
|
orange: '#FF6B35',
|
||||||
|
|
||||||
|
// === 动画 ===
|
||||||
|
transitionFast: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
|
transitionNormal: 'all 0.35s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
|
transitionSlow: 'all 0.5s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
// 兼容旧组件的导出
|
||||||
|
export const STOCK_CARD_THEME = {
|
||||||
|
cardBg: DEEP_SPACE_THEME.bgGlass,
|
||||||
|
borderColor: DEEP_SPACE_THEME.borderGold,
|
||||||
|
labelColor: DEEP_SPACE_THEME.textSecondary,
|
||||||
|
valueColor: DEEP_SPACE_THEME.textPrimary,
|
||||||
|
sectionTitleColor: DEEP_SPACE_THEME.textPrimary,
|
||||||
|
upColor: DEEP_SPACE_THEME.upColor,
|
||||||
|
downColor: DEEP_SPACE_THEME.downColor,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// 玻璃卡片样式生成器
|
||||||
|
// ============================================
|
||||||
|
export const glassCardStyle = {
|
||||||
|
// 主容器玻璃效果
|
||||||
|
container: {
|
||||||
|
bg: DEEP_SPACE_THEME.bgGlass,
|
||||||
|
backdropFilter: `blur(${DEEP_SPACE_THEME.blur})`,
|
||||||
|
WebkitBackdropFilter: `blur(${DEEP_SPACE_THEME.blur})`,
|
||||||
|
borderRadius: DEEP_SPACE_THEME.radiusXL,
|
||||||
|
border: `1px solid ${DEEP_SPACE_THEME.borderGlass}`,
|
||||||
|
boxShadow: `${DEEP_SPACE_THEME.floatShadow}, ${DEEP_SPACE_THEME.innerGlow}`,
|
||||||
|
position: 'relative' as const,
|
||||||
|
overflow: 'hidden' as const,
|
||||||
|
transition: DEEP_SPACE_THEME.transitionNormal,
|
||||||
|
_hover: {
|
||||||
|
bg: DEEP_SPACE_THEME.bgGlassHover,
|
||||||
|
borderColor: DEEP_SPACE_THEME.borderGoldHover,
|
||||||
|
boxShadow: `${DEEP_SPACE_THEME.glowGoldHover}, ${DEEP_SPACE_THEME.floatShadow}`,
|
||||||
|
transform: 'translateY(-2px)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// 金色高亮边框版本
|
||||||
|
containerGold: {
|
||||||
|
bg: DEEP_SPACE_THEME.bgGlass,
|
||||||
|
backdropFilter: `blur(${DEEP_SPACE_THEME.blur})`,
|
||||||
|
WebkitBackdropFilter: `blur(${DEEP_SPACE_THEME.blur})`,
|
||||||
|
borderRadius: DEEP_SPACE_THEME.radiusXL,
|
||||||
|
border: `1px solid ${DEEP_SPACE_THEME.borderGold}`,
|
||||||
|
boxShadow: `${DEEP_SPACE_THEME.glowGold}, ${DEEP_SPACE_THEME.floatShadow}, ${DEEP_SPACE_THEME.innerGlow}`,
|
||||||
|
position: 'relative' as const,
|
||||||
|
overflow: 'hidden' as const,
|
||||||
|
transition: DEEP_SPACE_THEME.transitionNormal,
|
||||||
|
_hover: {
|
||||||
|
bg: DEEP_SPACE_THEME.bgGlassHover,
|
||||||
|
borderColor: DEEP_SPACE_THEME.borderGoldHover,
|
||||||
|
boxShadow: `${DEEP_SPACE_THEME.glowGoldHover}, ${DEEP_SPACE_THEME.floatShadow}`,
|
||||||
|
transform: 'translateY(-2px)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// 内嵌区块
|
||||||
|
insetSection: {
|
||||||
|
bg: DEEP_SPACE_THEME.bgInset,
|
||||||
|
borderRadius: DEEP_SPACE_THEME.radiusLG,
|
||||||
|
border: `1px solid ${DEEP_SPACE_THEME.borderGlass}`,
|
||||||
|
p: 4,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// 装饰元素
|
||||||
|
// ============================================
|
||||||
|
export const decorativeElements = {
|
||||||
|
// 顶部金色光条(Ash Thorp 风格)
|
||||||
|
topGlowBar: {
|
||||||
|
position: 'absolute' as const,
|
||||||
|
top: 0,
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translateX(-50%)',
|
||||||
|
width: '60%',
|
||||||
|
height: '1px',
|
||||||
|
background: `linear-gradient(90deg, transparent, ${DEEP_SPACE_THEME.gold}, transparent)`,
|
||||||
|
opacity: 0.6,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 角落光点
|
||||||
|
cornerGlow: {
|
||||||
|
position: 'absolute' as const,
|
||||||
|
width: '80px',
|
||||||
|
height: '80px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: `radial-gradient(circle, rgba(212, 175, 55, 0.15) 0%, transparent 70%)`,
|
||||||
|
filter: 'blur(20px)',
|
||||||
|
},
|
||||||
|
|
||||||
|
// 背景网格(微妙的科技感)
|
||||||
|
gridOverlay: {
|
||||||
|
position: 'absolute' as const,
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
backgroundImage: `
|
||||||
|
linear-gradient(rgba(212, 175, 55, 0.03) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, rgba(212, 175, 55, 0.03) 1px, transparent 1px)
|
||||||
|
`,
|
||||||
|
backgroundSize: '40px 40px',
|
||||||
|
pointerEvents: 'none' as const,
|
||||||
|
opacity: 0.5,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DeepSpaceTheme = typeof DEEP_SPACE_THEME;
|
||||||
export type StockCardTheme = typeof STOCK_CARD_THEME;
|
export type StockCardTheme = typeof STOCK_CARD_THEME;
|
||||||
|
|||||||
@@ -1,246 +1,193 @@
|
|||||||
/**
|
/**
|
||||||
* StockQuoteCard - 股票行情卡片组件
|
* StockQuoteCard - 股票行情卡片组件
|
||||||
*
|
*
|
||||||
* 采用四卡片布局 FUI 风格设计
|
* 深空 FUI 设计风格(Glassmorphism + Ash Thorp + James Turrell)
|
||||||
* 参考:交易热度、估值安全、情绪风险等分区展示
|
* - 半透明玻璃态卡片,漂浮在深空中
|
||||||
|
* - 光影深度,弥散背景光
|
||||||
|
* - 极致圆角,科幻数据终端感
|
||||||
|
*
|
||||||
|
* 保留原有所有功能:
|
||||||
|
* - 股票头部(名称、代码、行业、对比、关注、分享)
|
||||||
|
* - 价格显示(当前价、涨跌幅)
|
||||||
|
* - 次要行情(今开、昨收、最高、最低)
|
||||||
|
* - 关键指标(PE、市值、股本、换手率、52周)
|
||||||
|
* - 主力动态(净流入、机构持仓、买卖比)
|
||||||
|
* - 公司信息(成立、注册资本、所在地、官网、简介)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Flex,
|
Flex,
|
||||||
Grid,
|
|
||||||
VStack,
|
|
||||||
HStack,
|
HStack,
|
||||||
Skeleton,
|
VStack,
|
||||||
Text,
|
Text,
|
||||||
Badge,
|
Badge,
|
||||||
|
IconButton,
|
||||||
|
Tooltip,
|
||||||
|
Skeleton,
|
||||||
|
Progress,
|
||||||
|
Link,
|
||||||
Icon,
|
Icon,
|
||||||
useDisclosure,
|
useDisclosure,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { TrendingUp, Activity, DollarSign, AlertTriangle } from 'lucide-react';
|
import { Share2, Calendar, Coins, MapPin, Globe } from 'lucide-react';
|
||||||
|
import FavoriteButton from '@components/FavoriteButton';
|
||||||
|
|
||||||
import { StockCompareModal } from './components';
|
import { StockCompareModal, CompareStockInput } from './components';
|
||||||
import { useStockQuoteData, useStockCompare } from './hooks';
|
import { useStockQuoteData, useStockCompare } from './hooks';
|
||||||
|
import { DEEP_SPACE_THEME, glassCardStyle, decorativeElements } from './components/theme';
|
||||||
|
import { formatPrice, formatChangePercent, formatNetInflow } from './components/formatters';
|
||||||
|
import { formatRegisteredCapital, formatDate } from '../CompanyOverview/utils';
|
||||||
import type { StockQuoteCardProps } from './types';
|
import type { StockQuoteCardProps } from './types';
|
||||||
|
|
||||||
// FUI 主题色彩
|
const T = DEEP_SPACE_THEME;
|
||||||
const FUI = {
|
|
||||||
gold: '#D4AF37',
|
|
||||||
orange: '#FF6B35',
|
|
||||||
green: '#00D984',
|
|
||||||
red: '#FF4757',
|
|
||||||
cyan: '#00D4FF',
|
|
||||||
purple: '#A855F7',
|
|
||||||
bgCard: 'rgba(20, 20, 30, 0.95)',
|
|
||||||
bgCardHover: 'rgba(30, 30, 45, 0.98)',
|
|
||||||
border: 'rgba(255, 255, 255, 0.08)',
|
|
||||||
borderGold: 'rgba(212, 175, 55, 0.3)',
|
|
||||||
text: 'rgba(255, 255, 255, 0.95)',
|
|
||||||
textMuted: 'rgba(255, 255, 255, 0.6)',
|
|
||||||
};
|
|
||||||
|
|
||||||
// 单个指标卡片组件
|
/**
|
||||||
interface MetricCardProps {
|
* 装饰性光效组件
|
||||||
icon: React.ElementType;
|
*/
|
||||||
iconColor: string;
|
const GlowDecorations: React.FC = () => (
|
||||||
title: string;
|
<>
|
||||||
badge?: string;
|
{/* 顶部金色光条 */}
|
||||||
badgeColor?: string;
|
<Box {...decorativeElements.topGlowBar} />
|
||||||
mainValue: string | number;
|
|
||||||
mainColor?: string;
|
|
||||||
mainUnit?: string;
|
|
||||||
subItems: Array<{ label: string; value: string | number; color?: string }>;
|
|
||||||
rightIcon?: React.ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
const MetricCard: React.FC<MetricCardProps> = ({
|
{/* 左上角光晕 */}
|
||||||
icon: IconComponent,
|
|
||||||
iconColor,
|
|
||||||
title,
|
|
||||||
badge,
|
|
||||||
badgeColor = FUI.textMuted,
|
|
||||||
mainValue,
|
|
||||||
mainColor = FUI.orange,
|
|
||||||
mainUnit,
|
|
||||||
subItems,
|
|
||||||
rightIcon,
|
|
||||||
}) => (
|
|
||||||
<Box
|
|
||||||
bg={FUI.bgCard}
|
|
||||||
borderRadius="lg"
|
|
||||||
border="1px solid"
|
|
||||||
borderColor={FUI.border}
|
|
||||||
p={4}
|
|
||||||
position="relative"
|
|
||||||
overflow="hidden"
|
|
||||||
transition="all 0.25s ease"
|
|
||||||
_hover={{
|
|
||||||
bg: FUI.bgCardHover,
|
|
||||||
borderColor: FUI.borderGold,
|
|
||||||
transform: 'translateY(-2px)',
|
|
||||||
boxShadow: `0 8px 24px rgba(0, 0, 0, 0.3), 0 0 1px ${FUI.gold}`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* 左上角光条 */}
|
|
||||||
<Box
|
<Box
|
||||||
position="absolute"
|
{...decorativeElements.cornerGlow}
|
||||||
top={0}
|
top="-40px"
|
||||||
left={0}
|
left="-40px"
|
||||||
w="40px"
|
|
||||||
h="2px"
|
|
||||||
bg={iconColor}
|
|
||||||
opacity={0.8}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 顶部:图标 + 标题 + 标签 */}
|
{/* 右下角光晕 */}
|
||||||
<HStack spacing={2} mb={3}>
|
<Box
|
||||||
<Icon as={IconComponent} boxSize={4} color={iconColor} />
|
{...decorativeElements.cornerGlow}
|
||||||
<Text fontSize="sm" fontWeight="600" color={FUI.text}>
|
bottom="-40px"
|
||||||
{title}
|
right="-40px"
|
||||||
</Text>
|
background={`radial-gradient(circle, rgba(0, 212, 255, 0.1) 0%, transparent 70%)`}
|
||||||
{badge && (
|
/>
|
||||||
<Badge
|
|
||||||
fontSize="10px"
|
|
||||||
px={2}
|
|
||||||
py={0.5}
|
|
||||||
bg="rgba(255, 255, 255, 0.1)"
|
|
||||||
color={badgeColor}
|
|
||||||
borderRadius="sm"
|
|
||||||
fontWeight="500"
|
|
||||||
>
|
|
||||||
{badge}
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
{rightIcon && (
|
|
||||||
<Box ml="auto" opacity={0.5}>
|
|
||||||
{rightIcon}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</HStack>
|
|
||||||
|
|
||||||
{/* 主数值 */}
|
{/* 背景网格 */}
|
||||||
<Text
|
<Box {...decorativeElements.gridOverlay} />
|
||||||
fontSize="2xl"
|
</>
|
||||||
fontWeight="bold"
|
);
|
||||||
color={mainColor}
|
|
||||||
lineHeight="1.2"
|
|
||||||
mb={2}
|
|
||||||
>
|
|
||||||
{mainValue}
|
|
||||||
{mainUnit && (
|
|
||||||
<Text as="span" fontSize="md" fontWeight="normal" ml={1}>
|
|
||||||
{mainUnit}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
{/* 子项目 */}
|
/**
|
||||||
<VStack align="stretch" spacing={1}>
|
* 加载骨架屏
|
||||||
{subItems.map((item, idx) => (
|
*/
|
||||||
<HStack key={idx} justify="space-between" fontSize="xs">
|
const LoadingSkeleton: React.FC = () => (
|
||||||
<Text color={FUI.textMuted}>{item.label}</Text>
|
<Box
|
||||||
<Text color={item.color || FUI.text} fontWeight="500">
|
{...glassCardStyle.containerGold}
|
||||||
{item.value}
|
p={8}
|
||||||
</Text>
|
>
|
||||||
|
<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>
|
||||||
))}
|
<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>
|
</VStack>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
||||||
// 股票信息卡片(第一个)
|
/**
|
||||||
interface StockInfoCardProps {
|
* 玻璃态内嵌区块
|
||||||
name: string;
|
*/
|
||||||
code: string;
|
interface GlassSectionProps {
|
||||||
currentPrice: number;
|
title: string;
|
||||||
changePercent: number;
|
children: React.ReactNode;
|
||||||
industry?: string;
|
flex?: number | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StockInfoCard: React.FC<StockInfoCardProps> = ({
|
const GlassSection: React.FC<GlassSectionProps> = ({ title, children, flex = 1 }) => (
|
||||||
name,
|
<Box
|
||||||
code,
|
flex={flex}
|
||||||
currentPrice,
|
bg={T.bgInset}
|
||||||
changePercent,
|
borderRadius={T.radiusLG}
|
||||||
industry,
|
border={`1px solid ${T.borderGlass}`}
|
||||||
}) => {
|
p={5}
|
||||||
const isUp = changePercent >= 0;
|
position="relative"
|
||||||
const priceColor = isUp ? FUI.red : FUI.green;
|
transition={T.transitionFast}
|
||||||
const trendText = isUp ? '强势上涨' : '震荡下跌';
|
_hover={{
|
||||||
|
borderColor: T.borderGoldHover,
|
||||||
return (
|
bg: 'rgba(15, 18, 35, 0.6)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* 区块顶部光条 */}
|
||||||
<Box
|
<Box
|
||||||
bg={FUI.bgCard}
|
position="absolute"
|
||||||
borderRadius="lg"
|
top={0}
|
||||||
border="1px solid"
|
left="20px"
|
||||||
borderColor={FUI.border}
|
right="20px"
|
||||||
p={4}
|
height="1px"
|
||||||
position="relative"
|
background={`linear-gradient(90deg, transparent, ${T.gold}40, transparent)`}
|
||||||
overflow="hidden"
|
/>
|
||||||
transition="all 0.25s ease"
|
|
||||||
_hover={{
|
<Text
|
||||||
bg: FUI.bgCardHover,
|
fontSize="13px"
|
||||||
borderColor: FUI.borderGold,
|
fontWeight="600"
|
||||||
transform: 'translateY(-2px)',
|
color={T.textSecondary}
|
||||||
boxShadow: `0 8px 24px rgba(0, 0, 0, 0.3), 0 0 1px ${FUI.gold}`,
|
mb={3}
|
||||||
}}
|
textTransform="uppercase"
|
||||||
|
letterSpacing="0.1em"
|
||||||
>
|
>
|
||||||
{/* 左上角光条 */}
|
{title}
|
||||||
<Box
|
</Text>
|
||||||
position="absolute"
|
{children}
|
||||||
top={0}
|
</Box>
|
||||||
left={0}
|
);
|
||||||
w="40px"
|
|
||||||
h="2px"
|
|
||||||
bg={priceColor}
|
|
||||||
opacity={0.8}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* 股票名称和代码 */}
|
/**
|
||||||
<HStack spacing={2} mb={3}>
|
* 指标行组件
|
||||||
<Text fontSize="lg" fontWeight="bold" color={FUI.text}>
|
*/
|
||||||
{name}
|
interface MetricRowProps {
|
||||||
</Text>
|
label: string;
|
||||||
<Badge
|
value: string | number;
|
||||||
fontSize="10px"
|
valueColor?: string;
|
||||||
px={2}
|
highlight?: boolean;
|
||||||
py={0.5}
|
}
|
||||||
bg="rgba(255, 255, 255, 0.1)"
|
|
||||||
color={FUI.textMuted}
|
|
||||||
borderRadius="sm"
|
|
||||||
>
|
|
||||||
{code}
|
|
||||||
</Badge>
|
|
||||||
</HStack>
|
|
||||||
|
|
||||||
{/* 价格和涨跌幅 */}
|
const MetricRow: React.FC<MetricRowProps> = ({
|
||||||
<HStack spacing={3} mb={2}>
|
label,
|
||||||
<Text fontSize="2xl" fontWeight="bold" color={priceColor}>
|
value,
|
||||||
{currentPrice.toFixed(2)}
|
valueColor = T.textWhite,
|
||||||
</Text>
|
highlight = false,
|
||||||
<Badge
|
}) => (
|
||||||
fontSize="sm"
|
<HStack justify="space-between" fontSize="14px">
|
||||||
px={2}
|
<Text color={T.textMuted}>{label}</Text>
|
||||||
py={1}
|
<Text
|
||||||
bg={isUp ? 'rgba(255, 71, 87, 0.15)' : 'rgba(0, 217, 132, 0.15)'}
|
color={valueColor}
|
||||||
color={priceColor}
|
fontWeight={highlight ? '700' : '600'}
|
||||||
borderRadius="md"
|
fontSize={highlight ? '16px' : '14px'}
|
||||||
fontWeight="bold"
|
textShadow={highlight ? `0 0 10px ${valueColor}40` : undefined}
|
||||||
>
|
>
|
||||||
{isUp ? '↗' : '↘'} {isUp ? '+' : ''}{changePercent.toFixed(2)}%
|
{value}
|
||||||
</Badge>
|
</Text>
|
||||||
</HStack>
|
</HStack>
|
||||||
|
);
|
||||||
{/* 走势标签 */}
|
|
||||||
<HStack fontSize="xs" color={FUI.textMuted}>
|
|
||||||
<Icon as={TrendingUp} boxSize={3} color={priceColor} />
|
|
||||||
<Text>走势:</Text>
|
|
||||||
<Text color={priceColor} fontWeight="500">{trendText}</Text>
|
|
||||||
</HStack>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const StockQuoteCard: React.FC<StockQuoteCardProps> = ({
|
const StockQuoteCard: React.FC<StockQuoteCardProps> = ({
|
||||||
stockCode,
|
stockCode,
|
||||||
@@ -269,112 +216,311 @@ const StockQuoteCard: React.FC<StockQuoteCardProps> = ({
|
|||||||
clearCompare();
|
clearCompare();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 加载中骨架屏
|
// 加载中
|
||||||
if (isLoading || !quoteData) {
|
if (isLoading || !quoteData) {
|
||||||
return (
|
return <LoadingSkeleton />;
|
||||||
<Grid templateColumns="repeat(4, 1fr)" gap={4}>
|
|
||||||
{[1, 2, 3, 4].map((i) => (
|
|
||||||
<Box
|
|
||||||
key={i}
|
|
||||||
bg={FUI.bgCard}
|
|
||||||
borderRadius="lg"
|
|
||||||
border="1px solid"
|
|
||||||
borderColor={FUI.border}
|
|
||||||
p={4}
|
|
||||||
>
|
|
||||||
<Skeleton height="20px" width="120px" mb={3} startColor="rgba(255,255,255,0.05)" endColor="rgba(255,255,255,0.1)" />
|
|
||||||
<Skeleton height="32px" width="80px" mb={2} startColor="rgba(255,255,255,0.05)" endColor="rgba(255,255,255,0.1)" />
|
|
||||||
<Skeleton height="14px" width="100%" startColor="rgba(255,255,255,0.05)" endColor="rgba(255,255,255,0.1)" />
|
|
||||||
</Box>
|
|
||||||
))}
|
|
||||||
</Grid>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 格式化数值
|
// 涨跌判断
|
||||||
const formatVolume = (val: number) => {
|
const isUp = quoteData.changePercent >= 0;
|
||||||
if (!val) return '-';
|
const priceColor = isUp ? T.upColor : T.downColor;
|
||||||
if (val >= 100000000) return `${(val / 100000000).toFixed(2)}亿`;
|
const priceGlow = isUp ? T.upGlow : T.downGlow;
|
||||||
if (val >= 10000) return `${(val / 10000).toFixed(0)}万`;
|
const priceBg = isUp ? T.upColorMuted : T.downColorMuted;
|
||||||
return val.toString();
|
const inflowColor = (quoteData.mainNetInflow || 0) >= 0 ? T.upColor : T.downColor;
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Grid templateColumns="repeat(4, 1fr)" gap={4}>
|
<Box
|
||||||
{/* 卡片1: 股票基本信息 */}
|
{...glassCardStyle.containerGold}
|
||||||
<StockInfoCard
|
p={8}
|
||||||
name={quoteData.name}
|
>
|
||||||
code={quoteData.code}
|
<GlowDecorations />
|
||||||
currentPrice={quoteData.currentPrice}
|
|
||||||
changePercent={quoteData.changePercent}
|
|
||||||
industry={quoteData.industry}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* 卡片2: 交易热度 */}
|
{/* 内容区域(在装饰层之上)*/}
|
||||||
<MetricCard
|
<VStack align="stretch" spacing={6} position="relative" zIndex={1}>
|
||||||
icon={Activity}
|
|
||||||
iconColor={FUI.orange}
|
|
||||||
title="交易热度"
|
|
||||||
badge="流动性"
|
|
||||||
mainValue={quoteData.marketCap || '-'}
|
|
||||||
mainColor={FUI.orange}
|
|
||||||
subItems={[
|
|
||||||
{
|
|
||||||
label: '换手率',
|
|
||||||
value: quoteData.turnoverRate ? `${quoteData.turnoverRate.toFixed(2)}%` : '-',
|
|
||||||
color: (quoteData.turnoverRate || 0) > 5 ? FUI.orange : FUI.text,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '52周区间',
|
|
||||||
value: `${quoteData.week52Low?.toFixed(2) || '-'} - ${quoteData.week52High?.toFixed(2) || '-'}`,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* 卡片3: 估值安全 */}
|
{/* ========== 头部区域 ========== */}
|
||||||
<MetricCard
|
<Flex justify="space-between" align="center">
|
||||||
icon={DollarSign}
|
{/* 左侧:股票名称 + 代码 + 行业 */}
|
||||||
iconColor={FUI.cyan}
|
<HStack spacing={4} align="center">
|
||||||
title="估值 VS 安全"
|
<Text
|
||||||
badge="便宜否"
|
fontSize="28px"
|
||||||
mainValue={quoteData.pe ? quoteData.pe.toFixed(2) : '-'}
|
fontWeight="800"
|
||||||
mainColor={FUI.cyan}
|
color={T.textPrimary}
|
||||||
mainUnit=""
|
textShadow={`0 0 20px ${T.gold}40`}
|
||||||
subItems={[
|
>
|
||||||
{
|
{quoteData.name}
|
||||||
label: '市盈率(PE)',
|
</Text>
|
||||||
value: quoteData.pe ? (quoteData.pe > 50 ? '高估值' : quoteData.pe > 20 ? '合理' : '低估值') : '-',
|
<Text fontSize="18px" color={T.textMuted} fontWeight="normal">
|
||||||
color: quoteData.pe ? (quoteData.pe > 50 ? FUI.orange : quoteData.pe > 20 ? FUI.gold : FUI.green) : FUI.textMuted,
|
({quoteData.code})
|
||||||
},
|
</Text>
|
||||||
{
|
|
||||||
label: '发行总股本',
|
|
||||||
value: quoteData.totalShares ? `${quoteData.totalShares}亿股` : '-',
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* 卡片4: 股本结构 */}
|
{/* 行业标签 */}
|
||||||
<MetricCard
|
{(quoteData.industryL1 || quoteData.industry) && (
|
||||||
icon={AlertTriangle}
|
<Badge
|
||||||
iconColor={FUI.green}
|
bg="transparent"
|
||||||
title="股本结构"
|
color={T.textSecondary}
|
||||||
badge="资金面"
|
fontSize="13px"
|
||||||
mainValue={quoteData.floatShares ? `${quoteData.floatShares}` : '-'}
|
fontWeight="500"
|
||||||
mainColor={FUI.green}
|
border="1px solid"
|
||||||
mainUnit="亿股"
|
borderColor={T.borderGold}
|
||||||
subItems={[
|
px={3}
|
||||||
{
|
py={1}
|
||||||
label: '流通股本',
|
borderRadius={T.radiusMD}
|
||||||
value: quoteData.floatShares ? `${quoteData.floatShares}亿股` : '-',
|
>
|
||||||
},
|
{quoteData.industryL1 && quoteData.industry
|
||||||
{
|
? `${quoteData.industryL1} · ${quoteData.industry}`
|
||||||
label: '行业',
|
: quoteData.industry || quoteData.industryL1}
|
||||||
value: quoteData.industry || '-',
|
</Badge>
|
||||||
},
|
)}
|
||||||
]}
|
|
||||||
/>
|
{/* 指数标签 */}
|
||||||
</Grid>
|
{quoteData.indexTags && quoteData.indexTags.length > 0 && (
|
||||||
|
<Text fontSize="13px" color={T.textMuted}>
|
||||||
|
{quoteData.indexTags.join('、')}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</HStack>
|
||||||
|
|
||||||
|
{/* 右侧:操作按钮 */}
|
||||||
|
<HStack spacing={3}>
|
||||||
|
<CompareStockInput
|
||||||
|
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={4}>
|
||||||
|
<Text
|
||||||
|
fontSize="52px"
|
||||||
|
fontWeight="bold"
|
||||||
|
color={priceColor}
|
||||||
|
textShadow={priceGlow}
|
||||||
|
lineHeight="1"
|
||||||
|
>
|
||||||
|
{formatPrice(quoteData.currentPrice)}
|
||||||
|
</Text>
|
||||||
|
<Badge
|
||||||
|
bg={priceBg}
|
||||||
|
color={priceColor}
|
||||||
|
fontSize="20px"
|
||||||
|
fontWeight="bold"
|
||||||
|
px={4}
|
||||||
|
py={2}
|
||||||
|
borderRadius={T.radiusMD}
|
||||||
|
boxShadow={priceGlow}
|
||||||
|
>
|
||||||
|
{formatChangePercent(quoteData.changePercent)}
|
||||||
|
</Badge>
|
||||||
|
</HStack>
|
||||||
|
|
||||||
|
{/* ========== 次要行情 ========== */}
|
||||||
|
<HStack spacing={6} fontSize="14px" flexWrap="wrap">
|
||||||
|
<Text color={T.textMuted}>
|
||||||
|
今开:
|
||||||
|
<Text as="span" color={T.textWhite} fontWeight="600" ml={1}>
|
||||||
|
{formatPrice(quoteData.todayOpen)}
|
||||||
|
</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>
|
||||||
|
|
||||||
|
{/* ========== 数据区块(Bento Grid)========== */}
|
||||||
|
<Flex gap={5} flexWrap={{ base: 'wrap', lg: 'nowrap' }}>
|
||||||
|
{/* 关键指标 */}
|
||||||
|
<GlassSection title="关键指标" flex={1}>
|
||||||
|
<VStack align="stretch" spacing={2.5}>
|
||||||
|
<MetricRow
|
||||||
|
label="市盈率 (PE)"
|
||||||
|
value={quoteData.pe ? quoteData.pe.toFixed(2) : '-'}
|
||||||
|
valueColor={T.cyan}
|
||||||
|
highlight
|
||||||
|
/>
|
||||||
|
<MetricRow
|
||||||
|
label="流通市值"
|
||||||
|
value={quoteData.marketCap || '-'}
|
||||||
|
valueColor={T.textPrimary}
|
||||||
|
highlight
|
||||||
|
/>
|
||||||
|
<MetricRow
|
||||||
|
label="发行总股本"
|
||||||
|
value={quoteData.totalShares ? `${quoteData.totalShares}亿股` : '-'}
|
||||||
|
/>
|
||||||
|
<MetricRow
|
||||||
|
label="流通股本"
|
||||||
|
value={quoteData.floatShares ? `${quoteData.floatShares}亿股` : '-'}
|
||||||
|
/>
|
||||||
|
<MetricRow
|
||||||
|
label="换手率"
|
||||||
|
value={quoteData.turnoverRate !== undefined ? `${quoteData.turnoverRate.toFixed(2)}%` : '-'}
|
||||||
|
valueColor={quoteData.turnoverRate && quoteData.turnoverRate > 5 ? T.orange : T.textWhite}
|
||||||
|
/>
|
||||||
|
<MetricRow
|
||||||
|
label="52周波动"
|
||||||
|
value={`${formatPrice(quoteData.week52Low)} - ${formatPrice(quoteData.week52High)}`}
|
||||||
|
/>
|
||||||
|
</VStack>
|
||||||
|
</GlassSection>
|
||||||
|
|
||||||
|
{/* 主力动态 */}
|
||||||
|
<GlassSection title="主力动态" flex={1}>
|
||||||
|
<VStack align="stretch" spacing={3}>
|
||||||
|
<MetricRow
|
||||||
|
label="主力净流入"
|
||||||
|
value={formatNetInflow(quoteData.mainNetInflow)}
|
||||||
|
valueColor={inflowColor}
|
||||||
|
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>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
{/* ========== 公司信息 ========== */}
|
||||||
|
{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>
|
||||||
|
</Box>
|
||||||
|
|
||||||
{/* 股票对比弹窗 */}
|
{/* 股票对比弹窗 */}
|
||||||
<StockCompareModal
|
<StockCompareModal
|
||||||
|
|||||||
Reference in New Issue
Block a user