diff --git a/src/views/Company/components/MarketDataView/README.md b/src/views/Company/components/MarketDataView/README.md
index 6d3a38cb..ad3e89c1 100644
--- a/src/views/Company/components/MarketDataView/README.md
+++ b/src/views/Company/components/MarketDataView/README.md
@@ -12,7 +12,7 @@ MarketDataView/
├── README.md # 本文档
│
├── hooks/
-│ └── useMarketData.ts # 市场数据获取
+│ └── useMarketData.ts # 市场数据获取(含 AbortController)
│
├── services/
│ └── marketService.ts # API 服务层
@@ -32,14 +32,23 @@ MarketDataView/
│ │
│ ├── panels/ # 数据面板
│ │ ├── index.ts # 统一导出
-│ │ ├── TradeDataPanel/ # 交易数据面板
-│ │ │ ├── index.tsx # 面板入口
-│ │ │ ├── KLineModule.tsx # K线模块
-│ │ │ └── MetricOverlaySearch.tsx # 指标叠加搜索
-│ │ ├── FundingPanel.tsx # 资金流向面板
-│ │ ├── BigDealPanel.tsx # 大宗交易面板
-│ │ ├── UnusualPanel.tsx # 异动信息面板
-│ │ └── PledgePanel.tsx # 股权质押面板
+│ │ │
+│ │ ├── TradeDataPanel/ # 交易数据面板(K线模块)
+│ │ │ ├── index.tsx # 统一导出
+│ │ │ ├── KLineModule.tsx # K线主模块(157行,memo优化)
+│ │ │ ├── constants.ts # 指标选项常量
+│ │ │ ├── styles.ts # 按钮样式常量
+│ │ │ ├── MetricOverlaySearch.tsx # 指标叠加搜索
+│ │ │ └── components/ # 子组件
+│ │ │ ├── index.ts # 统一导出
+│ │ │ ├── KLineToolbar.tsx # 工具栏(模式切换、指标选择)
+│ │ │ ├── DailyKLineChart.tsx # 日K图表(useMemo缓存)
+│ │ │ └── MinuteChartWithOrderBook.tsx # 分时图+五档盘口
+│ │ │
+│ │ ├── FundingPanel.tsx # 融资融券面板(memo优化)
+│ │ ├── BigDealPanel.tsx # 大宗交易面板(memo优化)
+│ │ ├── UnusualPanel.tsx # 龙虎榜面板(memo优化)
+│ │ └── PledgePanel.tsx # 股权质押面板(memo优化)
│ │
│ └── StockSummaryCard/ # 股票摘要卡片
│ ├── index.tsx # 卡片入口
@@ -62,10 +71,10 @@ MarketDataView/
| 模块 | 说明 |
|------|------|
-| 交易数据 | K线图、分时图、成交量 |
-| 资金流向 | 主力资金、北向资金、融资融券 |
+| 交易数据 | K线图、分时图、成交量、五档盘口 |
+| 融资融券 | 融资余额、融券余额、资金趋势 |
| 大宗交易 | 大宗交易记录、成交统计 |
-| 异动信息 | 涨跌停、龙虎榜、异常波动 |
+| 龙虎榜 | 买入卖出席位、净买入额 |
| 股权质押 | 质押比例、质押明细 |
## 主题系统
@@ -87,15 +96,41 @@ const darkGoldTheme = {
```
MarketDataView
-├── SubTabContainer # Tab 容器
-│ ├── TradeDataPanel # 交易数据
-│ │ └── KLineModule
-│ ├── FundingPanel # 资金流向
-│ ├── BigDealPanel # 大宗交易
-│ ├── UnusualPanel # 异动信息
-│ └── PledgePanel # 股权质押
+├── StockSummaryCard # 股票概览
+└── SubTabContainer # Tab 容器
+ ├── TradeDataPanel # 交易数据
+ │ └── KLineModule
+ │ ├── KLineToolbar # 工具栏
+ │ ├── DailyKLineChart # 日K图表
+ │ └── MinuteChartWithOrderBook # 分时+盘口
+ ├── FundingPanel # 融资融券
+ ├── BigDealPanel # 大宗交易
+ ├── UnusualPanel # 龙虎榜
+ └── PledgePanel # 股权质押
```
+## 性能优化
+
+### 已实现的优化
+
+| 优化项 | 说明 |
+|--------|------|
+| React.memo | 所有 Panel 和子组件使用 memo 包装 |
+| useMemo | 图表配置缓存,避免重复计算技术指标 |
+| useCallback | 事件处理函数稳定化 |
+| AbortController | 请求取消,防止内存泄漏 |
+| Tab 懒加载 | 切换 Tab 时按需加载数据 |
+
+### TradeDataPanel 重构
+
+KLineModule 从 611 行精简至 157 行,拆分为独立子组件:
+
+| 子组件 | 职责 | 行数 |
+|--------|------|------|
+| KLineToolbar | 模式切换、指标选择、时间范围 | 275 |
+| DailyKLineChart | 日K图表渲染、useMemo缓存 | 85 |
+| MinuteChartWithOrderBook | 分时图、实时行情、五档盘口 | 212 |
+
## 使用示例
```tsx
@@ -103,3 +138,39 @@ import MarketDataView from '@views/Company/components/MarketDataView';
```
+
+### 单独使用 K 线模块
+
+```tsx
+import { KLineModule } from '@views/Company/components/MarketDataView/components/panels/TradeDataPanel';
+
+
+```
+
+### 单独使用子组件
+
+```tsx
+import {
+ KLineToolbar,
+ DailyKLineChart,
+ MinuteChartWithOrderBook,
+} from '@views/Company/components/MarketDataView/components/panels/TradeDataPanel';
+
+// 仅工具栏
+
+
+// 仅日K图表
+
+
+// 仅分时图+盘口
+
+```
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 4ec87784..12e18409 100644
--- a/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/KLineModule.tsx
+++ b/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/KLineModule.tsx
@@ -1,112 +1,24 @@
// src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/KLineModule.tsx
// K线模块 - 日K线/分时图切换展示(黑金主题 + 专业技术指标 + 商品数据叠加 + 分时盘口)
-import React, { useState, useMemo, useCallback, useEffect } from 'react';
-import {
- Box,
- Text,
- VStack,
- HStack,
- Button,
- ButtonGroup,
- Badge,
- Center,
- Spinner,
- Icon,
- Select,
- Menu,
- MenuButton,
- MenuList,
- MenuItem,
- MenuDivider,
- Tooltip,
- Grid,
- GridItem,
-} from '@chakra-ui/react';
-import { RepeatIcon, InfoIcon, ChevronDownIcon, ViewIcon, ViewOffIcon } from '@chakra-ui/icons';
-import { BarChart2, Clock, TrendingUp, Calendar, LineChart, Activity, Pencil } from 'lucide-react';
-import ReactECharts from 'echarts-for-react';
+import React, { useState, useMemo, useCallback, memo } from 'react';
+import { Box } from '@chakra-ui/react';
-// 导入实时行情 Hook 和五档盘口组件
-import { useRealtimeQuote } from '@views/StockOverview/components/FlexScreen/hooks';
-import OrderBookPanel from '@views/StockOverview/components/FlexScreen/components/OrderBookPanel';
-
-import { darkGoldTheme, PERIOD_OPTIONS } from '../../../constants';
-import {
- getKLineDarkGoldOption,
- getMinuteKLineDarkGoldOption,
- type IndicatorType,
- type MainIndicatorType,
- type DrawingType,
-} from '../../../utils/chartOptions';
+import { darkGoldTheme } from '../../../constants';
+import type { IndicatorType, MainIndicatorType, DrawingType } from '../../../utils/chartOptions';
import type { KLineModuleProps, OverlayMetricData } from '../../../types';
-import MetricOverlaySearch from './MetricOverlaySearch';
+import type { ChartMode } from './constants';
-// 空状态组件(内联)
-const EmptyState: React.FC<{ title: string; description: string }> = ({ title, description }) => (
-
-
-
-
- {title}
- {description}
-
-
-
-);
+// 子组件导入
+import { KLineToolbar, DailyKLineChart, MinuteChartWithOrderBook } from './components';
// 重新导出类型供外部使用
export type { KLineModuleProps } from '../../../types';
-type ChartMode = 'daily' | 'minute';
-
-// 副图指标选项
-const SUB_INDICATOR_OPTIONS: { value: IndicatorType; label: string; description: string }[] = [
- { value: 'MACD', label: 'MACD', description: '平滑异同移动平均线' },
- { value: 'KDJ', label: 'KDJ', description: '随机指标' },
- { value: 'RSI', label: 'RSI', description: '相对强弱指标' },
- { value: 'WR', label: 'WR', description: '威廉指标(超买超卖)' },
- { value: 'CCI', label: 'CCI', description: '商品通道指标' },
- { value: 'BIAS', label: 'BIAS', description: '乖离率' },
- { value: 'VOL', label: '仅成交量', description: '不显示副图指标' },
-];
-
-// 主图指标选项
-const MAIN_INDICATOR_OPTIONS: { value: MainIndicatorType; label: string; description: string }[] = [
- { value: 'MA', label: 'MA均线', description: 'MA5/MA10/MA20' },
- { value: 'BOLL', label: '布林带', description: '布林通道指标' },
- { value: 'NONE', label: '无', description: '不显示主图指标' },
-];
-
-// 绘图工具选项
-const DRAWING_OPTIONS: { value: DrawingType; label: string; description: string }[] = [
- { value: 'NONE', label: '无', description: '不显示绘图工具' },
- { value: 'SUPPORT_RESISTANCE', label: '支撑/阻力', description: '自动识别支撑位和阻力位' },
- { value: 'TREND_LINE', 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;
-
+/**
+ * K线模块主组件
+ * 职责:状态管理、组合子组件、事件处理
+ */
const KLineModule: React.FC = ({
theme,
tradeData,
@@ -119,6 +31,7 @@ const KLineModule: React.FC = ({
onPeriodChange,
stockCode,
}) => {
+ // ========== 状态管理 ==========
const [mode, setMode] = useState('daily');
const [subIndicator, setSubIndicator] = useState('MACD');
const [mainIndicator, setMainIndicator] = useState('MA');
@@ -126,33 +39,10 @@ const KLineModule: React.FC = ({
const [drawingType, setDrawingType] = useState('NONE');
const [overlayMetrics, setOverlayMetrics] = useState([]);
const [showOrderBook, setShowOrderBook] = useState(true);
+
+ // ========== 计算属性 ==========
const hasMinuteData = minuteData && minuteData.data && minuteData.data.length > 0;
- // 实时行情数据(用于五档盘口)
- const subscribedCodes = useMemo(() => {
- if (!stockCode || mode !== 'minute') return [];
- return [stockCode];
- }, [stockCode, mode]);
-
- const { quotes, connected } = useRealtimeQuote(subscribedCodes);
-
- // 获取当前股票的行情数据
- const currentQuote = useMemo(() => {
- if (!stockCode) return null;
- // 尝试不同的代码格式
- return quotes[stockCode] || quotes[`${stockCode}.SH`] || quotes[`${stockCode}.SZ`] || null;
- }, [quotes, stockCode]);
-
- // 判断是否在交易时间
- const isInTradingHours = useMemo(() => {
- const now = new Date();
- const hours = now.getHours();
- const minutes = now.getMinutes();
- const totalMinutes = hours * 60 + minutes;
- // 9:15-11:30 或 13:00-15:00
- return (totalMinutes >= 555 && totalMinutes <= 690) || (totalMinutes >= 780 && totalMinutes <= 900);
- }, []);
-
// 计算股票数据的日期范围(用于查询商品数据)
const stockDateRange = useMemo(() => {
if (tradeData.length === 0) return undefined;
@@ -162,6 +52,26 @@ const KLineModule: React.FC = ({
};
}, [tradeData]);
+ // ========== 事件处理 ==========
+
+ // 切换到分时模式时自动加载数据
+ const handleModeChange = useCallback((newMode: ChartMode) => {
+ setMode(newMode);
+ if (newMode === 'minute' && !hasMinuteData && !minuteLoading) {
+ onLoadMinuteData();
+ }
+ }, [hasMinuteData, minuteLoading, onLoadMinuteData]);
+
+ // 切换显示/隐藏分析
+ const handleToggleAnalysis = useCallback(() => {
+ setShowAnalysis(prev => !prev);
+ }, []);
+
+ // 切换显示/隐藏盘口
+ const handleToggleOrderBook = useCallback(() => {
+ setShowOrderBook(prev => !prev);
+ }, []);
+
// 添加叠加指标
const handleAddOverlayMetric = useCallback((metric: OverlayMetricData) => {
setOverlayMetrics(prev => [...prev, metric]);
@@ -172,440 +82,75 @@ const KLineModule: React.FC = ({
setOverlayMetrics(prev => prev.filter(m => m.metric_id !== metricId));
}, []);
- // 切换到分时模式时自动加载数据(使用 useCallback 避免不必要的重渲染)
- const handleModeChange = useCallback((newMode: ChartMode) => {
- setMode(newMode);
- if (newMode === 'minute' && !hasMinuteData && !minuteLoading) {
- onLoadMinuteData();
- }
- }, [hasMinuteData, minuteLoading, onLoadMinuteData]);
+ // 主图指标变更
+ const handleMainIndicatorChange = useCallback((indicator: MainIndicatorType) => {
+ setMainIndicator(indicator);
+ }, []);
+ // 副图指标变更
+ const handleSubIndicatorChange = useCallback((indicator: IndicatorType) => {
+ setSubIndicator(indicator);
+ }, []);
+
+ // 绘图工具变更
+ const handleDrawingTypeChange = useCallback((type: DrawingType) => {
+ setDrawingType(type);
+ }, []);
+
+ // ========== 渲染 ==========
return (
-
- {/* 卡片头部 */}
-
-
-
-
- {mode === 'daily' ? (
-
- ) : (
-
- )}
-
-
- {mode === 'daily' ? '日K线图' : '分时走势'}
-
- {mode === 'minute' && minuteData?.trade_date && (
-
- {minuteData.trade_date}
-
- )}
-
+
+ {/* 工具栏 */}
+
-
- {/* 日K模式下显示时间范围选择器和指标选择 */}
- {mode === 'daily' && (
- <>
- {/* 时间范围选择器 */}
- {onPeriodChange && (
-
-
-
-
- )}
-
- {/* 隐藏/显示涨幅分析 */}
-
- : }
- onClick={() => setShowAnalysis(!showAnalysis)}
- {...(showAnalysis ? INACTIVE_BUTTON_STYLE : ACTIVE_BUTTON_STYLE)}
- minW="90px"
- >
- {showAnalysis ? '隐藏分析' : '显示分析'}
-
-
-
- {/* 主图指标选择 */}
-
-
- {/* 副图指标选择 */}
-
-
- {/* 绘图工具选择 */}
-
-
- {/* 商品数据叠加搜索 */}
-
- >
- )}
-
- {/* 分时模式下的控制按钮 */}
- {mode === 'minute' && (
- <>
- {/* 显示/隐藏盘口 */}
-
-
-
-
- {/* 刷新按钮 */}
- }
- size="sm"
- variant="outline"
- onClick={onLoadMinuteData}
- isLoading={minuteLoading}
- loadingText="获取中"
- {...INACTIVE_BUTTON_STYLE}
- >
- 刷新
-
- >
- )}
-
- {/* 模式切换按钮组 */}
-
- }
- onClick={() => handleModeChange('daily')}
- {...(mode === 'daily' ? ACTIVE_BUTTON_STYLE : INACTIVE_BUTTON_STYLE)}
- >
- 日K
-
- }
- onClick={() => handleModeChange('minute')}
- {...(mode === 'minute' ? ACTIVE_BUTTON_STYLE : INACTIVE_BUTTON_STYLE)}
- >
- 分时
-
-
-
-
-
-
- {/* 卡片内容 */}
+ {/* 图表内容区域 */}
{mode === 'daily' ? (
- // 日K线图(带技术指标)
- tradeData.length > 0 ? (
-
-
-
- ) : (
-
- )
+ // 日K线图
+
) : (
// 分时走势图 + 五档盘口
- minuteLoading ? (
-
-
-
-
- 加载分时数据中...
-
-
-
- ) : hasMinuteData ? (
-
- {/* 分时图表 */}
-
-
-
-
-
-
- {/* 五档盘口 */}
- {showOrderBook && (
-
-
- {/* 盘口标题 */}
-
-
- 五档盘口
-
- {/* 连接状态指示 */}
-
- {isInTradingHours && (
-
- {connected.SSE || connected.SZSE ? '实时' : '离线'}
-
- )}
-
-
-
- {/* 当前价格信息 */}
- {currentQuote && (
-
-
- 当前价
- 0 ? '#ff4d4d' :
- currentQuote.changePct < 0 ? '#22c55e' :
- darkGoldTheme.textPrimary
- }
- >
- {currentQuote.price?.toFixed(2) || '-'}
-
-
-
- 涨跌幅
- 0 ? '#ff4d4d' :
- currentQuote.changePct < 0 ? '#22c55e' :
- darkGoldTheme.textMuted
- }
- >
- {currentQuote.changePct > 0 ? '+' : ''}{currentQuote.changePct?.toFixed(2) || '0.00'}%
-
-
-
- )}
-
- {/* 五档盘口面板 */}
- {currentQuote && (currentQuote.bidPrices?.length > 0 || currentQuote.askPrices?.length > 0) ? (
-
- ) : (
-
-
-
- {isInTradingHours ? '获取盘口数据中...' : '非交易时间'}
-
- {!isInTradingHours && (
-
- 交易时间: 9:30-11:30, 13:00-15:00
-
- )}
-
-
- )}
-
-
- )}
-
- ) : (
-
- )
+
)}
);
};
-export default KLineModule;
+export default memo(KLineModule);
diff --git a/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/components/DailyKLineChart.tsx b/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/components/DailyKLineChart.tsx
new file mode 100644
index 00000000..5d03248c
--- /dev/null
+++ b/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/components/DailyKLineChart.tsx
@@ -0,0 +1,90 @@
+// src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/components/DailyKLineChart.tsx
+// 日K线图表组件
+
+import React, { memo, useMemo } from 'react';
+import { Box, Text, VStack, Center, Icon } from '@chakra-ui/react';
+import { InfoIcon } from '@chakra-ui/icons';
+import ReactECharts from 'echarts-for-react';
+
+import { darkGoldTheme } from '../../../../constants';
+import {
+ getKLineDarkGoldOption,
+ type IndicatorType,
+ type MainIndicatorType,
+ type DrawingType,
+} from '../../../../utils/chartOptions';
+import type { TradeDayData, RiseAnalysis, OverlayMetricData } from '../../../../types';
+
+export interface DailyKLineChartProps {
+ tradeData: TradeDayData[];
+ analysisMap: Record;
+ subIndicator: IndicatorType;
+ mainIndicator: MainIndicatorType;
+ showAnalysis: boolean;
+ drawingType: DrawingType;
+ overlayMetrics: OverlayMetricData[];
+ onChartClick?: (params: { seriesName?: string; data?: [number, number] }) => void;
+}
+
+/**
+ * 空状态组件
+ */
+const EmptyState: React.FC<{ title: string; description: string }> = ({ title, description }) => (
+
+
+
+
+ {title}
+ {description}
+
+
+
+);
+
+/**
+ * 日K线图表组件
+ */
+const DailyKLineChart: React.FC = ({
+ tradeData,
+ analysisMap,
+ subIndicator,
+ mainIndicator,
+ showAnalysis,
+ drawingType,
+ overlayMetrics,
+ onChartClick,
+}) => {
+ // 缓存图表配置
+ const chartOption = useMemo(() => {
+ if (tradeData.length === 0) return {};
+ return getKLineDarkGoldOption(
+ tradeData,
+ analysisMap,
+ subIndicator,
+ mainIndicator,
+ showAnalysis,
+ drawingType,
+ overlayMetrics
+ );
+ }, [tradeData, analysisMap, subIndicator, mainIndicator, showAnalysis, drawingType, overlayMetrics]);
+
+ // 空数据状态
+ if (tradeData.length === 0) {
+ return ;
+ }
+
+ return (
+
+
+
+ );
+};
+
+export default memo(DailyKLineChart);
diff --git a/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/components/KLineToolbar.tsx b/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/components/KLineToolbar.tsx
new file mode 100644
index 00000000..18811ab3
--- /dev/null
+++ b/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/components/KLineToolbar.tsx
@@ -0,0 +1,335 @@
+// src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/components/KLineToolbar.tsx
+// K线工具栏组件
+
+import React, { memo } from 'react';
+import {
+ Box,
+ Text,
+ VStack,
+ HStack,
+ Button,
+ ButtonGroup,
+ Badge,
+ Icon,
+ Select,
+ Menu,
+ MenuButton,
+ MenuList,
+ MenuItem,
+ Tooltip,
+} from '@chakra-ui/react';
+import { RepeatIcon, ChevronDownIcon, ViewIcon, ViewOffIcon } from '@chakra-ui/icons';
+import { BarChart2, TrendingUp, Calendar, LineChart, Activity, Pencil } from 'lucide-react';
+
+import { darkGoldTheme, PERIOD_OPTIONS } from '../../../../constants';
+import type { IndicatorType, MainIndicatorType, DrawingType } from '../../../../utils/chartOptions';
+import type { OverlayMetricData, MinuteData } from '../../../../types';
+import {
+ SUB_INDICATOR_OPTIONS,
+ MAIN_INDICATOR_OPTIONS,
+ DRAWING_OPTIONS,
+ type ChartMode,
+} from '../constants';
+import {
+ ACTIVE_BUTTON_STYLE,
+ INACTIVE_BUTTON_STYLE,
+ MENU_LIST_STYLE,
+ getMenuItemStyle,
+ SELECT_STYLE,
+} from '../styles';
+import MetricOverlaySearch from '../MetricOverlaySearch';
+
+export interface KLineToolbarProps {
+ // 模式相关
+ mode: ChartMode;
+ onModeChange: (mode: ChartMode) => void;
+
+ // 日K模式
+ selectedPeriod?: number;
+ onPeriodChange?: (period: number) => void;
+ showAnalysis: boolean;
+ onToggleAnalysis: () => void;
+ mainIndicator: MainIndicatorType;
+ onMainIndicatorChange: (indicator: MainIndicatorType) => void;
+ subIndicator: IndicatorType;
+ onSubIndicatorChange: (indicator: IndicatorType) => void;
+ drawingType: DrawingType;
+ onDrawingTypeChange: (type: DrawingType) => void;
+ overlayMetrics: OverlayMetricData[];
+ onAddOverlayMetric: (metric: OverlayMetricData) => void;
+ onRemoveOverlayMetric: (metricId: string) => void;
+ stockDateRange?: { startDate: string; endDate: string };
+
+ // 分时模式
+ minuteData?: MinuteData | null;
+ minuteLoading: boolean;
+ showOrderBook: boolean;
+ onToggleOrderBook: () => void;
+ onRefreshMinuteData: () => void;
+}
+
+const KLineToolbar: React.FC = ({
+ mode,
+ onModeChange,
+ selectedPeriod,
+ onPeriodChange,
+ showAnalysis,
+ onToggleAnalysis,
+ mainIndicator,
+ onMainIndicatorChange,
+ subIndicator,
+ onSubIndicatorChange,
+ drawingType,
+ onDrawingTypeChange,
+ overlayMetrics,
+ onAddOverlayMetric,
+ onRemoveOverlayMetric,
+ stockDateRange,
+ minuteData,
+ minuteLoading,
+ showOrderBook,
+ onToggleOrderBook,
+ onRefreshMinuteData,
+}) => {
+ return (
+
+
+ {/* 左侧标题区域 */}
+
+
+ {mode === 'daily' ? (
+
+ ) : (
+
+ )}
+
+
+ {mode === 'daily' ? '日K线图' : '分时走势'}
+
+ {mode === 'minute' && minuteData?.trade_date && (
+
+ {minuteData.trade_date}
+
+ )}
+
+
+ {/* 右侧控制按钮区域 */}
+
+ {/* 日K模式下的控制按钮 */}
+ {mode === 'daily' && (
+ <>
+ {/* 时间范围选择器 */}
+ {onPeriodChange && (
+
+
+
+
+ )}
+
+ {/* 隐藏/显示涨幅分析 */}
+
+ : }
+ onClick={onToggleAnalysis}
+ {...(showAnalysis ? INACTIVE_BUTTON_STYLE : ACTIVE_BUTTON_STYLE)}
+ minW="90px"
+ >
+ {showAnalysis ? '隐藏分析' : '显示分析'}
+
+
+
+ {/* 主图指标选择 */}
+
+
+ {/* 副图指标选择 */}
+
+
+ {/* 绘图工具选择 */}
+
+
+ {/* 商品数据叠加搜索 */}
+
+ >
+ )}
+
+ {/* 分时模式下的控制按钮 */}
+ {mode === 'minute' && (
+ <>
+ {/* 显示/隐藏盘口 */}
+
+
+
+
+ {/* 刷新按钮 */}
+ }
+ size="sm"
+ variant="outline"
+ onClick={onRefreshMinuteData}
+ isLoading={minuteLoading}
+ loadingText="获取中"
+ {...INACTIVE_BUTTON_STYLE}
+ >
+ 刷新
+
+ >
+ )}
+
+ {/* 模式切换按钮组 */}
+
+ }
+ onClick={() => onModeChange('daily')}
+ {...(mode === 'daily' ? ACTIVE_BUTTON_STYLE : INACTIVE_BUTTON_STYLE)}
+ >
+ 日K
+
+ }
+ onClick={() => onModeChange('minute')}
+ {...(mode === 'minute' ? ACTIVE_BUTTON_STYLE : INACTIVE_BUTTON_STYLE)}
+ >
+ 分时
+
+
+
+
+
+ );
+};
+
+export default memo(KLineToolbar);
diff --git a/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/components/MinuteChartWithOrderBook.tsx b/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/components/MinuteChartWithOrderBook.tsx
new file mode 100644
index 00000000..10aaef25
--- /dev/null
+++ b/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/components/MinuteChartWithOrderBook.tsx
@@ -0,0 +1,229 @@
+// src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/components/MinuteChartWithOrderBook.tsx
+// 分时图 + 五档盘口组件
+
+import React, { memo, useMemo } from 'react';
+import {
+ Box,
+ Text,
+ VStack,
+ HStack,
+ Center,
+ Spinner,
+ Badge,
+ Grid,
+ GridItem,
+ Icon,
+} from '@chakra-ui/react';
+import { InfoIcon } from '@chakra-ui/icons';
+import ReactECharts from 'echarts-for-react';
+
+// 导入实时行情 Hook 和五档盘口组件
+import { useRealtimeQuote } from '@views/StockOverview/components/FlexScreen/hooks';
+import OrderBookPanel from '@views/StockOverview/components/FlexScreen/components/OrderBookPanel';
+
+import { darkGoldTheme } from '../../../../constants';
+import { getMinuteKLineDarkGoldOption } from '../../../../utils/chartOptions';
+import type { MinuteData } from '../../../../types';
+
+export interface MinuteChartWithOrderBookProps {
+ minuteData: MinuteData | null;
+ minuteLoading: boolean;
+ stockCode?: string;
+ showOrderBook: boolean;
+}
+
+/**
+ * 空状态组件
+ */
+const EmptyState: React.FC<{ title: string; description: string }> = ({ title, description }) => (
+
+
+
+
+ {title}
+ {description}
+
+
+
+);
+
+/**
+ * 分时图 + 五档盘口组件
+ */
+const MinuteChartWithOrderBook: React.FC = ({
+ minuteData,
+ minuteLoading,
+ stockCode,
+ showOrderBook,
+}) => {
+ const hasMinuteData = minuteData && minuteData.data && minuteData.data.length > 0;
+
+ // 实时行情订阅
+ const subscribedCodes = useMemo(() => {
+ if (!stockCode) return [];
+ return [stockCode];
+ }, [stockCode]);
+
+ const { quotes, connected } = useRealtimeQuote(subscribedCodes);
+
+ // 获取当前股票的行情数据
+ const currentQuote = useMemo(() => {
+ if (!stockCode) return null;
+ return quotes[stockCode] || quotes[`${stockCode}.SH`] || quotes[`${stockCode}.SZ`] || null;
+ }, [quotes, stockCode]);
+
+ // 判断是否在交易时间
+ const isInTradingHours = useMemo(() => {
+ const now = new Date();
+ const hours = now.getHours();
+ const minutes = now.getMinutes();
+ const totalMinutes = hours * 60 + minutes;
+ // 9:15-11:30 或 13:00-15:00
+ return (totalMinutes >= 555 && totalMinutes <= 690) || (totalMinutes >= 780 && totalMinutes <= 900);
+ }, []);
+
+ // 缓存图表配置
+ const chartOption = useMemo(() => {
+ if (!minuteData) return {};
+ return getMinuteKLineDarkGoldOption(minuteData);
+ }, [minuteData]);
+
+ // 加载中状态
+ if (minuteLoading) {
+ return (
+
+
+
+
+ 加载分时数据中...
+
+
+
+ );
+ }
+
+ // 无数据状态
+ if (!hasMinuteData) {
+ return ;
+ }
+
+ return (
+
+ {/* 分时图表 */}
+
+
+
+
+
+
+ {/* 五档盘口 */}
+ {showOrderBook && (
+
+
+ {/* 盘口标题 */}
+
+
+ 五档盘口
+
+ {/* 连接状态指示 */}
+
+ {isInTradingHours && (
+
+ {connected.SSE || connected.SZSE ? '实时' : '离线'}
+
+ )}
+
+
+
+ {/* 当前价格信息 */}
+ {currentQuote && (
+
+
+ 当前价
+ 0 ? '#ff4d4d' :
+ currentQuote.changePct < 0 ? '#22c55e' :
+ darkGoldTheme.textPrimary
+ }
+ >
+ {currentQuote.price?.toFixed(2) || '-'}
+
+
+
+ 涨跌幅
+ 0 ? '#ff4d4d' :
+ currentQuote.changePct < 0 ? '#22c55e' :
+ darkGoldTheme.textMuted
+ }
+ >
+ {currentQuote.changePct > 0 ? '+' : ''}{currentQuote.changePct?.toFixed(2) || '0.00'}%
+
+
+
+ )}
+
+ {/* 五档盘口面板 */}
+ {currentQuote && (currentQuote.bidPrices?.length > 0 || currentQuote.askPrices?.length > 0) ? (
+
+ ) : (
+
+
+
+ {isInTradingHours ? '获取盘口数据中...' : '非交易时间'}
+
+ {!isInTradingHours && (
+
+ 交易时间: 9:30-11:30, 13:00-15:00
+
+ )}
+
+
+ )}
+
+
+ )}
+
+ );
+};
+
+export default memo(MinuteChartWithOrderBook);
diff --git a/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/components/index.ts b/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/components/index.ts
new file mode 100644
index 00000000..622ad3c8
--- /dev/null
+++ b/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/components/index.ts
@@ -0,0 +1,11 @@
+// src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/components/index.ts
+// 子组件统一导出
+
+export { default as KLineToolbar } from './KLineToolbar';
+export type { KLineToolbarProps } from './KLineToolbar';
+
+export { default as DailyKLineChart } from './DailyKLineChart';
+export type { DailyKLineChartProps } from './DailyKLineChart';
+
+export { default as MinuteChartWithOrderBook } from './MinuteChartWithOrderBook';
+export type { MinuteChartWithOrderBookProps } from './MinuteChartWithOrderBook';
diff --git a/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/constants.ts b/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/constants.ts
new file mode 100644
index 00000000..8b890e3f
--- /dev/null
+++ b/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/constants.ts
@@ -0,0 +1,41 @@
+// src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/constants.ts
+// K线模块常量定义
+
+import type { IndicatorType, MainIndicatorType, DrawingType } from '../../../utils/chartOptions';
+
+/**
+ * 图表模式类型
+ */
+export type ChartMode = 'daily' | 'minute';
+
+/**
+ * 副图指标选项
+ */
+export const SUB_INDICATOR_OPTIONS: { value: IndicatorType; label: string; description: string }[] = [
+ { value: 'MACD', label: 'MACD', description: '平滑异同移动平均线' },
+ { value: 'KDJ', label: 'KDJ', description: '随机指标' },
+ { value: 'RSI', label: 'RSI', description: '相对强弱指标' },
+ { value: 'WR', label: 'WR', description: '威廉指标(超买超卖)' },
+ { value: 'CCI', label: 'CCI', description: '商品通道指标' },
+ { value: 'BIAS', label: 'BIAS', description: '乖离率' },
+ { value: 'VOL', label: '仅成交量', description: '不显示副图指标' },
+];
+
+/**
+ * 主图指标选项
+ */
+export const MAIN_INDICATOR_OPTIONS: { value: MainIndicatorType; label: string; description: string }[] = [
+ { value: 'MA', label: 'MA均线', description: 'MA5/MA10/MA20' },
+ { value: 'BOLL', label: '布林带', description: '布林通道指标' },
+ { value: 'NONE', label: '无', description: '不显示主图指标' },
+];
+
+/**
+ * 绘图工具选项
+ */
+export const DRAWING_OPTIONS: { value: DrawingType; label: string; description: string }[] = [
+ { value: 'NONE', label: '无', description: '不显示绘图工具' },
+ { value: 'SUPPORT_RESISTANCE', label: '支撑/阻力', description: '自动识别支撑位和阻力位' },
+ { value: 'TREND_LINE', label: '趋势线', description: '基于线性回归的趋势线' },
+ { value: 'ALL', label: '全部显示', description: '显示所有参考线' },
+];
diff --git a/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/index.tsx b/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/index.tsx
index 68ab6687..c9deb315 100644
--- a/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/index.tsx
+++ b/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/index.tsx
@@ -1,54 +1,20 @@
// src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/index.tsx
-// 交易数据面板 - K线模块(日K/分钟切换)
+// 交易数据面板 - 统一导出
-import React from 'react';
+// 默认导出 KLineModule 作为 TradeDataPanel
+export { default } from './KLineModule';
-import KLineModule from './KLineModule';
-import type { Theme, TradeDayData, MinuteData, RiseAnalysis } from '../../../types';
-
-export interface TradeDataPanelProps {
- theme: Theme;
- tradeData: TradeDayData[];
- minuteData: MinuteData | null;
- minuteLoading: boolean;
- analysisMap: Record;
- onLoadMinuteData: () => void;
- onChartClick: (params: { seriesName?: string; data?: [number, number] }) => void;
- selectedPeriod?: number;
- onPeriodChange?: (period: number) => void;
- stockCode?: string;
-}
-
-const TradeDataPanel: React.FC = ({
- theme,
- tradeData,
- minuteData,
- minuteLoading,
- analysisMap,
- onLoadMinuteData,
- onChartClick,
- selectedPeriod,
- onPeriodChange,
- stockCode,
-}) => {
- return (
-
- );
-};
-
-export default TradeDataPanel;
-
-// 导出子组件供外部按需使用
+// 导出 KLineModule 及其类型
export { default as KLineModule } from './KLineModule';
export type { KLineModuleProps } from './KLineModule';
+
+// 导出子组件供外部按需使用
+export { KLineToolbar, DailyKLineChart, MinuteChartWithOrderBook } from './components';
+export type { KLineToolbarProps, DailyKLineChartProps, MinuteChartWithOrderBookProps } from './components';
+
+// 导出常量和样式
+export * from './constants';
+export * from './styles';
+
+// 保持向后兼容的类型别名
+export type { KLineModuleProps as TradeDataPanelProps } from './KLineModule';
diff --git a/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/styles.ts b/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/styles.ts
new file mode 100644
index 00000000..d28fe9d0
--- /dev/null
+++ b/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/styles.ts
@@ -0,0 +1,65 @@
+// src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/styles.ts
+// K线模块样式常量
+
+import { darkGoldTheme } from '../../../constants';
+
+/**
+ * 激活状态按钮样式
+ */
+export 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;
+
+/**
+ * 非激活状态按钮样式
+ */
+export 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;
+
+/**
+ * 菜单项样式
+ */
+export const MENU_LIST_STYLE = {
+ bg: '#1a1a2e',
+ borderColor: darkGoldTheme.border,
+ boxShadow: '0 4px 20px rgba(0,0,0,0.5)',
+} as const;
+
+/**
+ * 获取菜单项样式
+ */
+export const getMenuItemStyle = (isActive: boolean) => ({
+ bg: isActive ? 'rgba(212, 175, 55, 0.2)' : 'transparent',
+ color: isActive ? darkGoldTheme.gold : darkGoldTheme.textPrimary,
+ _hover: { bg: 'rgba(212, 175, 55, 0.1)' },
+});
+
+/**
+ * Select 下拉框样式
+ */
+export const SELECT_STYLE = {
+ bg: 'transparent',
+ borderColor: darkGoldTheme.border,
+ color: darkGoldTheme.textPrimary,
+ _hover: { borderColor: darkGoldTheme.gold },
+ _focus: { borderColor: darkGoldTheme.gold, boxShadow: 'none' },
+ sx: {
+ option: {
+ background: '#1a1a2e',
+ color: darkGoldTheme.textPrimary,
+ },
+ },
+} as const;