diff --git a/src/components/Charts/ECharts.tsx b/src/components/Charts/ECharts.tsx new file mode 100644 index 00000000..db0422dd --- /dev/null +++ b/src/components/Charts/ECharts.tsx @@ -0,0 +1,40 @@ +/** + * ECharts 包装组件 + * + * 基于 echarts-for-react,使用按需引入的 echarts 实例 + * 减少打包体积约 500KB + * + * @example + * ```tsx + * import ECharts from '@components/Charts/ECharts'; + * + * + * ``` + */ + +import React, { forwardRef } from 'react'; +import ReactEChartsCore from 'echarts-for-react/lib/core'; +import { echarts } from '@lib/echarts'; + +// Re-export ReactEChartsCore props type +import type { EChartsReactProps } from 'echarts-for-react'; + +export type EChartsProps = Omit; + +/** + * ECharts 图表组件 + * 自动使用按需引入的 echarts 实例 + */ +const ECharts = forwardRef((props, ref) => { + return ( + + ); +}); + +ECharts.displayName = 'ECharts'; + +export default ECharts; diff --git a/src/components/Charts/Stock/MiniTimelineChart.js b/src/components/Charts/Stock/MiniTimelineChart.js index e2235fec..c9717654 100644 --- a/src/components/Charts/Stock/MiniTimelineChart.js +++ b/src/components/Charts/Stock/MiniTimelineChart.js @@ -1,7 +1,7 @@ // src/components/Charts/Stock/MiniTimelineChart.js import React, { useState, useEffect, useMemo, useRef, useCallback } from 'react'; import ReactECharts from 'echarts-for-react'; -import * as echarts from 'echarts'; +import { echarts } from '@lib/echarts'; import dayjs from 'dayjs'; import { fetchKlineData, diff --git a/src/components/ChatBot/EChartsRenderer.js b/src/components/ChatBot/EChartsRenderer.js index 6e9dd206..d6886f00 100644 --- a/src/components/ChatBot/EChartsRenderer.js +++ b/src/components/ChatBot/EChartsRenderer.js @@ -3,7 +3,7 @@ import React, { useEffect, useRef } from 'react'; import { Box, useColorModeValue } from '@chakra-ui/react'; -import * as echarts from 'echarts'; +import { echarts } from '@lib/echarts'; /** * ECharts 图表渲染组件 diff --git a/src/components/StockChart/KLineChartModal.tsx b/src/components/StockChart/KLineChartModal.tsx index 07409313..560f639e 100644 --- a/src/components/StockChart/KLineChartModal.tsx +++ b/src/components/StockChart/KLineChartModal.tsx @@ -2,7 +2,8 @@ import React, { useEffect, useRef, useState, useCallback } from 'react'; import { createPortal } from 'react-dom'; import { useSelector } from 'react-redux'; -import * as echarts from 'echarts'; +import { echarts } from '@lib/echarts'; +import type { ECharts } from '@lib/echarts'; import dayjs from 'dayjs'; import { stockService } from '@services/eventService'; import { selectIsMobile } from '@store/slices/deviceSlice'; diff --git a/src/components/StockChart/StockChartAntdModal.js b/src/components/StockChart/StockChartAntdModal.js index bb1a0257..c90b9169 100644 --- a/src/components/StockChart/StockChartAntdModal.js +++ b/src/components/StockChart/StockChartAntdModal.js @@ -2,7 +2,7 @@ import React, { useState, useEffect, useRef } from 'react'; import { Modal, Button, Spin, Typography } from 'antd'; import ReactECharts from 'echarts-for-react'; -import * as echarts from 'echarts'; +import { echarts } from '@lib/echarts'; import dayjs from 'dayjs'; import { stockService } from '../../services/eventService'; import CitedContent from '../Citation/CitedContent'; diff --git a/src/components/StockChart/TimelineChartModal.tsx b/src/components/StockChart/TimelineChartModal.tsx index 66668e8c..b55bd717 100644 --- a/src/components/StockChart/TimelineChartModal.tsx +++ b/src/components/StockChart/TimelineChartModal.tsx @@ -17,7 +17,7 @@ import { Alert, AlertIcon, } from '@chakra-ui/react'; -import * as echarts from 'echarts'; +import { echarts, type ECharts, type EChartsOption } from '@lib/echarts'; import dayjs from 'dayjs'; import { klineDataCache, getCacheKey, fetchKlineData } from '@utils/stock/klineDataCache'; import { selectIsMobile } from '@store/slices/deviceSlice'; diff --git a/src/components/SubTabContainer/index.tsx b/src/components/SubTabContainer/index.tsx index 53d7b9fe..441f5b10 100644 --- a/src/components/SubTabContainer/index.tsx +++ b/src/components/SubTabContainer/index.tsx @@ -31,6 +31,8 @@ import { HStack, Text, Spacer, + Center, + Spinner, } from '@chakra-ui/react'; import type { ComponentType } from 'react'; import type { IconType } from 'react-icons'; @@ -311,7 +313,18 @@ const SubTabContainer: React.FC = memo(({ return ( {shouldRender && Component ? ( - + + + + } + > ) : null} diff --git a/src/lib/echarts.ts b/src/lib/echarts.ts new file mode 100644 index 00000000..0bd967d1 --- /dev/null +++ b/src/lib/echarts.ts @@ -0,0 +1,120 @@ +/** + * ECharts 按需导入配置 + * + * 使用方式: + * import { echarts } from '@lib/echarts'; + * + * 优势: + * - 减小打包体积(从 ~800KB 降至 ~200-300KB) + * - Tree-shaking 支持 + * - 统一管理图表类型和组件 + */ + +// 核心模块 +import * as echarts from 'echarts/core'; + +// 图表类型 - 按需导入 +import { + LineChart, + BarChart, + PieChart, + CandlestickChart, + ScatterChart, +} from 'echarts/charts'; + +// 组件 - 按需导入 +import { + TitleComponent, + TooltipComponent, + LegendComponent, + GridComponent, + DataZoomComponent, + ToolboxComponent, + MarkLineComponent, + MarkPointComponent, + MarkAreaComponent, + DatasetComponent, + TransformComponent, +} from 'echarts/components'; + +// 渲染器 +import { CanvasRenderer } from 'echarts/renderers'; + +// 类型导出 +import type { + ECharts, + EChartsOption, + SetOptionOpts, + ComposeOption, +} from 'echarts/core'; + +import type { + LineSeriesOption, + BarSeriesOption, + PieSeriesOption, + CandlestickSeriesOption, + ScatterSeriesOption, +} from 'echarts/charts'; + +import type { + TitleComponentOption, + TooltipComponentOption, + LegendComponentOption, + GridComponentOption, + DataZoomComponentOption, + ToolboxComponentOption, + MarkLineComponentOption, + MarkPointComponentOption, + MarkAreaComponentOption, + DatasetComponentOption, +} from 'echarts/components'; + +// 注册必需的组件 +echarts.use([ + // 图表类型 + LineChart, + BarChart, + PieChart, + CandlestickChart, + ScatterChart, + // 组件 + TitleComponent, + TooltipComponent, + LegendComponent, + GridComponent, + DataZoomComponent, + ToolboxComponent, + MarkLineComponent, + MarkPointComponent, + MarkAreaComponent, + DatasetComponent, + TransformComponent, + // 渲染器 + CanvasRenderer, +]); + +// 组合类型定义(用于 TypeScript 类型推断) +export type ECOption = ComposeOption< + | LineSeriesOption + | BarSeriesOption + | PieSeriesOption + | CandlestickSeriesOption + | ScatterSeriesOption + | TitleComponentOption + | TooltipComponentOption + | LegendComponentOption + | GridComponentOption + | DataZoomComponentOption + | ToolboxComponentOption + | MarkLineComponentOption + | MarkPointComponentOption + | MarkAreaComponentOption + | DatasetComponentOption +>; + +// 导出 +export { echarts }; +export type { ECharts, EChartsOption, SetOptionOpts }; + +// 默认导出(兼容 import * as echarts from 'echarts' 的用法) +export default echarts; diff --git a/src/views/Company/components/CompanyOverview/components/shareholder/ConcentrationCard.tsx b/src/views/Company/components/CompanyOverview/components/shareholder/ConcentrationCard.tsx index 0cad157d..d3fa7102 100644 --- a/src/views/Company/components/CompanyOverview/components/shareholder/ConcentrationCard.tsx +++ b/src/views/Company/components/CompanyOverview/components/shareholder/ConcentrationCard.tsx @@ -16,7 +16,7 @@ import { SimpleGrid, } from "@chakra-ui/react"; import { FaChartPie, FaArrowUp, FaArrowDown } from "react-icons/fa"; -import * as echarts from "echarts"; +import { echarts, type ECharts, type EChartsOption } from '@lib/echarts'; import type { Concentration } from "../../types"; import { THEME } from "../../BasicInfoTab/config"; diff --git a/src/views/Company/components/FinancialPanorama/hooks/useFinancialData.ts b/src/views/Company/components/FinancialPanorama/hooks/useFinancialData.ts index 35bb70d0..68cf886c 100644 --- a/src/views/Company/components/FinancialPanorama/hooks/useFinancialData.ts +++ b/src/views/Company/components/FinancialPanorama/hooks/useFinancialData.ts @@ -179,8 +179,8 @@ export const useFinancialData = ( setSelectedPeriodsState(periods); }, []); - // 加载所有财务数据(初始加载) - const loadAllFinancialData = useCallback(async () => { + // 加载核心财务数据(初始加载:stockInfo + metrics + comparison) + const loadCoreFinancialData = useCallback(async () => { if (!stockCode || stockCode.length !== 6) { logger.warn('useFinancialData', '无效的股票代码', { stockCode }); toast({ @@ -191,55 +191,45 @@ export const useFinancialData = ( return; } - logger.debug('useFinancialData', '开始加载全部财务数据', { stockCode, selectedPeriods }); + logger.debug('useFinancialData', '开始加载核心财务数据', { stockCode, selectedPeriods }); setLoading(true); setError(null); try { - // 并行加载所有数据 + // 只加载核心数据(概览面板需要的) const [ stockInfoRes, - balanceRes, - incomeRes, - cashflowRes, metricsRes, - businessRes, - forecastRes, - rankRes, comparisonRes, + businessRes, ] = await Promise.all([ financialService.getStockInfo(stockCode), - financialService.getBalanceSheet(stockCode, selectedPeriods), - financialService.getIncomeStatement(stockCode, selectedPeriods), - financialService.getCashflow(stockCode, selectedPeriods), financialService.getFinancialMetrics(stockCode, selectedPeriods), - financialService.getMainBusiness(stockCode, 4), - financialService.getForecast(stockCode), - financialService.getIndustryRank(stockCode, 4), financialService.getPeriodComparison(stockCode, selectedPeriods), + financialService.getMainBusiness(stockCode, 4), ]); // 设置数据 if (stockInfoRes.success) setStockInfo(stockInfoRes.data); - if (balanceRes.success) setBalanceSheet(balanceRes.data); - if (incomeRes.success) setIncomeStatement(incomeRes.data); - if (cashflowRes.success) setCashflow(cashflowRes.data); if (metricsRes.success) setFinancialMetrics(metricsRes.data); - if (businessRes.success) setMainBusiness(businessRes.data); - if (forecastRes.success) setForecast(forecastRes.data); - if (rankRes.success) setIndustryRank(rankRes.data); if (comparisonRes.success) setComparison(comparisonRes.data); + if (businessRes.success) setMainBusiness(businessRes.data); - logger.info('useFinancialData', '全部财务数据加载成功', { stockCode }); + logger.info('useFinancialData', '核心财务数据加载成功', { stockCode }); } catch (err) { const errorMessage = err instanceof Error ? err.message : '未知错误'; setError(errorMessage); - logger.error('useFinancialData', 'loadAllFinancialData', err, { stockCode, selectedPeriods }); + logger.error('useFinancialData', 'loadCoreFinancialData', err, { stockCode, selectedPeriods }); } finally { setLoading(false); } }, [stockCode, selectedPeriods, toast]); + // 加载所有财务数据(用于刷新) + const loadAllFinancialData = useCallback(async () => { + await loadCoreFinancialData(); + }, [loadCoreFinancialData]); + // 监听 props 中的 stockCode 变化 useEffect(() => { if (initialStockCode && initialStockCode !== stockCode) { diff --git a/src/views/Company/components/FinancialPanorama/index.tsx b/src/views/Company/components/FinancialPanorama/index.tsx index 6e0dd619..d68dd2d8 100644 --- a/src/views/Company/components/FinancialPanorama/index.tsx +++ b/src/views/Company/components/FinancialPanorama/index.tsx @@ -122,8 +122,8 @@ const FinancialPanorama: React.FC = ({ stockCode: propSt // 颜色配置 const { bgColor, hoverBg, positiveColor, negativeColor } = COLORS; - // 点击指标行显示图表 - const showMetricChart = ( + // 点击指标行显示图表(使用 useCallback 避免不必要的重渲染) + const showMetricChart = useCallback(( metricName: string, _metricKey: string, data: Array<{ period: string; [key: string]: unknown }>, @@ -221,7 +221,7 @@ const FinancialPanorama: React.FC = ({ stockCode: propSt ); onOpen(); - }; + }, [onOpen, positiveColor, negativeColor]); // Tab 配置 - 财务指标分类 + 三大财务报表 const tabConfigs: SubTabConfig[] = useMemo( diff --git a/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/KLineModule.tsx b/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/KLineModule.tsx index e1abe4c9..4ec87784 100644 --- a/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/KLineModule.tsx +++ b/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/KLineModule.tsx @@ -86,6 +86,27 @@ const DRAWING_OPTIONS: { value: DrawingType; label: string; description: string { value: 'ALL', label: '全部显示', description: '显示所有参考线' }, ]; +// 黑金主题按钮样式(提取到组件外部避免每次渲染重建) +const ACTIVE_BUTTON_STYLE = { + bg: `linear-gradient(135deg, ${darkGoldTheme.gold} 0%, ${darkGoldTheme.orange} 100%)`, + color: '#1a1a2e', + borderColor: darkGoldTheme.gold, + _hover: { + bg: `linear-gradient(135deg, ${darkGoldTheme.goldLight} 0%, ${darkGoldTheme.gold} 100%)`, + }, +} as const; + +const INACTIVE_BUTTON_STYLE = { + bg: 'transparent', + color: darkGoldTheme.textMuted, + borderColor: darkGoldTheme.border, + _hover: { + bg: 'rgba(212, 175, 55, 0.1)', + borderColor: darkGoldTheme.gold, + color: darkGoldTheme.gold, + }, +} as const; + const KLineModule: React.FC = ({ theme, tradeData, @@ -151,34 +172,13 @@ const KLineModule: React.FC = ({ setOverlayMetrics(prev => prev.filter(m => m.metric_id !== metricId)); }, []); - // 切换到分时模式时自动加载数据 - const handleModeChange = (newMode: ChartMode) => { + // 切换到分时模式时自动加载数据(使用 useCallback 避免不必要的重渲染) + const handleModeChange = useCallback((newMode: ChartMode) => { setMode(newMode); if (newMode === 'minute' && !hasMinuteData && !minuteLoading) { onLoadMinuteData(); } - }; - - // 黑金主题按钮样式 - const activeButtonStyle = { - bg: `linear-gradient(135deg, ${darkGoldTheme.gold} 0%, ${darkGoldTheme.orange} 100%)`, - color: '#1a1a2e', - borderColor: darkGoldTheme.gold, - _hover: { - bg: `linear-gradient(135deg, ${darkGoldTheme.goldLight} 0%, ${darkGoldTheme.gold} 100%)`, - }, - }; - - const inactiveButtonStyle = { - bg: 'transparent', - color: darkGoldTheme.textMuted, - borderColor: darkGoldTheme.border, - _hover: { - bg: 'rgba(212, 175, 55, 0.1)', - borderColor: darkGoldTheme.gold, - color: darkGoldTheme.gold, - }, - }; + }, [hasMinuteData, minuteLoading, onLoadMinuteData]); return ( = ({ variant="outline" leftIcon={showAnalysis ? : } onClick={() => setShowAnalysis(!showAnalysis)} - {...(showAnalysis ? inactiveButtonStyle : activeButtonStyle)} + {...(showAnalysis ? INACTIVE_BUTTON_STYLE : ACTIVE_BUTTON_STYLE)} minW="90px" > {showAnalysis ? '隐藏分析' : '显示分析'} @@ -278,7 +278,7 @@ const KLineModule: React.FC = ({ size="sm" variant="outline" rightIcon={} - {...inactiveButtonStyle} + {...INACTIVE_BUTTON_STYLE} minW="90px" > {MAIN_INDICATOR_OPTIONS.find(o => o.value === mainIndicator)?.label || 'MA均线'} @@ -319,7 +319,7 @@ const KLineModule: React.FC = ({ variant="outline" rightIcon={} leftIcon={} - {...inactiveButtonStyle} + {...INACTIVE_BUTTON_STYLE} minW="100px" > {SUB_INDICATOR_OPTIONS.find(o => o.value === subIndicator)?.label || 'MACD'} @@ -360,7 +360,7 @@ const KLineModule: React.FC = ({ variant="outline" rightIcon={} leftIcon={} - {...(drawingType !== 'NONE' ? activeButtonStyle : inactiveButtonStyle)} + {...(drawingType !== 'NONE' ? ACTIVE_BUTTON_STYLE : INACTIVE_BUTTON_STYLE)} minW="90px" > {DRAWING_OPTIONS.find(o => o.value === drawingType)?.label || '绘图'} @@ -411,7 +411,7 @@ const KLineModule: React.FC = ({ size="sm" variant="outline" onClick={() => setShowOrderBook(!showOrderBook)} - {...(showOrderBook ? activeButtonStyle : inactiveButtonStyle)} + {...(showOrderBook ? ACTIVE_BUTTON_STYLE : INACTIVE_BUTTON_STYLE)} minW="80px" > {showOrderBook ? '隐藏盘口' : '显示盘口'} @@ -426,7 +426,7 @@ const KLineModule: React.FC = ({ onClick={onLoadMinuteData} isLoading={minuteLoading} loadingText="获取中" - {...inactiveButtonStyle} + {...INACTIVE_BUTTON_STYLE} > 刷新 @@ -438,14 +438,14 @@ const KLineModule: React.FC = ({ diff --git a/src/views/Company/components/MarketDataView/hooks/useMarketData.ts b/src/views/Company/components/MarketDataView/hooks/useMarketData.ts index 092eca80..33cd6971 100644 --- a/src/views/Company/components/MarketDataView/hooks/useMarketData.ts +++ b/src/views/Company/components/MarketDataView/hooks/useMarketData.ts @@ -84,37 +84,36 @@ export const useMarketData = ( } }, [stockCode]); + // 记录已加载的数据类型 + const loadedDataRef = useRef({ + summary: false, + trade: false, + funding: false, + bigDeal: false, + unusual: false, + pledge: false, + }); + /** - * 加载所有市场数据(涨幅分析延迟加载) + * 加载核心市场数据(仅 summary 和 trade) */ - const loadMarketData = useCallback(async () => { + const loadCoreData = useCallback(async () => { if (!stockCode) return; - logger.debug('useMarketData', '开始加载市场数据', { stockCode, period }); + logger.debug('useMarketData', '开始加载核心市场数据', { stockCode, period }); setLoading(true); setAnalysisMap({}); // 清空旧的分析数据 try { - // 先加载核心数据(不含涨幅分析) - const [ - summaryRes, - tradeRes, - fundingRes, - bigDealRes, - unusualRes, - pledgeRes, - ] = await Promise.all([ + const [summaryRes, tradeRes] = await Promise.all([ marketService.getMarketSummary(stockCode), marketService.getTradeData(stockCode, period), - marketService.getFundingData(stockCode, 30), - marketService.getBigDealData(stockCode, 30), - marketService.getUnusualData(stockCode, 30), - marketService.getPledgeData(stockCode), ]); // 设置概览数据 if (summaryRes.success) { setSummary(summaryRes.data); + loadedDataRef.current.summary = true; } // 设置交易数据 @@ -122,41 +121,79 @@ export const useMarketData = ( if (tradeRes.success) { loadedTradeData = tradeRes.data; setTradeData(loadedTradeData); + loadedDataRef.current.trade = true; } - // 设置融资融券数据 - if (fundingRes.success) { - setFundingData(fundingRes.data); - } - - // 设置大宗交易数据(包含 daily_stats) - if (bigDealRes.success) { - setBigDealData(bigDealRes); - } - - // 设置龙虎榜数据(包含 grouped_data) - if (unusualRes.success) { - setUnusualData(unusualRes); - } - - // 设置股权质押数据 - if (pledgeRes.success) { - setPledgeData(pledgeRes.data); - } - - logger.info('useMarketData', '市场数据加载成功', { stockCode }); + logger.info('useMarketData', '核心市场数据加载成功', { stockCode }); // 核心数据加载完成后,异步加载涨幅分析(不阻塞界面) if (loadedTradeData.length > 0) { loadRiseAnalysis(loadedTradeData); } } catch (error) { - logger.error('useMarketData', 'loadMarketData', error, { stockCode, period }); + logger.error('useMarketData', 'loadCoreData', error, { stockCode, period }); } finally { setLoading(false); } }, [stockCode, period, loadRiseAnalysis]); + /** + * 按需加载指定类型的数据 + */ + const loadDataByType = useCallback(async (dataType: 'funding' | 'bigDeal' | 'unusual' | 'pledge') => { + if (!stockCode) return; + if (loadedDataRef.current[dataType]) return; // 已加载则跳过 + + logger.debug('useMarketData', `按需加载 ${dataType} 数据`, { stockCode }); + + try { + switch (dataType) { + case 'funding': { + const res = await marketService.getFundingData(stockCode, 30); + if (res.success) { + setFundingData(res.data); + loadedDataRef.current.funding = true; + } + break; + } + case 'bigDeal': { + const res = await marketService.getBigDealData(stockCode, 30); + if (res.success) { + setBigDealData(res); + loadedDataRef.current.bigDeal = true; + } + break; + } + case 'unusual': { + const res = await marketService.getUnusualData(stockCode, 30); + if (res.success) { + setUnusualData(res); + loadedDataRef.current.unusual = true; + } + break; + } + case 'pledge': { + const res = await marketService.getPledgeData(stockCode); + if (res.success) { + setPledgeData(res.data); + loadedDataRef.current.pledge = true; + } + break; + } + } + logger.info('useMarketData', `${dataType} 数据加载成功`, { stockCode }); + } catch (error) { + logger.error('useMarketData', `loadDataByType:${dataType}`, error, { stockCode }); + } + }, [stockCode]); + + /** + * 加载所有市场数据(用于刷新) + */ + const loadMarketData = useCallback(async () => { + await loadCoreData(); + }, [loadCoreData]); + /** * 加载分钟K线数据 */ @@ -234,19 +271,28 @@ export const useMarketData = ( await Promise.all([loadMarketData(), loadMinuteData()]); }, [loadMarketData, loadMinuteData]); - // 监听股票代码变化,加载所有数据(首次加载或切换股票) + // 监听股票代码变化,加载核心数据(首次加载或切换股票) useEffect(() => { if (stockCode) { - // stockCode 变化时,加载所有数据 + // stockCode 变化时,重置已加载状态并加载核心数据 if (stockCode !== prevStockCodeRef.current || !isInitializedRef.current) { + // 重置已加载状态 + loadedDataRef.current = { + summary: false, + trade: false, + funding: false, + bigDeal: false, + unusual: false, + pledge: false, + }; + // 只加载核心数据(summary + trade) loadMarketData(); - loadMinuteData(); prevStockCodeRef.current = stockCode; - prevPeriodRef.current = period; // 同步重置 period ref,避免切换股票后误触发 refreshTradeData + prevPeriodRef.current = period; isInitializedRef.current = true; } } - }, [stockCode, period, loadMarketData, loadMinuteData]); + }, [stockCode, period, loadMarketData]); // 监听时间周期变化,只刷新日K线数据 useEffect(() => { @@ -273,6 +319,7 @@ export const useMarketData = ( refetch, loadMinuteData, refreshTradeData, + loadDataByType, }; }; diff --git a/src/views/Company/components/MarketDataView/index.tsx b/src/views/Company/components/MarketDataView/index.tsx index c0b4f188..3096e405 100644 --- a/src/views/Company/components/MarketDataView/index.tsx +++ b/src/views/Company/components/MarketDataView/index.tsx @@ -68,8 +68,25 @@ const MarketDataView: React.FC = ({ stockCode: propStockCod analysisMap, refetch, loadMinuteData, + loadDataByType, } = useMarketData(stockCode, selectedPeriod); + // Tab 切换时按需加载数据 + const handleTabChange = useCallback((index: number) => { + setActiveTab(index); + // 根据 tab index 加载对应数据 + const tabDataMap: Record = { + 0: 'funding', + 1: 'bigDeal', + 2: 'unusual', + 3: 'pledge', + }; + const dataType = tabDataMap[index]; + if (dataType) { + loadDataByType(dataType); + } + }, [loadDataByType]); + // 监听 props 中的 stockCode 变化 useEffect(() => { if (propStockCode && propStockCode !== stockCode) { @@ -173,7 +190,7 @@ const MarketDataView: React.FC = ({ stockCode: propStockCod componentProps={componentProps} themePreset="blackGold" index={activeTab} - onTabChange={(index) => setActiveTab(index)} + onTabChange={handleTabChange} isLazy /> )} diff --git a/src/views/Company/components/MarketDataView/types.ts b/src/views/Company/components/MarketDataView/types.ts index b6bd20c4..c9fabdfb 100644 --- a/src/views/Company/components/MarketDataView/types.ts +++ b/src/views/Company/components/MarketDataView/types.ts @@ -364,6 +364,11 @@ export interface OverlayMetricData { color?: string; } +/** + * 按需加载的数据类型 + */ +export type LazyDataType = 'funding' | 'bigDeal' | 'unusual' | 'pledge'; + /** * useMarketData Hook 返回值 */ @@ -383,4 +388,5 @@ export interface UseMarketDataReturn { refetch: () => Promise; loadMinuteData: () => Promise; refreshTradeData: () => Promise; + loadDataByType: (dataType: LazyDataType) => Promise; } diff --git a/src/views/Company/components/MarketDataView/utils/chartOptions.ts b/src/views/Company/components/MarketDataView/utils/chartOptions.ts index 2ed81be8..5efa9a66 100644 --- a/src/views/Company/components/MarketDataView/utils/chartOptions.ts +++ b/src/views/Company/components/MarketDataView/utils/chartOptions.ts @@ -1,7 +1,7 @@ // src/views/Company/components/MarketDataView/utils/chartOptions.ts // MarketDataView ECharts 图表配置生成器 -import type { EChartsOption } from 'echarts'; +import type { EChartsOption } from '@lib/echarts'; import type { Theme, TradeDayData, diff --git a/src/views/Company/config.ts b/src/views/Company/config.ts index ee011a28..59055d59 100644 --- a/src/views/Company/config.ts +++ b/src/views/Company/config.ts @@ -39,15 +39,27 @@ export const THEME: CompanyTheme = { }; // ============================================ -// Tab 懒加载组件 +// Tab 懒加载组件(带 webpack chunk 命名) // ============================================ -const CompanyOverview = lazy(() => import('./components/CompanyOverview')); -const DeepAnalysis = lazy(() => import('./components/DeepAnalysis')); -const MarketDataView = lazy(() => import('./components/MarketDataView')); -const FinancialPanorama = lazy(() => import('./components/FinancialPanorama')); -const ForecastReport = lazy(() => import('./components/ForecastReport')); -const DynamicTracking = lazy(() => import('./components/DynamicTracking')); +const CompanyOverview = lazy(() => + import(/* webpackChunkName: "company-overview" */ './components/CompanyOverview') +); +const DeepAnalysis = lazy(() => + import(/* webpackChunkName: "company-deep-analysis" */ './components/DeepAnalysis') +); +const MarketDataView = lazy(() => + import(/* webpackChunkName: "company-market-data" */ './components/MarketDataView') +); +const FinancialPanorama = lazy(() => + import(/* webpackChunkName: "company-financial" */ './components/FinancialPanorama') +); +const ForecastReport = lazy(() => + import(/* webpackChunkName: "company-forecast" */ './components/ForecastReport') +); +const DynamicTracking = lazy(() => + import(/* webpackChunkName: "company-tracking" */ './components/DynamicTracking') +); // ============================================ // Tab 配置 diff --git a/src/views/EventDetail/components/RelatedStocks.js b/src/views/EventDetail/components/RelatedStocks.js index e15b30ea..7a67fba8 100644 --- a/src/views/EventDetail/components/RelatedStocks.js +++ b/src/views/EventDetail/components/RelatedStocks.js @@ -47,7 +47,7 @@ import { FaRedo, FaSearch } from 'react-icons/fa'; -import * as echarts from 'echarts'; +import { echarts } from '@lib/echarts'; import StockChartModal from '../../../components/StockChart/StockChartModal'; import { eventService, stockService } from '../../../services/eventService'; diff --git a/src/views/StockOverview/components/FlexScreen/components/MiniTimelineChart.tsx b/src/views/StockOverview/components/FlexScreen/components/MiniTimelineChart.tsx index c808a170..ee4d222f 100644 --- a/src/views/StockOverview/components/FlexScreen/components/MiniTimelineChart.tsx +++ b/src/views/StockOverview/components/FlexScreen/components/MiniTimelineChart.tsx @@ -4,8 +4,7 @@ */ import React, { useEffect, useRef, useState, useMemo } from 'react'; import { Box, Spinner, Center, Text } from '@chakra-ui/react'; -import * as echarts from 'echarts'; -import type { ECharts, EChartsOption } from 'echarts'; +import { echarts, type ECharts, type EChartsOption } from '@lib/echarts'; import { getApiBase } from '@utils/apiConfig'; import type { MiniTimelineChartProps, TimelineDataPoint } from '../types'; diff --git a/src/views/StockOverview/components/HotspotOverview/components/IndexMinuteChart.js b/src/views/StockOverview/components/HotspotOverview/components/IndexMinuteChart.js index 3e8e410a..c75e7db6 100644 --- a/src/views/StockOverview/components/HotspotOverview/components/IndexMinuteChart.js +++ b/src/views/StockOverview/components/HotspotOverview/components/IndexMinuteChart.js @@ -4,7 +4,7 @@ */ import React, { useRef, useEffect, useCallback, useMemo } from 'react'; import { Box } from '@chakra-ui/react'; -import * as echarts from 'echarts'; +import { echarts } from '@lib/echarts'; import { getAlertMarkPointsGrouped } from '../utils/chartHelpers'; import { colors, glassEffect } from '../../../theme/glassTheme'; diff --git a/src/views/StockOverview/index.js b/src/views/StockOverview/index.js index b5db5ff6..ad113709 100644 --- a/src/views/StockOverview/index.js +++ b/src/views/StockOverview/index.js @@ -56,7 +56,7 @@ import TradeDatePicker from '@components/TradeDatePicker'; import HotspotOverview from './components/HotspotOverview'; import FlexScreen from './components/FlexScreen'; import { BsGraphUp, BsLightningFill } from 'react-icons/bs'; -import * as echarts from 'echarts'; +import { echarts } from '@lib/echarts'; import { logger } from '../../utils/logger'; import tradingDays from '../../data/tradingDays.json'; import { useStockOverviewEvents } from './hooks/useStockOverviewEvents';