Files
vf_react/src/views/Company/components/FinancialPanorama/index.tsx
zdl 11544909d3 style(MarketDataView): 缩小页面间距
- Container py: 6 → 4
- VStack spacing: 6 → 4

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-19 18:55:04 +08:00

252 lines
6.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 财务全景组件
* 重构后的主组件,使用模块化结构和 SubTabContainer 二级导航
*/
import React, { useState, useMemo, useCallback } from 'react';
import {
Box,
Container,
VStack,
Card,
CardBody,
Text,
Alert,
AlertIcon,
useDisclosure,
} from '@chakra-ui/react';
import {
BarChart3,
DollarSign,
TrendingUp,
PieChart,
Percent,
TrendingDown,
Activity,
Shield,
Receipt,
Banknote,
} from 'lucide-react';
// 通用组件
import SubTabContainer, { type SubTabConfig } from '@components/SubTabContainer';
// 内部模块导入
import { useFinancialData, type DataTypeKey } from './hooks';
import { COLORS } from './constants';
import { calculateYoYChange, getCellBackground } from './utils';
import {
PeriodSelector,
FinancialOverviewPanel,
MainBusinessAnalysis,
ComparisonAnalysis,
MetricChartModal,
FinancialPanoramaSkeleton,
} from './components';
import {
BalanceSheetTab,
IncomeStatementTab,
CashflowTab,
ProfitabilityTab,
PerShareTab,
GrowthTab,
OperationalTab,
SolvencyTab,
ExpenseTab,
CashflowMetricsTab,
} from './tabs';
import type { FinancialPanoramaProps } from './types';
/**
* 财务全景主组件
*/
// Tab key 映射表SubTabContainer index -> DataTypeKey
const TAB_KEY_MAP: DataTypeKey[] = [
'profitability',
'perShare',
'growth',
'operational',
'solvency',
'expense',
'cashflowMetrics',
'balance',
'income',
'cashflow',
];
const FinancialPanorama: React.FC<FinancialPanoramaProps> = ({ stockCode: propStockCode }) => {
// 使用数据加载 Hook
const {
stockInfo,
balanceSheet,
incomeStatement,
cashflow,
financialMetrics,
mainBusiness,
comparison,
loading,
loadingTab,
error,
refetchByTab,
selectedPeriods,
setSelectedPeriods,
handleTabChange: handleTabChangeWithPeriodCheck,
activeTab,
} = useFinancialData({ stockCode: propStockCode });
// 处理 Tab 切换(使用 hook 提供的带期数检查的函数)
const handleTabChange = useCallback((index: number, tabKey: string) => {
const dataTypeKey = TAB_KEY_MAP[index] || (tabKey as DataTypeKey);
handleTabChangeWithPeriodCheck(dataTypeKey);
}, [handleTabChangeWithPeriodCheck]);
// 处理刷新 - 只刷新当前 Tab
const handleRefresh = useCallback(() => {
refetchByTab(activeTab);
}, [refetchByTab, activeTab]);
// UI 状态
const { isOpen, onOpen, onClose } = useDisclosure();
const [modalProps, setModalProps] = useState<{
metricName: string;
data: Array<{ period: string; [key: string]: unknown }>;
dataPath: string;
}>({ metricName: '', data: [], dataPath: '' });
// 点击指标行显示图表
const showMetricChart = useCallback((
metricName: string,
_metricKey: string,
data: Array<{ period: string; [key: string]: unknown }>,
dataPath: string
) => {
setModalProps({ metricName, data, dataPath });
onOpen();
}, [onOpen]);
// Tab 配置 - 财务指标分类 + 三大财务报表
const tabConfigs: SubTabConfig[] = useMemo(
() => [
// 财务指标分类7个
{ key: 'profitability', name: '盈利能力', icon: PieChart, component: ProfitabilityTab },
{ key: 'perShare', name: '每股指标', icon: Percent, component: PerShareTab },
{ key: 'growth', name: '成长能力', icon: TrendingUp, component: GrowthTab },
{ key: 'operational', name: '运营效率', icon: Activity, component: OperationalTab },
{ key: 'solvency', name: '偿债能力', icon: Shield, component: SolvencyTab },
{ key: 'expense', name: '费用率', icon: Receipt, component: ExpenseTab },
{ key: 'cashflowMetrics', name: '现金流指标', icon: Banknote, component: CashflowMetricsTab },
// 三大财务报表
{ key: 'balance', name: '资产负债表', icon: BarChart3, component: BalanceSheetTab },
{ key: 'income', name: '利润表', icon: DollarSign, component: IncomeStatementTab },
{ key: 'cashflow', name: '现金流量表', icon: TrendingDown, component: CashflowTab },
],
[]
);
// 传递给 Tab 组件的 props颜色使用常量不需要在依赖数组中
const componentProps = useMemo(
() => ({
// 数据
balanceSheet,
incomeStatement,
cashflow,
financialMetrics,
// 加载状态
loading,
loadingTab,
// 工具函数
showMetricChart,
calculateYoYChange,
getCellBackground,
// 颜色配置(使用常量)
...COLORS,
}),
[
balanceSheet,
incomeStatement,
cashflow,
financialMetrics,
loading,
loadingTab,
showMetricChart,
]
);
// 初始加载显示骨架屏
if (loading && !stockInfo) {
return (
<Container maxW="container.xl" py={5}>
<FinancialPanoramaSkeleton />
</Container>
);
}
return (
<Container maxW="container.xl" py={5}>
<VStack spacing={6} align="stretch">
{/* 财务全景面板(三列布局:成长能力、盈利与回报、风险与运营) */}
<FinancialOverviewPanel
stockInfo={stockInfo}
financialMetrics={financialMetrics}
/>
{/* 营收与利润趋势 */}
{comparison && comparison.length > 0 && (
<ComparisonAnalysis comparison={comparison} />
)}
{/* 主营业务 */}
{stockInfo && (
<Box>
<Text fontSize="lg" fontWeight="bold" mb={4} color="#D4AF37">
</Text>
<MainBusinessAnalysis mainBusiness={mainBusiness} />
</Box>
)}
{/* 三大财务报表 - 使用 SubTabContainer 二级导航 */}
<Card bg="gray.900" shadow="md" border="1px solid" borderColor="rgba(212, 175, 55, 0.3)">
<CardBody p={0}>
<SubTabContainer
tabs={tabConfigs}
componentProps={componentProps}
themePreset="blackGold"
isLazy
size="sm"
onTabChange={handleTabChange}
rightElement={
<PeriodSelector
selectedPeriods={selectedPeriods}
onPeriodsChange={setSelectedPeriods}
onRefresh={handleRefresh}
isLoading={loadingTab !== null || loading}
/>
}
/>
</CardBody>
</Card>
{/* 错误提示 */}
{error && (
<Alert status="error">
<AlertIcon />
{error}
</Alert>
)}
{/* 指标图表弹窗 */}
<MetricChartModal
isOpen={isOpen}
onClose={onClose}
metricName={modalProps.metricName}
data={modalProps.data}
dataPath={modalProps.dataPath}
/>
</VStack>
</Container>
);
};
export default FinancialPanorama;