- Container py: 6 → 4 - VStack spacing: 6 → 4 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
252 lines
6.8 KiB
TypeScript
252 lines
6.8 KiB
TypeScript
/**
|
||
* 财务全景组件
|
||
* 重构后的主组件,使用模块化结构和 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;
|