perf(FinancialPanorama): Tab 组件添加 memo 优化

- MetricsCategoryTab: 使用共享主题,主组件和 7 个子组件添加 memo
- BalanceSheetTab: 添加 memo
- IncomeStatementTab: 添加 memo
- CashflowTab: 添加 memo
- FinancialMetricsTab: 添加 memo
- 减少不必要的重渲染

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
zdl
2025-12-19 14:44:32 +08:00
parent 54cce55c29
commit 41da6fa372
5 changed files with 41 additions and 109 deletions

View File

@@ -2,7 +2,7 @@
* 资产负债表 Tab
*/
import React from 'react';
import React, { memo } from 'react';
import { Box, VStack, HStack, Heading, Badge, Text, Spinner, Center } from '@chakra-ui/react';
import { BalanceSheetTable } from '../components';
import type { BalanceSheetData } from '../types';
@@ -19,7 +19,7 @@ export interface BalanceSheetTabProps {
hoverBg: string;
}
const BalanceSheetTab: React.FC<BalanceSheetTabProps> = ({
const BalanceSheetTabInner: React.FC<BalanceSheetTabProps> = ({
balanceSheet,
loading,
showMetricChart,
@@ -72,4 +72,5 @@ const BalanceSheetTab: React.FC<BalanceSheetTabProps> = ({
);
};
const BalanceSheetTab = memo(BalanceSheetTabInner);
export default BalanceSheetTab;

View File

@@ -2,7 +2,7 @@
* 现金流量表 Tab
*/
import React from 'react';
import React, { memo } from 'react';
import { Box, VStack, HStack, Heading, Badge, Text, Spinner, Center } from '@chakra-ui/react';
import { CashflowTable } from '../components';
import type { CashflowData } from '../types';
@@ -19,7 +19,7 @@ export interface CashflowTabProps {
hoverBg: string;
}
const CashflowTab: React.FC<CashflowTabProps> = ({
const CashflowTabInner: React.FC<CashflowTabProps> = ({
cashflow,
loading,
showMetricChart,
@@ -72,4 +72,5 @@ const CashflowTab: React.FC<CashflowTabProps> = ({
);
};
const CashflowTab = memo(CashflowTabInner);
export default CashflowTab;

View File

@@ -2,7 +2,7 @@
* 财务指标 Tab
*/
import React from 'react';
import React, { memo } from 'react';
import { Spinner, Center } from '@chakra-ui/react';
import { FinancialMetricsTable } from '../components';
import type { FinancialMetricsData } from '../types';
@@ -19,7 +19,7 @@ export interface FinancialMetricsTabProps {
hoverBg: string;
}
const FinancialMetricsTab: React.FC<FinancialMetricsTabProps> = ({
const FinancialMetricsTabInner: React.FC<FinancialMetricsTabProps> = ({
financialMetrics,
loading,
showMetricChart,
@@ -54,4 +54,5 @@ const FinancialMetricsTab: React.FC<FinancialMetricsTabProps> = ({
);
};
const FinancialMetricsTab = memo(FinancialMetricsTabInner);
export default FinancialMetricsTab;

View File

@@ -2,7 +2,7 @@
* 利润表 Tab
*/
import React from 'react';
import React, { memo } from 'react';
import { Box, VStack, HStack, Heading, Badge, Text, Spinner, Center } from '@chakra-ui/react';
import { IncomeStatementTable } from '../components';
import type { IncomeStatementData } from '../types';
@@ -19,7 +19,7 @@ export interface IncomeStatementTabProps {
hoverBg: string;
}
const IncomeStatementTab: React.FC<IncomeStatementTabProps> = ({
const IncomeStatementTabInner: React.FC<IncomeStatementTabProps> = ({
incomeStatement,
loading,
showMetricChart,
@@ -72,4 +72,5 @@ const IncomeStatementTab: React.FC<IncomeStatementTabProps> = ({
);
};
const IncomeStatementTab = memo(IncomeStatementTabInner);
export default IncomeStatementTab;

View File

@@ -3,84 +3,26 @@
* 接受 categoryKey 显示单个分类的指标表格
*/
import React, { useMemo } from 'react';
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 } from '../utils';
import { getValueByPath, isNegativeIndicator, BLACK_GOLD_TABLE_THEME, getTableStyles, calculateYoY } from '../utils';
import type { FinancialMetricsData } from '../types';
type CategoryKey = keyof typeof FINANCIAL_METRICS_CATEGORIES;
// Ant Design 黑金主题配置
const BLACK_GOLD_THEME = {
token: {
colorBgContainer: 'transparent',
colorText: '#E2E8F0',
colorTextHeading: '#D4AF37',
colorBorderSecondary: 'rgba(212, 175, 55, 0.2)',
},
components: {
Table: {
headerBg: 'rgba(26, 32, 44, 0.8)',
headerColor: '#D4AF37',
rowHoverBg: 'rgba(212, 175, 55, 0.1)',
borderColor: 'rgba(212, 175, 55, 0.15)',
cellPaddingBlock: 8,
cellPaddingInline: 12,
},
},
};
// 黑金主题CSS
const tableStyles = `
.metrics-category-table .ant-table {
background: transparent !important;
}
.metrics-category-table .ant-table-thead > tr > th {
background: rgba(26, 32, 44, 0.8) !important;
color: #D4AF37 !important;
border-bottom: 1px solid rgba(212, 175, 55, 0.3) !important;
font-weight: 600;
font-size: 13px;
}
.metrics-category-table .ant-table-tbody > tr > td {
border-bottom: 1px solid rgba(212, 175, 55, 0.1) !important;
color: #E2E8F0;
font-size: 12px;
}
.metrics-category-table .ant-table-tbody > tr:hover > td {
background: rgba(212, 175, 55, 0.08) !important;
}
.metrics-category-table .ant-table-cell-fix-left,
.metrics-category-table .ant-table-cell-fix-right {
background: #1A202C !important;
}
.metrics-category-table .ant-table-tbody > tr:hover .ant-table-cell-fix-left,
.metrics-category-table .ant-table-tbody > tr:hover .ant-table-cell-fix-right {
background: rgba(26, 32, 44, 0.95) !important;
}
.metrics-category-table .positive-change {
const TABLE_CLASS_NAME = 'metrics-category-table';
const tableStyles = getTableStyles(TABLE_CLASS_NAME) + `
.${TABLE_CLASS_NAME} .positive-value {
color: #E53E3E;
}
.metrics-category-table .negative-change {
.${TABLE_CLASS_NAME} .negative-value {
color: #48BB78;
}
.metrics-category-table .positive-value {
color: #E53E3E;
}
.metrics-category-table .negative-value {
color: #48BB78;
}
.metrics-category-table .ant-table-placeholder {
background: transparent !important;
}
.metrics-category-table .ant-empty-description {
color: #A0AEC0;
}
`;
export interface MetricsCategoryTabProps {
@@ -105,7 +47,7 @@ interface TableRowData {
[period: string]: unknown;
}
const MetricsCategoryTab: React.FC<MetricsCategoryTabProps> = ({
const MetricsCategoryTabInner: React.FC<MetricsCategoryTabProps> = ({
categoryKey,
financialMetrics,
loading,
@@ -162,29 +104,13 @@ const MetricsCategoryTab: React.FC<MetricsCategoryTabProps> = ({
});
}, [financialMetrics, displayData, category]);
// 计算同比变化
const calculateYoY = (
// 计算同比变化(使用共享函数)
const calcYoY = (
currentValue: number | undefined,
currentPeriod: string,
path: string
): number | null => {
if (currentValue === undefined || currentValue === null) return null;
const currentDate = new Date(currentPeriod);
const lastYearPeriod = financialMetrics.find((item) => {
const date = new Date(item.period);
return (
date.getFullYear() === currentDate.getFullYear() - 1 &&
date.getMonth() === currentDate.getMonth()
);
});
if (!lastYearPeriod) return null;
const lastYearValue = getValueByPath<number>(lastYearPeriod, path);
if (lastYearValue === undefined || lastYearValue === 0) return null;
return ((currentValue - lastYearValue) / Math.abs(lastYearValue)) * 100;
return calculateYoY(financialMetrics, currentValue, currentPeriod, path, getValueByPath);
};
// 构建列定义
@@ -219,7 +145,7 @@ const MetricsCategoryTab: React.FC<MetricsCategoryTabProps> = ({
width: 100,
align: 'right' as const,
render: (value: number | undefined, record: TableRowData) => {
const yoy = calculateYoY(value, item.period, record.path);
const yoy = calcYoY(value, item.period, record.path);
const isNegative = isNegativeIndicator(record.key);
// 对于负向指标,增加是坏事(绿色),减少是好事(红色)
@@ -287,7 +213,7 @@ const MetricsCategoryTab: React.FC<MetricsCategoryTabProps> = ({
<Box>
<Box className="metrics-category-table">
<style>{tableStyles}</style>
<ConfigProvider theme={BLACK_GOLD_THEME}>
<ConfigProvider theme={BLACK_GOLD_TABLE_THEME}>
<Table
columns={columns}
dataSource={tableData}
@@ -309,33 +235,35 @@ const MetricsCategoryTab: React.FC<MetricsCategoryTabProps> = ({
);
};
// 为每个分类创建预配置的组件
export const ProfitabilityTab: React.FC<Omit<MetricsCategoryTabProps, 'categoryKey'>> = (props) => (
const MetricsCategoryTab = memo(MetricsCategoryTabInner);
// 为每个分类创建预配置的组件(使用 memo
export const ProfitabilityTab = memo<Omit<MetricsCategoryTabProps, 'categoryKey'>>((props) => (
<MetricsCategoryTab categoryKey="profitability" {...props} />
);
));
export const PerShareTab: React.FC<Omit<MetricsCategoryTabProps, 'categoryKey'>> = (props) => (
export const PerShareTab = memo<Omit<MetricsCategoryTabProps, 'categoryKey'>>((props) => (
<MetricsCategoryTab categoryKey="perShare" {...props} />
);
));
export const GrowthTab: React.FC<Omit<MetricsCategoryTabProps, 'categoryKey'>> = (props) => (
export const GrowthTab = memo<Omit<MetricsCategoryTabProps, 'categoryKey'>>((props) => (
<MetricsCategoryTab categoryKey="growth" {...props} />
);
));
export const OperationalTab: React.FC<Omit<MetricsCategoryTabProps, 'categoryKey'>> = (props) => (
export const OperationalTab = memo<Omit<MetricsCategoryTabProps, 'categoryKey'>>((props) => (
<MetricsCategoryTab categoryKey="operational" {...props} />
);
));
export const SolvencyTab: React.FC<Omit<MetricsCategoryTabProps, 'categoryKey'>> = (props) => (
export const SolvencyTab = memo<Omit<MetricsCategoryTabProps, 'categoryKey'>>((props) => (
<MetricsCategoryTab categoryKey="solvency" {...props} />
);
));
export const ExpenseTab: React.FC<Omit<MetricsCategoryTabProps, 'categoryKey'>> = (props) => (
export const ExpenseTab = memo<Omit<MetricsCategoryTabProps, 'categoryKey'>>((props) => (
<MetricsCategoryTab categoryKey="expense" {...props} />
);
));
export const CashflowMetricsTab: React.FC<Omit<MetricsCategoryTabProps, 'categoryKey'>> = (props) => (
export const CashflowMetricsTab = memo<Omit<MetricsCategoryTabProps, 'categoryKey'>>((props) => (
<MetricsCategoryTab categoryKey="cashflow" {...props} />
);
));
export default MetricsCategoryTab;