更新Company页面的UI为FUI风格
This commit is contained in:
15
app.py
15
app.py
@@ -8734,6 +8734,9 @@ def get_stock_quote_detail(stock_code):
|
|||||||
|
|
||||||
if trade_result:
|
if trade_result:
|
||||||
row = row_to_dict(trade_result)
|
row = row_to_dict(trade_result)
|
||||||
|
# 调试日志:打印所有字段
|
||||||
|
app.logger.info(f"[quote-detail] stock={base_code}, row keys={list(row.keys())}")
|
||||||
|
app.logger.info(f"[quote-detail] total_shares={row.get('total_shares')}, float_shares={row.get('float_shares')}, pe_ratio={row.get('pe_ratio')}")
|
||||||
result_data['name'] = row.get('SECNAME') or ''
|
result_data['name'] = row.get('SECNAME') or ''
|
||||||
result_data['current_price'] = float(row.get('close_price') or 0)
|
result_data['current_price'] = float(row.get('close_price') or 0)
|
||||||
result_data['change_percent'] = float(row.get('change_pct') or 0)
|
result_data['change_percent'] = float(row.get('change_pct') or 0)
|
||||||
@@ -8741,17 +8744,19 @@ def get_stock_quote_detail(stock_code):
|
|||||||
result_data['yesterday_close'] = float(row.get('pre_close') or 0)
|
result_data['yesterday_close'] = float(row.get('pre_close') or 0)
|
||||||
result_data['today_high'] = float(row.get('high') or 0)
|
result_data['today_high'] = float(row.get('high') or 0)
|
||||||
result_data['today_low'] = float(row.get('low') or 0)
|
result_data['today_low'] = float(row.get('low') or 0)
|
||||||
result_data['pe'] = float(row.get('pe_ratio') or 0) if row.get('pe_ratio') else None
|
pe_value = row.get('pe_ratio') or row.get('F026N')
|
||||||
|
result_data['pe'] = float(pe_value) if pe_value else None
|
||||||
result_data['turnover_rate'] = float(row.get('turnover_rate') or 0)
|
result_data['turnover_rate'] = float(row.get('turnover_rate') or 0)
|
||||||
result_data['sw_industry_l1'] = row.get('sw_industry_l1') or ''
|
result_data['sw_industry_l1'] = row.get('sw_industry_l1') or ''
|
||||||
result_data['sw_industry_l2'] = row.get('sw_industry_l2') or ''
|
result_data['sw_industry_l2'] = row.get('sw_industry_l2') or ''
|
||||||
result_data['industry_l1'] = row.get('industry_l1') or ''
|
result_data['industry_l1'] = row.get('industry_l1') or ''
|
||||||
result_data['industry'] = row.get('sw_industry_l2') or row.get('sw_industry_l1') or ''
|
result_data['industry'] = row.get('sw_industry_l2') or row.get('sw_industry_l1') or ''
|
||||||
|
|
||||||
# 计算股本和市值
|
# 计算股本和市值(兼容别名和原始字段名)
|
||||||
total_shares = float(row.get('total_shares') or 0)
|
total_shares = float(row.get('total_shares') or row.get('F020N') or 0)
|
||||||
float_shares = float(row.get('float_shares') or 0)
|
float_shares = float(row.get('float_shares') or row.get('F021N') or 0)
|
||||||
close_price = float(row.get('close_price') or 0)
|
close_price = float(row.get('close_price') or row.get('F007N') or 0)
|
||||||
|
app.logger.info(f"[quote-detail] calculated: total_shares={total_shares}, float_shares={float_shares}")
|
||||||
|
|
||||||
# 发行总股本(亿股)
|
# 发行总股本(亿股)
|
||||||
if total_shares > 0:
|
if total_shares > 0:
|
||||||
|
|||||||
@@ -61,11 +61,11 @@ export interface SubTabTheme {
|
|||||||
const THEME_PRESETS: Record<string, SubTabTheme> = {
|
const THEME_PRESETS: Record<string, SubTabTheme> = {
|
||||||
blackGold: {
|
blackGold: {
|
||||||
bg: 'transparent',
|
bg: 'transparent',
|
||||||
borderColor: 'rgba(212, 175, 55, 0.2)',
|
borderColor: 'rgba(212, 175, 55, 0.15)',
|
||||||
tabSelectedBg: 'linear-gradient(135deg, #D4AF37 0%, #B8960C 100%)',
|
tabSelectedBg: 'linear-gradient(135deg, rgba(212, 175, 55, 0.95) 0%, rgba(184, 150, 12, 0.95) 100%)',
|
||||||
tabSelectedColor: '#0A0A14',
|
tabSelectedColor: '#0A0A14',
|
||||||
tabUnselectedColor: 'rgba(212, 175, 55, 0.8)',
|
tabUnselectedColor: 'rgba(212, 175, 55, 0.75)',
|
||||||
tabHoverBg: 'rgba(212, 175, 55, 0.1)',
|
tabHoverBg: 'rgba(212, 175, 55, 0.12)',
|
||||||
},
|
},
|
||||||
default: {
|
default: {
|
||||||
bg: 'white',
|
bg: 'white',
|
||||||
@@ -162,11 +162,11 @@ const SubTabContainer: React.FC<SubTabContainerProps> = memo(({
|
|||||||
bg={theme.bg}
|
bg={theme.bg}
|
||||||
borderBottom="1px solid"
|
borderBottom="1px solid"
|
||||||
borderColor={theme.borderColor}
|
borderColor={theme.borderColor}
|
||||||
pl={0}
|
pl={2}
|
||||||
pr={2}
|
pr={4}
|
||||||
py={1.5}
|
py={3}
|
||||||
flexWrap="nowrap"
|
flexWrap="nowrap"
|
||||||
gap={1}
|
gap={2}
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
overflowX="auto"
|
overflowX="auto"
|
||||||
css={{
|
css={{
|
||||||
@@ -178,29 +178,51 @@ const SubTabContainer: React.FC<SubTabContainerProps> = memo(({
|
|||||||
<Tab
|
<Tab
|
||||||
key={tab.key}
|
key={tab.key}
|
||||||
color={theme.tabUnselectedColor}
|
color={theme.tabUnselectedColor}
|
||||||
borderRadius="full"
|
borderRadius="md"
|
||||||
px={3}
|
px={5}
|
||||||
py={1.5}
|
py={2.5}
|
||||||
fontSize="xs"
|
fontSize="sm"
|
||||||
|
fontWeight="500"
|
||||||
whiteSpace="nowrap"
|
whiteSpace="nowrap"
|
||||||
flexShrink={0}
|
flexShrink={0}
|
||||||
border="1px solid transparent"
|
border="1px solid transparent"
|
||||||
transition="all 0.2s cubic-bezier(0.4, 0, 0.2, 1)"
|
position="relative"
|
||||||
|
letterSpacing="0.05em"
|
||||||
|
transition="all 0.25s cubic-bezier(0.4, 0, 0.2, 1)"
|
||||||
|
_before={{
|
||||||
|
content: '""',
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: '-1px',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translateX(-50%)',
|
||||||
|
width: '0%',
|
||||||
|
height: '2px',
|
||||||
|
bg: '#D4AF37',
|
||||||
|
transition: 'width 0.25s ease',
|
||||||
|
}}
|
||||||
_selected={{
|
_selected={{
|
||||||
bg: theme.tabSelectedBg,
|
bg: theme.tabSelectedBg,
|
||||||
color: theme.tabSelectedColor,
|
color: theme.tabSelectedColor,
|
||||||
fontWeight: 'bold',
|
fontWeight: '700',
|
||||||
boxShadow: '0 0 12px rgba(212, 175, 55, 0.4)',
|
boxShadow: '0 4px 16px rgba(212, 175, 55, 0.35), 0 0 20px rgba(212, 175, 55, 0.15)',
|
||||||
border: '1px solid rgba(212, 175, 55, 0.5)',
|
border: '1px solid rgba(212, 175, 55, 0.6)',
|
||||||
|
transform: 'translateY(-1px)',
|
||||||
|
_before: {
|
||||||
|
width: '80%',
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
_hover={{
|
_hover={{
|
||||||
bg: theme.tabHoverBg,
|
bg: theme.tabHoverBg,
|
||||||
border: '1px solid rgba(212, 175, 55, 0.3)',
|
border: '1px solid rgba(212, 175, 55, 0.35)',
|
||||||
|
transform: 'translateY(-1px)',
|
||||||
|
_before: {
|
||||||
|
width: '60%',
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<HStack spacing={1.5}>
|
<HStack spacing={2}>
|
||||||
{tab.icon && <Icon as={tab.icon} boxSize={3.5} />}
|
{tab.icon && <Icon as={tab.icon} boxSize={4} />}
|
||||||
<Text letterSpacing="wide">{tab.name}</Text>
|
<Text>{tab.name}</Text>
|
||||||
</HStack>
|
</HStack>
|
||||||
</Tab>
|
</Tab>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -10,22 +10,20 @@ import { STOCK_CARD_THEME } from './theme';
|
|||||||
|
|
||||||
export interface KeyMetricsProps {
|
export interface KeyMetricsProps {
|
||||||
pe: number;
|
pe: number;
|
||||||
eps?: number;
|
|
||||||
pb: number;
|
|
||||||
marketCap: string;
|
marketCap: string;
|
||||||
totalShares?: number; // 发行总股本(亿股)
|
totalShares?: number; // 发行总股本(亿股)
|
||||||
floatShares?: number; // 流通股本(亿股)
|
floatShares?: number; // 流通股本(亿股)
|
||||||
|
turnoverRate?: number; // 换手率(%)
|
||||||
week52Low: number;
|
week52Low: number;
|
||||||
week52High: number;
|
week52High: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const KeyMetrics: React.FC<KeyMetricsProps> = memo(({
|
export const KeyMetrics: React.FC<KeyMetricsProps> = memo(({
|
||||||
pe,
|
pe,
|
||||||
eps,
|
|
||||||
pb,
|
|
||||||
marketCap,
|
marketCap,
|
||||||
totalShares,
|
totalShares,
|
||||||
floatShares,
|
floatShares,
|
||||||
|
turnoverRate,
|
||||||
week52Low,
|
week52Low,
|
||||||
week52High,
|
week52High,
|
||||||
}) => {
|
}) => {
|
||||||
@@ -45,25 +43,13 @@ export const KeyMetrics: React.FC<KeyMetricsProps> = memo(({
|
|||||||
<HStack justify="space-between">
|
<HStack justify="space-between">
|
||||||
<Text color={labelColor}>市盈率(PE):</Text>
|
<Text color={labelColor}>市盈率(PE):</Text>
|
||||||
<Text color={valueColor} fontWeight="bold" fontSize="16px">
|
<Text color={valueColor} fontWeight="bold" fontSize="16px">
|
||||||
{pe.toFixed(2)}
|
{pe ? pe.toFixed(2) : '-'}
|
||||||
</Text>
|
|
||||||
</HStack>
|
|
||||||
<HStack justify="space-between">
|
|
||||||
<Text color={labelColor}>每股收益(EPS):</Text>
|
|
||||||
<Text color={valueColor} fontWeight="bold" fontSize="16px">
|
|
||||||
{eps?.toFixed(3) || '-'}
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
|
||||||
<HStack justify="space-between">
|
|
||||||
<Text color={labelColor}>市净率(PB):</Text>
|
|
||||||
<Text color={valueColor} fontWeight="bold" fontSize="16px">
|
|
||||||
{pb.toFixed(2)}
|
|
||||||
</Text>
|
</Text>
|
||||||
</HStack>
|
</HStack>
|
||||||
<HStack justify="space-between">
|
<HStack justify="space-between">
|
||||||
<Text color={labelColor}>流通市值:</Text>
|
<Text color={labelColor}>流通市值:</Text>
|
||||||
<Text color={valueColor} fontWeight="bold" fontSize="16px">
|
<Text color={valueColor} fontWeight="bold" fontSize="16px">
|
||||||
{marketCap}
|
{marketCap || '-'}
|
||||||
</Text>
|
</Text>
|
||||||
</HStack>
|
</HStack>
|
||||||
<HStack justify="space-between">
|
<HStack justify="space-between">
|
||||||
@@ -72,6 +58,18 @@ export const KeyMetrics: React.FC<KeyMetricsProps> = memo(({
|
|||||||
{totalShares ? `${totalShares}亿股` : '-'}
|
{totalShares ? `${totalShares}亿股` : '-'}
|
||||||
</Text>
|
</Text>
|
||||||
</HStack>
|
</HStack>
|
||||||
|
<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">
|
<HStack justify="space-between">
|
||||||
<Text color={labelColor}>52周波动:</Text>
|
<Text color={labelColor}>52周波动:</Text>
|
||||||
<Text color={valueColor} fontWeight="bold" fontSize="16px">
|
<Text color={valueColor} fontWeight="bold" fontSize="16px">
|
||||||
|
|||||||
@@ -35,11 +35,10 @@ const transformQuoteData = (apiData: any, stockCode: string): StockQuoteCardData
|
|||||||
|
|
||||||
// 关键指标
|
// 关键指标
|
||||||
pe: apiData.pe || apiData.pe_ttm || 0,
|
pe: apiData.pe || apiData.pe_ttm || 0,
|
||||||
eps: apiData.eps || apiData.basic_eps || undefined,
|
|
||||||
pb: apiData.pb || apiData.pb_mrq || 0,
|
|
||||||
marketCap: apiData.market_cap || apiData.marketCap || apiData.circ_mv || '0',
|
marketCap: apiData.market_cap || apiData.marketCap || apiData.circ_mv || '0',
|
||||||
totalShares: apiData.total_shares || apiData.totalShares || undefined,
|
totalShares: apiData.total_shares || apiData.totalShares || undefined,
|
||||||
floatShares: apiData.float_shares || apiData.floatShares || undefined,
|
floatShares: apiData.float_shares || apiData.floatShares || undefined,
|
||||||
|
turnoverRate: apiData.turnover_rate || apiData.turnoverRate || undefined,
|
||||||
week52Low: apiData.week52_low || apiData.week52Low || 0,
|
week52Low: apiData.week52_low || apiData.week52Low || 0,
|
||||||
week52High: apiData.week52_high || apiData.week52High || 0,
|
week52High: apiData.week52_high || apiData.week52High || 0,
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* StockQuoteCard - 股票行情卡片组件
|
* StockQuoteCard - 股票行情卡片组件
|
||||||
*
|
*
|
||||||
* 展示股票的实时行情、关键指标和主力动态
|
* 展示股票的实时行情、关键指标和主力动态
|
||||||
* 采用原子组件拆分,提高可维护性和复用性
|
* 采用 FUI 科幻风格设计 - Ash Thorp / Linear.app 风格
|
||||||
*
|
*
|
||||||
* 优化:数据获取已下沉到组件内部,Props 从 11 个精简为 4 个
|
* 优化:数据获取已下沉到组件内部,Props 从 11 个精简为 4 个
|
||||||
*/
|
*/
|
||||||
@@ -10,11 +10,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Card,
|
|
||||||
CardBody,
|
|
||||||
Flex,
|
Flex,
|
||||||
VStack,
|
VStack,
|
||||||
Skeleton,
|
Skeleton,
|
||||||
|
Text,
|
||||||
useDisclosure,
|
useDisclosure,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
|
|
||||||
@@ -26,11 +25,69 @@ import {
|
|||||||
MainForceInfo,
|
MainForceInfo,
|
||||||
CompanyInfo,
|
CompanyInfo,
|
||||||
StockCompareModal,
|
StockCompareModal,
|
||||||
STOCK_CARD_THEME,
|
|
||||||
} from './components';
|
} from './components';
|
||||||
import { useStockQuoteData, useStockCompare } from './hooks';
|
import { useStockQuoteData, useStockCompare } from './hooks';
|
||||||
import type { StockQuoteCardProps } from './types';
|
import type { StockQuoteCardProps } from './types';
|
||||||
|
|
||||||
|
// FUI 主题色彩
|
||||||
|
const FUI_THEME = {
|
||||||
|
gold: '#D4AF37',
|
||||||
|
goldLight: 'rgba(212, 175, 55, 0.15)',
|
||||||
|
goldGlow: 'rgba(212, 175, 55, 0.4)',
|
||||||
|
bgCard: 'linear-gradient(145deg, rgba(26, 26, 46, 0.95) 0%, rgba(15, 15, 26, 0.98) 100%)',
|
||||||
|
border: 'rgba(212, 175, 55, 0.2)',
|
||||||
|
borderHover: 'rgba(212, 175, 55, 0.4)',
|
||||||
|
textPrimary: 'rgba(255, 255, 255, 0.95)',
|
||||||
|
textSecondary: 'rgba(255, 255, 255, 0.7)',
|
||||||
|
};
|
||||||
|
|
||||||
|
// FUI 角落装饰组件
|
||||||
|
const CornerDecoration: React.FC<{ position: 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight' }> = ({ position }) => {
|
||||||
|
const positionStyles = {
|
||||||
|
topLeft: { top: '8px', left: '8px', borderTop: '2px solid', borderLeft: '2px solid' },
|
||||||
|
topRight: { top: '8px', right: '8px', borderTop: '2px solid', borderRight: '2px solid' },
|
||||||
|
bottomLeft: { bottom: '8px', left: '8px', borderBottom: '2px solid', borderLeft: '2px solid' },
|
||||||
|
bottomRight: { bottom: '8px', right: '8px', borderBottom: '2px solid', borderRight: '2px solid' },
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
w="12px"
|
||||||
|
h="12px"
|
||||||
|
borderColor={FUI_THEME.goldGlow}
|
||||||
|
opacity={0.7}
|
||||||
|
{...positionStyles[position]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// FUI 卡片标题组件
|
||||||
|
const FUICardTitle: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
||||||
|
<Text
|
||||||
|
fontSize="11px"
|
||||||
|
fontWeight="600"
|
||||||
|
letterSpacing="0.15em"
|
||||||
|
textTransform="uppercase"
|
||||||
|
color={FUI_THEME.gold}
|
||||||
|
mb={4}
|
||||||
|
display="flex"
|
||||||
|
alignItems="center"
|
||||||
|
_before={{
|
||||||
|
content: '""',
|
||||||
|
display: 'inline-block',
|
||||||
|
width: '4px',
|
||||||
|
height: '4px',
|
||||||
|
bg: FUI_THEME.gold,
|
||||||
|
borderRadius: '1px',
|
||||||
|
mr: 2,
|
||||||
|
boxShadow: `0 0 6px ${FUI_THEME.gold}`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
|
||||||
const StockQuoteCard: React.FC<StockQuoteCardProps> = ({
|
const StockQuoteCard: React.FC<StockQuoteCardProps> = ({
|
||||||
stockCode,
|
stockCode,
|
||||||
isInWatchlist = false,
|
isInWatchlist = false,
|
||||||
@@ -64,26 +121,70 @@ const StockQuoteCard: React.FC<StockQuoteCardProps> = ({
|
|||||||
clearCompare();
|
clearCompare();
|
||||||
};
|
};
|
||||||
|
|
||||||
const { cardBg, borderColor } = STOCK_CARD_THEME;
|
// 加载中或无数据时显示 FUI 风格骨架屏
|
||||||
|
|
||||||
// 加载中或无数据时显示骨架屏
|
|
||||||
if (isLoading || !quoteData) {
|
if (isLoading || !quoteData) {
|
||||||
return (
|
return (
|
||||||
<Card bg={cardBg} shadow="sm" borderWidth="1px" borderColor={borderColor}>
|
<Box
|
||||||
<CardBody>
|
position="relative"
|
||||||
|
bg={FUI_THEME.bgCard}
|
||||||
|
borderRadius="lg"
|
||||||
|
border="1px solid"
|
||||||
|
borderColor={FUI_THEME.border}
|
||||||
|
p={6}
|
||||||
|
overflow="hidden"
|
||||||
|
boxShadow={`0 4px 20px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.05)`}
|
||||||
|
>
|
||||||
|
<CornerDecoration position="topLeft" />
|
||||||
|
<CornerDecoration position="topRight" />
|
||||||
|
<CornerDecoration position="bottomLeft" />
|
||||||
|
<CornerDecoration position="bottomRight" />
|
||||||
<VStack spacing={4} align="stretch">
|
<VStack spacing={4} align="stretch">
|
||||||
<Skeleton height="30px" width="200px" />
|
<Skeleton height="30px" width="200px" startColor="rgba(212, 175, 55, 0.1)" endColor="rgba(212, 175, 55, 0.2)" />
|
||||||
<Skeleton height="60px" />
|
<Skeleton height="60px" startColor="rgba(212, 175, 55, 0.1)" endColor="rgba(212, 175, 55, 0.2)" />
|
||||||
<Skeleton height="80px" />
|
<Skeleton height="80px" startColor="rgba(212, 175, 55, 0.1)" endColor="rgba(212, 175, 55, 0.2)" />
|
||||||
</VStack>
|
</VStack>
|
||||||
</CardBody>
|
</Box>
|
||||||
</Card>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card bg={cardBg} shadow="sm" borderWidth="1px" borderColor={borderColor}>
|
<Box
|
||||||
<CardBody>
|
position="relative"
|
||||||
|
bg={FUI_THEME.bgCard}
|
||||||
|
borderRadius="lg"
|
||||||
|
border="1px solid"
|
||||||
|
borderColor={FUI_THEME.border}
|
||||||
|
overflow="hidden"
|
||||||
|
boxShadow={`0 4px 20px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.05)`}
|
||||||
|
backdropFilter="blur(12px)"
|
||||||
|
transition="all 0.3s ease"
|
||||||
|
_hover={{
|
||||||
|
borderColor: FUI_THEME.borderHover,
|
||||||
|
boxShadow: `0 8px 32px rgba(0, 0, 0, 0.4), 0 0 20px ${FUI_THEME.goldLight}, inset 0 1px 0 rgba(255, 255, 255, 0.08)`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* FUI 角落装饰 */}
|
||||||
|
<CornerDecoration position="topLeft" />
|
||||||
|
<CornerDecoration position="topRight" />
|
||||||
|
<CornerDecoration position="bottomLeft" />
|
||||||
|
<CornerDecoration position="bottomRight" />
|
||||||
|
|
||||||
|
{/* 顶部光效线条 */}
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
top={0}
|
||||||
|
left="20%"
|
||||||
|
right="20%"
|
||||||
|
height="1px"
|
||||||
|
bg={`linear-gradient(90deg, transparent, ${FUI_THEME.gold}, transparent)`}
|
||||||
|
opacity={0.6}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 内容区域 */}
|
||||||
|
<Box p={6}>
|
||||||
|
{/* FUI 标题 */}
|
||||||
|
<FUICardTitle>个股详情 · STOCK QUOTE</FUICardTitle>
|
||||||
|
|
||||||
{/* 顶部:股票名称 + 关注/分享按钮 + 更新时间 */}
|
{/* 顶部:股票名称 + 关注/分享按钮 + 更新时间 */}
|
||||||
<StockHeader
|
<StockHeader
|
||||||
name={quoteData.name}
|
name={quoteData.name}
|
||||||
@@ -110,6 +211,13 @@ const StockQuoteCard: React.FC<StockQuoteCardProps> = ({
|
|||||||
isLoading={isCompareLoading}
|
isLoading={isCompareLoading}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* 分隔线 */}
|
||||||
|
<Box
|
||||||
|
my={4}
|
||||||
|
height="1px"
|
||||||
|
bg={`linear-gradient(90deg, transparent, ${FUI_THEME.border}, transparent)`}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* 1:2 布局 */}
|
{/* 1:2 布局 */}
|
||||||
<Flex gap={8}>
|
<Flex gap={8}>
|
||||||
{/* 左栏:价格信息 (flex=1) */}
|
{/* 左栏:价格信息 (flex=1) */}
|
||||||
@@ -127,14 +235,20 @@ const StockQuoteCard: React.FC<StockQuoteCardProps> = ({
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* 右栏:关键指标 + 主力动态 (flex=2) */}
|
{/* 右栏:关键指标 + 主力动态 (flex=2) */}
|
||||||
<Flex flex="2" minWidth="0" gap={8} borderLeftWidth="1px" borderColor={borderColor} pl={8}>
|
<Flex
|
||||||
|
flex="2"
|
||||||
|
minWidth="0"
|
||||||
|
gap={8}
|
||||||
|
borderLeftWidth="1px"
|
||||||
|
borderColor={FUI_THEME.border}
|
||||||
|
pl={8}
|
||||||
|
>
|
||||||
<KeyMetrics
|
<KeyMetrics
|
||||||
pe={quoteData.pe}
|
pe={quoteData.pe}
|
||||||
eps={quoteData.eps}
|
|
||||||
pb={quoteData.pb}
|
|
||||||
marketCap={quoteData.marketCap}
|
marketCap={quoteData.marketCap}
|
||||||
totalShares={quoteData.totalShares}
|
totalShares={quoteData.totalShares}
|
||||||
floatShares={quoteData.floatShares}
|
floatShares={quoteData.floatShares}
|
||||||
|
turnoverRate={quoteData.turnoverRate}
|
||||||
week52Low={quoteData.week52Low}
|
week52Low={quoteData.week52Low}
|
||||||
week52High={quoteData.week52High}
|
week52High={quoteData.week52High}
|
||||||
/>
|
/>
|
||||||
@@ -148,9 +262,29 @@ const StockQuoteCard: React.FC<StockQuoteCardProps> = ({
|
|||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
{/* 公司信息区块 */}
|
{/* 公司信息区块 */}
|
||||||
{basicInfo && <CompanyInfo basicInfo={basicInfo} />}
|
{basicInfo && (
|
||||||
</CardBody>
|
<>
|
||||||
</Card>
|
<Box
|
||||||
|
my={4}
|
||||||
|
height="1px"
|
||||||
|
bg={`linear-gradient(90deg, transparent, ${FUI_THEME.border}, transparent)`}
|
||||||
|
/>
|
||||||
|
<CompanyInfo basicInfo={basicInfo} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* 底部光效线条 */}
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
bottom={0}
|
||||||
|
left="30%"
|
||||||
|
right="30%"
|
||||||
|
height="1px"
|
||||||
|
bg={`linear-gradient(90deg, transparent, ${FUI_THEME.goldGlow}, transparent)`}
|
||||||
|
opacity={0.4}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -26,11 +26,10 @@ export interface StockQuoteCardData {
|
|||||||
|
|
||||||
// 关键指标
|
// 关键指标
|
||||||
pe: number; // 市盈率
|
pe: number; // 市盈率
|
||||||
eps?: number; // 每股收益
|
|
||||||
pb: number; // 市净率
|
|
||||||
marketCap: string; // 流通市值(已格式化,如 "2.73万亿")
|
marketCap: string; // 流通市值(已格式化,如 "2.73万亿")
|
||||||
totalShares?: number; // 发行总股本(亿股)
|
totalShares?: number; // 发行总股本(亿股)
|
||||||
floatShares?: number; // 流通股本(亿股)
|
floatShares?: number; // 流通股本(亿股)
|
||||||
|
turnoverRate?: number; // 换手率(%)
|
||||||
week52Low: number; // 52周最低
|
week52Low: number; // 52周最低
|
||||||
week52High: number; // 52周最高
|
week52High: number; // 52周最高
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user