feat(FinancialPanorama): 新增 FinancialOverviewPanel 三模块布局

- 复用 MetricCard 组件构建三列布局
- 成长能力:利润增长、营收增长、预增标签
- 盈利与回报:ROE、净利率、毛利率
- 风险与运营:资产负债率、流动比率、研发费用率

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
zdl
2025-12-16 20:17:08 +08:00
parent 83b24b6d54
commit faf2446203
2 changed files with 190 additions and 0 deletions

View File

@@ -0,0 +1,188 @@
/**
* 财务全景面板组件 - 三列布局
* 复用 MarketDataView 的 MetricCard 组件
*/
import React, { memo } from 'react';
import { SimpleGrid, HStack, VStack, Text, Badge } from '@chakra-ui/react';
import { TrendingUp, Coins, Shield, TrendingDown, Activity, PieChart } from 'lucide-react';
import { formatUtils } from '@services/financialService';
// 复用 MarketDataView 的组件
import MetricCard from '../../MarketDataView/components/StockSummaryCard/MetricCard';
import { StatusTag } from '../../MarketDataView/components/StockSummaryCard/atoms';
import { darkGoldTheme } from '../../MarketDataView/constants';
import type { StockInfo, FinancialMetricsData } from '../types';
export interface FinancialOverviewPanelProps {
stockInfo: StockInfo | null;
financialMetrics: FinancialMetricsData[];
}
/**
* 获取成长状态
*/
const getGrowthStatus = (value: number | undefined): { text: string; color: string } => {
if (value === undefined || value === null) return { text: '-', color: darkGoldTheme.textMuted };
if (value > 30) return { text: '高速增长', color: darkGoldTheme.green };
if (value > 10) return { text: '稳健增长', color: darkGoldTheme.gold };
if (value > 0) return { text: '低速增长', color: darkGoldTheme.orange };
if (value > -10) return { text: '小幅下滑', color: darkGoldTheme.orange };
return { text: '大幅下滑', color: darkGoldTheme.red };
};
/**
* 获取 ROE 状态
*/
const getROEStatus = (value: number | undefined): { text: string; color: string } => {
if (value === undefined || value === null) return { text: '-', color: darkGoldTheme.textMuted };
if (value > 20) return { text: '优秀', color: darkGoldTheme.green };
if (value > 15) return { text: '良好', color: darkGoldTheme.gold };
if (value > 10) return { text: '一般', color: darkGoldTheme.orange };
return { text: '较低', color: darkGoldTheme.red };
};
/**
* 获取资产负债率状态
*/
const getDebtStatus = (value: number | undefined): { text: string; color: string } => {
if (value === undefined || value === null) return { text: '-', color: darkGoldTheme.textMuted };
if (value < 40) return { text: '安全', color: darkGoldTheme.green };
if (value < 60) return { text: '适中', color: darkGoldTheme.gold };
if (value < 70) return { text: '偏高', color: darkGoldTheme.orange };
return { text: '风险', color: darkGoldTheme.red };
};
/**
* 财务全景面板组件
*/
export const FinancialOverviewPanel: React.FC<FinancialOverviewPanelProps> = memo(({
stockInfo,
financialMetrics,
}) => {
if (!stockInfo && (!financialMetrics || financialMetrics.length === 0)) {
return null;
}
// 获取最新一期财务指标
const latestMetrics = financialMetrics?.[0];
// 成长指标(来自 stockInfo
const revenueGrowth = stockInfo?.growth_rates?.revenue_growth;
const profitGrowth = stockInfo?.growth_rates?.profit_growth;
const forecast = stockInfo?.latest_forecast;
// 盈利指标(来自 financialMetrics
const roe = latestMetrics?.profitability?.roe;
const netProfitMargin = latestMetrics?.profitability?.net_profit_margin;
const grossMargin = latestMetrics?.profitability?.gross_margin;
// 风险与运营指标(来自 financialMetrics
const assetLiabilityRatio = latestMetrics?.solvency?.asset_liability_ratio;
const currentRatio = latestMetrics?.solvency?.current_ratio;
const rdExpenseRatio = latestMetrics?.expense_ratios?.rd_expense_ratio;
// 计算状态
const growthStatus = getGrowthStatus(profitGrowth);
const roeStatus = getROEStatus(roe);
const debtStatus = getDebtStatus(assetLiabilityRatio);
// 格式化涨跌显示
const formatGrowth = (value: number | undefined) => {
if (value === undefined || value === null) return '-';
const sign = value >= 0 ? '+' : '';
return `${sign}${value.toFixed(2)}%`;
};
return (
<SimpleGrid columns={{ base: 1, md: 3 }} spacing={3}>
{/* 卡片1: 成长能力 */}
<MetricCard
title="成长能力"
subtitle="增长动力"
leftIcon={<TrendingUp size={14} />}
rightIcon={<Activity size={14} />}
mainLabel="利润增长"
mainValue={formatGrowth(profitGrowth)}
mainColor={profitGrowth !== undefined && profitGrowth >= 0 ? darkGoldTheme.green : darkGoldTheme.red}
subText={
<VStack align="start" spacing={1}>
<HStack spacing={1} flexWrap="wrap">
<Text></Text>
<Text
fontWeight="bold"
color={revenueGrowth !== undefined && revenueGrowth >= 0 ? darkGoldTheme.green : darkGoldTheme.red}
>
{formatGrowth(revenueGrowth)}
</Text>
<StatusTag text={growthStatus.text} color={growthStatus.color} />
</HStack>
{forecast && (
<Badge
bg="rgba(212, 175, 55, 0.15)"
color={darkGoldTheme.gold}
fontSize="xs"
px={2}
py={0.5}
borderRadius="md"
>
{forecast.forecast_type} {forecast.content}
</Badge>
)}
</VStack>
}
/>
{/* 卡片2: 盈利与回报 */}
<MetricCard
title="盈利与回报"
subtitle="赚钱能力"
leftIcon={<Coins size={14} />}
rightIcon={<PieChart size={14} />}
mainLabel="ROE"
mainValue={formatUtils.formatPercent(roe)}
mainColor={darkGoldTheme.orange}
subText={
<VStack align="start" spacing={0.5}>
<Text color={roeStatus.color} fontWeight="medium">
{roeStatus.text}
</Text>
<HStack spacing={1} flexWrap="wrap">
<Text> {formatUtils.formatPercent(netProfitMargin)}</Text>
<Text>|</Text>
<Text> {formatUtils.formatPercent(grossMargin)}</Text>
</HStack>
</VStack>
}
/>
{/* 卡片3: 风险与运营 */}
<MetricCard
title="风险与运营"
subtitle="安全边际"
leftIcon={<Shield size={14} />}
rightIcon={<TrendingDown size={14} />}
mainLabel="资产负债率"
mainValue={formatUtils.formatPercent(assetLiabilityRatio)}
mainColor={debtStatus.color}
subText={
<VStack align="start" spacing={0.5}>
<Text color={debtStatus.color} fontWeight="medium">
{debtStatus.text}
</Text>
<HStack spacing={1} flexWrap="wrap">
<Text> {currentRatio?.toFixed(2) ?? '-'}</Text>
<Text>|</Text>
<Text> {formatUtils.formatPercent(rdExpenseRatio)}</Text>
</HStack>
</VStack>
}
/>
</SimpleGrid>
);
});
FinancialOverviewPanel.displayName = 'FinancialOverviewPanel';
export default FinancialOverviewPanel;