From 93af3b5b5bb5d071a695074f31d4cb858a76f104 Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Mon, 29 Dec 2025 16:44:57 +0800 Subject: [PATCH] =?UTF-8?q?feat(FinancialPanorama):=20SubTabContainer=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=88=86=E7=BB=84=EF=BC=8C=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E8=B5=84=E4=BA=A7=E8=B4=9F=E5=80=BA=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SubTabContainer 新增 groups 属性支持 Tab 分组显示 - 财务全景 Tab 分为"基础报表"和"财务指标分析"两组 - 默认 Tab 改为资产负债表(基础报表第一个) - 初始加载时并行请求资产负债表数据 - 表格操作列眼睛图标改为"详情"文字 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/components/SubTabContainer/index.tsx | 170 ++++++++++++------ .../components/UnifiedFinancialTable.tsx | 13 +- .../hooks/useFinancialData.ts | 10 +- .../components/FinancialPanorama/index.tsx | 50 ++++-- 4 files changed, 158 insertions(+), 85 deletions(-) diff --git a/src/components/SubTabContainer/index.tsx b/src/components/SubTabContainer/index.tsx index bd035b64..10ac38bf 100644 --- a/src/components/SubTabContainer/index.tsx +++ b/src/components/SubTabContainer/index.tsx @@ -50,6 +50,14 @@ export interface SubTabConfig { fallback?: React.ReactNode; } +/** + * Tab 分组配置 + */ +export interface SubTabGroup { + name: string; + tabs: SubTabConfig[]; +} + /** * 深空 FUI 主题配置 */ @@ -129,8 +137,10 @@ const THEME_PRESETS: Record = { }; export interface SubTabContainerProps { - /** Tab 配置数组 */ - tabs: SubTabConfig[]; + /** Tab 配置数组(与 groups 二选一) */ + tabs?: SubTabConfig[]; + /** Tab 分组配置(与 tabs 二选一) */ + groups?: SubTabGroup[]; /** 传递给 Tab 内容组件的 props */ componentProps?: Record; /** 默认选中的 Tab 索引 */ @@ -156,7 +166,8 @@ export interface SubTabContainerProps { } const SubTabContainer: React.FC = memo(({ - tabs, + tabs: tabsProp, + groups, componentProps = {}, defaultIndex = 0, index: controlledIndex, @@ -171,6 +182,21 @@ const SubTabContainer: React.FC = memo(({ }) => { // 获取尺寸配置 const sizeConfig = SIZE_CONFIG[size]; + + // 将分组展平为 tabs 数组,同时保留分组信息用于渲染分隔符 + const { tabs, groupBoundaries } = React.useMemo(() => { + if (groups && groups.length > 0) { + const flatTabs: SubTabConfig[] = []; + const boundaries: number[] = []; // 记录每个分组的起始索引 + groups.forEach((group) => { + boundaries.push(flatTabs.length); + flatTabs.push(...group.tabs); + }); + return { tabs: flatTabs, groupBoundaries: boundaries }; + } + return { tabs: tabsProp || [], groupBoundaries: [] }; + }, [groups, tabsProp]); + // 内部状态(非受控模式) const [internalIndex, setInternalIndex] = useState(defaultIndex); @@ -280,64 +306,90 @@ const SubTabContainer: React.FC = memo(({ > {tabs.map((tab, idx) => { const isSelected = idx === currentIndex; + // 检查是否需要在此 Tab 前显示分组标签 + const groupIndex = groupBoundaries.indexOf(idx); + const showGroupLabel = groups && groupIndex !== -1; return ( - - - {tab.icon && ( - - )} - {tab.name} - - + + {/* 分组标签 */} + {showGroupLabel && ( + 0 ? 3 : 0} + pr={2} + borderLeft={groupIndex > 0 ? '1px solid' : 'none'} + borderColor={DEEP_SPACE.borderGold} + ml={groupIndex > 0 ? 2 : 0} + > + + {groups[groupIndex].name} + + + )} + + + {tab.icon && ( + + )} + {tab.name} + + + ); })} diff --git a/src/views/Company/components/FinancialPanorama/components/UnifiedFinancialTable.tsx b/src/views/Company/components/FinancialPanorama/components/UnifiedFinancialTable.tsx index f039900d..730d600b 100644 --- a/src/views/Company/components/FinancialPanorama/components/UnifiedFinancialTable.tsx +++ b/src/views/Company/components/FinancialPanorama/components/UnifiedFinancialTable.tsx @@ -10,7 +10,6 @@ import React, { useMemo, memo } from 'react'; import { Box, Text, HStack, Badge as ChakraBadge, Button, Spinner, Center } from '@chakra-ui/react'; import { Table, ConfigProvider, Tooltip } from 'antd'; import type { ColumnsType } from 'antd/es/table'; -import { Eye } from 'lucide-react'; import { formatUtils } from '@services/financialService'; import { BLACK_GOLD_TABLE_THEME, getTableStyles, calculateYoY, getValueByPath, isNegativeIndicator } from '../utils'; import type { MetricConfig, MetricSectionConfig } from '../types'; @@ -308,15 +307,19 @@ const UnifiedFinancialTableInner: React.FC = ({ if (type === 'metrics') { return ( - { e.stopPropagation(); showMetricChart(record.name, record.key, data, record.path); }} - /> + > + 详情 + ); } diff --git a/src/views/Company/components/FinancialPanorama/hooks/useFinancialData.ts b/src/views/Company/components/FinancialPanorama/hooks/useFinancialData.ts index 50a886a5..b3e7ee1d 100644 --- a/src/views/Company/components/FinancialPanorama/hooks/useFinancialData.ts +++ b/src/views/Company/components/FinancialPanorama/hooks/useFinancialData.ts @@ -83,7 +83,7 @@ export const useFinancialData = ( // 参数状态 const [stockCode, setStockCode] = useState(initialStockCode); const [selectedPeriods, setSelectedPeriodsState] = useState(initialPeriods); - const [activeTab, setActiveTab] = useState('profitability'); + const [activeTab, setActiveTab] = useState('balance'); // 加载状态 const [loading, setLoading] = useState(false); @@ -252,19 +252,21 @@ export const useFinancialData = ( setError(null); try { - // 只加载核心数据(概览面板 + 归母净利润趋势图需要的) + // 只加载核心数据(概览面板 + 归母净利润趋势图 + 默认Tab资产负债表需要的) const [ stockInfoRes, metricsRes, comparisonRes, businessRes, incomeRes, + balanceRes, ] = await Promise.all([ financialService.getStockInfo(stockCode, options), financialService.getFinancialMetrics(stockCode, selectedPeriods, options), financialService.getPeriodComparison(stockCode, selectedPeriods, options), financialService.getMainBusiness(stockCode, 4, options), financialService.getIncomeStatement(stockCode, selectedPeriods, options), + financialService.getBalanceSheet(stockCode, selectedPeriods, options), ]); // 设置数据 @@ -279,6 +281,10 @@ export const useFinancialData = ( setIncomeStatement(incomeRes.data); dataPeriodsRef.current.income = selectedPeriods; } + if (balanceRes.success) { + setBalanceSheet(balanceRes.data); + dataPeriodsRef.current.balance = selectedPeriods; + } logger.info('useFinancialData', '核心财务数据加载成功', { stockCode }); } catch (err) { diff --git a/src/views/Company/components/FinancialPanorama/index.tsx b/src/views/Company/components/FinancialPanorama/index.tsx index f64d495f..dafc5c14 100644 --- a/src/views/Company/components/FinancialPanorama/index.tsx +++ b/src/views/Company/components/FinancialPanorama/index.tsx @@ -27,7 +27,7 @@ import { } from 'lucide-react'; // 通用组件 -import SubTabContainer, { type SubTabConfig } from '@components/SubTabContainer'; +import SubTabContainer, { type SubTabConfig, type SubTabGroup } from '@components/SubTabContainer'; // 内部模块导入 import { useFinancialData, type DataTypeKey } from './hooks'; @@ -59,7 +59,13 @@ import type { FinancialPanoramaProps } from './types'; * 财务全景主组件 */ // Tab key 映射表(SubTabContainer index -> DataTypeKey) +// 顺序:基础报表(3个) + 财务指标分析(7个) const TAB_KEY_MAP: DataTypeKey[] = [ + // 基础报表 + 'balance', + 'income', + 'cashflow', + // 财务指标分析 'profitability', 'perShare', 'growth', @@ -67,9 +73,6 @@ const TAB_KEY_MAP: DataTypeKey[] = [ 'solvency', 'expense', 'cashflowMetrics', - 'balance', - 'income', - 'cashflow', ]; const FinancialPanorama: React.FC = ({ stockCode: propStockCode }) => { @@ -122,21 +125,29 @@ const FinancialPanorama: React.FC = ({ stockCode: propSt onOpen(); }, [onOpen]); - // Tab 配置 - 财务指标分类 + 三大财务报表 - const tabConfigs: SubTabConfig[] = useMemo( + // Tab 分组配置 - 基础报表 + 财务指标分析 + const tabGroups: SubTabGroup[] = 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 }, + { + name: '基础报表', + tabs: [ + { key: 'balance', name: '资产负债表', icon: BarChart3, component: BalanceSheetTab }, + { key: 'income', name: '利润表', icon: DollarSign, component: IncomeStatementTab }, + { key: 'cashflow', name: '现金流量表', icon: TrendingDown, component: CashflowTab }, + ], + }, + { + name: '财务指标分析', + tabs: [ + { 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 }, + ], + }, ], [] ); @@ -206,7 +217,8 @@ const FinancialPanorama: React.FC = ({ stockCode: propSt