refactor(ForecastReport): 架构优化与性能提升

阶段一 - 核心优化:
- 所有子组件添加 React.memo 防止不必要重渲染
- 图表组件统一使用 EChartsWrapper 替代 ReactECharts
- 提取 isForecastYear、IMPORTANT_METRICS 到 constants.ts
- DetailTable 样式提取为 DETAIL_TABLE_STYLES 常量

阶段二 - 架构优化:
- 新增 hooks/useForecastData.ts:数据获取 + Map 缓存 + AbortController
- 新增 services/forecastService.ts:API 封装层
- 新增 utils/chartFormatters.ts:图表格式化工具函数
- 主组件精简:79行 → 63行,添加错误处理和重试功能

优化效果:
- 消除 4 处 isForecastYear 重复定义
- 样式从每次渲染重建改为常量复用
- 添加请求缓存,避免频繁切换时重复请求

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
zdl
2025-12-19 14:17:21 +08:00
parent ec9c5af0be
commit 00cd098968
13 changed files with 313 additions and 166 deletions

View File

@@ -1,10 +1,11 @@
/**
* 盈利预测报表视图 - 黑金主题
* 优化:使用 useForecastData Hook、错误处理、memo 包装
*/
import React, { useState, useEffect, useCallback } from 'react';
import { Box, SimpleGrid } from '@chakra-ui/react';
import { stockService } from '@services/eventService';
import React, { memo } from 'react';
import { Box, SimpleGrid, Center, VStack, Text, Button } from '@chakra-ui/react';
import { useForecastData } from './hooks';
import {
IncomeProfitGrowthChart,
EpsChart,
@@ -12,68 +13,51 @@ import {
DetailTable,
} from './components';
import LoadingState from '../LoadingState';
import { CHART_HEIGHT } from './constants';
import type { ForecastReportProps, ForecastData } from './types';
import type { ForecastReportProps } from './types';
const ForecastReport: React.FC<ForecastReportProps> = ({ stockCode: propStockCode }) => {
const [code, setCode] = useState(propStockCode || '600000');
const [data, setData] = useState<ForecastData | null>(null);
const [loading, setLoading] = useState(false);
const ForecastReport: React.FC<ForecastReportProps> = ({ stockCode }) => {
const { data, isLoading, error, refetch } = useForecastData(stockCode);
const load = useCallback(async () => {
if (!code) return;
setLoading(true);
try {
const resp = await stockService.getForecastReport(code);
if (resp && resp.success) {
setData(resp.data);
}
} finally {
setLoading(false);
}
}, [code]);
// 加载状态
if (isLoading && !data) {
return <LoadingState message="加载盈利预测数据中..." height="300px" />;
}
// 监听 props 中的 stockCode 变化
useEffect(() => {
if (propStockCode && propStockCode !== code) {
setCode(propStockCode);
}
}, [propStockCode, code]);
// 错误状态
if (error && !data) {
return (
<Center h="200px">
<VStack spacing={3}>
<Text color="red.400">{error}</Text>
<Button size="sm" colorScheme="yellow" variant="outline" onClick={refetch}>
</Button>
</VStack>
</Center>
);
}
// 加载数据
useEffect(() => {
if (code) {
load();
}
}, [code, load]);
// 数据
if (!data) return null;
return (
<Box>
{/* 加载状态 */}
{loading && !data && (
<LoadingState message="加载盈利预测数据中..." height="300px" />
)}
{/* 图表区域 - 3列布局 */}
{data && (
<SimpleGrid columns={{ base: 1, md: 3 }} spacing={4}>
<IncomeProfitGrowthChart
incomeProfitData={data.income_profit_trend}
growthData={data.growth_bars}
/>
<EpsChart data={data.eps_trend} />
<PePegChart data={data.pe_peg_axes} />
</SimpleGrid>
)}
<SimpleGrid columns={{ base: 1, md: 3 }} spacing={4}>
<IncomeProfitGrowthChart
incomeProfitData={data.income_profit_trend}
growthData={data.growth_bars}
/>
<EpsChart data={data.eps_trend} />
<PePegChart data={data.pe_peg_axes} />
</SimpleGrid>
{/* 详细数据表格 */}
{data && (
<Box mt={4}>
<DetailTable data={data.detail_table} />
</Box>
)}
<Box mt={4}>
<DetailTable data={data.detail_table} />
</Box>
</Box>
);
};
export default ForecastReport;
export default memo(ForecastReport);