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: {
|
mainBusiness: {
|
||||||
product_classification: [
|
product_classification: [
|
||||||
{
|
{
|
||||||
period: '2024-09-30',
|
period: '2024-09-30',
|
||||||
report_type: '2024年三季报',
|
report_type: '2024年三季报',
|
||||||
products: [
|
products: [
|
||||||
{ content: '零售金融业务', revenue: 81320000000, gross_margin: 68.5, profit_margin: 42.3, profit: 34398160000 },
|
{ content: '零售业务', revenue: 56822500000, gross_margin: 68.5, profit_margin: 42.3, profit: 24035877500 },
|
||||||
{ content: '对公金融业务', revenue: 68540000000, gross_margin: 62.8, profit_margin: 38.6, profit: 26456440000 },
|
{ content: '金融服务', revenue: 32470000000, gross_margin: 62.8, profit_margin: 38.6, profit: 12533420000 },
|
||||||
{ content: '金融市场业务', revenue: 12490000000, gross_margin: 75.2, profit_margin: 52.1, profit: 6507290000 },
|
{ 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 },
|
{ 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',
|
period: '2024-06-30',
|
||||||
report_type: '2024年中报',
|
report_type: '2024年中报',
|
||||||
products: [
|
products: [
|
||||||
{ content: '零售金融业务', revenue: 78650000000, gross_margin: 67.8, profit_margin: 41.5, profit: 32639750000 },
|
{ content: '零售业务', revenue: 54880000000, gross_margin: 67.8, profit_margin: 41.5, profit: 22775200000 },
|
||||||
{ content: '对公金融业务', revenue: 66280000000, gross_margin: 61.9, profit_margin: 37.8, profit: 25053840000 },
|
{ content: '金融服务', revenue: 31360000000, gross_margin: 61.9, profit_margin: 37.8, profit: 11854080000 },
|
||||||
{ content: '金融市场业务', revenue: 11870000000, gross_margin: 74.5, profit_margin: 51.2, profit: 6077440000 },
|
{ 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 },
|
{ 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',
|
period: '2024-03-31',
|
||||||
report_type: '2024年一季报',
|
report_type: '2024年一季报',
|
||||||
products: [
|
products: [
|
||||||
{ content: '零售金融业务', revenue: 38920000000, gross_margin: 67.2, profit_margin: 40.8, profit: 15879360000 },
|
{ content: '零售业务', revenue: 27090000000, gross_margin: 67.2, profit_margin: 40.8, profit: 11052720000 },
|
||||||
{ content: '对公金融业务', revenue: 32650000000, gross_margin: 61.2, profit_margin: 37.1, profit: 12113150000 },
|
{ content: '金融服务', revenue: 15480000000, gross_margin: 61.2, profit_margin: 37.1, profit: 5743080000 },
|
||||||
{ content: '金融市场业务', revenue: 5830000000, gross_margin: 73.8, profit_margin: 50.5, profit: 2944150000 },
|
{ 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 },
|
{ 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',
|
period: '2023-12-31',
|
||||||
report_type: '2023年年报',
|
report_type: '2023年年报',
|
||||||
products: [
|
products: [
|
||||||
{ content: '零售金融业务', revenue: 152680000000, gross_margin: 66.5, profit_margin: 40.2, profit: 61377360000 },
|
{ content: '零售业务', revenue: 106400000000, gross_margin: 66.5, profit_margin: 40.2, profit: 42772800000 },
|
||||||
{ content: '对公金融业务', revenue: 128450000000, gross_margin: 60.5, profit_margin: 36.5, profit: 46884250000 },
|
{ content: '金融服务', revenue: 60800000000, gross_margin: 60.5, profit_margin: 36.5, profit: 22192000000 },
|
||||||
{ content: '金融市场业务', revenue: 22870000000, gross_margin: 73.2, profit_margin: 49.8, profit: 11389260000 },
|
{ 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 },
|
{ content: '合计', revenue: 304000000000, gross_margin: 65.2, profit_margin: 39.2, profit: 119168000000 },
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import { formatUtils } from '@services/financialService';
|
|||||||
|
|
||||||
// 复用 MarketDataView 的组件
|
// 复用 MarketDataView 的组件
|
||||||
import MetricCard from '../../MarketDataView/components/StockSummaryCard/MetricCard';
|
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 { darkGoldTheme } from '../../MarketDataView/constants';
|
||||||
import { HistoricalComparisonTable } from './MainBusinessAnalysis';
|
import { HistoricalComparisonTable } from './MainBusinessAnalysis';
|
||||||
|
|
||||||
@@ -233,38 +233,12 @@ export const FinancialOverviewPanel: React.FC<FinancialOverviewPanelProps> = mem
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 卡片4: 主营业务 - 环形图+图例风格 */}
|
{/* 卡片4: 主营业务 - 使用 MetricCard 组件 */}
|
||||||
<DarkGoldCard cornerDecor position="relative" overflow="hidden">
|
<MetricCard
|
||||||
{/* Header: 标题区域 */}
|
title="主营业务"
|
||||||
<HStack justify="space-between" align="center" mb={2} position="relative" zIndex={1}>
|
subtitle="核心构成"
|
||||||
<HStack spacing={2}>
|
leftIcon={<PieChart size={14} />}
|
||||||
<Box
|
rightIcon={
|
||||||
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>
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
size="xs"
|
size="xs"
|
||||||
bg="rgba(59, 130, 246, 0.9)"
|
bg="rgba(59, 130, 246, 0.9)"
|
||||||
@@ -274,130 +248,78 @@ export const FinancialOverviewPanel: React.FC<FinancialOverviewPanelProps> = mem
|
|||||||
onClick={onOpen}
|
onClick={onOpen}
|
||||||
isDisabled={!mainBusinessData}
|
isDisabled={!mainBusinessData}
|
||||||
fontSize="2xs"
|
fontSize="2xs"
|
||||||
h="22px"
|
h="18px"
|
||||||
px={2}
|
px={1.5}
|
||||||
borderRadius="full"
|
borderRadius="full"
|
||||||
>
|
>
|
||||||
历史对比
|
历史对比
|
||||||
</Button>
|
</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);
|
||||||
|
|
||||||
{/* 分隔线 */}
|
return (
|
||||||
<Box h="1px" bg="linear-gradient(90deg, rgba(212, 175, 55, 0.4), transparent)" mb={2} />
|
<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: 环形图 + 图例 */}
|
{/* 图例网格(2列3行) */}
|
||||||
<HStack spacing={3} align="center" flex={1} position="relative" zIndex={1}>
|
<SimpleGrid columns={2} spacing={2} spacingY={0.5}>
|
||||||
{mainBusinessData ? (
|
{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}>
|
||||||
const BUSINESS_COLORS = ['#D4AF37', '#4A90D9', '#6B7280', '#10B981', '#F59E0B', '#EF4444'];
|
{item.content}
|
||||||
|
|
||||||
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)}%
|
|
||||||
</Text>
|
</Text>
|
||||||
<Text fontSize="2xs" color={darkGoldTheme.textMuted} whiteSpace="nowrap">
|
<Text fontSize="xs" fontWeight="bold" color="gray.100" flexShrink={0}>
|
||||||
{coreItem?.content?.slice(0, 4) || '-'}(核心)
|
{item.ratio.toFixed(1)}%
|
||||||
</Text>
|
</Text>
|
||||||
</VStack>
|
</HStack>
|
||||||
</Box>
|
))}
|
||||||
|
</SimpleGrid>
|
||||||
{/* 右侧:图例列表 */}
|
</VStack>
|
||||||
<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>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
})()}
|
|
||||||
</>
|
|
||||||
) : (
|
) : (
|
||||||
<VStack w="100%" justify="center" py={4}>
|
<Text textAlign="center">暂无数据</Text>
|
||||||
<Text fontSize="xs" color={darkGoldTheme.textMuted}>暂无数据</Text>
|
)
|
||||||
</VStack>
|
}
|
||||||
)}
|
/>
|
||||||
</HStack>
|
|
||||||
</DarkGoldCard>
|
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
|
|
||||||
{/* 历史对比弹窗 */}
|
{/* 历史对比弹窗 */}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ const MetricCard: React.FC<MetricCardProps> = ({
|
|||||||
const ambientColor = getAmbientColor(mainColor);
|
const ambientColor = getAmbientColor(mainColor);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DarkGoldCard cornerDecor={true} position="relative" overflow="hidden">
|
<DarkGoldCard cornerDecor={true} position="relative" overflow="hidden" display="flex" flexDirection="column">
|
||||||
{/* James Turrell 环境光效果 */}
|
{/* James Turrell 环境光效果 */}
|
||||||
<Box
|
<Box
|
||||||
position="absolute"
|
position="absolute"
|
||||||
@@ -98,18 +98,25 @@ const MetricCard: React.FC<MetricCardProps> = ({
|
|||||||
/>
|
/>
|
||||||
</VStack>
|
</VStack>
|
||||||
|
|
||||||
{/* 辅助信息 */}
|
{/* 辅助信息 - 在剩余高度居中 */}
|
||||||
<Box
|
<Box
|
||||||
color={darkGoldTheme.textMuted}
|
flex={1}
|
||||||
fontSize="xs"
|
display="flex"
|
||||||
|
alignItems="center"
|
||||||
position="relative"
|
position="relative"
|
||||||
zIndex={1}
|
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>
|
</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}
|
{children}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
Reference in New Issue
Block a user