perf: 优化各 Tab 数据加载为按需请求
MarketDataView (股票行情): - 初始只加载 summary + tradeData(2个接口) - funding/bigDeal/unusual/pledge 数据在切换 Tab 时按需加载 - 新增 loadDataByType 方法支持懒加载 FinancialPanorama (财务全景): - 初始只加载 stockInfo + metrics + comparison + mainBusiness(4个接口) - 从9个接口优化到4个接口 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
40
src/components/Charts/ECharts.tsx
Normal file
40
src/components/Charts/ECharts.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* ECharts 包装组件
|
||||||
|
*
|
||||||
|
* 基于 echarts-for-react,使用按需引入的 echarts 实例
|
||||||
|
* 减少打包体积约 500KB
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* import ECharts from '@components/Charts/ECharts';
|
||||||
|
*
|
||||||
|
* <ECharts option={chartOption} style={{ height: 300 }} />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
|
||||||
|
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<EChartsReactProps, 'echarts'>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ECharts 图表组件
|
||||||
|
* 自动使用按需引入的 echarts 实例
|
||||||
|
*/
|
||||||
|
const ECharts = forwardRef<ReactEChartsCore, EChartsProps>((props, ref) => {
|
||||||
|
return (
|
||||||
|
<ReactEChartsCore
|
||||||
|
ref={ref}
|
||||||
|
echarts={echarts}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
ECharts.displayName = 'ECharts';
|
||||||
|
|
||||||
|
export default ECharts;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// src/components/Charts/Stock/MiniTimelineChart.js
|
// src/components/Charts/Stock/MiniTimelineChart.js
|
||||||
import React, { useState, useEffect, useMemo, useRef, useCallback } from 'react';
|
import React, { useState, useEffect, useMemo, useRef, useCallback } from 'react';
|
||||||
import ReactECharts from 'echarts-for-react';
|
import ReactECharts from 'echarts-for-react';
|
||||||
import * as echarts from 'echarts';
|
import { echarts } from '@lib/echarts';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import {
|
import {
|
||||||
fetchKlineData,
|
fetchKlineData,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
import React, { useEffect, useRef } from 'react';
|
import React, { useEffect, useRef } from 'react';
|
||||||
import { Box, useColorModeValue } from '@chakra-ui/react';
|
import { Box, useColorModeValue } from '@chakra-ui/react';
|
||||||
import * as echarts from 'echarts';
|
import { echarts } from '@lib/echarts';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ECharts 图表渲染组件
|
* ECharts 图表渲染组件
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
import React, { useEffect, useRef, useState, useCallback } from 'react';
|
import React, { useEffect, useRef, useState, useCallback } from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { useSelector } from 'react-redux';
|
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 dayjs from 'dayjs';
|
||||||
import { stockService } from '@services/eventService';
|
import { stockService } from '@services/eventService';
|
||||||
import { selectIsMobile } from '@store/slices/deviceSlice';
|
import { selectIsMobile } from '@store/slices/deviceSlice';
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import { Modal, Button, Spin, Typography } from 'antd';
|
import { Modal, Button, Spin, Typography } from 'antd';
|
||||||
import ReactECharts from 'echarts-for-react';
|
import ReactECharts from 'echarts-for-react';
|
||||||
import * as echarts from 'echarts';
|
import { echarts } from '@lib/echarts';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { stockService } from '../../services/eventService';
|
import { stockService } from '../../services/eventService';
|
||||||
import CitedContent from '../Citation/CitedContent';
|
import CitedContent from '../Citation/CitedContent';
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
Alert,
|
Alert,
|
||||||
AlertIcon,
|
AlertIcon,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import * as echarts from 'echarts';
|
import { echarts, type ECharts, type EChartsOption } from '@lib/echarts';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { klineDataCache, getCacheKey, fetchKlineData } from '@utils/stock/klineDataCache';
|
import { klineDataCache, getCacheKey, fetchKlineData } from '@utils/stock/klineDataCache';
|
||||||
import { selectIsMobile } from '@store/slices/deviceSlice';
|
import { selectIsMobile } from '@store/slices/deviceSlice';
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ import {
|
|||||||
HStack,
|
HStack,
|
||||||
Text,
|
Text,
|
||||||
Spacer,
|
Spacer,
|
||||||
|
Center,
|
||||||
|
Spinner,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import type { ComponentType } from 'react';
|
import type { ComponentType } from 'react';
|
||||||
import type { IconType } from 'react-icons';
|
import type { IconType } from 'react-icons';
|
||||||
@@ -311,7 +313,18 @@ const SubTabContainer: React.FC<SubTabContainerProps> = memo(({
|
|||||||
return (
|
return (
|
||||||
<TabPanel key={tab.key} p={0}>
|
<TabPanel key={tab.key} p={0}>
|
||||||
{shouldRender && Component ? (
|
{shouldRender && Component ? (
|
||||||
<Suspense fallback={null}>
|
<Suspense
|
||||||
|
fallback={
|
||||||
|
<Center py={20}>
|
||||||
|
<Spinner
|
||||||
|
size="lg"
|
||||||
|
color={DEEP_SPACE.textGold}
|
||||||
|
thickness="3px"
|
||||||
|
speed="0.8s"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
}
|
||||||
|
>
|
||||||
<Component {...componentProps} />
|
<Component {...componentProps} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
120
src/lib/echarts.ts
Normal file
120
src/lib/echarts.ts
Normal file
@@ -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;
|
||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
SimpleGrid,
|
SimpleGrid,
|
||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import { FaChartPie, FaArrowUp, FaArrowDown } from "react-icons/fa";
|
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 type { Concentration } from "../../types";
|
||||||
import { THEME } from "../../BasicInfoTab/config";
|
import { THEME } from "../../BasicInfoTab/config";
|
||||||
|
|
||||||
|
|||||||
@@ -179,8 +179,8 @@ export const useFinancialData = (
|
|||||||
setSelectedPeriodsState(periods);
|
setSelectedPeriodsState(periods);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 加载所有财务数据(初始加载)
|
// 加载核心财务数据(初始加载:stockInfo + metrics + comparison)
|
||||||
const loadAllFinancialData = useCallback(async () => {
|
const loadCoreFinancialData = useCallback(async () => {
|
||||||
if (!stockCode || stockCode.length !== 6) {
|
if (!stockCode || stockCode.length !== 6) {
|
||||||
logger.warn('useFinancialData', '无效的股票代码', { stockCode });
|
logger.warn('useFinancialData', '无效的股票代码', { stockCode });
|
||||||
toast({
|
toast({
|
||||||
@@ -191,55 +191,45 @@ export const useFinancialData = (
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug('useFinancialData', '开始加载全部财务数据', { stockCode, selectedPeriods });
|
logger.debug('useFinancialData', '开始加载核心财务数据', { stockCode, selectedPeriods });
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 并行加载所有数据
|
// 只加载核心数据(概览面板需要的)
|
||||||
const [
|
const [
|
||||||
stockInfoRes,
|
stockInfoRes,
|
||||||
balanceRes,
|
|
||||||
incomeRes,
|
|
||||||
cashflowRes,
|
|
||||||
metricsRes,
|
metricsRes,
|
||||||
businessRes,
|
|
||||||
forecastRes,
|
|
||||||
rankRes,
|
|
||||||
comparisonRes,
|
comparisonRes,
|
||||||
|
businessRes,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
financialService.getStockInfo(stockCode),
|
financialService.getStockInfo(stockCode),
|
||||||
financialService.getBalanceSheet(stockCode, selectedPeriods),
|
|
||||||
financialService.getIncomeStatement(stockCode, selectedPeriods),
|
|
||||||
financialService.getCashflow(stockCode, selectedPeriods),
|
|
||||||
financialService.getFinancialMetrics(stockCode, selectedPeriods),
|
financialService.getFinancialMetrics(stockCode, selectedPeriods),
|
||||||
financialService.getMainBusiness(stockCode, 4),
|
|
||||||
financialService.getForecast(stockCode),
|
|
||||||
financialService.getIndustryRank(stockCode, 4),
|
|
||||||
financialService.getPeriodComparison(stockCode, selectedPeriods),
|
financialService.getPeriodComparison(stockCode, selectedPeriods),
|
||||||
|
financialService.getMainBusiness(stockCode, 4),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 设置数据
|
// 设置数据
|
||||||
if (stockInfoRes.success) setStockInfo(stockInfoRes.data);
|
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 (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 (comparisonRes.success) setComparison(comparisonRes.data);
|
||||||
|
if (businessRes.success) setMainBusiness(businessRes.data);
|
||||||
|
|
||||||
logger.info('useFinancialData', '全部财务数据加载成功', { stockCode });
|
logger.info('useFinancialData', '核心财务数据加载成功', { stockCode });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const errorMessage = err instanceof Error ? err.message : '未知错误';
|
const errorMessage = err instanceof Error ? err.message : '未知错误';
|
||||||
setError(errorMessage);
|
setError(errorMessage);
|
||||||
logger.error('useFinancialData', 'loadAllFinancialData', err, { stockCode, selectedPeriods });
|
logger.error('useFinancialData', 'loadCoreFinancialData', err, { stockCode, selectedPeriods });
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [stockCode, selectedPeriods, toast]);
|
}, [stockCode, selectedPeriods, toast]);
|
||||||
|
|
||||||
|
// 加载所有财务数据(用于刷新)
|
||||||
|
const loadAllFinancialData = useCallback(async () => {
|
||||||
|
await loadCoreFinancialData();
|
||||||
|
}, [loadCoreFinancialData]);
|
||||||
|
|
||||||
// 监听 props 中的 stockCode 变化
|
// 监听 props 中的 stockCode 变化
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (initialStockCode && initialStockCode !== stockCode) {
|
if (initialStockCode && initialStockCode !== stockCode) {
|
||||||
|
|||||||
@@ -122,8 +122,8 @@ const FinancialPanorama: React.FC<FinancialPanoramaProps> = ({ stockCode: propSt
|
|||||||
// 颜色配置
|
// 颜色配置
|
||||||
const { bgColor, hoverBg, positiveColor, negativeColor } = COLORS;
|
const { bgColor, hoverBg, positiveColor, negativeColor } = COLORS;
|
||||||
|
|
||||||
// 点击指标行显示图表
|
// 点击指标行显示图表(使用 useCallback 避免不必要的重渲染)
|
||||||
const showMetricChart = (
|
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 }>,
|
||||||
@@ -221,7 +221,7 @@ const FinancialPanorama: React.FC<FinancialPanoramaProps> = ({ stockCode: propSt
|
|||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
onOpen();
|
onOpen();
|
||||||
};
|
}, [onOpen, positiveColor, negativeColor]);
|
||||||
|
|
||||||
// Tab 配置 - 财务指标分类 + 三大财务报表
|
// Tab 配置 - 财务指标分类 + 三大财务报表
|
||||||
const tabConfigs: SubTabConfig[] = useMemo(
|
const tabConfigs: SubTabConfig[] = useMemo(
|
||||||
|
|||||||
@@ -86,6 +86,27 @@ const DRAWING_OPTIONS: { value: DrawingType; label: string; description: string
|
|||||||
{ value: 'ALL', label: '全部显示', description: '显示所有参考线' },
|
{ 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<KLineModuleProps> = ({
|
const KLineModule: React.FC<KLineModuleProps> = ({
|
||||||
theme,
|
theme,
|
||||||
tradeData,
|
tradeData,
|
||||||
@@ -151,34 +172,13 @@ const KLineModule: React.FC<KLineModuleProps> = ({
|
|||||||
setOverlayMetrics(prev => prev.filter(m => m.metric_id !== metricId));
|
setOverlayMetrics(prev => prev.filter(m => m.metric_id !== metricId));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 切换到分时模式时自动加载数据
|
// 切换到分时模式时自动加载数据(使用 useCallback 避免不必要的重渲染)
|
||||||
const handleModeChange = (newMode: ChartMode) => {
|
const handleModeChange = useCallback((newMode: ChartMode) => {
|
||||||
setMode(newMode);
|
setMode(newMode);
|
||||||
if (newMode === 'minute' && !hasMinuteData && !minuteLoading) {
|
if (newMode === 'minute' && !hasMinuteData && !minuteLoading) {
|
||||||
onLoadMinuteData();
|
onLoadMinuteData();
|
||||||
}
|
}
|
||||||
};
|
}, [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,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@@ -263,7 +263,7 @@ const KLineModule: React.FC<KLineModuleProps> = ({
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
leftIcon={showAnalysis ? <ViewOffIcon /> : <ViewIcon />}
|
leftIcon={showAnalysis ? <ViewOffIcon /> : <ViewIcon />}
|
||||||
onClick={() => setShowAnalysis(!showAnalysis)}
|
onClick={() => setShowAnalysis(!showAnalysis)}
|
||||||
{...(showAnalysis ? inactiveButtonStyle : activeButtonStyle)}
|
{...(showAnalysis ? INACTIVE_BUTTON_STYLE : ACTIVE_BUTTON_STYLE)}
|
||||||
minW="90px"
|
minW="90px"
|
||||||
>
|
>
|
||||||
{showAnalysis ? '隐藏分析' : '显示分析'}
|
{showAnalysis ? '隐藏分析' : '显示分析'}
|
||||||
@@ -278,7 +278,7 @@ const KLineModule: React.FC<KLineModuleProps> = ({
|
|||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
rightIcon={<ChevronDownIcon />}
|
rightIcon={<ChevronDownIcon />}
|
||||||
{...inactiveButtonStyle}
|
{...INACTIVE_BUTTON_STYLE}
|
||||||
minW="90px"
|
minW="90px"
|
||||||
>
|
>
|
||||||
{MAIN_INDICATOR_OPTIONS.find(o => o.value === mainIndicator)?.label || 'MA均线'}
|
{MAIN_INDICATOR_OPTIONS.find(o => o.value === mainIndicator)?.label || 'MA均线'}
|
||||||
@@ -319,7 +319,7 @@ const KLineModule: React.FC<KLineModuleProps> = ({
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
rightIcon={<ChevronDownIcon />}
|
rightIcon={<ChevronDownIcon />}
|
||||||
leftIcon={<Activity size={14} />}
|
leftIcon={<Activity size={14} />}
|
||||||
{...inactiveButtonStyle}
|
{...INACTIVE_BUTTON_STYLE}
|
||||||
minW="100px"
|
minW="100px"
|
||||||
>
|
>
|
||||||
{SUB_INDICATOR_OPTIONS.find(o => o.value === subIndicator)?.label || 'MACD'}
|
{SUB_INDICATOR_OPTIONS.find(o => o.value === subIndicator)?.label || 'MACD'}
|
||||||
@@ -360,7 +360,7 @@ const KLineModule: React.FC<KLineModuleProps> = ({
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
rightIcon={<ChevronDownIcon />}
|
rightIcon={<ChevronDownIcon />}
|
||||||
leftIcon={<Pencil size={14} />}
|
leftIcon={<Pencil size={14} />}
|
||||||
{...(drawingType !== 'NONE' ? activeButtonStyle : inactiveButtonStyle)}
|
{...(drawingType !== 'NONE' ? ACTIVE_BUTTON_STYLE : INACTIVE_BUTTON_STYLE)}
|
||||||
minW="90px"
|
minW="90px"
|
||||||
>
|
>
|
||||||
{DRAWING_OPTIONS.find(o => o.value === drawingType)?.label || '绘图'}
|
{DRAWING_OPTIONS.find(o => o.value === drawingType)?.label || '绘图'}
|
||||||
@@ -411,7 +411,7 @@ const KLineModule: React.FC<KLineModuleProps> = ({
|
|||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => setShowOrderBook(!showOrderBook)}
|
onClick={() => setShowOrderBook(!showOrderBook)}
|
||||||
{...(showOrderBook ? activeButtonStyle : inactiveButtonStyle)}
|
{...(showOrderBook ? ACTIVE_BUTTON_STYLE : INACTIVE_BUTTON_STYLE)}
|
||||||
minW="80px"
|
minW="80px"
|
||||||
>
|
>
|
||||||
{showOrderBook ? '隐藏盘口' : '显示盘口'}
|
{showOrderBook ? '隐藏盘口' : '显示盘口'}
|
||||||
@@ -426,7 +426,7 @@ const KLineModule: React.FC<KLineModuleProps> = ({
|
|||||||
onClick={onLoadMinuteData}
|
onClick={onLoadMinuteData}
|
||||||
isLoading={minuteLoading}
|
isLoading={minuteLoading}
|
||||||
loadingText="获取中"
|
loadingText="获取中"
|
||||||
{...inactiveButtonStyle}
|
{...INACTIVE_BUTTON_STYLE}
|
||||||
>
|
>
|
||||||
刷新
|
刷新
|
||||||
</Button>
|
</Button>
|
||||||
@@ -438,14 +438,14 @@ const KLineModule: React.FC<KLineModuleProps> = ({
|
|||||||
<Button
|
<Button
|
||||||
leftIcon={<BarChart2 size={14} />}
|
leftIcon={<BarChart2 size={14} />}
|
||||||
onClick={() => handleModeChange('daily')}
|
onClick={() => handleModeChange('daily')}
|
||||||
{...(mode === 'daily' ? activeButtonStyle : inactiveButtonStyle)}
|
{...(mode === 'daily' ? ACTIVE_BUTTON_STYLE : INACTIVE_BUTTON_STYLE)}
|
||||||
>
|
>
|
||||||
日K
|
日K
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
leftIcon={<LineChart size={14} />}
|
leftIcon={<LineChart size={14} />}
|
||||||
onClick={() => handleModeChange('minute')}
|
onClick={() => handleModeChange('minute')}
|
||||||
{...(mode === 'minute' ? activeButtonStyle : inactiveButtonStyle)}
|
{...(mode === 'minute' ? ACTIVE_BUTTON_STYLE : INACTIVE_BUTTON_STYLE)}
|
||||||
>
|
>
|
||||||
分时
|
分时
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -84,37 +84,36 @@ export const useMarketData = (
|
|||||||
}
|
}
|
||||||
}, [stockCode]);
|
}, [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;
|
if (!stockCode) return;
|
||||||
|
|
||||||
logger.debug('useMarketData', '开始加载市场数据', { stockCode, period });
|
logger.debug('useMarketData', '开始加载核心市场数据', { stockCode, period });
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setAnalysisMap({}); // 清空旧的分析数据
|
setAnalysisMap({}); // 清空旧的分析数据
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 先加载核心数据(不含涨幅分析)
|
const [summaryRes, tradeRes] = await Promise.all([
|
||||||
const [
|
|
||||||
summaryRes,
|
|
||||||
tradeRes,
|
|
||||||
fundingRes,
|
|
||||||
bigDealRes,
|
|
||||||
unusualRes,
|
|
||||||
pledgeRes,
|
|
||||||
] = await Promise.all([
|
|
||||||
marketService.getMarketSummary(stockCode),
|
marketService.getMarketSummary(stockCode),
|
||||||
marketService.getTradeData(stockCode, period),
|
marketService.getTradeData(stockCode, period),
|
||||||
marketService.getFundingData(stockCode, 30),
|
|
||||||
marketService.getBigDealData(stockCode, 30),
|
|
||||||
marketService.getUnusualData(stockCode, 30),
|
|
||||||
marketService.getPledgeData(stockCode),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 设置概览数据
|
// 设置概览数据
|
||||||
if (summaryRes.success) {
|
if (summaryRes.success) {
|
||||||
setSummary(summaryRes.data);
|
setSummary(summaryRes.data);
|
||||||
|
loadedDataRef.current.summary = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置交易数据
|
// 设置交易数据
|
||||||
@@ -122,41 +121,79 @@ export const useMarketData = (
|
|||||||
if (tradeRes.success) {
|
if (tradeRes.success) {
|
||||||
loadedTradeData = tradeRes.data;
|
loadedTradeData = tradeRes.data;
|
||||||
setTradeData(loadedTradeData);
|
setTradeData(loadedTradeData);
|
||||||
|
loadedDataRef.current.trade = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置融资融券数据
|
logger.info('useMarketData', '核心市场数据加载成功', { stockCode });
|
||||||
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 });
|
|
||||||
|
|
||||||
// 核心数据加载完成后,异步加载涨幅分析(不阻塞界面)
|
// 核心数据加载完成后,异步加载涨幅分析(不阻塞界面)
|
||||||
if (loadedTradeData.length > 0) {
|
if (loadedTradeData.length > 0) {
|
||||||
loadRiseAnalysis(loadedTradeData);
|
loadRiseAnalysis(loadedTradeData);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('useMarketData', 'loadMarketData', error, { stockCode, period });
|
logger.error('useMarketData', 'loadCoreData', error, { stockCode, period });
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [stockCode, period, loadRiseAnalysis]);
|
}, [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线数据
|
* 加载分钟K线数据
|
||||||
*/
|
*/
|
||||||
@@ -234,19 +271,28 @@ export const useMarketData = (
|
|||||||
await Promise.all([loadMarketData(), loadMinuteData()]);
|
await Promise.all([loadMarketData(), loadMinuteData()]);
|
||||||
}, [loadMarketData, loadMinuteData]);
|
}, [loadMarketData, loadMinuteData]);
|
||||||
|
|
||||||
// 监听股票代码变化,加载所有数据(首次加载或切换股票)
|
// 监听股票代码变化,加载核心数据(首次加载或切换股票)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (stockCode) {
|
if (stockCode) {
|
||||||
// stockCode 变化时,加载所有数据
|
// stockCode 变化时,重置已加载状态并加载核心数据
|
||||||
if (stockCode !== prevStockCodeRef.current || !isInitializedRef.current) {
|
if (stockCode !== prevStockCodeRef.current || !isInitializedRef.current) {
|
||||||
|
// 重置已加载状态
|
||||||
|
loadedDataRef.current = {
|
||||||
|
summary: false,
|
||||||
|
trade: false,
|
||||||
|
funding: false,
|
||||||
|
bigDeal: false,
|
||||||
|
unusual: false,
|
||||||
|
pledge: false,
|
||||||
|
};
|
||||||
|
// 只加载核心数据(summary + trade)
|
||||||
loadMarketData();
|
loadMarketData();
|
||||||
loadMinuteData();
|
|
||||||
prevStockCodeRef.current = stockCode;
|
prevStockCodeRef.current = stockCode;
|
||||||
prevPeriodRef.current = period; // 同步重置 period ref,避免切换股票后误触发 refreshTradeData
|
prevPeriodRef.current = period;
|
||||||
isInitializedRef.current = true;
|
isInitializedRef.current = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [stockCode, period, loadMarketData, loadMinuteData]);
|
}, [stockCode, period, loadMarketData]);
|
||||||
|
|
||||||
// 监听时间周期变化,只刷新日K线数据
|
// 监听时间周期变化,只刷新日K线数据
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -273,6 +319,7 @@ export const useMarketData = (
|
|||||||
refetch,
|
refetch,
|
||||||
loadMinuteData,
|
loadMinuteData,
|
||||||
refreshTradeData,
|
refreshTradeData,
|
||||||
|
loadDataByType,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -68,8 +68,25 @@ const MarketDataView: React.FC<MarketDataViewProps> = ({ stockCode: propStockCod
|
|||||||
analysisMap,
|
analysisMap,
|
||||||
refetch,
|
refetch,
|
||||||
loadMinuteData,
|
loadMinuteData,
|
||||||
|
loadDataByType,
|
||||||
} = useMarketData(stockCode, selectedPeriod);
|
} = useMarketData(stockCode, selectedPeriod);
|
||||||
|
|
||||||
|
// Tab 切换时按需加载数据
|
||||||
|
const handleTabChange = useCallback((index: number) => {
|
||||||
|
setActiveTab(index);
|
||||||
|
// 根据 tab index 加载对应数据
|
||||||
|
const tabDataMap: Record<number, 'funding' | 'bigDeal' | 'unusual' | 'pledge'> = {
|
||||||
|
0: 'funding',
|
||||||
|
1: 'bigDeal',
|
||||||
|
2: 'unusual',
|
||||||
|
3: 'pledge',
|
||||||
|
};
|
||||||
|
const dataType = tabDataMap[index];
|
||||||
|
if (dataType) {
|
||||||
|
loadDataByType(dataType);
|
||||||
|
}
|
||||||
|
}, [loadDataByType]);
|
||||||
|
|
||||||
// 监听 props 中的 stockCode 变化
|
// 监听 props 中的 stockCode 变化
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (propStockCode && propStockCode !== stockCode) {
|
if (propStockCode && propStockCode !== stockCode) {
|
||||||
@@ -173,7 +190,7 @@ const MarketDataView: React.FC<MarketDataViewProps> = ({ stockCode: propStockCod
|
|||||||
componentProps={componentProps}
|
componentProps={componentProps}
|
||||||
themePreset="blackGold"
|
themePreset="blackGold"
|
||||||
index={activeTab}
|
index={activeTab}
|
||||||
onTabChange={(index) => setActiveTab(index)}
|
onTabChange={handleTabChange}
|
||||||
isLazy
|
isLazy
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -364,6 +364,11 @@ export interface OverlayMetricData {
|
|||||||
color?: string;
|
color?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按需加载的数据类型
|
||||||
|
*/
|
||||||
|
export type LazyDataType = 'funding' | 'bigDeal' | 'unusual' | 'pledge';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* useMarketData Hook 返回值
|
* useMarketData Hook 返回值
|
||||||
*/
|
*/
|
||||||
@@ -383,4 +388,5 @@ export interface UseMarketDataReturn {
|
|||||||
refetch: () => Promise<void>;
|
refetch: () => Promise<void>;
|
||||||
loadMinuteData: () => Promise<void>;
|
loadMinuteData: () => Promise<void>;
|
||||||
refreshTradeData: () => Promise<void>;
|
refreshTradeData: () => Promise<void>;
|
||||||
|
loadDataByType: (dataType: LazyDataType) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// src/views/Company/components/MarketDataView/utils/chartOptions.ts
|
// src/views/Company/components/MarketDataView/utils/chartOptions.ts
|
||||||
// MarketDataView ECharts 图表配置生成器
|
// MarketDataView ECharts 图表配置生成器
|
||||||
|
|
||||||
import type { EChartsOption } from 'echarts';
|
import type { EChartsOption } from '@lib/echarts';
|
||||||
import type {
|
import type {
|
||||||
Theme,
|
Theme,
|
||||||
TradeDayData,
|
TradeDayData,
|
||||||
|
|||||||
@@ -39,15 +39,27 @@ export const THEME: CompanyTheme = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// Tab 懒加载组件
|
// Tab 懒加载组件(带 webpack chunk 命名)
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
const CompanyOverview = lazy(() => import('./components/CompanyOverview'));
|
const CompanyOverview = lazy(() =>
|
||||||
const DeepAnalysis = lazy(() => import('./components/DeepAnalysis'));
|
import(/* webpackChunkName: "company-overview" */ './components/CompanyOverview')
|
||||||
const MarketDataView = lazy(() => import('./components/MarketDataView'));
|
);
|
||||||
const FinancialPanorama = lazy(() => import('./components/FinancialPanorama'));
|
const DeepAnalysis = lazy(() =>
|
||||||
const ForecastReport = lazy(() => import('./components/ForecastReport'));
|
import(/* webpackChunkName: "company-deep-analysis" */ './components/DeepAnalysis')
|
||||||
const DynamicTracking = lazy(() => import('./components/DynamicTracking'));
|
);
|
||||||
|
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 配置
|
// Tab 配置
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ import {
|
|||||||
FaRedo,
|
FaRedo,
|
||||||
FaSearch
|
FaSearch
|
||||||
} from 'react-icons/fa';
|
} from 'react-icons/fa';
|
||||||
import * as echarts from 'echarts';
|
import { echarts } from '@lib/echarts';
|
||||||
import StockChartModal from '../../../components/StockChart/StockChartModal';
|
import StockChartModal from '../../../components/StockChart/StockChartModal';
|
||||||
|
|
||||||
import { eventService, stockService } from '../../../services/eventService';
|
import { eventService, stockService } from '../../../services/eventService';
|
||||||
|
|||||||
@@ -4,8 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
import React, { useEffect, useRef, useState, useMemo } from 'react';
|
import React, { useEffect, useRef, useState, useMemo } from 'react';
|
||||||
import { Box, Spinner, Center, Text } from '@chakra-ui/react';
|
import { Box, Spinner, Center, Text } from '@chakra-ui/react';
|
||||||
import * as echarts from 'echarts';
|
import { echarts, type ECharts, type EChartsOption } from '@lib/echarts';
|
||||||
import type { ECharts, EChartsOption } from 'echarts';
|
|
||||||
import { getApiBase } from '@utils/apiConfig';
|
import { getApiBase } from '@utils/apiConfig';
|
||||||
|
|
||||||
import type { MiniTimelineChartProps, TimelineDataPoint } from '../types';
|
import type { MiniTimelineChartProps, TimelineDataPoint } from '../types';
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
import React, { useRef, useEffect, useCallback, useMemo } from 'react';
|
import React, { useRef, useEffect, useCallback, useMemo } from 'react';
|
||||||
import { Box } from '@chakra-ui/react';
|
import { Box } from '@chakra-ui/react';
|
||||||
import * as echarts from 'echarts';
|
import { echarts } from '@lib/echarts';
|
||||||
import { getAlertMarkPointsGrouped } from '../utils/chartHelpers';
|
import { getAlertMarkPointsGrouped } from '../utils/chartHelpers';
|
||||||
import { colors, glassEffect } from '../../../theme/glassTheme';
|
import { colors, glassEffect } from '../../../theme/glassTheme';
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ import TradeDatePicker from '@components/TradeDatePicker';
|
|||||||
import HotspotOverview from './components/HotspotOverview';
|
import HotspotOverview from './components/HotspotOverview';
|
||||||
import FlexScreen from './components/FlexScreen';
|
import FlexScreen from './components/FlexScreen';
|
||||||
import { BsGraphUp, BsLightningFill } from 'react-icons/bs';
|
import { BsGraphUp, BsLightningFill } from 'react-icons/bs';
|
||||||
import * as echarts from 'echarts';
|
import { echarts } from '@lib/echarts';
|
||||||
import { logger } from '../../utils/logger';
|
import { logger } from '../../utils/logger';
|
||||||
import tradingDays from '../../data/tradingDays.json';
|
import tradingDays from '../../data/tradingDays.json';
|
||||||
import { useStockOverviewEvents } from './hooks/useStockOverviewEvents';
|
import { useStockOverviewEvents } from './hooks/useStockOverviewEvents';
|
||||||
|
|||||||
Reference in New Issue
Block a user