- 复用 MetricCard 组件构建三列布局 - 成长能力:利润增长、营收增长、预增标签 - 盈利与回报:ROE、净利率、毛利率 - 风险与运营:资产负债率、流动比率、研发费用率 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
189 lines
6.9 KiB
TypeScript
189 lines
6.9 KiB
TypeScript
/**
|
||
* 财务全景面板组件 - 三列布局
|
||
* 复用 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;
|