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

View File

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

View File

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

View File

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

View File

@@ -3,84 +3,26 @@
* 接受 categoryKey 显示单个分类的指标表格 * 接受 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 { Box, Text, HStack, Badge as ChakraBadge, Spinner, Center } from '@chakra-ui/react';
import { Table, ConfigProvider, Tooltip } from 'antd'; import { Table, ConfigProvider, Tooltip } from 'antd';
import type { ColumnsType } from 'antd/es/table'; import type { ColumnsType } from 'antd/es/table';
import { Eye } from 'lucide-react'; import { Eye } from 'lucide-react';
import { formatUtils } from '@services/financialService'; import { formatUtils } from '@services/financialService';
import { FINANCIAL_METRICS_CATEGORIES } from '../constants'; 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'; import type { FinancialMetricsData } from '../types';
type CategoryKey = keyof typeof FINANCIAL_METRICS_CATEGORIES; type CategoryKey = keyof typeof FINANCIAL_METRICS_CATEGORIES;
// Ant Design 黑金主题配置 const TABLE_CLASS_NAME = 'metrics-category-table';
const BLACK_GOLD_THEME = { const tableStyles = getTableStyles(TABLE_CLASS_NAME) + `
token: { .${TABLE_CLASS_NAME} .positive-value {
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 {
color: #E53E3E; color: #E53E3E;
} }
.metrics-category-table .negative-change { .${TABLE_CLASS_NAME} .negative-value {
color: #48BB78; 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 { export interface MetricsCategoryTabProps {
@@ -105,7 +47,7 @@ interface TableRowData {
[period: string]: unknown; [period: string]: unknown;
} }
const MetricsCategoryTab: React.FC<MetricsCategoryTabProps> = ({ const MetricsCategoryTabInner: React.FC<MetricsCategoryTabProps> = ({
categoryKey, categoryKey,
financialMetrics, financialMetrics,
loading, loading,
@@ -162,29 +104,13 @@ const MetricsCategoryTab: React.FC<MetricsCategoryTabProps> = ({
}); });
}, [financialMetrics, displayData, category]); }, [financialMetrics, displayData, category]);
// 计算同比变化 // 计算同比变化(使用共享函数)
const calculateYoY = ( const calcYoY = (
currentValue: number | undefined, currentValue: number | undefined,
currentPeriod: string, currentPeriod: string,
path: string path: string
): number | null => { ): number | null => {
if (currentValue === undefined || currentValue === null) return null; return calculateYoY(financialMetrics, currentValue, currentPeriod, path, getValueByPath);
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;
}; };
// 构建列定义 // 构建列定义
@@ -219,7 +145,7 @@ const MetricsCategoryTab: React.FC<MetricsCategoryTabProps> = ({
width: 100, width: 100,
align: 'right' as const, align: 'right' as const,
render: (value: number | undefined, record: TableRowData) => { 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); const isNegative = isNegativeIndicator(record.key);
// 对于负向指标,增加是坏事(绿色),减少是好事(红色) // 对于负向指标,增加是坏事(绿色),减少是好事(红色)
@@ -287,7 +213,7 @@ const MetricsCategoryTab: React.FC<MetricsCategoryTabProps> = ({
<Box> <Box>
<Box className="metrics-category-table"> <Box className="metrics-category-table">
<style>{tableStyles}</style> <style>{tableStyles}</style>
<ConfigProvider theme={BLACK_GOLD_THEME}> <ConfigProvider theme={BLACK_GOLD_TABLE_THEME}>
<Table <Table
columns={columns} columns={columns}
dataSource={tableData} dataSource={tableData}
@@ -309,33 +235,35 @@ const MetricsCategoryTab: React.FC<MetricsCategoryTabProps> = ({
); );
}; };
// 为每个分类创建预配置的组件 const MetricsCategoryTab = memo(MetricsCategoryTabInner);
export const ProfitabilityTab: React.FC<Omit<MetricsCategoryTabProps, 'categoryKey'>> = (props) => (
// 为每个分类创建预配置的组件(使用 memo
export const ProfitabilityTab = memo<Omit<MetricsCategoryTabProps, 'categoryKey'>>((props) => (
<MetricsCategoryTab categoryKey="profitability" {...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} /> <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} /> <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} /> <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} /> <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} /> <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} /> <MetricsCategoryTab categoryKey="cashflow" {...props} />
); ));
export default MetricsCategoryTab; export default MetricsCategoryTab;