/** * 财务全景组件 * 重构后的主组件,使用模块化结构和 SubTabContainer 二级导航 */ import React, { useState, useMemo, useCallback, ReactNode } from 'react'; import { Box, Container, VStack, Card, CardBody, Text, Alert, AlertIcon, Skeleton, Modal, ModalOverlay, ModalContent, ModalHeader, ModalBody, ModalCloseButton, useDisclosure, Table, Thead, Tbody, Tr, Th, Td, TableContainer, Divider, } from '@chakra-ui/react'; import { BarChart3, DollarSign, TrendingUp, PieChart, Percent, TrendingDown, Activity, Shield, Receipt, Banknote, } from 'lucide-react'; import ReactECharts from 'echarts-for-react'; import { formatUtils } from '@services/financialService'; // 通用组件 import SubTabContainer, { type SubTabConfig } from '@components/SubTabContainer'; // 内部模块导入 import { useFinancialData, type DataTypeKey } from './hooks'; import { COLORS } from './constants'; import { calculateYoYChange, getCellBackground, getMetricChartOption } from './utils'; import { PeriodSelector, FinancialOverviewPanel, MainBusinessAnalysis, ComparisonAnalysis } 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 = ({ stockCode: propStockCode }) => { // 使用数据加载 Hook const { stockInfo, balanceSheet, incomeStatement, cashflow, financialMetrics, mainBusiness, comparison, loading, loadingTab, error, refetchByTab, selectedPeriods, setSelectedPeriods, setActiveTab, activeTab, } = useFinancialData({ stockCode: propStockCode }); // 处理 Tab 切换 const handleTabChange = useCallback((index: number, tabKey: string) => { const dataTypeKey = TAB_KEY_MAP[index] || (tabKey as DataTypeKey); setActiveTab(dataTypeKey); }, [setActiveTab]); // 处理刷新 - 只刷新当前 Tab const handleRefresh = useCallback(() => { refetchByTab(activeTab); }, [refetchByTab, activeTab]); // UI 状态 const { isOpen, onOpen, onClose } = useDisclosure(); const [modalContent, setModalContent] = useState(null); // 颜色配置 const { bgColor, hoverBg, positiveColor, negativeColor } = COLORS; // 点击指标行显示图表 const showMetricChart = ( metricName: string, _metricKey: string, data: Array<{ period: string; [key: string]: unknown }>, dataPath: string ) => { const chartData = data .map((item) => { const value = dataPath.split('.').reduce((obj: unknown, key: string) => { if (obj && typeof obj === 'object') { return (obj as Record)[key]; } return undefined; }, item) as number | undefined; return { period: formatUtils.getReportType(item.period), date: item.period, value: value ?? 0, }; }) .reverse(); const option = getMetricChartOption(metricName, chartData); setModalContent( {chartData.map((item, idx) => { // 计算环比 const qoq = idx > 0 ? ((item.value - chartData[idx - 1].value) / Math.abs(chartData[idx - 1].value)) * 100 : null; // 计算同比 const currentDate = new Date(item.date); const lastYearItem = chartData.find((d) => { const date = new Date(d.date); return ( date.getFullYear() === currentDate.getFullYear() - 1 && date.getMonth() === currentDate.getMonth() ); }); const yoy = lastYearItem ? ((item.value - lastYearItem.value) / Math.abs(lastYearItem.value)) * 100 : null; return ( ); })}
报告期 数值 同比 环比
{item.period} {formatUtils.formatLargeNumber(item.value)} 0 ? positiveColor : yoy !== null && yoy < 0 ? negativeColor : 'gray.500' } > {yoy !== null ? `${yoy.toFixed(2)}%` : '-'} 0 ? positiveColor : qoq !== null && qoq < 0 ? negativeColor : 'gray.500' } > {qoq !== null ? `${qoq.toFixed(2)}%` : '-'}
); 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, // 工具函数 showMetricChart, calculateYoYChange, getCellBackground, // 颜色配置 positiveColor, negativeColor, bgColor, hoverBg, }), [ balanceSheet, incomeStatement, cashflow, financialMetrics, showMetricChart, positiveColor, negativeColor, bgColor, hoverBg, ] ); return ( {/* 财务全景面板(三列布局:成长能力、盈利与回报、风险与运营) */} {loading ? ( ) : ( )} {/* 营收与利润趋势 */} {!loading && comparison && comparison.length > 0 && ( )} {/* 主营业务 */} {!loading && stockInfo && ( 主营业务 )} {/* 三大财务报表 - 使用 SubTabContainer 二级导航 */} {!loading && stockInfo && ( } /> )} {/* 错误提示 */} {error && ( {error} )} {/* 弹出模态框 */} 指标详情 {modalContent} ); }; export default FinancialPanorama;