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:
zdl
2025-12-29 16:31:26 +08:00
parent 5e49706b1f
commit 8aedbf2bbb
4 changed files with 112 additions and 171 deletions

View File

@@ -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 },
]
},

View File

@@ -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>
{/* 历史对比弹窗 */}

View File

@@ -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>
{/* 底部渐变线 */}

View File

@@ -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>