From 0e29f1aff4e3c6876bf2ad01df3b4b1feef2c967 Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Fri, 19 Dec 2025 14:44:20 +0800 Subject: [PATCH] =?UTF-8?q?refactor(FinancialPanorama):=20=E6=8F=90?= =?UTF-8?q?=E5=8F=96=20MetricChartModal=20=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 从 index.tsx 提取独立的指标图表弹窗组件 - 使用 memo 包装优化性能 - 包含图表展示和同比/环比计算表格 - 减少主组件约 100 行代码 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../components/MetricChartModal.tsx | 173 ++++++++++++++++++ .../FinancialPanorama/components/index.ts | 2 + 2 files changed, 175 insertions(+) create mode 100644 src/views/Company/components/FinancialPanorama/components/MetricChartModal.tsx diff --git a/src/views/Company/components/FinancialPanorama/components/MetricChartModal.tsx b/src/views/Company/components/FinancialPanorama/components/MetricChartModal.tsx new file mode 100644 index 00000000..137914ce --- /dev/null +++ b/src/views/Company/components/FinancialPanorama/components/MetricChartModal.tsx @@ -0,0 +1,173 @@ +/** + * 指标图表弹窗组件 + * + * 显示指标的趋势图表和详细数据表格 + */ + +import React, { useMemo, memo } from 'react'; +import { + Box, + Modal, + ModalOverlay, + ModalContent, + ModalHeader, + ModalBody, + ModalCloseButton, + Table, + Thead, + Tbody, + Tr, + Th, + Td, + TableContainer, + Divider, +} from '@chakra-ui/react'; +import ReactECharts from 'echarts-for-react'; +import { formatUtils } from '@services/financialService'; +import { getMetricChartOption } from '../utils'; +import { COLORS } from '../constants'; + +/** 图表数据项 */ +interface ChartDataItem { + period: string; + date: string; + value: number; +} + +/** 组件 Props */ +export interface MetricChartModalProps { + /** 是否打开 */ + isOpen: boolean; + /** 关闭回调 */ + onClose: () => void; + /** 指标名称 */ + metricName: string; + /** 原始数据 */ + data: Array<{ period: string; [key: string]: unknown }>; + /** 数据路径 */ + dataPath: string; +} + +/** + * 从数据路径获取值 + */ +const getValueByPath = (item: Record, path: string): number | undefined => { + return path.split('.').reduce((obj: unknown, key: string) => { + if (obj && typeof obj === 'object') { + return (obj as Record)[key]; + } + return undefined; + }, item) as number | undefined; +}; + +/** + * 指标图表弹窗 + */ +const MetricChartModalInner: React.FC = ({ + isOpen, + onClose, + metricName, + data, + dataPath, +}) => { + const { positiveColor, negativeColor } = COLORS; + + // 计算图表数据 + const chartData = useMemo((): ChartDataItem[] => { + if (!data || data.length === 0) return []; + + return data + .map((item) => { + const value = getValueByPath(item as Record, dataPath); + return { + period: formatUtils.getReportType(item.period), + date: item.period, + value: value ?? 0, + }; + }) + .reverse(); + }, [data, dataPath]); + + // 图表配置 + const chartOption = useMemo(() => { + return getMetricChartOption(metricName, chartData); + }, [metricName, chartData]); + + // 计算同比环比 + const tableRows = useMemo(() => { + return chartData.map((item, idx) => { + // 计算环比 (QoQ) + const qoq = + idx > 0 && chartData[idx - 1].value !== 0 + ? ((item.value - chartData[idx - 1].value) / Math.abs(chartData[idx - 1].value)) * 100 + : null; + + // 计算同比 (YoY) + 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 && lastYearItem.value !== 0 + ? ((item.value - lastYearItem.value) / Math.abs(lastYearItem.value)) * 100 + : null; + + return { ...item, yoy, qoq }; + }); + }, [chartData]); + + // 获取变化颜色 + const getChangeColor = (value: number | null) => { + if (value === null) return 'gray.500'; + return value > 0 ? positiveColor : value < 0 ? negativeColor : 'gray.500'; + }; + + return ( + + + + {metricName} - 指标详情 + + + + + + + + + + + + + + + + + {tableRows.map((item, idx) => ( + + + + + + + ))} + +
报告期数值同比环比
{item.period}{formatUtils.formatLargeNumber(item.value)} + {item.yoy !== null ? `${item.yoy.toFixed(2)}%` : '-'} + + {item.qoq !== null ? `${item.qoq.toFixed(2)}%` : '-'} +
+
+
+
+
+
+ ); +}; + +export const MetricChartModal = memo(MetricChartModalInner); +export default MetricChartModal; diff --git a/src/views/Company/components/FinancialPanorama/components/index.ts b/src/views/Company/components/FinancialPanorama/components/index.ts index 6bfaf227..e2d002c1 100644 --- a/src/views/Company/components/FinancialPanorama/components/index.ts +++ b/src/views/Company/components/FinancialPanorama/components/index.ts @@ -15,3 +15,5 @@ export { MainBusinessAnalysis } from './MainBusinessAnalysis'; export { IndustryRankingView } from './IndustryRankingView'; export { StockComparison } from './StockComparison'; export { ComparisonAnalysis } from './ComparisonAnalysis'; +export { MetricChartModal } from './MetricChartModal'; +export type { MetricChartModalProps } from './MetricChartModal';