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:
@@ -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;
|
||||
@@ -3,6 +3,8 @@
|
||||
*/
|
||||
|
||||
export { PeriodSelector } from './PeriodSelector';
|
||||
export { FinancialOverviewPanel } from './FinancialOverviewPanel';
|
||||
// 保留旧组件导出(向后兼容)
|
||||
export { KeyMetricsOverview } from './KeyMetricsOverview';
|
||||
export { StockInfoHeader } from './StockInfoHeader';
|
||||
export { BalanceSheetTable } from './BalanceSheetTable';
|
||||
|
||||
Reference in New Issue
Block a user