refactor(FinancialPanorama): 重构为 SubTabContainer 二级导航

- 主组件从 Chakra Tabs 迁移到 SubTabContainer
  - 新增 PeriodSelector 时间选择器组件
  - IndustryRankingView 增加深色主题支持
  - 拆分出 6 个独立 Tab 组件到 tabs/ 目录
  - 类型定义优化,props 改为可选
This commit is contained in:
zdl
2025-12-16 16:33:25 +08:00
parent e08b9d2104
commit 6a4c475d3a
13 changed files with 697 additions and 238 deletions

View File

@@ -1,9 +1,9 @@
/**
* 财务全景组件
* 重构后的主组件,使用模块化结构
* 重构后的主组件,使用模块化结构和 SubTabContainer 二级导航
*/
import React, { useState, ReactNode } from 'react';
import React, { useState, useMemo, ReactNode } from 'react';
import {
Box,
Container,
@@ -11,20 +11,12 @@ import {
HStack,
Card,
CardBody,
CardHeader,
Heading,
Text,
Badge,
Select,
IconButton,
Alert,
AlertIcon,
Skeleton,
Tabs,
TabList,
TabPanels,
Tab,
TabPanel,
Modal,
ModalOverlay,
ModalContent,
@@ -40,25 +32,25 @@ import {
Td,
TableContainer,
Divider,
Tooltip,
} from '@chakra-ui/react';
import { RepeatIcon } from '@chakra-ui/icons';
import { BarChart3, DollarSign, TrendingUp } from 'lucide-react';
import ReactECharts from 'echarts-for-react';
import { formatUtils } from '@services/financialService';
// 通用组件
import SubTabContainer, { type SubTabConfig } from '@components/SubTabContainer';
// 内部模块导入
import { useFinancialData } from './hooks';
import { COLORS } from './constants';
import { calculateYoYChange, getCellBackground, getMetricChartOption } from './utils';
import {
StockInfoHeader,
BalanceSheetTable,
IncomeStatementTable,
CashflowTable,
FinancialMetricsTable,
MainBusinessAnalysis,
ComparisonAnalysis,
} from './components';
import { BalanceSheetTab, IncomeStatementTab, CashflowTab } from './tabs';
import type { FinancialPanoramaProps } from './types';
/**
@@ -73,29 +65,24 @@ const FinancialPanorama: React.FC<FinancialPanoramaProps> = ({ stockCode: propSt
cashflow,
financialMetrics,
mainBusiness,
forecast,
industryRank,
comparison,
loading,
error,
refetch,
currentStockCode,
selectedPeriods,
setSelectedPeriods,
} = useFinancialData({ stockCode: propStockCode });
// UI 状态
const [activeTab, setActiveTab] = useState(0);
const { isOpen, onOpen, onClose } = useDisclosure();
const [modalContent, setModalContent] = useState<ReactNode>(null);
// 颜色配置
const { bgColor, hoverBg, positiveColor, negativeColor, borderColor } = COLORS;
const { bgColor, hoverBg, positiveColor, negativeColor } = COLORS;
// 点击指标行显示图表
const showMetricChart = (
metricName: string,
metricKey: string,
_metricKey: string,
data: Array<{ period: string; [key: string]: unknown }>,
dataPath: string
) => {
@@ -204,6 +191,45 @@ const FinancialPanorama: React.FC<FinancialPanoramaProps> = ({ stockCode: propSt
hoverBg,
};
// Tab 配置 - 只保留三大财务报表
const tabConfigs: SubTabConfig[] = useMemo(
() => [
{ key: 'balance', name: '资产负债表', icon: BarChart3, component: BalanceSheetTab },
{ key: 'income', name: '利润表', icon: DollarSign, component: IncomeStatementTab },
{ key: 'cashflow', name: '现金流量表', icon: TrendingUp, component: CashflowTab },
],
[]
);
// 传递给 Tab 组件的 props
const componentProps = useMemo(
() => ({
// 数据
balanceSheet,
incomeStatement,
cashflow,
// 工具函数
showMetricChart,
calculateYoYChange,
getCellBackground,
// 颜色配置
positiveColor,
negativeColor,
bgColor,
hoverBg,
}),
[
balanceSheet,
incomeStatement,
cashflow,
showMetricChart,
positiveColor,
negativeColor,
bgColor,
hoverBg,
]
);
return (
<Container maxW="container.xl" py={5}>
<VStack spacing={6} align="stretch">
@@ -250,124 +276,35 @@ const FinancialPanorama: React.FC<FinancialPanoramaProps> = ({ stockCode: propSt
/>
)}
{/* 主要内容区域 */}
{/* 财务指标速览 */}
{!loading && stockInfo && (
<Tabs
index={activeTab}
onChange={setActiveTab}
variant="enclosed"
colorScheme="blue"
>
<TabList>
<Tab></Tab>
<Tab></Tab>
<Tab></Tab>
<Tab></Tab>
<Tab></Tab>
<Tab></Tab>
</TabList>
<FinancialMetricsTable data={financialMetrics} {...tableProps} />
)}
<TabPanels>
{/* 财务概览 */}
<TabPanel>
<VStack spacing={4} align="stretch">
<ComparisonAnalysis comparison={comparison} />
<FinancialMetricsTable data={financialMetrics} {...tableProps} />
</VStack>
</TabPanel>
{/* 主营业务 */}
{!loading && stockInfo && (
<Card>
<CardBody>
<Text fontSize="lg" fontWeight="bold" mb={4}>
</Text>
<MainBusinessAnalysis mainBusiness={mainBusiness} />
</CardBody>
</Card>
)}
{/* 资产负债表 */}
<TabPanel>
<Card>
<CardHeader>
<VStack align="stretch" spacing={2}>
<HStack justify="space-between">
<Heading size="md"></Heading>
<HStack spacing={2}>
<Badge colorScheme="blue">
{Math.min(balanceSheet.length, 8)}
</Badge>
<Text fontSize="sm" color="gray.500">
绿 |
</Text>
</HStack>
</HStack>
<Text fontSize="xs" color="gray.500">
</Text>
</VStack>
</CardHeader>
<CardBody>
<BalanceSheetTable data={balanceSheet} {...tableProps} />
</CardBody>
</Card>
</TabPanel>
{/* 利润表 */}
<TabPanel>
<Card>
<CardHeader>
<VStack align="stretch" spacing={2}>
<HStack justify="space-between">
<Heading size="md"></Heading>
<HStack spacing={2}>
<Badge colorScheme="blue">
{Math.min(incomeStatement.length, 8)}
</Badge>
<Text fontSize="sm" color="gray.500">
绿 |
</Text>
</HStack>
</HStack>
<Text fontSize="xs" color="gray.500">
Q1Q3
</Text>
</VStack>
</CardHeader>
<CardBody>
<IncomeStatementTable data={incomeStatement} {...tableProps} />
</CardBody>
</Card>
</TabPanel>
{/* 现金流量表 */}
<TabPanel>
<Card>
<CardHeader>
<VStack align="stretch" spacing={2}>
<HStack justify="space-between">
<Heading size="md"></Heading>
<HStack spacing={2}>
<Badge colorScheme="blue">
{Math.min(cashflow.length, 8)}
</Badge>
<Text fontSize="sm" color="gray.500">
绿 |
</Text>
</HStack>
</HStack>
<Text fontSize="xs" color="gray.500">
绿
</Text>
</VStack>
</CardHeader>
<CardBody>
<CashflowTable data={cashflow} {...tableProps} />
</CardBody>
</Card>
</TabPanel>
{/* 财务指标 */}
<TabPanel>
<FinancialMetricsTable data={financialMetrics} {...tableProps} />
</TabPanel>
{/* 主营业务 */}
<TabPanel>
<MainBusinessAnalysis mainBusiness={mainBusiness} />
</TabPanel>
</TabPanels>
</Tabs>
{/* 三大财务报表 - 使用 SubTabContainer 二级导航 */}
{!loading && stockInfo && (
<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
/>
</CardBody>
</Card>
)}
{/* 错误提示 */}