refactor(FinancialOverviewPanel): 主营业务卡片使用 MetricCard 组件重构
- 主营业务卡片改用 MetricCard 组件,与其他三个卡片结构统一 - 环形图改为堆叠条形图+图例网格布局(2列) - MetricCard 的 subText 区域支持剩余高度居中 - DarkGoldCard 内容层继承 flex 布局属性 - Mock 数据扩展为 6 个业务分类 - 历史对比按钮高度调整为 18px 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -272,16 +272,19 @@ export const generateFinancialData = (stockCode) => {
|
||||
}
|
||||
})),
|
||||
|
||||
// 主营业务 - 按产品/业务分类
|
||||
// 主营业务 - 按产品/业务分类(6个业务)
|
||||
mainBusiness: {
|
||||
product_classification: [
|
||||
{
|
||||
period: '2024-09-30',
|
||||
report_type: '2024年三季报',
|
||||
products: [
|
||||
{ content: '零售金融业务', revenue: 81320000000, gross_margin: 68.5, profit_margin: 42.3, profit: 34398160000 },
|
||||
{ content: '对公金融业务', revenue: 68540000000, gross_margin: 62.8, profit_margin: 38.6, profit: 26456440000 },
|
||||
{ content: '金融市场业务', revenue: 12490000000, gross_margin: 75.2, profit_margin: 52.1, profit: 6507290000 },
|
||||
{ content: '零售业务', revenue: 56822500000, gross_margin: 68.5, profit_margin: 42.3, profit: 24035877500 },
|
||||
{ content: '金融服务', revenue: 32470000000, gross_margin: 62.8, profit_margin: 38.6, profit: 12533420000 },
|
||||
{ content: '对冲投资', revenue: 24352500000, gross_margin: 75.2, profit_margin: 52.1, profit: 12687652500 },
|
||||
{ content: '云服务', revenue: 19482000000, gross_margin: 72.0, profit_margin: 48.5, profit: 9448770000 },
|
||||
{ content: '物流网络', revenue: 16235000000, gross_margin: 58.5, profit_margin: 32.8, profit: 5325080000 },
|
||||
{ content: '其他业务', revenue: 12988000000, gross_margin: 55.2, profit_margin: 28.6, profit: 3714568000 },
|
||||
{ content: '合计', revenue: 162350000000, gross_margin: 67.5, profit_margin: 41.2, profit: 66883200000 },
|
||||
]
|
||||
},
|
||||
@@ -289,9 +292,12 @@ export const generateFinancialData = (stockCode) => {
|
||||
period: '2024-06-30',
|
||||
report_type: '2024年中报',
|
||||
products: [
|
||||
{ content: '零售金融业务', revenue: 78650000000, gross_margin: 67.8, profit_margin: 41.5, profit: 32639750000 },
|
||||
{ content: '对公金融业务', revenue: 66280000000, gross_margin: 61.9, profit_margin: 37.8, profit: 25053840000 },
|
||||
{ content: '金融市场业务', revenue: 11870000000, gross_margin: 74.5, profit_margin: 51.2, profit: 6077440000 },
|
||||
{ content: '零售业务', revenue: 54880000000, gross_margin: 67.8, profit_margin: 41.5, profit: 22775200000 },
|
||||
{ content: '金融服务', revenue: 31360000000, gross_margin: 61.9, profit_margin: 37.8, profit: 11854080000 },
|
||||
{ content: '对冲投资', revenue: 23520000000, gross_margin: 74.5, profit_margin: 51.2, profit: 12042240000 },
|
||||
{ content: '云服务', revenue: 18816000000, gross_margin: 71.2, profit_margin: 47.8, profit: 8994048000 },
|
||||
{ content: '物流网络', revenue: 15680000000, gross_margin: 57.8, profit_margin: 32.1, profit: 5033280000 },
|
||||
{ content: '其他业务', revenue: 12544000000, gross_margin: 54.5, profit_margin: 28.0, profit: 3512320000 },
|
||||
{ content: '合计', revenue: 156800000000, gross_margin: 66.8, profit_margin: 40.5, profit: 63504000000 },
|
||||
]
|
||||
},
|
||||
@@ -299,9 +305,12 @@ export const generateFinancialData = (stockCode) => {
|
||||
period: '2024-03-31',
|
||||
report_type: '2024年一季报',
|
||||
products: [
|
||||
{ content: '零售金融业务', revenue: 38920000000, gross_margin: 67.2, profit_margin: 40.8, profit: 15879360000 },
|
||||
{ content: '对公金融业务', revenue: 32650000000, gross_margin: 61.2, profit_margin: 37.1, profit: 12113150000 },
|
||||
{ content: '金融市场业务', revenue: 5830000000, gross_margin: 73.8, profit_margin: 50.5, profit: 2944150000 },
|
||||
{ content: '零售业务', revenue: 27090000000, gross_margin: 67.2, profit_margin: 40.8, profit: 11052720000 },
|
||||
{ content: '金融服务', revenue: 15480000000, gross_margin: 61.2, profit_margin: 37.1, profit: 5743080000 },
|
||||
{ content: '对冲投资', revenue: 11610000000, gross_margin: 73.8, profit_margin: 50.5, profit: 5863050000 },
|
||||
{ content: '云服务', revenue: 9288000000, gross_margin: 70.5, profit_margin: 47.0, profit: 4365360000 },
|
||||
{ content: '物流网络', revenue: 7740000000, gross_margin: 57.0, profit_margin: 31.5, profit: 2438100000 },
|
||||
{ content: '其他业务', revenue: 6192000000, gross_margin: 53.8, profit_margin: 27.2, profit: 1684224000 },
|
||||
{ content: '合计', revenue: 77400000000, gross_margin: 66.1, profit_margin: 39.8, profit: 30805200000 },
|
||||
]
|
||||
},
|
||||
@@ -309,9 +318,12 @@ export const generateFinancialData = (stockCode) => {
|
||||
period: '2023-12-31',
|
||||
report_type: '2023年年报',
|
||||
products: [
|
||||
{ content: '零售金融业务', revenue: 152680000000, gross_margin: 66.5, profit_margin: 40.2, profit: 61377360000 },
|
||||
{ content: '对公金融业务', revenue: 128450000000, gross_margin: 60.5, profit_margin: 36.5, profit: 46884250000 },
|
||||
{ content: '金融市场业务', revenue: 22870000000, gross_margin: 73.2, profit_margin: 49.8, profit: 11389260000 },
|
||||
{ content: '零售业务', revenue: 106400000000, gross_margin: 66.5, profit_margin: 40.2, profit: 42772800000 },
|
||||
{ content: '金融服务', revenue: 60800000000, gross_margin: 60.5, profit_margin: 36.5, profit: 22192000000 },
|
||||
{ content: '对冲投资', revenue: 45600000000, gross_margin: 73.2, profit_margin: 49.8, profit: 22708800000 },
|
||||
{ content: '云服务', revenue: 36480000000, gross_margin: 69.8, profit_margin: 46.2, profit: 16853760000 },
|
||||
{ content: '物流网络', revenue: 30400000000, gross_margin: 56.2, profit_margin: 30.8, profit: 9363200000 },
|
||||
{ content: '其他业务', revenue: 24320000000, gross_margin: 53.0, profit_margin: 26.5, profit: 6444800000 },
|
||||
{ content: '合计', revenue: 304000000000, gross_margin: 65.2, profit_margin: 39.2, profit: 119168000000 },
|
||||
]
|
||||
},
|
||||
|
||||
@@ -25,7 +25,7 @@ import { formatUtils } from '@services/financialService';
|
||||
|
||||
// 复用 MarketDataView 的组件
|
||||
import MetricCard from '../../MarketDataView/components/StockSummaryCard/MetricCard';
|
||||
import { StatusTag, DarkGoldCard } from '../../MarketDataView/components/StockSummaryCard/atoms';
|
||||
import { StatusTag } from '../../MarketDataView/components/StockSummaryCard/atoms';
|
||||
import { darkGoldTheme } from '../../MarketDataView/constants';
|
||||
import { HistoricalComparisonTable } from './MainBusinessAnalysis';
|
||||
|
||||
@@ -233,38 +233,12 @@ export const FinancialOverviewPanel: React.FC<FinancialOverviewPanelProps> = mem
|
||||
}
|
||||
/>
|
||||
|
||||
{/* 卡片4: 主营业务 - 环形图+图例风格 */}
|
||||
<DarkGoldCard cornerDecor position="relative" overflow="hidden">
|
||||
{/* Header: 标题区域 */}
|
||||
<HStack justify="space-between" align="center" mb={2} position="relative" zIndex={1}>
|
||||
<HStack spacing={2}>
|
||||
<Box
|
||||
color={darkGoldTheme.gold}
|
||||
sx={{ '& > *': { filter: 'drop-shadow(0 0 4px rgba(212, 175, 55, 0.6))' } }}
|
||||
>
|
||||
<PieChart size={14} />
|
||||
</Box>
|
||||
<Text
|
||||
color={darkGoldTheme.gold}
|
||||
fontSize="sm"
|
||||
fontWeight="bold"
|
||||
letterSpacing="wide"
|
||||
>
|
||||
主营业务
|
||||
</Text>
|
||||
<Badge
|
||||
variant="outline"
|
||||
fontSize="2xs"
|
||||
px={1.5}
|
||||
py={0.5}
|
||||
borderRadius="sm"
|
||||
borderColor="rgba(212, 175, 55, 0.5)"
|
||||
color={darkGoldTheme.gold}
|
||||
>
|
||||
核心构成
|
||||
</Badge>
|
||||
</HStack>
|
||||
|
||||
{/* 卡片4: 主营业务 - 使用 MetricCard 组件 */}
|
||||
<MetricCard
|
||||
title="主营业务"
|
||||
subtitle="核心构成"
|
||||
leftIcon={<PieChart size={14} />}
|
||||
rightIcon={
|
||||
<Button
|
||||
size="xs"
|
||||
bg="rgba(59, 130, 246, 0.9)"
|
||||
@@ -274,130 +248,78 @@ export const FinancialOverviewPanel: React.FC<FinancialOverviewPanelProps> = mem
|
||||
onClick={onOpen}
|
||||
isDisabled={!mainBusinessData}
|
||||
fontSize="2xs"
|
||||
h="22px"
|
||||
px={2}
|
||||
h="18px"
|
||||
px={1.5}
|
||||
borderRadius="full"
|
||||
>
|
||||
历史对比
|
||||
</Button>
|
||||
</HStack>
|
||||
}
|
||||
mainLabel="营收总额"
|
||||
mainValue={mainBusinessData ? `¥${((() => {
|
||||
const items = mainBusinessData.businessItems.filter((item: BusinessItem) => item.content !== '合计');
|
||||
const totalItem = mainBusinessData.businessItems.find((item: BusinessItem) => item.content === '合计');
|
||||
return totalItem?.revenue || items.reduce((sum: number, item: BusinessItem) => sum + (item.revenue || 0), 0);
|
||||
})() / 100000000).toFixed(1)}` : '-'}
|
||||
mainColor={darkGoldTheme.gold}
|
||||
mainSuffix="亿"
|
||||
subText={
|
||||
mainBusinessData ? (
|
||||
(() => {
|
||||
const BUSINESS_COLORS = ['#D4AF37', '#4A90D9', '#6B7280', '#10B981', '#9333EA', '#EF4444'];
|
||||
const items = mainBusinessData.businessItems.filter((item: BusinessItem) => item.content !== '合计');
|
||||
const totalItem = mainBusinessData.businessItems.find((item: BusinessItem) => item.content === '合计');
|
||||
const totalRevenue = totalItem?.revenue || items.reduce((sum: number, item: BusinessItem) => sum + (item.revenue || 0), 0);
|
||||
const businessWithRatio = items.map((item: BusinessItem, idx: number) => ({
|
||||
...item,
|
||||
ratio: totalRevenue > 0 ? ((item.revenue || 0) / totalRevenue * 100) : 0,
|
||||
color: BUSINESS_COLORS[idx % BUSINESS_COLORS.length],
|
||||
})).sort((a, b) => b.ratio - a.ratio);
|
||||
|
||||
{/* 分隔线 */}
|
||||
<Box h="1px" bg="linear-gradient(90deg, rgba(212, 175, 55, 0.4), transparent)" mb={2} />
|
||||
return (
|
||||
<VStack spacing={2} align="stretch">
|
||||
{/* 堆叠条形图 */}
|
||||
<HStack
|
||||
h="8px"
|
||||
borderRadius="sm"
|
||||
overflow="hidden"
|
||||
spacing={0}
|
||||
border="1px solid"
|
||||
borderColor="rgba(212, 175, 55, 0.3)"
|
||||
>
|
||||
{businessWithRatio.slice(0, 6).map((item, idx) => (
|
||||
<Box
|
||||
key={idx}
|
||||
w={`${item.ratio}%`}
|
||||
h="100%"
|
||||
bg={item.color}
|
||||
transition="width 0.5s ease"
|
||||
/>
|
||||
))}
|
||||
</HStack>
|
||||
|
||||
{/* Body: 环形图 + 图例 */}
|
||||
<HStack spacing={3} align="center" flex={1} position="relative" zIndex={1}>
|
||||
{mainBusinessData ? (
|
||||
<>
|
||||
{(() => {
|
||||
// 业务颜色配置
|
||||
const BUSINESS_COLORS = ['#D4AF37', '#4A90D9', '#6B7280', '#10B981', '#F59E0B', '#EF4444'];
|
||||
|
||||
const items = mainBusinessData.businessItems.filter(
|
||||
(item: BusinessItem) => item.content !== '合计'
|
||||
);
|
||||
const totalItem = mainBusinessData.businessItems.find(
|
||||
(item: BusinessItem) => item.content === '合计'
|
||||
);
|
||||
const totalRevenue = totalItem?.revenue || items.reduce((sum: number, item: BusinessItem) => sum + (item.revenue || 0), 0);
|
||||
|
||||
// 计算各业务占比
|
||||
const businessWithRatio = items.map((item: BusinessItem, idx: number) => ({
|
||||
...item,
|
||||
ratio: totalRevenue > 0 ? ((item.revenue || 0) / totalRevenue * 100) : 0,
|
||||
color: BUSINESS_COLORS[idx % BUSINESS_COLORS.length],
|
||||
})).sort((a, b) => b.ratio - a.ratio);
|
||||
|
||||
// 核心业务(占比最大)
|
||||
const coreItem = businessWithRatio[0];
|
||||
|
||||
// 计算环形图弧度
|
||||
let cumulativeAngle = 0;
|
||||
const segments = businessWithRatio.map((item) => {
|
||||
const startAngle = cumulativeAngle;
|
||||
const angle = (item.ratio / 100) * 360;
|
||||
cumulativeAngle += angle;
|
||||
return { ...item, startAngle, angle };
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* 左侧:环形图 */}
|
||||
<Box position="relative" w="90px" h="90px" flexShrink={0}>
|
||||
<svg width="90" height="90" viewBox="0 0 90 90">
|
||||
{segments.map((seg, idx) => {
|
||||
const radius = 38;
|
||||
const strokeWidth = 12;
|
||||
const circumference = 2 * Math.PI * radius;
|
||||
const dashLength = (seg.angle / 360) * circumference;
|
||||
const dashOffset = -((seg.startAngle / 360) * circumference);
|
||||
|
||||
return (
|
||||
<circle
|
||||
key={idx}
|
||||
cx="45"
|
||||
cy="45"
|
||||
r={radius}
|
||||
fill="none"
|
||||
stroke={seg.color}
|
||||
strokeWidth={strokeWidth}
|
||||
strokeDasharray={`${dashLength} ${circumference}`}
|
||||
strokeDashoffset={dashOffset}
|
||||
transform="rotate(-90 45 45)"
|
||||
style={{ transition: 'stroke-dasharray 0.5s ease' }}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</svg>
|
||||
{/* 中心文字 */}
|
||||
<VStack
|
||||
position="absolute"
|
||||
top="50%"
|
||||
left="50%"
|
||||
transform="translate(-50%, -50%)"
|
||||
spacing={0}
|
||||
>
|
||||
<Text
|
||||
fontSize="md"
|
||||
fontWeight="bold"
|
||||
color="#FFD700"
|
||||
fontFamily="'DIN Alternate', monospace"
|
||||
>
|
||||
{coreItem?.ratio.toFixed(1)}%
|
||||
{/* 图例网格(2列3行) */}
|
||||
<SimpleGrid columns={2} spacing={2} spacingY={0.5}>
|
||||
{businessWithRatio.slice(0, 6).map((item, idx) => (
|
||||
<HStack key={idx} spacing={1.5}>
|
||||
<Box w="10px" h="10px" borderRadius="sm" bg={item.color} flexShrink={0} />
|
||||
<Text fontSize="xs" fontWeight="medium" color="gray.300" noOfLines={1} flex={1}>
|
||||
{item.content}
|
||||
</Text>
|
||||
<Text fontSize="2xs" color={darkGoldTheme.textMuted} whiteSpace="nowrap">
|
||||
{coreItem?.content?.slice(0, 4) || '-'}(核心)
|
||||
<Text fontSize="xs" fontWeight="bold" color="gray.100" flexShrink={0}>
|
||||
{item.ratio.toFixed(1)}%
|
||||
</Text>
|
||||
</VStack>
|
||||
</Box>
|
||||
|
||||
{/* 右侧:图例列表 */}
|
||||
<VStack align="stretch" spacing={1.5} flex={1} py={1}>
|
||||
{businessWithRatio.slice(0, 4).map((item, idx) => (
|
||||
<HStack key={idx} justify="space-between" spacing={2}>
|
||||
<HStack spacing={1.5}>
|
||||
<Box w="10px" h="10px" borderRadius="sm" bg={item.color} />
|
||||
<Text fontSize="xs" color="gray.300" noOfLines={1} maxW="70px">
|
||||
{item.content}
|
||||
</Text>
|
||||
</HStack>
|
||||
<Text fontSize="xs" fontWeight="medium" color="gray.200">
|
||||
{item.ratio.toFixed(1)}%
|
||||
</Text>
|
||||
</HStack>
|
||||
))}
|
||||
</VStack>
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
</>
|
||||
</HStack>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</VStack>
|
||||
);
|
||||
})()
|
||||
) : (
|
||||
<VStack w="100%" justify="center" py={4}>
|
||||
<Text fontSize="xs" color={darkGoldTheme.textMuted}>暂无数据</Text>
|
||||
</VStack>
|
||||
)}
|
||||
</HStack>
|
||||
</DarkGoldCard>
|
||||
<Text textAlign="center">暂无数据</Text>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</SimpleGrid>
|
||||
|
||||
{/* 历史对比弹窗 */}
|
||||
|
||||
@@ -50,7 +50,7 @@ const MetricCard: React.FC<MetricCardProps> = ({
|
||||
const ambientColor = getAmbientColor(mainColor);
|
||||
|
||||
return (
|
||||
<DarkGoldCard cornerDecor={true} position="relative" overflow="hidden">
|
||||
<DarkGoldCard cornerDecor={true} position="relative" overflow="hidden" display="flex" flexDirection="column">
|
||||
{/* James Turrell 环境光效果 */}
|
||||
<Box
|
||||
position="absolute"
|
||||
@@ -98,18 +98,25 @@ const MetricCard: React.FC<MetricCardProps> = ({
|
||||
/>
|
||||
</VStack>
|
||||
|
||||
{/* 辅助信息 */}
|
||||
{/* 辅助信息 - 在剩余高度居中 */}
|
||||
<Box
|
||||
color={darkGoldTheme.textMuted}
|
||||
fontSize="xs"
|
||||
flex={1}
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
position="relative"
|
||||
zIndex={1}
|
||||
p={2}
|
||||
bg="rgba(0, 0, 0, 0.2)"
|
||||
borderRadius="md"
|
||||
border="1px solid rgba(212, 175, 55, 0.1)"
|
||||
>
|
||||
{subText}
|
||||
<Box
|
||||
w="100%"
|
||||
color={darkGoldTheme.textMuted}
|
||||
fontSize="xs"
|
||||
p={2}
|
||||
bg="rgba(0, 0, 0, 0.2)"
|
||||
borderRadius="md"
|
||||
border="1px solid rgba(212, 175, 55, 0.1)"
|
||||
>
|
||||
{subText}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* 底部渐变线 */}
|
||||
|
||||
@@ -111,7 +111,7 @@ const DarkGoldCard: React.FC<DarkGoldCardProps> = ({
|
||||
/>
|
||||
|
||||
{/* 内容 */}
|
||||
<Box position="relative" zIndex={1}>
|
||||
<Box position="relative" zIndex={1} display="inherit" flexDirection="inherit" flex={1}>
|
||||
{children}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
Reference in New Issue
Block a user