refactor(FinancialPanorama): 重构为 7+3 Tab 架构

- 财务指标拆分为 7 个分类 Tab(盈利/每股/成长/运营/偿债/费用/现金流)
- 保留 3 大报表 Tab(资产负债表/利润表/现金流量表)
- 新增 KeyMetricsOverview 关键指标速览组件
- 新增 FinancialTable 通用表格组件
- Hook 支持按 Tab 独立刷新数据
- PeriodSelector 整合到 SubTabContainer 右侧
- 删除废弃的 OverviewTab/MainBusinessTab

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
zdl
2025-12-16 19:59:16 +08:00
parent 42215b2d59
commit bc6d370f55
20 changed files with 2414 additions and 1082 deletions

View File

@@ -3,17 +3,14 @@
* 重构后的主组件,使用模块化结构和 SubTabContainer 二级导航
*/
import React, { useState, useMemo, ReactNode } from 'react';
import React, { useState, useMemo, useCallback, ReactNode } from 'react';
import {
Box,
Container,
VStack,
HStack,
Card,
CardBody,
Text,
Select,
IconButton,
Alert,
AlertIcon,
Skeleton,
@@ -33,8 +30,18 @@ import {
TableContainer,
Divider,
} from '@chakra-ui/react';
import { RepeatIcon } from '@chakra-ui/icons';
import { BarChart3, DollarSign, TrendingUp } from 'lucide-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';
@@ -42,20 +49,41 @@ import { formatUtils } from '@services/financialService';
import SubTabContainer, { type SubTabConfig } from '@components/SubTabContainer';
// 内部模块导入
import { useFinancialData } from './hooks';
import { useFinancialData, type DataTypeKey } from './hooks';
import { COLORS } from './constants';
import { calculateYoYChange, getCellBackground, getMetricChartOption } from './utils';
import { PeriodSelector, KeyMetricsOverview, StockInfoHeader, MainBusinessAnalysis } from './components';
import {
StockInfoHeader,
FinancialMetricsTable,
MainBusinessAnalysis,
} from './components';
import { BalanceSheetTab, IncomeStatementTab, CashflowTab } from './tabs';
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 {
@@ -66,12 +94,26 @@ const FinancialPanorama: React.FC<FinancialPanoramaProps> = ({ stockCode: propSt
financialMetrics,
mainBusiness,
loading,
loadingTab,
error,
refetch,
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<ReactNode>(null);
@@ -180,23 +222,21 @@ const FinancialPanorama: React.FC<FinancialPanoramaProps> = ({ stockCode: propSt
onOpen();
};
// 通用表格属性
const tableProps = {
showMetricChart,
calculateYoYChange,
getCellBackground,
positiveColor,
negativeColor,
bgColor,
hoverBg,
};
// Tab 配置 - 只保留三大财务报表
// 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: TrendingUp, component: CashflowTab },
{ key: 'cashflow', name: '现金流量表', icon: TrendingDown, component: CashflowTab },
],
[]
);
@@ -208,6 +248,7 @@ const FinancialPanorama: React.FC<FinancialPanoramaProps> = ({ stockCode: propSt
balanceSheet,
incomeStatement,
cashflow,
financialMetrics,
// 工具函数
showMetricChart,
calculateYoYChange,
@@ -222,6 +263,7 @@ const FinancialPanorama: React.FC<FinancialPanoramaProps> = ({ stockCode: propSt
balanceSheet,
incomeStatement,
cashflow,
financialMetrics,
showMetricChart,
positiveColor,
negativeColor,
@@ -233,38 +275,6 @@ const FinancialPanorama: React.FC<FinancialPanoramaProps> = ({ stockCode: propSt
return (
<Container maxW="container.xl" py={5}>
<VStack spacing={6} align="stretch">
{/* 时间选择器 */}
<Card>
<CardBody>
<HStack justify="space-between">
<HStack>
<Text fontSize="sm" color="gray.600">
</Text>
<Select
value={selectedPeriods}
onChange={(e) => setSelectedPeriods(Number(e.target.value))}
w="150px"
size="sm"
>
<option value={4}>4</option>
<option value={8}>8</option>
<option value={12}>12</option>
<option value={16}>16</option>
</Select>
</HStack>
<IconButton
icon={<RepeatIcon />}
onClick={refetch}
isLoading={loading}
variant="outline"
size="sm"
aria-label="刷新数据"
/>
</HStack>
</CardBody>
</Card>
{/* 股票信息头部 */}
{loading ? (
<Skeleton height="150px" />
@@ -276,16 +286,16 @@ const FinancialPanorama: React.FC<FinancialPanoramaProps> = ({ stockCode: propSt
/>
)}
{/* 财务指标速览 */}
{!loading && stockInfo && (
<FinancialMetricsTable data={financialMetrics} {...tableProps} />
{/* 关键指标速览 */}
{!loading && stockInfo && financialMetrics.length > 0 && (
<KeyMetricsOverview financialMetrics={financialMetrics} />
)}
{/* 主营业务 */}
{!loading && stockInfo && (
<Card>
<Card bg="gray.900" shadow="md" border="1px solid" borderColor="rgba(212, 175, 55, 0.3)">
<CardBody>
<Text fontSize="lg" fontWeight="bold" mb={4}>
<Text fontSize="lg" fontWeight="bold" mb={4} color="#D4AF37">
</Text>
<MainBusinessAnalysis mainBusiness={mainBusiness} />
@@ -302,6 +312,15 @@ const FinancialPanorama: React.FC<FinancialPanoramaProps> = ({ stockCode: propSt
componentProps={componentProps}
themePreset="blackGold"
isLazy
onTabChange={handleTabChange}
rightElement={
<PeriodSelector
selectedPeriods={selectedPeriods}
onPeriodsChange={setSelectedPeriods}
onRefresh={handleRefresh}
isLoading={loadingTab !== null}
/>
}
/>
</CardBody>
</Card>