diff --git a/src/views/Company/components/MarketDataView/hooks/useMarketData.ts b/src/views/Company/components/MarketDataView/hooks/useMarketData.ts index cfff6891..6231b8b3 100644 --- a/src/views/Company/components/MarketDataView/hooks/useMarketData.ts +++ b/src/views/Company/components/MarketDataView/hooks/useMarketData.ts @@ -76,15 +76,21 @@ export const useMarketData = ( const riseAnalysisRes = await marketService.getRiseAnalysis(stockCode); if (riseAnalysisRes.success && riseAnalysisRes.data) { + // 性能优化:使用 Map 预构建日期索引,将 O(n*m) 降为 O(n+m) + const dateToIndexMap = new Map(); + tradeDataForMapping.forEach((item, idx) => { + dateToIndexMap.set(item.date.substring(0, 10), idx); + }); + + // 使用 Map 查找,O(1) 复杂度 const tempAnalysisMap: Record = {}; riseAnalysisRes.data.forEach((analysis) => { - const dateIndex = tradeDataForMapping.findIndex( - (item) => item.date.substring(0, 10) === analysis.trade_date - ); - if (dateIndex !== -1) { + const dateIndex = dateToIndexMap.get(analysis.trade_date); + if (dateIndex !== undefined) { tempAnalysisMap[dateIndex] = analysis; } }); + setAnalysisMap(tempAnalysisMap); logger.info('useMarketData', '涨幅分析加载成功', { stockCode, count: Object.keys(tempAnalysisMap).length }); } @@ -164,7 +170,7 @@ export const useMarketData = ( */ const loadDataByType = useCallback(async (dataType: 'funding' | 'bigDeal' | 'unusual' | 'pledge') => { if (!stockCode) return; - if (loadedDataRef.current[dataType]) return; // 已加载则跳过 + if (loadedDataRef.current[dataType]) return; // 取消之前的 Tab 数据请求 tabDataControllerRef.current?.abort(); @@ -345,11 +351,15 @@ export const useMarketData = ( }, [period, refreshTradeData, stockCode]); // 组件卸载时取消所有进行中的请求 + // 注意:在 React StrictMode 下,组件会快速卸载再挂载 + // 为避免取消正在进行的请求,这里不再自动取消 + // 请求会在 loadCoreData 开头通过 coreDataControllerRef.current?.abort() 取消旧请求 useEffect(() => { return () => { - coreDataControllerRef.current?.abort(); - tabDataControllerRef.current?.abort(); - minuteDataControllerRef.current?.abort(); + // 不再在这里取消请求,让请求自然完成 + // coreDataControllerRef.current?.abort(); + // tabDataControllerRef.current?.abort(); + // minuteDataControllerRef.current?.abort(); }; }, []); diff --git a/src/views/Company/components/MarketDataView/index.tsx b/src/views/Company/components/MarketDataView/index.tsx index 6cec32a3..51030aaf 100644 --- a/src/views/Company/components/MarketDataView/index.tsx +++ b/src/views/Company/components/MarketDataView/index.tsx @@ -4,6 +4,8 @@ import React, { useState, useEffect, ReactNode, useMemo, useCallback, memo } from 'react'; import { Box, + Card, + CardBody, Container, VStack, useDisclosure, @@ -87,6 +89,14 @@ const MarketDataView: React.FC = ({ stockCode: propStockCod } }, [propStockCode, stockCode]); + // 首次渲染时加载默认 Tab(融资融券)的数据 + useEffect(() => { + // 默认 Tab 是融资融券(index 0) + if (activeTab === 0) { + loadDataByType('funding'); + } + }, [loadDataByType, activeTab]); + // 处理图表点击事件 const handleChartClick = useCallback( (params: { seriesName?: string; data?: [number, number] }) => { @@ -146,15 +156,19 @@ const MarketDataView: React.FC = ({ stockCode: propStockCod /> {/* 主要内容区域 - Tab */} - + + + + + diff --git a/src/views/Company/components/MarketDataView/utils/chartOptions.ts b/src/views/Company/components/MarketDataView/utils/chartOptions.ts index 652bf3e1..8a7c2c8a 100644 --- a/src/views/Company/components/MarketDataView/utils/chartOptions.ts +++ b/src/views/Company/components/MarketDataView/utils/chartOptions.ts @@ -889,26 +889,60 @@ export const getKLineDarkGoldOption = ( const highPrices = tradeData.map((item) => item.high); const lowPrices = tradeData.map((item) => item.low); - // 计算主图指标 + // 计算主图指标 - MA 始终计算(主图必需) const ma5 = calculateMA(closePrices, 5); const ma10 = calculateMA(closePrices, 10); const ma20 = calculateMA(closePrices, 20); - // 计算布林带(如果选择) + // 计算布林带(仅当选择 BOLL 时) const boll = mainIndicator === 'BOLL' ? calculateBOLL(closePrices) : null; - // 计算副图指标 - const macdData = calculateMACD(closePrices); - const kdjData = calculateKDJ(highPrices, lowPrices, closePrices); - const rsi6 = calculateRSI(closePrices, 6); - const rsi12 = calculateRSI(closePrices, 12); - const rsi24 = calculateRSI(closePrices, 24); - const wr14 = calculateWR(highPrices, lowPrices, closePrices, 14); - const wr6 = calculateWR(highPrices, lowPrices, closePrices, 6); - const cci14 = calculateCCI(highPrices, lowPrices, closePrices, 14); - const bias6 = calculateBIAS(closePrices, 6); - const bias12 = calculateBIAS(closePrices, 12); - const bias24 = calculateBIAS(closePrices, 24); + // 副图指标 - 按需计算(性能优化:只计算当前显示的指标) + type MACDData = { dif: (number | null)[]; dea: (number | null)[]; macd: (number | null)[] }; + type KDJData = { k: (number | null)[]; d: (number | null)[]; j: (number | null)[] }; + type RSIData = { rsi6: (number | null)[]; rsi12: (number | null)[]; rsi24: (number | null)[] }; + type WRData = { wr6: (number | null)[]; wr14: (number | null)[] }; + type BIASData = { bias6: (number | null)[]; bias12: (number | null)[]; bias24: (number | null)[] }; + + let macdData: MACDData | null = null; + let kdjData: KDJData | null = null; + let rsiData: RSIData | null = null; + let wrData: WRData | null = null; + let cci14: (number | null)[] | null = null; + let biasData: BIASData | null = null; + + // 根据 subIndicator 按需计算对应指标 + switch (subIndicator) { + case 'MACD': + macdData = calculateMACD(closePrices); + break; + case 'KDJ': + kdjData = calculateKDJ(highPrices, lowPrices, closePrices); + break; + case 'RSI': + rsiData = { + rsi6: calculateRSI(closePrices, 6), + rsi12: calculateRSI(closePrices, 12), + rsi24: calculateRSI(closePrices, 24), + }; + break; + case 'WR': + wrData = { + wr6: calculateWR(highPrices, lowPrices, closePrices, 6), + wr14: calculateWR(highPrices, lowPrices, closePrices, 14), + }; + break; + case 'CCI': + cci14 = calculateCCI(highPrices, lowPrices, closePrices, 14); + break; + case 'BIAS': + biasData = { + bias6: calculateBIAS(closePrices, 6), + bias12: calculateBIAS(closePrices, 12), + bias24: calculateBIAS(closePrices, 24), + }; + break; + } // 创建涨幅分析标记点(仅当 showAnalysis 为 true 时) const scatterData: [number, number][] = []; @@ -1307,8 +1341,8 @@ export const getKLineDarkGoldOption = ( }, }); - // 添加副图指标 - if (subIndicator === 'MACD') { + // 添加副图指标(使用按需计算的数据) + if (subIndicator === 'MACD' && macdData) { // MACD柱状图 series.push({ name: 'MACD', @@ -1347,7 +1381,7 @@ export const getKLineDarkGoldOption = ( lineStyle: { color: cyan, width: 1 }, }); legendData.push('MACD', 'DIF', 'DEA'); - } else if (subIndicator === 'KDJ') { + } else if (subIndicator === 'KDJ' && kdjData) { series.push( { name: 'K', @@ -1381,14 +1415,14 @@ export const getKLineDarkGoldOption = ( } ); legendData.push('K', 'D', 'J'); - } else if (subIndicator === 'RSI') { + } else if (subIndicator === 'RSI' && rsiData) { series.push( { name: 'RSI6', type: 'line', xAxisIndex: 2, yAxisIndex: 2, - data: rsi6, + data: rsiData.rsi6, smooth: true, symbol: 'none', lineStyle: { color: gold, width: 1 }, @@ -1398,7 +1432,7 @@ export const getKLineDarkGoldOption = ( type: 'line', xAxisIndex: 2, yAxisIndex: 2, - data: rsi12, + data: rsiData.rsi12, smooth: true, symbol: 'none', lineStyle: { color: cyan, width: 1 }, @@ -1408,21 +1442,21 @@ export const getKLineDarkGoldOption = ( type: 'line', xAxisIndex: 2, yAxisIndex: 2, - data: rsi24, + data: rsiData.rsi24, smooth: true, symbol: 'none', lineStyle: { color: purple, width: 1 }, } ); legendData.push('RSI6', 'RSI12', 'RSI24'); - } else if (subIndicator === 'WR') { + } else if (subIndicator === 'WR' && wrData) { series.push( { name: 'WR14', type: 'line', xAxisIndex: 2, yAxisIndex: 2, - data: wr14, + data: wrData.wr14, smooth: true, symbol: 'none', lineStyle: { color: gold, width: 1 }, @@ -1432,14 +1466,14 @@ export const getKLineDarkGoldOption = ( type: 'line', xAxisIndex: 2, yAxisIndex: 2, - data: wr6, + data: wrData.wr6, smooth: true, symbol: 'none', lineStyle: { color: cyan, width: 1 }, } ); legendData.push('WR14', 'WR6'); - } else if (subIndicator === 'CCI') { + } else if (subIndicator === 'CCI' && cci14) { series.push({ name: 'CCI', type: 'line', @@ -1474,14 +1508,14 @@ export const getKLineDarkGoldOption = ( } ); legendData.push('CCI'); - } else if (subIndicator === 'BIAS') { + } else if (subIndicator === 'BIAS' && biasData) { series.push( { name: 'BIAS6', type: 'line', xAxisIndex: 2, yAxisIndex: 2, - data: bias6, + data: biasData.bias6, smooth: true, symbol: 'none', lineStyle: { color: gold, width: 1 }, @@ -1491,7 +1525,7 @@ export const getKLineDarkGoldOption = ( type: 'line', xAxisIndex: 2, yAxisIndex: 2, - data: bias12, + data: biasData.bias12, smooth: true, symbol: 'none', lineStyle: { color: cyan, width: 1 }, @@ -1501,7 +1535,7 @@ export const getKLineDarkGoldOption = ( type: 'line', xAxisIndex: 2, yAxisIndex: 2, - data: bias24, + data: biasData.bias24, smooth: true, symbol: 'none', lineStyle: { color: purple, width: 1 }, diff --git a/src/views/Company/config.ts b/src/views/Company/config.ts index 83fa7a52..ac43bbdc 100644 --- a/src/views/Company/config.ts +++ b/src/views/Company/config.ts @@ -12,6 +12,8 @@ import type { CompanyTheme, TabConfig } from './types'; import { FinancialPanoramaSkeleton } from './components/FinancialPanorama/components'; import { ForecastSkeleton } from './components/ForecastReport/components'; import { MarketDataSkeleton } from './components/MarketDataView/components'; +import DynamicTrackingNavSkeleton from './components/DynamicTracking/components/DynamicTrackingNavSkeleton'; +import CompanyOverviewNavSkeleton from './components/CompanyOverview/BasicInfoTab/components/CompanyOverviewNavSkeleton'; // ============================================ // 黑金主题配置 @@ -76,6 +78,7 @@ export const TAB_CONFIG: TabConfig[] = [ name: '公司概览', icon: Building2, component: CompanyOverview, + fallback: React.createElement(CompanyOverviewNavSkeleton), }, { key: 'analysis', @@ -109,6 +112,7 @@ export const TAB_CONFIG: TabConfig[] = [ name: '动态跟踪', icon: Newspaper, component: DynamicTracking, + fallback: React.createElement(DynamicTrackingNavSkeleton), }, ];