feat(SubTabContainer): 支持自定义 Suspense fallback
- SubTabConfig 添加 fallback 属性 - 财务全景/盈利预测配置骨架屏 fallback - 解决点击 Tab 先显示 Spinner 再显示骨架屏的问题 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,60 +0,0 @@
|
||||
/**
|
||||
* 资产负债表 Tab
|
||||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import { Box, Spinner, Center } from '@chakra-ui/react';
|
||||
import { BalanceSheetTable } from '../components';
|
||||
import type { BalanceSheetData } from '../types';
|
||||
|
||||
export interface BalanceSheetTabProps {
|
||||
balanceSheet: BalanceSheetData[];
|
||||
loading?: boolean;
|
||||
showMetricChart: (name: string, key: string, data: unknown[], path: string) => void;
|
||||
calculateYoYChange: (value: number, period: string, data: unknown[], path: string) => { change: number; intensity: number };
|
||||
getCellBackground: (change: number, intensity: number) => string;
|
||||
positiveColor: string;
|
||||
negativeColor: string;
|
||||
bgColor: string;
|
||||
hoverBg: string;
|
||||
}
|
||||
|
||||
const BalanceSheetTabInner: React.FC<BalanceSheetTabProps> = ({
|
||||
balanceSheet,
|
||||
loading,
|
||||
showMetricChart,
|
||||
calculateYoYChange,
|
||||
getCellBackground,
|
||||
positiveColor,
|
||||
negativeColor,
|
||||
bgColor,
|
||||
hoverBg,
|
||||
}) => {
|
||||
// 加载中状态
|
||||
if (loading && (!Array.isArray(balanceSheet) || balanceSheet.length === 0)) {
|
||||
return (
|
||||
<Center py={12}>
|
||||
<Spinner size="lg" color="#D4AF37" thickness="3px" />
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
const tableProps = {
|
||||
showMetricChart,
|
||||
calculateYoYChange,
|
||||
getCellBackground,
|
||||
positiveColor,
|
||||
negativeColor,
|
||||
bgColor,
|
||||
hoverBg,
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<BalanceSheetTable data={balanceSheet} {...tableProps} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const BalanceSheetTab = memo(BalanceSheetTabInner);
|
||||
export default BalanceSheetTab;
|
||||
@@ -1,60 +0,0 @@
|
||||
/**
|
||||
* 现金流量表 Tab
|
||||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import { Box, Spinner, Center } from '@chakra-ui/react';
|
||||
import { CashflowTable } from '../components';
|
||||
import type { CashflowData } from '../types';
|
||||
|
||||
export interface CashflowTabProps {
|
||||
cashflow: CashflowData[];
|
||||
loading?: boolean;
|
||||
showMetricChart: (name: string, key: string, data: unknown[], path: string) => void;
|
||||
calculateYoYChange: (value: number, period: string, data: unknown[], path: string) => { change: number; intensity: number };
|
||||
getCellBackground: (change: number, intensity: number) => string;
|
||||
positiveColor: string;
|
||||
negativeColor: string;
|
||||
bgColor: string;
|
||||
hoverBg: string;
|
||||
}
|
||||
|
||||
const CashflowTabInner: React.FC<CashflowTabProps> = ({
|
||||
cashflow,
|
||||
loading,
|
||||
showMetricChart,
|
||||
calculateYoYChange,
|
||||
getCellBackground,
|
||||
positiveColor,
|
||||
negativeColor,
|
||||
bgColor,
|
||||
hoverBg,
|
||||
}) => {
|
||||
// 加载中状态
|
||||
if (loading && (!Array.isArray(cashflow) || cashflow.length === 0)) {
|
||||
return (
|
||||
<Center py={12}>
|
||||
<Spinner size="lg" color="#D4AF37" thickness="3px" />
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
const tableProps = {
|
||||
showMetricChart,
|
||||
calculateYoYChange,
|
||||
getCellBackground,
|
||||
positiveColor,
|
||||
negativeColor,
|
||||
bgColor,
|
||||
hoverBg,
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<CashflowTable data={cashflow} {...tableProps} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const CashflowTab = memo(CashflowTabInner);
|
||||
export default CashflowTab;
|
||||
@@ -1,58 +0,0 @@
|
||||
/**
|
||||
* 财务指标 Tab
|
||||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import { Spinner, Center } from '@chakra-ui/react';
|
||||
import { FinancialMetricsTable } from '../components';
|
||||
import type { FinancialMetricsData } from '../types';
|
||||
|
||||
export interface FinancialMetricsTabProps {
|
||||
financialMetrics: FinancialMetricsData[];
|
||||
loading?: boolean;
|
||||
showMetricChart: (name: string, key: string, data: unknown[], path: string) => void;
|
||||
calculateYoYChange: (value: number, period: string, data: unknown[], path: string) => { change: number; intensity: number };
|
||||
getCellBackground: (change: number, intensity: number) => string;
|
||||
positiveColor: string;
|
||||
negativeColor: string;
|
||||
bgColor: string;
|
||||
hoverBg: string;
|
||||
}
|
||||
|
||||
const FinancialMetricsTabInner: React.FC<FinancialMetricsTabProps> = ({
|
||||
financialMetrics,
|
||||
loading,
|
||||
showMetricChart,
|
||||
calculateYoYChange,
|
||||
getCellBackground,
|
||||
positiveColor,
|
||||
negativeColor,
|
||||
bgColor,
|
||||
hoverBg,
|
||||
}) => {
|
||||
// 加载中状态
|
||||
if (loading && (!Array.isArray(financialMetrics) || financialMetrics.length === 0)) {
|
||||
return (
|
||||
<Center py={12}>
|
||||
<Spinner size="lg" color="#D4AF37" thickness="3px" />
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
const tableProps = {
|
||||
showMetricChart,
|
||||
calculateYoYChange,
|
||||
getCellBackground,
|
||||
positiveColor,
|
||||
negativeColor,
|
||||
bgColor,
|
||||
hoverBg,
|
||||
};
|
||||
|
||||
return (
|
||||
<FinancialMetricsTable data={financialMetrics} {...tableProps} />
|
||||
);
|
||||
};
|
||||
|
||||
const FinancialMetricsTab = memo(FinancialMetricsTabInner);
|
||||
export default FinancialMetricsTab;
|
||||
@@ -1,60 +0,0 @@
|
||||
/**
|
||||
* 利润表 Tab
|
||||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import { Box, Spinner, Center } from '@chakra-ui/react';
|
||||
import { IncomeStatementTable } from '../components';
|
||||
import type { IncomeStatementData } from '../types';
|
||||
|
||||
export interface IncomeStatementTabProps {
|
||||
incomeStatement: IncomeStatementData[];
|
||||
loading?: boolean;
|
||||
showMetricChart: (name: string, key: string, data: unknown[], path: string) => void;
|
||||
calculateYoYChange: (value: number, period: string, data: unknown[], path: string) => { change: number; intensity: number };
|
||||
getCellBackground: (change: number, intensity: number) => string;
|
||||
positiveColor: string;
|
||||
negativeColor: string;
|
||||
bgColor: string;
|
||||
hoverBg: string;
|
||||
}
|
||||
|
||||
const IncomeStatementTabInner: React.FC<IncomeStatementTabProps> = ({
|
||||
incomeStatement,
|
||||
loading,
|
||||
showMetricChart,
|
||||
calculateYoYChange,
|
||||
getCellBackground,
|
||||
positiveColor,
|
||||
negativeColor,
|
||||
bgColor,
|
||||
hoverBg,
|
||||
}) => {
|
||||
// 加载中状态
|
||||
if (loading && (!Array.isArray(incomeStatement) || incomeStatement.length === 0)) {
|
||||
return (
|
||||
<Center py={12}>
|
||||
<Spinner size="lg" color="#D4AF37" thickness="3px" />
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
const tableProps = {
|
||||
showMetricChart,
|
||||
calculateYoYChange,
|
||||
getCellBackground,
|
||||
positiveColor,
|
||||
negativeColor,
|
||||
bgColor,
|
||||
hoverBg,
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<IncomeStatementTable data={incomeStatement} {...tableProps} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const IncomeStatementTab = memo(IncomeStatementTabInner);
|
||||
export default IncomeStatementTab;
|
||||
@@ -1,269 +0,0 @@
|
||||
/**
|
||||
* 财务指标分类 Tab - Ant Design 黑金主题
|
||||
* 接受 categoryKey 显示单个分类的指标表格
|
||||
*/
|
||||
|
||||
import React, { useMemo, memo } from 'react';
|
||||
import { Box, Text, HStack, Badge as ChakraBadge, 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 { FINANCIAL_METRICS_CATEGORIES } from '../constants';
|
||||
import { getValueByPath, isNegativeIndicator, BLACK_GOLD_TABLE_THEME, getTableStyles, calculateYoY } from '../utils';
|
||||
import type { FinancialMetricsData } from '../types';
|
||||
|
||||
type CategoryKey = keyof typeof FINANCIAL_METRICS_CATEGORIES;
|
||||
|
||||
const TABLE_CLASS_NAME = 'metrics-category-table';
|
||||
const tableStyles = getTableStyles(TABLE_CLASS_NAME) + `
|
||||
.${TABLE_CLASS_NAME} .positive-value {
|
||||
color: #E53E3E;
|
||||
}
|
||||
.${TABLE_CLASS_NAME} .negative-value {
|
||||
color: #48BB78;
|
||||
}
|
||||
`;
|
||||
|
||||
export interface MetricsCategoryTabProps {
|
||||
categoryKey: CategoryKey;
|
||||
financialMetrics: FinancialMetricsData[];
|
||||
loading?: boolean;
|
||||
showMetricChart: (name: string, key: string, data: unknown[], path: string) => void;
|
||||
calculateYoYChange: (value: number, period: string, data: unknown[], path: string) => { change: number; intensity: number };
|
||||
getCellBackground: (change: number, intensity: number) => string;
|
||||
positiveColor: string;
|
||||
negativeColor: string;
|
||||
bgColor: string;
|
||||
hoverBg: string;
|
||||
}
|
||||
|
||||
// 表格行数据类型
|
||||
interface TableRowData {
|
||||
key: string;
|
||||
name: string;
|
||||
path: string;
|
||||
isCore?: boolean;
|
||||
[period: string]: unknown;
|
||||
}
|
||||
|
||||
const MetricsCategoryTabInner: React.FC<MetricsCategoryTabProps> = ({
|
||||
categoryKey,
|
||||
financialMetrics,
|
||||
loading,
|
||||
showMetricChart,
|
||||
calculateYoYChange,
|
||||
}) => {
|
||||
// 加载中状态
|
||||
if (loading && (!Array.isArray(financialMetrics) || financialMetrics.length === 0)) {
|
||||
return (
|
||||
<Center py={12}>
|
||||
<Spinner size="lg" color="#D4AF37" thickness="3px" />
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
// 数组安全检查
|
||||
if (!Array.isArray(financialMetrics) || financialMetrics.length === 0) {
|
||||
return (
|
||||
<Box p={4} textAlign="center" color="gray.400">
|
||||
暂无财务指标数据
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
const maxColumns = Math.min(financialMetrics.length, 6);
|
||||
const displayData = financialMetrics.slice(0, maxColumns);
|
||||
const category = FINANCIAL_METRICS_CATEGORIES[categoryKey];
|
||||
|
||||
if (!category) {
|
||||
return (
|
||||
<Box p={4} textAlign="center" color="gray.400">
|
||||
未找到指标分类配置
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// 构建表格数据
|
||||
const tableData = useMemo(() => {
|
||||
return category.metrics.map((metric) => {
|
||||
const row: TableRowData = {
|
||||
key: metric.key,
|
||||
name: metric.name,
|
||||
path: metric.path,
|
||||
isCore: metric.isCore,
|
||||
};
|
||||
|
||||
// 添加各期数值
|
||||
displayData.forEach((item) => {
|
||||
const value = getValueByPath<number>(item, metric.path);
|
||||
row[item.period] = value;
|
||||
});
|
||||
|
||||
return row;
|
||||
});
|
||||
}, [financialMetrics, displayData, category]);
|
||||
|
||||
// 计算同比变化(使用共享函数)
|
||||
const calcYoY = (
|
||||
currentValue: number | undefined,
|
||||
currentPeriod: string,
|
||||
path: string
|
||||
): number | null => {
|
||||
return calculateYoY(financialMetrics, currentValue, currentPeriod, path, getValueByPath);
|
||||
};
|
||||
|
||||
// 构建列定义
|
||||
const columns: ColumnsType<TableRowData> = useMemo(() => {
|
||||
const cols: ColumnsType<TableRowData> = [
|
||||
{
|
||||
title: category.title,
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
fixed: 'left',
|
||||
width: 200,
|
||||
render: (name: string, record: TableRowData) => (
|
||||
<HStack spacing={2}>
|
||||
<Text fontWeight="medium" fontSize="xs">{name}</Text>
|
||||
{record.isCore && (
|
||||
<ChakraBadge size="sm" bg="rgba(212, 175, 55, 0.2)" color="#D4AF37">
|
||||
核心
|
||||
</ChakraBadge>
|
||||
)}
|
||||
</HStack>
|
||||
),
|
||||
},
|
||||
...displayData.map((item) => ({
|
||||
title: (
|
||||
<Box textAlign="center">
|
||||
<Text fontSize="xs">{formatUtils.getReportType(item.period)}</Text>
|
||||
<Text fontSize="2xs" color="gray.400">{item.period.substring(0, 10)}</Text>
|
||||
</Box>
|
||||
),
|
||||
dataIndex: item.period,
|
||||
key: item.period,
|
||||
width: 100,
|
||||
align: 'right' as const,
|
||||
render: (value: number | undefined, record: TableRowData) => {
|
||||
const yoy = calcYoY(value, item.period, record.path);
|
||||
const isNegative = isNegativeIndicator(record.key);
|
||||
|
||||
// 对于负向指标,增加是坏事(绿色),减少是好事(红色)
|
||||
const changeColor = isNegative
|
||||
? (yoy && yoy > 0 ? 'negative-change' : 'positive-change')
|
||||
: (yoy && yoy > 0 ? 'positive-change' : 'negative-change');
|
||||
|
||||
// 成长能力指标特殊处理:正值红色,负值绿色
|
||||
const valueColor = categoryKey === 'growth'
|
||||
? (value !== undefined && value > 0 ? 'positive-value' : value !== undefined && value < 0 ? 'negative-value' : '')
|
||||
: '';
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
title={
|
||||
<Box>
|
||||
<Text>{record.name}: {value?.toFixed(2) || '-'}</Text>
|
||||
{yoy !== null && <Text>同比: {yoy.toFixed(2)}%</Text>}
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<Box position="relative">
|
||||
<Text fontSize="xs" className={valueColor || undefined}>
|
||||
{value?.toFixed(2) || '-'}
|
||||
</Text>
|
||||
{yoy !== null && Math.abs(yoy) > 20 && value !== undefined && Math.abs(value) > 0.01 && (
|
||||
<Text
|
||||
position="absolute"
|
||||
top="-12px"
|
||||
right="0"
|
||||
fontSize="10px"
|
||||
className={changeColor}
|
||||
>
|
||||
{yoy > 0 ? '↑' : '↓'}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
})),
|
||||
{
|
||||
title: '',
|
||||
key: 'action',
|
||||
width: 40,
|
||||
fixed: 'right',
|
||||
render: (_: unknown, record: TableRowData) => (
|
||||
<Eye
|
||||
size={14}
|
||||
color="#D4AF37"
|
||||
style={{ cursor: 'pointer', opacity: 0.7 }}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
showMetricChart(record.name, record.key, financialMetrics, record.path);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return cols;
|
||||
}, [displayData, financialMetrics, showMetricChart, category, categoryKey]);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box className="metrics-category-table">
|
||||
<style>{tableStyles}</style>
|
||||
<ConfigProvider theme={BLACK_GOLD_TABLE_THEME}>
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={tableData}
|
||||
pagination={false}
|
||||
size="small"
|
||||
scroll={{ x: 'max-content' }}
|
||||
onRow={(record) => ({
|
||||
onClick: () => {
|
||||
showMetricChart(record.name, record.key, financialMetrics, record.path);
|
||||
},
|
||||
style: { cursor: 'pointer' },
|
||||
})}
|
||||
locale={{ emptyText: '暂无数据' }}
|
||||
/>
|
||||
</ConfigProvider>
|
||||
</Box>
|
||||
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const MetricsCategoryTab = memo(MetricsCategoryTabInner);
|
||||
|
||||
// 为每个分类创建预配置的组件(使用 memo)
|
||||
export const ProfitabilityTab = memo<Omit<MetricsCategoryTabProps, 'categoryKey'>>((props) => (
|
||||
<MetricsCategoryTab categoryKey="profitability" {...props} />
|
||||
));
|
||||
|
||||
export const PerShareTab = memo<Omit<MetricsCategoryTabProps, 'categoryKey'>>((props) => (
|
||||
<MetricsCategoryTab categoryKey="perShare" {...props} />
|
||||
));
|
||||
|
||||
export const GrowthTab = memo<Omit<MetricsCategoryTabProps, 'categoryKey'>>((props) => (
|
||||
<MetricsCategoryTab categoryKey="growth" {...props} />
|
||||
));
|
||||
|
||||
export const OperationalTab = memo<Omit<MetricsCategoryTabProps, 'categoryKey'>>((props) => (
|
||||
<MetricsCategoryTab categoryKey="operational" {...props} />
|
||||
));
|
||||
|
||||
export const SolvencyTab = memo<Omit<MetricsCategoryTabProps, 'categoryKey'>>((props) => (
|
||||
<MetricsCategoryTab categoryKey="solvency" {...props} />
|
||||
));
|
||||
|
||||
export const ExpenseTab = memo<Omit<MetricsCategoryTabProps, 'categoryKey'>>((props) => (
|
||||
<MetricsCategoryTab categoryKey="expense" {...props} />
|
||||
));
|
||||
|
||||
export const CashflowMetricsTab = memo<Omit<MetricsCategoryTabProps, 'categoryKey'>>((props) => (
|
||||
<MetricsCategoryTab categoryKey="cashflow" {...props} />
|
||||
));
|
||||
|
||||
export default MetricsCategoryTab;
|
||||
@@ -0,0 +1,264 @@
|
||||
/**
|
||||
* 统一的财务 Tab 组件
|
||||
*
|
||||
* 使用 UnifiedFinancialTable 实现所有 10 个财务表格:
|
||||
* - 7 个财务指标分类 Tab
|
||||
* - 3 个财务报表 Tab
|
||||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import { UnifiedFinancialTable, type FinancialDataItem } from '../components/UnifiedFinancialTable';
|
||||
import {
|
||||
FINANCIAL_METRICS_CATEGORIES,
|
||||
CURRENT_ASSETS_METRICS,
|
||||
NON_CURRENT_ASSETS_METRICS,
|
||||
TOTAL_ASSETS_METRICS,
|
||||
CURRENT_LIABILITIES_METRICS,
|
||||
NON_CURRENT_LIABILITIES_METRICS,
|
||||
TOTAL_LIABILITIES_METRICS,
|
||||
EQUITY_METRICS,
|
||||
INCOME_STATEMENT_SECTIONS,
|
||||
CASHFLOW_METRICS,
|
||||
} from '../constants';
|
||||
import type { FinancialMetricsData, BalanceSheetData, IncomeStatementData, CashflowData } from '../types';
|
||||
|
||||
// ==================== 通用 Props 类型 ====================
|
||||
|
||||
/** 财务指标 Tab Props */
|
||||
export interface MetricsTabProps {
|
||||
financialMetrics: FinancialMetricsData[];
|
||||
loading?: boolean;
|
||||
loadingTab?: string | null;
|
||||
showMetricChart: (name: string, key: string, data: FinancialDataItem[], path: string) => void;
|
||||
calculateYoYChange: (value: number, period: string, data: unknown[], path: string) => { change: number; intensity: number };
|
||||
getCellBackground: (change: number, intensity: number) => string;
|
||||
positiveColor: string;
|
||||
negativeColor: string;
|
||||
bgColor: string;
|
||||
hoverBg: string;
|
||||
}
|
||||
|
||||
/** 资产负债表 Tab Props */
|
||||
export interface BalanceSheetTabProps {
|
||||
balanceSheet: BalanceSheetData[];
|
||||
loading?: boolean;
|
||||
loadingTab?: string | null;
|
||||
showMetricChart: (name: string, key: string, data: FinancialDataItem[], path: string) => void;
|
||||
calculateYoYChange: (value: number, period: string, data: unknown[], path: string) => { change: number; intensity: number };
|
||||
getCellBackground: (change: number, intensity: number) => string;
|
||||
positiveColor: string;
|
||||
negativeColor: string;
|
||||
bgColor: string;
|
||||
hoverBg: string;
|
||||
}
|
||||
|
||||
/** 利润表 Tab Props */
|
||||
export interface IncomeStatementTabProps {
|
||||
incomeStatement: IncomeStatementData[];
|
||||
loading?: boolean;
|
||||
loadingTab?: string | null;
|
||||
showMetricChart: (name: string, key: string, data: FinancialDataItem[], path: string) => void;
|
||||
calculateYoYChange: (value: number, period: string, data: unknown[], path: string) => { change: number; intensity: number };
|
||||
getCellBackground: (change: number, intensity: number) => string;
|
||||
positiveColor: string;
|
||||
negativeColor: string;
|
||||
bgColor: string;
|
||||
hoverBg: string;
|
||||
}
|
||||
|
||||
/** 现金流量表 Tab Props */
|
||||
export interface CashflowTabProps {
|
||||
cashflow: CashflowData[];
|
||||
loading?: boolean;
|
||||
loadingTab?: string | null;
|
||||
showMetricChart: (name: string, key: string, data: FinancialDataItem[], path: string) => void;
|
||||
calculateYoYChange: (value: number, period: string, data: unknown[], path: string) => { change: number; intensity: number };
|
||||
getCellBackground: (change: number, intensity: number) => string;
|
||||
positiveColor: string;
|
||||
negativeColor: string;
|
||||
bgColor: string;
|
||||
hoverBg: string;
|
||||
}
|
||||
|
||||
// ==================== 财务指标 Tab (7个) ====================
|
||||
|
||||
/** 盈利能力 Tab */
|
||||
export const ProfitabilityTab = memo<MetricsTabProps>(({ financialMetrics, loading, showMetricChart }) => {
|
||||
const category = FINANCIAL_METRICS_CATEGORIES.profitability;
|
||||
return (
|
||||
<UnifiedFinancialTable
|
||||
type="metrics"
|
||||
data={financialMetrics as unknown as FinancialDataItem[]}
|
||||
categoryKey="profitability"
|
||||
categoryTitle={category.title}
|
||||
metrics={category.metrics}
|
||||
showMetricChart={showMetricChart}
|
||||
loading={loading}
|
||||
/>
|
||||
);
|
||||
});
|
||||
ProfitabilityTab.displayName = 'ProfitabilityTab';
|
||||
|
||||
/** 每股指标 Tab */
|
||||
export const PerShareTab = memo<MetricsTabProps>(({ financialMetrics, loading, showMetricChart }) => {
|
||||
const category = FINANCIAL_METRICS_CATEGORIES.perShare;
|
||||
return (
|
||||
<UnifiedFinancialTable
|
||||
type="metrics"
|
||||
data={financialMetrics as unknown as FinancialDataItem[]}
|
||||
categoryKey="perShare"
|
||||
categoryTitle={category.title}
|
||||
metrics={category.metrics}
|
||||
showMetricChart={showMetricChart}
|
||||
loading={loading}
|
||||
/>
|
||||
);
|
||||
});
|
||||
PerShareTab.displayName = 'PerShareTab';
|
||||
|
||||
/** 成长能力 Tab */
|
||||
export const GrowthTab = memo<MetricsTabProps>(({ financialMetrics, loading, showMetricChart }) => {
|
||||
const category = FINANCIAL_METRICS_CATEGORIES.growth;
|
||||
return (
|
||||
<UnifiedFinancialTable
|
||||
type="metrics"
|
||||
data={financialMetrics as unknown as FinancialDataItem[]}
|
||||
categoryKey="growth"
|
||||
categoryTitle={category.title}
|
||||
metrics={category.metrics}
|
||||
showMetricChart={showMetricChart}
|
||||
loading={loading}
|
||||
isGrowthCategory
|
||||
/>
|
||||
);
|
||||
});
|
||||
GrowthTab.displayName = 'GrowthTab';
|
||||
|
||||
/** 运营效率 Tab */
|
||||
export const OperationalTab = memo<MetricsTabProps>(({ financialMetrics, loading, showMetricChart }) => {
|
||||
const category = FINANCIAL_METRICS_CATEGORIES.operational;
|
||||
return (
|
||||
<UnifiedFinancialTable
|
||||
type="metrics"
|
||||
data={financialMetrics as unknown as FinancialDataItem[]}
|
||||
categoryKey="operational"
|
||||
categoryTitle={category.title}
|
||||
metrics={category.metrics}
|
||||
showMetricChart={showMetricChart}
|
||||
loading={loading}
|
||||
/>
|
||||
);
|
||||
});
|
||||
OperationalTab.displayName = 'OperationalTab';
|
||||
|
||||
/** 偿债能力 Tab */
|
||||
export const SolvencyTab = memo<MetricsTabProps>(({ financialMetrics, loading, showMetricChart }) => {
|
||||
const category = FINANCIAL_METRICS_CATEGORIES.solvency;
|
||||
return (
|
||||
<UnifiedFinancialTable
|
||||
type="metrics"
|
||||
data={financialMetrics as unknown as FinancialDataItem[]}
|
||||
categoryKey="solvency"
|
||||
categoryTitle={category.title}
|
||||
metrics={category.metrics}
|
||||
showMetricChart={showMetricChart}
|
||||
loading={loading}
|
||||
/>
|
||||
);
|
||||
});
|
||||
SolvencyTab.displayName = 'SolvencyTab';
|
||||
|
||||
/** 费用率 Tab */
|
||||
export const ExpenseTab = memo<MetricsTabProps>(({ financialMetrics, loading, showMetricChart }) => {
|
||||
const category = FINANCIAL_METRICS_CATEGORIES.expense;
|
||||
return (
|
||||
<UnifiedFinancialTable
|
||||
type="metrics"
|
||||
data={financialMetrics as unknown as FinancialDataItem[]}
|
||||
categoryKey="expense"
|
||||
categoryTitle={category.title}
|
||||
metrics={category.metrics}
|
||||
showMetricChart={showMetricChart}
|
||||
loading={loading}
|
||||
/>
|
||||
);
|
||||
});
|
||||
ExpenseTab.displayName = 'ExpenseTab';
|
||||
|
||||
/** 现金流指标 Tab */
|
||||
export const CashflowMetricsTab = memo<MetricsTabProps>(({ financialMetrics, loading, showMetricChart }) => {
|
||||
const category = FINANCIAL_METRICS_CATEGORIES.cashflow;
|
||||
return (
|
||||
<UnifiedFinancialTable
|
||||
type="metrics"
|
||||
data={financialMetrics as unknown as FinancialDataItem[]}
|
||||
categoryKey="cashflow"
|
||||
categoryTitle={category.title}
|
||||
metrics={category.metrics}
|
||||
showMetricChart={showMetricChart}
|
||||
loading={loading}
|
||||
/>
|
||||
);
|
||||
});
|
||||
CashflowMetricsTab.displayName = 'CashflowMetricsTab';
|
||||
|
||||
// ==================== 财务报表 Tab (3个) ====================
|
||||
|
||||
// 资产负债表分组配置
|
||||
const BALANCE_SHEET_SECTIONS = [
|
||||
CURRENT_ASSETS_METRICS,
|
||||
NON_CURRENT_ASSETS_METRICS,
|
||||
TOTAL_ASSETS_METRICS,
|
||||
CURRENT_LIABILITIES_METRICS,
|
||||
NON_CURRENT_LIABILITIES_METRICS,
|
||||
TOTAL_LIABILITIES_METRICS,
|
||||
EQUITY_METRICS,
|
||||
];
|
||||
|
||||
/** 资产负债表 Tab */
|
||||
export const BalanceSheetTab = memo<BalanceSheetTabProps>(({ balanceSheet, loading, showMetricChart }) => (
|
||||
<UnifiedFinancialTable
|
||||
type="statement"
|
||||
data={balanceSheet as unknown as FinancialDataItem[]}
|
||||
sections={BALANCE_SHEET_SECTIONS}
|
||||
showMetricChart={showMetricChart}
|
||||
loading={loading}
|
||||
/>
|
||||
));
|
||||
BalanceSheetTab.displayName = 'BalanceSheetTab';
|
||||
|
||||
/** 利润表 Tab */
|
||||
export const IncomeStatementTab = memo<IncomeStatementTabProps>(({ incomeStatement, loading, showMetricChart }) => (
|
||||
<UnifiedFinancialTable
|
||||
type="statement"
|
||||
data={incomeStatement as unknown as FinancialDataItem[]}
|
||||
sections={INCOME_STATEMENT_SECTIONS}
|
||||
hideTotalSectionTitle={false}
|
||||
showMetricChart={showMetricChart}
|
||||
loading={loading}
|
||||
/>
|
||||
));
|
||||
IncomeStatementTab.displayName = 'IncomeStatementTab';
|
||||
|
||||
// 现金流量表配置(转换为 sections 格式)
|
||||
const CASHFLOW_SECTIONS = [{
|
||||
title: '现金流量',
|
||||
key: 'cashflow',
|
||||
metrics: CASHFLOW_METRICS.map(m => ({
|
||||
...m,
|
||||
isCore: ['operating_net', 'free_cash_flow'].includes(m.key),
|
||||
})),
|
||||
}];
|
||||
|
||||
/** 现金流量表 Tab */
|
||||
export const CashflowTab = memo<CashflowTabProps>(({ cashflow, loading, showMetricChart }) => (
|
||||
<UnifiedFinancialTable
|
||||
type="statement"
|
||||
data={cashflow as unknown as FinancialDataItem[]}
|
||||
sections={CASHFLOW_SECTIONS}
|
||||
hideTotalSectionTitle
|
||||
showMetricChart={showMetricChart}
|
||||
loading={loading}
|
||||
/>
|
||||
));
|
||||
CashflowTab.displayName = 'CashflowTab';
|
||||
@@ -1,14 +1,12 @@
|
||||
/**
|
||||
* Tab 组件统一导出
|
||||
*
|
||||
* 使用 UnifiedTabs 实现的 10 个财务表格 Tab
|
||||
*/
|
||||
|
||||
// 三大财务报表
|
||||
export { default as BalanceSheetTab } from './BalanceSheetTab';
|
||||
export { default as IncomeStatementTab } from './IncomeStatementTab';
|
||||
export { default as CashflowTab } from './CashflowTab';
|
||||
|
||||
// 财务指标分类 tabs
|
||||
// 统一 Tab 组件导出
|
||||
export {
|
||||
// 7 个财务指标 Tab
|
||||
ProfitabilityTab,
|
||||
PerShareTab,
|
||||
GrowthTab,
|
||||
@@ -16,13 +14,20 @@ export {
|
||||
SolvencyTab,
|
||||
ExpenseTab,
|
||||
CashflowMetricsTab,
|
||||
} from './MetricsCategoryTab';
|
||||
// 3 个财务报表 Tab
|
||||
BalanceSheetTab,
|
||||
IncomeStatementTab,
|
||||
CashflowTab,
|
||||
} from './UnifiedTabs';
|
||||
|
||||
// 旧的综合财务指标 tab(保留兼容)
|
||||
export { default as FinancialMetricsTab } from './FinancialMetricsTab';
|
||||
// 类型导出
|
||||
export type {
|
||||
MetricsTabProps,
|
||||
BalanceSheetTabProps,
|
||||
IncomeStatementTabProps,
|
||||
CashflowTabProps,
|
||||
} from './UnifiedTabs';
|
||||
|
||||
export type { BalanceSheetTabProps } from './BalanceSheetTab';
|
||||
export type { IncomeStatementTabProps } from './IncomeStatementTab';
|
||||
export type { CashflowTabProps } from './CashflowTab';
|
||||
export type { FinancialMetricsTabProps } from './FinancialMetricsTab';
|
||||
export type { MetricsCategoryTabProps } from './MetricsCategoryTab';
|
||||
// 兼容旧的类型别名
|
||||
export type { MetricsTabProps as MetricsCategoryTabProps } from './UnifiedTabs';
|
||||
export type { MetricsTabProps as FinancialMetricsTabProps } from './UnifiedTabs';
|
||||
|
||||
Reference in New Issue
Block a user