perf(FinancialPanorama): 表格组件使用共享配置 + memo
- BalanceSheetTable: 使用共享主题,添加 memo - IncomeStatementTable: 使用共享主题,添加 memo - CashflowTable: 使用共享主题,添加 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 @@
|
|||||||
* 资产负债表组件 - Ant Design 黑金主题
|
* 资产负债表组件 - Ant Design 黑金主题
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo, memo } from 'react';
|
||||||
import { Box, Text, HStack, Badge as ChakraBadge } from '@chakra-ui/react';
|
import { Box, Text, HStack, Badge as ChakraBadge } 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';
|
||||||
@@ -17,79 +17,11 @@ import {
|
|||||||
TOTAL_LIABILITIES_METRICS,
|
TOTAL_LIABILITIES_METRICS,
|
||||||
EQUITY_METRICS,
|
EQUITY_METRICS,
|
||||||
} from '../constants';
|
} from '../constants';
|
||||||
import { getValueByPath } from '../utils';
|
import { getValueByPath, BLACK_GOLD_TABLE_THEME, getTableStyles, calculateYoY } from '../utils';
|
||||||
import type { BalanceSheetTableProps, MetricConfig } from '../types';
|
import type { BalanceSheetTableProps, MetricConfig } from '../types';
|
||||||
|
|
||||||
// Ant Design 黑金主题配置
|
const TABLE_CLASS_NAME = 'balance-sheet-table';
|
||||||
const BLACK_GOLD_THEME = {
|
const tableStyles = getTableStyles(TABLE_CLASS_NAME);
|
||||||
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 = `
|
|
||||||
.balance-sheet-table .ant-table {
|
|
||||||
background: transparent !important;
|
|
||||||
}
|
|
||||||
.balance-sheet-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;
|
|
||||||
}
|
|
||||||
.balance-sheet-table .ant-table-tbody > tr > td {
|
|
||||||
border-bottom: 1px solid rgba(212, 175, 55, 0.1) !important;
|
|
||||||
color: #E2E8F0;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
.balance-sheet-table .ant-table-tbody > tr:hover > td {
|
|
||||||
background: rgba(212, 175, 55, 0.08) !important;
|
|
||||||
}
|
|
||||||
.balance-sheet-table .ant-table-tbody > tr.total-row > td {
|
|
||||||
background: rgba(212, 175, 55, 0.15) !important;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.balance-sheet-table .ant-table-tbody > tr.section-header > td {
|
|
||||||
background: rgba(212, 175, 55, 0.08) !important;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #D4AF37;
|
|
||||||
}
|
|
||||||
.balance-sheet-table .ant-table-cell-fix-left,
|
|
||||||
.balance-sheet-table .ant-table-cell-fix-right {
|
|
||||||
background: #1A202C !important;
|
|
||||||
}
|
|
||||||
.balance-sheet-table .ant-table-tbody > tr:hover .ant-table-cell-fix-left,
|
|
||||||
.balance-sheet-table .ant-table-tbody > tr:hover .ant-table-cell-fix-right {
|
|
||||||
background: rgba(26, 32, 44, 0.95) !important;
|
|
||||||
}
|
|
||||||
.balance-sheet-table .positive-change {
|
|
||||||
color: #E53E3E;
|
|
||||||
}
|
|
||||||
.balance-sheet-table .negative-change {
|
|
||||||
color: #48BB78;
|
|
||||||
}
|
|
||||||
.balance-sheet-table .ant-table-placeholder {
|
|
||||||
background: transparent !important;
|
|
||||||
}
|
|
||||||
.balance-sheet-table .ant-empty-description {
|
|
||||||
color: #A0AEC0;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
// 表格行数据类型
|
// 表格行数据类型
|
||||||
interface TableRowData {
|
interface TableRowData {
|
||||||
@@ -103,7 +35,7 @@ interface TableRowData {
|
|||||||
[period: string]: unknown;
|
[period: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BalanceSheetTable: React.FC<BalanceSheetTableProps> = ({
|
const BalanceSheetTableInner: React.FC<BalanceSheetTableProps> = ({
|
||||||
data,
|
data,
|
||||||
showMetricChart,
|
showMetricChart,
|
||||||
calculateYoYChange,
|
calculateYoYChange,
|
||||||
@@ -172,29 +104,13 @@ export const BalanceSheetTable: React.FC<BalanceSheetTableProps> = ({
|
|||||||
return rows;
|
return rows;
|
||||||
}, [data, displayData]);
|
}, [data, displayData]);
|
||||||
|
|
||||||
// 计算同比变化
|
// 计算同比变化(使用共享函数)
|
||||||
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(data, currentValue, currentPeriod, path, getValueByPath);
|
||||||
|
|
||||||
const currentDate = new Date(currentPeriod);
|
|
||||||
const lastYearPeriod = data.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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 构建列定义
|
// 构建列定义
|
||||||
@@ -236,7 +152,7 @@ export const BalanceSheetTable: React.FC<BalanceSheetTableProps> = ({
|
|||||||
render: (value: number | undefined, record: TableRowData) => {
|
render: (value: number | undefined, record: TableRowData) => {
|
||||||
if (record.isSection) return null;
|
if (record.isSection) return null;
|
||||||
|
|
||||||
const yoy = calculateYoY(value, item.period, record.path);
|
const yoy = calcYoY(value, item.period, record.path);
|
||||||
const formattedValue = formatUtils.formatLargeNumber(value, 0);
|
const formattedValue = formatUtils.formatLargeNumber(value, 0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -296,7 +212,7 @@ export const BalanceSheetTable: React.FC<BalanceSheetTableProps> = ({
|
|||||||
return (
|
return (
|
||||||
<Box className="balance-sheet-table">
|
<Box className="balance-sheet-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}
|
||||||
@@ -323,4 +239,5 @@ export const BalanceSheetTable: React.FC<BalanceSheetTableProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const BalanceSheetTable = memo(BalanceSheetTableInner);
|
||||||
export default BalanceSheetTable;
|
export default BalanceSheetTable;
|
||||||
|
|||||||
@@ -2,82 +2,24 @@
|
|||||||
* 现金流量表组件 - Ant Design 黑金主题
|
* 现金流量表组件 - Ant Design 黑金主题
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo, memo } from 'react';
|
||||||
import { Box, Text, HStack, Badge as ChakraBadge } from '@chakra-ui/react';
|
import { Box, Text, HStack, Badge as ChakraBadge } 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 { CASHFLOW_METRICS } from '../constants';
|
import { CASHFLOW_METRICS } from '../constants';
|
||||||
import { getValueByPath } from '../utils';
|
import { getValueByPath, BLACK_GOLD_TABLE_THEME, getTableStyles, calculateYoY } from '../utils';
|
||||||
import type { CashflowTableProps } from '../types';
|
import type { CashflowTableProps } from '../types';
|
||||||
|
|
||||||
// Ant Design 黑金主题配置
|
const TABLE_CLASS_NAME = 'cashflow-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 = `
|
|
||||||
.cashflow-table .ant-table {
|
|
||||||
background: transparent !important;
|
|
||||||
}
|
|
||||||
.cashflow-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;
|
|
||||||
}
|
|
||||||
.cashflow-table .ant-table-tbody > tr > td {
|
|
||||||
border-bottom: 1px solid rgba(212, 175, 55, 0.1) !important;
|
|
||||||
color: #E2E8F0;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
.cashflow-table .ant-table-tbody > tr:hover > td {
|
|
||||||
background: rgba(212, 175, 55, 0.08) !important;
|
|
||||||
}
|
|
||||||
.cashflow-table .ant-table-cell-fix-left,
|
|
||||||
.cashflow-table .ant-table-cell-fix-right {
|
|
||||||
background: #1A202C !important;
|
|
||||||
}
|
|
||||||
.cashflow-table .ant-table-tbody > tr:hover .ant-table-cell-fix-left,
|
|
||||||
.cashflow-table .ant-table-tbody > tr:hover .ant-table-cell-fix-right {
|
|
||||||
background: rgba(26, 32, 44, 0.95) !important;
|
|
||||||
}
|
|
||||||
.cashflow-table .positive-value {
|
|
||||||
color: #E53E3E;
|
color: #E53E3E;
|
||||||
}
|
}
|
||||||
.cashflow-table .negative-value {
|
.${TABLE_CLASS_NAME} .negative-value {
|
||||||
color: #48BB78;
|
color: #48BB78;
|
||||||
}
|
}
|
||||||
.cashflow-table .positive-change {
|
|
||||||
color: #E53E3E;
|
|
||||||
}
|
|
||||||
.cashflow-table .negative-change {
|
|
||||||
color: #48BB78;
|
|
||||||
}
|
|
||||||
.cashflow-table .ant-table-placeholder {
|
|
||||||
background: transparent !important;
|
|
||||||
}
|
|
||||||
.cashflow-table .ant-empty-description {
|
|
||||||
color: #A0AEC0;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// 核心指标
|
// 核心指标
|
||||||
@@ -92,7 +34,7 @@ interface TableRowData {
|
|||||||
[period: string]: unknown;
|
[period: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CashflowTable: React.FC<CashflowTableProps> = ({
|
const CashflowTableInner: React.FC<CashflowTableProps> = ({
|
||||||
data,
|
data,
|
||||||
showMetricChart,
|
showMetricChart,
|
||||||
calculateYoYChange,
|
calculateYoYChange,
|
||||||
@@ -131,29 +73,13 @@ export const CashflowTable: React.FC<CashflowTableProps> = ({
|
|||||||
});
|
});
|
||||||
}, [data, displayData]);
|
}, [data, displayData]);
|
||||||
|
|
||||||
// 计算同比变化
|
// 计算同比变化(使用共享函数)
|
||||||
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(data, currentValue, currentPeriod, path, getValueByPath);
|
||||||
|
|
||||||
const currentDate = new Date(currentPeriod);
|
|
||||||
const lastYearPeriod = data.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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 构建列定义
|
// 构建列定义
|
||||||
@@ -188,7 +114,7 @@ export const CashflowTable: React.FC<CashflowTableProps> = ({
|
|||||||
width: 110,
|
width: 110,
|
||||||
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 formattedValue = formatUtils.formatLargeNumber(value, 1);
|
const formattedValue = formatUtils.formatLargeNumber(value, 1);
|
||||||
const isNegative = value !== undefined && value < 0;
|
const isNegative = value !== undefined && value < 0;
|
||||||
|
|
||||||
@@ -246,7 +172,7 @@ export const CashflowTable: React.FC<CashflowTableProps> = ({
|
|||||||
return (
|
return (
|
||||||
<Box className="cashflow-table">
|
<Box className="cashflow-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}
|
||||||
@@ -266,4 +192,5 @@ export const CashflowTable: React.FC<CashflowTableProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const CashflowTable = memo(CashflowTableInner);
|
||||||
export default CashflowTable;
|
export default CashflowTable;
|
||||||
|
|||||||
@@ -2,92 +2,25 @@
|
|||||||
* 利润表组件 - Ant Design 黑金主题
|
* 利润表组件 - Ant Design 黑金主题
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo, memo } from 'react';
|
||||||
import { Box, Text, HStack, Badge as ChakraBadge } from '@chakra-ui/react';
|
import { Box, Text, HStack, Badge as ChakraBadge } 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 { INCOME_STATEMENT_SECTIONS } from '../constants';
|
import { INCOME_STATEMENT_SECTIONS } from '../constants';
|
||||||
import { getValueByPath, isNegativeIndicator } from '../utils';
|
import { getValueByPath, isNegativeIndicator, BLACK_GOLD_TABLE_THEME, getTableStyles, calculateYoY } from '../utils';
|
||||||
import type { IncomeStatementTableProps, MetricConfig } from '../types';
|
import type { IncomeStatementTableProps, MetricConfig } from '../types';
|
||||||
|
|
||||||
// Ant Design 黑金主题配置
|
const TABLE_CLASS_NAME = 'income-statement-table';
|
||||||
const BLACK_GOLD_THEME = {
|
const tableStyles = getTableStyles(TABLE_CLASS_NAME) + `
|
||||||
token: {
|
.${TABLE_CLASS_NAME} .ant-table-tbody > tr.subtotal-row > td {
|
||||||
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 = `
|
|
||||||
.income-statement-table .ant-table {
|
|
||||||
background: transparent !important;
|
|
||||||
}
|
|
||||||
.income-statement-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;
|
|
||||||
}
|
|
||||||
.income-statement-table .ant-table-tbody > tr > td {
|
|
||||||
border-bottom: 1px solid rgba(212, 175, 55, 0.1) !important;
|
|
||||||
color: #E2E8F0;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
.income-statement-table .ant-table-tbody > tr:hover > td {
|
|
||||||
background: rgba(212, 175, 55, 0.08) !important;
|
|
||||||
}
|
|
||||||
.income-statement-table .ant-table-tbody > tr.total-row > td {
|
|
||||||
background: rgba(212, 175, 55, 0.15) !important;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.income-statement-table .ant-table-tbody > tr.subtotal-row > td {
|
|
||||||
background: rgba(212, 175, 55, 0.1) !important;
|
background: rgba(212, 175, 55, 0.1) !important;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
.income-statement-table .ant-table-tbody > tr.section-header > td {
|
.${TABLE_CLASS_NAME} .negative-value {
|
||||||
background: rgba(212, 175, 55, 0.08) !important;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #D4AF37;
|
|
||||||
}
|
|
||||||
.income-statement-table .ant-table-cell-fix-left,
|
|
||||||
.income-statement-table .ant-table-cell-fix-right {
|
|
||||||
background: #1A202C !important;
|
|
||||||
}
|
|
||||||
.income-statement-table .ant-table-tbody > tr:hover .ant-table-cell-fix-left,
|
|
||||||
.income-statement-table .ant-table-tbody > tr:hover .ant-table-cell-fix-right {
|
|
||||||
background: rgba(26, 32, 44, 0.95) !important;
|
|
||||||
}
|
|
||||||
.income-statement-table .positive-change {
|
|
||||||
color: #E53E3E;
|
color: #E53E3E;
|
||||||
}
|
}
|
||||||
.income-statement-table .negative-change {
|
|
||||||
color: #48BB78;
|
|
||||||
}
|
|
||||||
.income-statement-table .negative-value {
|
|
||||||
color: #E53E3E;
|
|
||||||
}
|
|
||||||
.income-statement-table .ant-table-placeholder {
|
|
||||||
background: transparent !important;
|
|
||||||
}
|
|
||||||
.income-statement-table .ant-empty-description {
|
|
||||||
color: #A0AEC0;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// 表格行数据类型
|
// 表格行数据类型
|
||||||
@@ -103,7 +36,7 @@ interface TableRowData {
|
|||||||
[period: string]: unknown;
|
[period: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const IncomeStatementTable: React.FC<IncomeStatementTableProps> = ({
|
const IncomeStatementTableInner: React.FC<IncomeStatementTableProps> = ({
|
||||||
data,
|
data,
|
||||||
showMetricChart,
|
showMetricChart,
|
||||||
calculateYoYChange,
|
calculateYoYChange,
|
||||||
@@ -160,29 +93,13 @@ export const IncomeStatementTable: React.FC<IncomeStatementTableProps> = ({
|
|||||||
return rows;
|
return rows;
|
||||||
}, [data, displayData]);
|
}, [data, displayData]);
|
||||||
|
|
||||||
// 计算同比变化
|
// 计算同比变化(使用共享函数)
|
||||||
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(data, currentValue, currentPeriod, path, getValueByPath);
|
||||||
|
|
||||||
const currentDate = new Date(currentPeriod);
|
|
||||||
const lastYearPeriod = data.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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 构建列定义
|
// 构建列定义
|
||||||
@@ -224,7 +141,7 @@ export const IncomeStatementTable: React.FC<IncomeStatementTableProps> = ({
|
|||||||
render: (value: number | undefined, record: TableRowData) => {
|
render: (value: number | undefined, record: TableRowData) => {
|
||||||
if (record.isSection) return null;
|
if (record.isSection) return null;
|
||||||
|
|
||||||
const yoy = calculateYoY(value, item.period, record.path);
|
const yoy = calcYoY(value, item.period, record.path);
|
||||||
const isEPS = record.key.includes('eps');
|
const isEPS = record.key.includes('eps');
|
||||||
const formattedValue = isEPS ? value?.toFixed(3) : formatUtils.formatLargeNumber(value, 0);
|
const formattedValue = isEPS ? value?.toFixed(3) : formatUtils.formatLargeNumber(value, 0);
|
||||||
const isNegative = value !== undefined && value < 0;
|
const isNegative = value !== undefined && value < 0;
|
||||||
@@ -295,7 +212,7 @@ export const IncomeStatementTable: React.FC<IncomeStatementTableProps> = ({
|
|||||||
return (
|
return (
|
||||||
<Box className="income-statement-table">
|
<Box className="income-statement-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}
|
||||||
@@ -323,4 +240,5 @@ export const IncomeStatementTable: React.FC<IncomeStatementTableProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const IncomeStatementTable = memo(IncomeStatementTableInner);
|
||||||
export default IncomeStatementTable;
|
export default IncomeStatementTable;
|
||||||
|
|||||||
Reference in New Issue
Block a user