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:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user