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
|
* 资产负债表 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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user