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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user