refactor(FinancialPanorama): 优化主组件 Props 传递
- 使用 MetricChartModal 替代内联 Modal - 简化 showMetricChart 回调 - componentProps 使用展开语法传递颜色常量 - 简化 useMemo 依赖数组 - 移除未使用的 imports 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
* 重构后的主组件,使用模块化结构和 SubTabContainer 二级导航
|
* 重构后的主组件,使用模块化结构和 SubTabContainer 二级导航
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState, useMemo, useCallback, ReactNode } from 'react';
|
import React, { useState, useMemo, useCallback } from 'react';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Container,
|
Container,
|
||||||
@@ -13,21 +13,7 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
Alert,
|
Alert,
|
||||||
AlertIcon,
|
AlertIcon,
|
||||||
Modal,
|
|
||||||
ModalOverlay,
|
|
||||||
ModalContent,
|
|
||||||
ModalHeader,
|
|
||||||
ModalBody,
|
|
||||||
ModalCloseButton,
|
|
||||||
useDisclosure,
|
useDisclosure,
|
||||||
Table,
|
|
||||||
Thead,
|
|
||||||
Tbody,
|
|
||||||
Tr,
|
|
||||||
Th,
|
|
||||||
Td,
|
|
||||||
TableContainer,
|
|
||||||
Divider,
|
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import {
|
import {
|
||||||
BarChart3,
|
BarChart3,
|
||||||
@@ -41,8 +27,6 @@ import {
|
|||||||
Receipt,
|
Receipt,
|
||||||
Banknote,
|
Banknote,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import ReactECharts from 'echarts-for-react';
|
|
||||||
import { formatUtils } from '@services/financialService';
|
|
||||||
|
|
||||||
// 通用组件
|
// 通用组件
|
||||||
import SubTabContainer, { type SubTabConfig } from '@components/SubTabContainer';
|
import SubTabContainer, { type SubTabConfig } from '@components/SubTabContainer';
|
||||||
@@ -51,8 +35,14 @@ import LoadingState from '../LoadingState';
|
|||||||
// 内部模块导入
|
// 内部模块导入
|
||||||
import { useFinancialData, type DataTypeKey } from './hooks';
|
import { useFinancialData, type DataTypeKey } from './hooks';
|
||||||
import { COLORS } from './constants';
|
import { COLORS } from './constants';
|
||||||
import { calculateYoYChange, getCellBackground, getMetricChartOption } from './utils';
|
import { calculateYoYChange, getCellBackground } from './utils';
|
||||||
import { PeriodSelector, FinancialOverviewPanel, MainBusinessAnalysis, ComparisonAnalysis } from './components';
|
import {
|
||||||
|
PeriodSelector,
|
||||||
|
FinancialOverviewPanel,
|
||||||
|
MainBusinessAnalysis,
|
||||||
|
ComparisonAnalysis,
|
||||||
|
MetricChartModal,
|
||||||
|
} from './components';
|
||||||
import {
|
import {
|
||||||
BalanceSheetTab,
|
BalanceSheetTab,
|
||||||
IncomeStatementTab,
|
IncomeStatementTab,
|
||||||
@@ -117,111 +107,22 @@ const FinancialPanorama: React.FC<FinancialPanoramaProps> = ({ stockCode: propSt
|
|||||||
|
|
||||||
// UI 状态
|
// UI 状态
|
||||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
const [modalContent, setModalContent] = useState<ReactNode>(null);
|
const [modalProps, setModalProps] = useState<{
|
||||||
|
metricName: string;
|
||||||
|
data: Array<{ period: string; [key: string]: unknown }>;
|
||||||
|
dataPath: string;
|
||||||
|
}>({ metricName: '', data: [], dataPath: '' });
|
||||||
|
|
||||||
// 颜色配置
|
// 点击指标行显示图表
|
||||||
const { bgColor, hoverBg, positiveColor, negativeColor } = COLORS;
|
|
||||||
|
|
||||||
// 点击指标行显示图表(使用 useCallback 避免不必要的重渲染)
|
|
||||||
const showMetricChart = useCallback((
|
const showMetricChart = useCallback((
|
||||||
metricName: string,
|
metricName: string,
|
||||||
_metricKey: string,
|
_metricKey: string,
|
||||||
data: Array<{ period: string; [key: string]: unknown }>,
|
data: Array<{ period: string; [key: string]: unknown }>,
|
||||||
dataPath: string
|
dataPath: string
|
||||||
) => {
|
) => {
|
||||||
const chartData = data
|
setModalProps({ metricName, data, dataPath });
|
||||||
.map((item) => {
|
|
||||||
const value = dataPath.split('.').reduce((obj: unknown, key: string) => {
|
|
||||||
if (obj && typeof obj === 'object') {
|
|
||||||
return (obj as Record<string, unknown>)[key];
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}, item) as number | undefined;
|
|
||||||
return {
|
|
||||||
period: formatUtils.getReportType(item.period),
|
|
||||||
date: item.period,
|
|
||||||
value: value ?? 0,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.reverse();
|
|
||||||
|
|
||||||
const option = getMetricChartOption(metricName, chartData);
|
|
||||||
|
|
||||||
setModalContent(
|
|
||||||
<Box>
|
|
||||||
<ReactECharts option={option} style={{ height: '400px', width: '100%' }} />
|
|
||||||
<Divider my={4} />
|
|
||||||
<TableContainer>
|
|
||||||
<Table size="sm">
|
|
||||||
<Thead>
|
|
||||||
<Tr>
|
|
||||||
<Th>报告期</Th>
|
|
||||||
<Th isNumeric>数值</Th>
|
|
||||||
<Th isNumeric>同比</Th>
|
|
||||||
<Th isNumeric>环比</Th>
|
|
||||||
</Tr>
|
|
||||||
</Thead>
|
|
||||||
<Tbody>
|
|
||||||
{chartData.map((item, idx) => {
|
|
||||||
// 计算环比
|
|
||||||
const qoq =
|
|
||||||
idx > 0
|
|
||||||
? ((item.value - chartData[idx - 1].value) /
|
|
||||||
Math.abs(chartData[idx - 1].value)) *
|
|
||||||
100
|
|
||||||
: null;
|
|
||||||
|
|
||||||
// 计算同比
|
|
||||||
const currentDate = new Date(item.date);
|
|
||||||
const lastYearItem = chartData.find((d) => {
|
|
||||||
const date = new Date(d.date);
|
|
||||||
return (
|
|
||||||
date.getFullYear() === currentDate.getFullYear() - 1 &&
|
|
||||||
date.getMonth() === currentDate.getMonth()
|
|
||||||
);
|
|
||||||
});
|
|
||||||
const yoy = lastYearItem
|
|
||||||
? ((item.value - lastYearItem.value) / Math.abs(lastYearItem.value)) * 100
|
|
||||||
: null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tr key={idx}>
|
|
||||||
<Td>{item.period}</Td>
|
|
||||||
<Td isNumeric>{formatUtils.formatLargeNumber(item.value)}</Td>
|
|
||||||
<Td
|
|
||||||
isNumeric
|
|
||||||
color={
|
|
||||||
yoy !== null && yoy > 0
|
|
||||||
? positiveColor
|
|
||||||
: yoy !== null && yoy < 0
|
|
||||||
? negativeColor
|
|
||||||
: 'gray.500'
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{yoy !== null ? `${yoy.toFixed(2)}%` : '-'}
|
|
||||||
</Td>
|
|
||||||
<Td
|
|
||||||
isNumeric
|
|
||||||
color={
|
|
||||||
qoq !== null && qoq > 0
|
|
||||||
? positiveColor
|
|
||||||
: qoq !== null && qoq < 0
|
|
||||||
? negativeColor
|
|
||||||
: 'gray.500'
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{qoq !== null ? `${qoq.toFixed(2)}%` : '-'}
|
|
||||||
</Td>
|
|
||||||
</Tr>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Tbody>
|
|
||||||
</Table>
|
|
||||||
</TableContainer>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
onOpen();
|
onOpen();
|
||||||
}, [onOpen, positiveColor, negativeColor]);
|
}, [onOpen]);
|
||||||
|
|
||||||
// Tab 配置 - 财务指标分类 + 三大财务报表
|
// Tab 配置 - 财务指标分类 + 三大财务报表
|
||||||
const tabConfigs: SubTabConfig[] = useMemo(
|
const tabConfigs: SubTabConfig[] = useMemo(
|
||||||
@@ -242,7 +143,7 @@ const FinancialPanorama: React.FC<FinancialPanoramaProps> = ({ stockCode: propSt
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
// 传递给 Tab 组件的 props
|
// 传递给 Tab 组件的 props(颜色使用常量,不需要在依赖数组中)
|
||||||
const componentProps = useMemo(
|
const componentProps = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
// 数据
|
// 数据
|
||||||
@@ -257,11 +158,8 @@ const FinancialPanorama: React.FC<FinancialPanoramaProps> = ({ stockCode: propSt
|
|||||||
showMetricChart,
|
showMetricChart,
|
||||||
calculateYoYChange,
|
calculateYoYChange,
|
||||||
getCellBackground,
|
getCellBackground,
|
||||||
// 颜色配置
|
// 颜色配置(使用常量)
|
||||||
positiveColor,
|
...COLORS,
|
||||||
negativeColor,
|
|
||||||
bgColor,
|
|
||||||
hoverBg,
|
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
balanceSheet,
|
balanceSheet,
|
||||||
@@ -271,10 +169,6 @@ const FinancialPanorama: React.FC<FinancialPanoramaProps> = ({ stockCode: propSt
|
|||||||
loading,
|
loading,
|
||||||
loadingTab,
|
loadingTab,
|
||||||
showMetricChart,
|
showMetricChart,
|
||||||
positiveColor,
|
|
||||||
negativeColor,
|
|
||||||
bgColor,
|
|
||||||
hoverBg,
|
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -335,15 +229,14 @@ const FinancialPanorama: React.FC<FinancialPanoramaProps> = ({ stockCode: propSt
|
|||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 弹出模态框 */}
|
{/* 指标图表弹窗 */}
|
||||||
<Modal isOpen={isOpen} onClose={onClose} size="xl">
|
<MetricChartModal
|
||||||
<ModalOverlay />
|
isOpen={isOpen}
|
||||||
<ModalContent maxW="900px">
|
onClose={onClose}
|
||||||
<ModalHeader>指标详情</ModalHeader>
|
metricName={modalProps.metricName}
|
||||||
<ModalCloseButton />
|
data={modalProps.data}
|
||||||
<ModalBody pb={6}>{modalContent}</ModalBody>
|
dataPath={modalProps.dataPath}
|
||||||
</ModalContent>
|
/>
|
||||||
</Modal>
|
|
||||||
</VStack>
|
</VStack>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user