refactor(TradeDataPanel): 拆分 KLineModule 为独立子组件
- KLineModule: 611行精简至157行,专注状态管理 - 提取 KLineToolbar: 工具栏组件(模式切换、指标选择) - 提取 DailyKLineChart: 日K图表(useMemo缓存配置) - 提取 MinuteChartWithOrderBook: 分时图+五档盘口 - 提取 constants.ts: 指标选项常量 - 提取 styles.ts: 按钮样式常量 - 所有组件使用 React.memo 优化 - 更新 README.md 文档 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -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';
|
||||
|
||||
<MarketDataView stockCode="600000" />
|
||||
```
|
||||
|
||||
### 单独使用 K 线模块
|
||||
|
||||
```tsx
|
||||
import { KLineModule } from '@views/Company/components/MarketDataView/components/panels/TradeDataPanel';
|
||||
|
||||
<KLineModule
|
||||
tradeData={tradeData}
|
||||
minuteData={minuteData}
|
||||
analysisMap={analysisMap}
|
||||
onLoadMinuteData={loadMinuteData}
|
||||
onChartClick={handleChartClick}
|
||||
selectedPeriod={60}
|
||||
onPeriodChange={setPeriod}
|
||||
stockCode="600000"
|
||||
/>
|
||||
```
|
||||
|
||||
### 单独使用子组件
|
||||
|
||||
```tsx
|
||||
import {
|
||||
KLineToolbar,
|
||||
DailyKLineChart,
|
||||
MinuteChartWithOrderBook,
|
||||
} from '@views/Company/components/MarketDataView/components/panels/TradeDataPanel';
|
||||
|
||||
// 仅工具栏
|
||||
<KLineToolbar mode="daily" onModeChange={setMode} ... />
|
||||
|
||||
// 仅日K图表
|
||||
<DailyKLineChart tradeData={data} analysisMap={map} subIndicator="MACD" ... />
|
||||
|
||||
// 仅分时图+盘口
|
||||
<MinuteChartWithOrderBook minuteData={data} stockCode="600000" showOrderBook />
|
||||
```
|
||||
|
||||
@@ -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 }) => (
|
||||
<Center h="300px">
|
||||
<VStack spacing={4}>
|
||||
<Icon as={InfoIcon} color={darkGoldTheme.textMuted} boxSize={12} />
|
||||
<VStack spacing={2}>
|
||||
<Text color={darkGoldTheme.textMuted} fontSize="lg">{title}</Text>
|
||||
<Text color={darkGoldTheme.textMuted} fontSize="sm" textAlign="center">{description}</Text>
|
||||
</VStack>
|
||||
</VStack>
|
||||
</Center>
|
||||
);
|
||||
// 子组件导入
|
||||
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<KLineModuleProps> = ({
|
||||
theme,
|
||||
tradeData,
|
||||
@@ -119,6 +31,7 @@ const KLineModule: React.FC<KLineModuleProps> = ({
|
||||
onPeriodChange,
|
||||
stockCode,
|
||||
}) => {
|
||||
// ========== 状态管理 ==========
|
||||
const [mode, setMode] = useState<ChartMode>('daily');
|
||||
const [subIndicator, setSubIndicator] = useState<IndicatorType>('MACD');
|
||||
const [mainIndicator, setMainIndicator] = useState<MainIndicatorType>('MA');
|
||||
@@ -126,33 +39,10 @@ const KLineModule: React.FC<KLineModuleProps> = ({
|
||||
const [drawingType, setDrawingType] = useState<DrawingType>('NONE');
|
||||
const [overlayMetrics, setOverlayMetrics] = useState<OverlayMetricData[]>([]);
|
||||
const [showOrderBook, setShowOrderBook] = useState<boolean>(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<KLineModuleProps> = ({
|
||||
};
|
||||
}, [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<KLineModuleProps> = ({
|
||||
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 (
|
||||
<Box
|
||||
bg="transparent"
|
||||
overflow="hidden"
|
||||
>
|
||||
{/* 卡片头部 */}
|
||||
<Box py={4} borderBottom="1px solid" borderColor={darkGoldTheme.border}>
|
||||
<HStack justify="space-between" align="center" flexWrap="wrap" gap={2}>
|
||||
<HStack spacing={3}>
|
||||
<Box
|
||||
p={2}
|
||||
borderRadius="lg"
|
||||
bg={darkGoldTheme.tagBg}
|
||||
>
|
||||
{mode === 'daily' ? (
|
||||
<TrendingUp size={20} color={darkGoldTheme.gold} />
|
||||
) : (
|
||||
<LineChart size={20} color={darkGoldTheme.gold} />
|
||||
)}
|
||||
</Box>
|
||||
<Text
|
||||
fontSize="lg"
|
||||
fontWeight="bold"
|
||||
bgGradient={`linear(to-r, ${darkGoldTheme.gold}, ${darkGoldTheme.orange})`}
|
||||
bgClip="text"
|
||||
>
|
||||
{mode === 'daily' ? '日K线图' : '分时走势'}
|
||||
</Text>
|
||||
{mode === 'minute' && minuteData?.trade_date && (
|
||||
<Badge
|
||||
bg={darkGoldTheme.tagBg}
|
||||
color={darkGoldTheme.gold}
|
||||
fontSize="xs"
|
||||
px={2}
|
||||
py={1}
|
||||
borderRadius="md"
|
||||
>
|
||||
{minuteData.trade_date}
|
||||
</Badge>
|
||||
)}
|
||||
</HStack>
|
||||
<Box bg="transparent" overflow="hidden">
|
||||
{/* 工具栏 */}
|
||||
<KLineToolbar
|
||||
mode={mode}
|
||||
onModeChange={handleModeChange}
|
||||
selectedPeriod={selectedPeriod}
|
||||
onPeriodChange={onPeriodChange}
|
||||
showAnalysis={showAnalysis}
|
||||
onToggleAnalysis={handleToggleAnalysis}
|
||||
mainIndicator={mainIndicator}
|
||||
onMainIndicatorChange={handleMainIndicatorChange}
|
||||
subIndicator={subIndicator}
|
||||
onSubIndicatorChange={handleSubIndicatorChange}
|
||||
drawingType={drawingType}
|
||||
onDrawingTypeChange={handleDrawingTypeChange}
|
||||
overlayMetrics={overlayMetrics}
|
||||
onAddOverlayMetric={handleAddOverlayMetric}
|
||||
onRemoveOverlayMetric={handleRemoveOverlayMetric}
|
||||
stockDateRange={stockDateRange}
|
||||
minuteData={minuteData}
|
||||
minuteLoading={minuteLoading}
|
||||
showOrderBook={showOrderBook}
|
||||
onToggleOrderBook={handleToggleOrderBook}
|
||||
onRefreshMinuteData={onLoadMinuteData}
|
||||
/>
|
||||
|
||||
<HStack spacing={2} flexWrap="wrap">
|
||||
{/* 日K模式下显示时间范围选择器和指标选择 */}
|
||||
{mode === 'daily' && (
|
||||
<>
|
||||
{/* 时间范围选择器 */}
|
||||
{onPeriodChange && (
|
||||
<HStack spacing={1}>
|
||||
<Icon as={Calendar} boxSize={4} color={darkGoldTheme.textMuted} />
|
||||
<Select
|
||||
size="sm"
|
||||
value={selectedPeriod}
|
||||
onChange={(e) => onPeriodChange(Number(e.target.value))}
|
||||
bg="transparent"
|
||||
borderColor={darkGoldTheme.border}
|
||||
color={darkGoldTheme.textPrimary}
|
||||
maxW="85px"
|
||||
_hover={{ borderColor: darkGoldTheme.gold }}
|
||||
_focus={{ borderColor: darkGoldTheme.gold, boxShadow: 'none' }}
|
||||
sx={{
|
||||
option: {
|
||||
background: '#1a1a2e',
|
||||
color: darkGoldTheme.textPrimary,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{PERIOD_OPTIONS.map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</HStack>
|
||||
)}
|
||||
|
||||
{/* 隐藏/显示涨幅分析 */}
|
||||
<Tooltip label={showAnalysis ? '隐藏涨幅分析标记' : '显示涨幅分析标记'} placement="top" hasArrow>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
leftIcon={showAnalysis ? <ViewOffIcon /> : <ViewIcon />}
|
||||
onClick={() => setShowAnalysis(!showAnalysis)}
|
||||
{...(showAnalysis ? INACTIVE_BUTTON_STYLE : ACTIVE_BUTTON_STYLE)}
|
||||
minW="90px"
|
||||
>
|
||||
{showAnalysis ? '隐藏分析' : '显示分析'}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
{/* 主图指标选择 */}
|
||||
<Menu>
|
||||
<Tooltip label="主图指标" placement="top" hasArrow>
|
||||
<MenuButton
|
||||
as={Button}
|
||||
size="sm"
|
||||
variant="outline"
|
||||
rightIcon={<ChevronDownIcon />}
|
||||
{...INACTIVE_BUTTON_STYLE}
|
||||
minW="90px"
|
||||
>
|
||||
{MAIN_INDICATOR_OPTIONS.find(o => o.value === mainIndicator)?.label || 'MA均线'}
|
||||
</MenuButton>
|
||||
</Tooltip>
|
||||
<MenuList
|
||||
bg="#1a1a2e"
|
||||
borderColor={darkGoldTheme.border}
|
||||
boxShadow="0 4px 20px rgba(0,0,0,0.5)"
|
||||
>
|
||||
{MAIN_INDICATOR_OPTIONS.map((option) => (
|
||||
<MenuItem
|
||||
key={option.value}
|
||||
onClick={() => setMainIndicator(option.value)}
|
||||
bg={mainIndicator === option.value ? 'rgba(212, 175, 55, 0.2)' : 'transparent'}
|
||||
color={mainIndicator === option.value ? darkGoldTheme.gold : darkGoldTheme.textPrimary}
|
||||
_hover={{ bg: 'rgba(212, 175, 55, 0.1)' }}
|
||||
>
|
||||
<VStack align="start" spacing={0}>
|
||||
<Text fontSize="sm" fontWeight={mainIndicator === option.value ? 'bold' : 'normal'}>
|
||||
{option.label}
|
||||
</Text>
|
||||
<Text fontSize="xs" color={darkGoldTheme.textMuted}>
|
||||
{option.description}
|
||||
</Text>
|
||||
</VStack>
|
||||
</MenuItem>
|
||||
))}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
|
||||
{/* 副图指标选择 */}
|
||||
<Menu>
|
||||
<Tooltip label="副图指标" placement="top" hasArrow>
|
||||
<MenuButton
|
||||
as={Button}
|
||||
size="sm"
|
||||
variant="outline"
|
||||
rightIcon={<ChevronDownIcon />}
|
||||
leftIcon={<Activity size={14} />}
|
||||
{...INACTIVE_BUTTON_STYLE}
|
||||
minW="100px"
|
||||
>
|
||||
{SUB_INDICATOR_OPTIONS.find(o => o.value === subIndicator)?.label || 'MACD'}
|
||||
</MenuButton>
|
||||
</Tooltip>
|
||||
<MenuList
|
||||
bg="#1a1a2e"
|
||||
borderColor={darkGoldTheme.border}
|
||||
boxShadow="0 4px 20px rgba(0,0,0,0.5)"
|
||||
>
|
||||
{SUB_INDICATOR_OPTIONS.map((option) => (
|
||||
<MenuItem
|
||||
key={option.value}
|
||||
onClick={() => setSubIndicator(option.value)}
|
||||
bg={subIndicator === option.value ? 'rgba(212, 175, 55, 0.2)' : 'transparent'}
|
||||
color={subIndicator === option.value ? darkGoldTheme.gold : darkGoldTheme.textPrimary}
|
||||
_hover={{ bg: 'rgba(212, 175, 55, 0.1)' }}
|
||||
>
|
||||
<VStack align="start" spacing={0}>
|
||||
<Text fontSize="sm" fontWeight={subIndicator === option.value ? 'bold' : 'normal'}>
|
||||
{option.label}
|
||||
</Text>
|
||||
<Text fontSize="xs" color={darkGoldTheme.textMuted}>
|
||||
{option.description}
|
||||
</Text>
|
||||
</VStack>
|
||||
</MenuItem>
|
||||
))}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
|
||||
{/* 绘图工具选择 */}
|
||||
<Menu>
|
||||
<Tooltip label="绘图工具" placement="top" hasArrow>
|
||||
<MenuButton
|
||||
as={Button}
|
||||
size="sm"
|
||||
variant="outline"
|
||||
rightIcon={<ChevronDownIcon />}
|
||||
leftIcon={<Pencil size={14} />}
|
||||
{...(drawingType !== 'NONE' ? ACTIVE_BUTTON_STYLE : INACTIVE_BUTTON_STYLE)}
|
||||
minW="90px"
|
||||
>
|
||||
{DRAWING_OPTIONS.find(o => o.value === drawingType)?.label || '绘图'}
|
||||
</MenuButton>
|
||||
</Tooltip>
|
||||
<MenuList
|
||||
bg="#1a1a2e"
|
||||
borderColor={darkGoldTheme.border}
|
||||
boxShadow="0 4px 20px rgba(0,0,0,0.5)"
|
||||
>
|
||||
{DRAWING_OPTIONS.map((option) => (
|
||||
<MenuItem
|
||||
key={option.value}
|
||||
onClick={() => setDrawingType(option.value)}
|
||||
bg={drawingType === option.value ? 'rgba(212, 175, 55, 0.2)' : 'transparent'}
|
||||
color={drawingType === option.value ? darkGoldTheme.gold : darkGoldTheme.textPrimary}
|
||||
_hover={{ bg: 'rgba(212, 175, 55, 0.1)' }}
|
||||
>
|
||||
<VStack align="start" spacing={0}>
|
||||
<Text fontSize="sm" fontWeight={drawingType === option.value ? 'bold' : 'normal'}>
|
||||
{option.label}
|
||||
</Text>
|
||||
<Text fontSize="xs" color={darkGoldTheme.textMuted}>
|
||||
{option.description}
|
||||
</Text>
|
||||
</VStack>
|
||||
</MenuItem>
|
||||
))}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
|
||||
{/* 商品数据叠加搜索 */}
|
||||
<MetricOverlaySearch
|
||||
overlayMetrics={overlayMetrics}
|
||||
onAddMetric={handleAddOverlayMetric}
|
||||
onRemoveMetric={handleRemoveOverlayMetric}
|
||||
stockDateRange={stockDateRange}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 分时模式下的控制按钮 */}
|
||||
{mode === 'minute' && (
|
||||
<>
|
||||
{/* 显示/隐藏盘口 */}
|
||||
<Tooltip label={showOrderBook ? '隐藏盘口' : '显示盘口'} placement="top" hasArrow>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => setShowOrderBook(!showOrderBook)}
|
||||
{...(showOrderBook ? ACTIVE_BUTTON_STYLE : INACTIVE_BUTTON_STYLE)}
|
||||
minW="80px"
|
||||
>
|
||||
{showOrderBook ? '隐藏盘口' : '显示盘口'}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
{/* 刷新按钮 */}
|
||||
<Button
|
||||
leftIcon={<RepeatIcon />}
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={onLoadMinuteData}
|
||||
isLoading={minuteLoading}
|
||||
loadingText="获取中"
|
||||
{...INACTIVE_BUTTON_STYLE}
|
||||
>
|
||||
刷新
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 模式切换按钮组 */}
|
||||
<ButtonGroup size="sm" isAttached>
|
||||
<Button
|
||||
leftIcon={<BarChart2 size={14} />}
|
||||
onClick={() => handleModeChange('daily')}
|
||||
{...(mode === 'daily' ? ACTIVE_BUTTON_STYLE : INACTIVE_BUTTON_STYLE)}
|
||||
>
|
||||
日K
|
||||
</Button>
|
||||
<Button
|
||||
leftIcon={<LineChart size={14} />}
|
||||
onClick={() => handleModeChange('minute')}
|
||||
{...(mode === 'minute' ? ACTIVE_BUTTON_STYLE : INACTIVE_BUTTON_STYLE)}
|
||||
>
|
||||
分时
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</HStack>
|
||||
</HStack>
|
||||
</Box>
|
||||
|
||||
{/* 卡片内容 */}
|
||||
{/* 图表内容区域 */}
|
||||
<Box pt={4}>
|
||||
{mode === 'daily' ? (
|
||||
// 日K线图(带技术指标)
|
||||
tradeData.length > 0 ? (
|
||||
<Box h="650px">
|
||||
<ReactECharts
|
||||
option={getKLineDarkGoldOption(tradeData, analysisMap, subIndicator, mainIndicator, showAnalysis, drawingType, overlayMetrics)}
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
theme="dark"
|
||||
notMerge={true}
|
||||
onEvents={{ click: onChartClick }}
|
||||
opts={{ renderer: 'canvas' }}
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
<EmptyState title="暂无日K线数据" description="该股票暂无交易数据" />
|
||||
)
|
||||
// 日K线图
|
||||
<DailyKLineChart
|
||||
tradeData={tradeData}
|
||||
analysisMap={analysisMap}
|
||||
subIndicator={subIndicator}
|
||||
mainIndicator={mainIndicator}
|
||||
showAnalysis={showAnalysis}
|
||||
drawingType={drawingType}
|
||||
overlayMetrics={overlayMetrics}
|
||||
onChartClick={onChartClick}
|
||||
/>
|
||||
) : (
|
||||
// 分时走势图 + 五档盘口
|
||||
minuteLoading ? (
|
||||
<Center h="450px">
|
||||
<VStack spacing={4}>
|
||||
<Spinner
|
||||
thickness="4px"
|
||||
speed="0.65s"
|
||||
emptyColor="rgba(212, 175, 55, 0.2)"
|
||||
color={darkGoldTheme.gold}
|
||||
size="lg"
|
||||
/>
|
||||
<Text color={darkGoldTheme.textMuted} fontSize="sm">
|
||||
加载分时数据中...
|
||||
</Text>
|
||||
</VStack>
|
||||
</Center>
|
||||
) : hasMinuteData ? (
|
||||
<Grid templateColumns={showOrderBook ? '1fr 220px' : '1fr'} gap={4} h="450px">
|
||||
{/* 分时图表 */}
|
||||
<GridItem>
|
||||
<Box h="100%">
|
||||
<ReactECharts
|
||||
option={getMinuteKLineDarkGoldOption(minuteData)}
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
theme="dark"
|
||||
notMerge={true}
|
||||
opts={{ renderer: 'canvas' }}
|
||||
/>
|
||||
</Box>
|
||||
</GridItem>
|
||||
|
||||
{/* 五档盘口 */}
|
||||
{showOrderBook && (
|
||||
<GridItem>
|
||||
<Box
|
||||
h="100%"
|
||||
bg="rgba(0, 0, 0, 0.3)"
|
||||
borderRadius="lg"
|
||||
border="1px solid"
|
||||
borderColor={darkGoldTheme.border}
|
||||
p={3}
|
||||
overflowY="auto"
|
||||
>
|
||||
{/* 盘口标题 */}
|
||||
<HStack justify="space-between" mb={3}>
|
||||
<Text fontSize="sm" fontWeight="bold" color={darkGoldTheme.gold}>
|
||||
五档盘口
|
||||
</Text>
|
||||
{/* 连接状态指示 */}
|
||||
<HStack spacing={1}>
|
||||
{isInTradingHours && (
|
||||
<Badge
|
||||
bg={connected.SSE || connected.SZSE ? 'green.500' : 'gray.500'}
|
||||
color="white"
|
||||
fontSize="2xs"
|
||||
px={1}
|
||||
>
|
||||
{connected.SSE || connected.SZSE ? '实时' : '离线'}
|
||||
</Badge>
|
||||
)}
|
||||
</HStack>
|
||||
</HStack>
|
||||
|
||||
{/* 当前价格信息 */}
|
||||
{currentQuote && (
|
||||
<VStack spacing={1} mb={3} align="stretch">
|
||||
<HStack justify="space-between">
|
||||
<Text fontSize="xs" color={darkGoldTheme.textMuted}>当前价</Text>
|
||||
<Text
|
||||
fontSize="lg"
|
||||
fontWeight="bold"
|
||||
color={
|
||||
currentQuote.changePct > 0 ? '#ff4d4d' :
|
||||
currentQuote.changePct < 0 ? '#22c55e' :
|
||||
darkGoldTheme.textPrimary
|
||||
}
|
||||
>
|
||||
{currentQuote.price?.toFixed(2) || '-'}
|
||||
</Text>
|
||||
</HStack>
|
||||
<HStack justify="space-between">
|
||||
<Text fontSize="xs" color={darkGoldTheme.textMuted}>涨跌幅</Text>
|
||||
<Text
|
||||
fontSize="sm"
|
||||
color={
|
||||
currentQuote.changePct > 0 ? '#ff4d4d' :
|
||||
currentQuote.changePct < 0 ? '#22c55e' :
|
||||
darkGoldTheme.textMuted
|
||||
}
|
||||
>
|
||||
{currentQuote.changePct > 0 ? '+' : ''}{currentQuote.changePct?.toFixed(2) || '0.00'}%
|
||||
</Text>
|
||||
</HStack>
|
||||
</VStack>
|
||||
)}
|
||||
|
||||
{/* 五档盘口面板 */}
|
||||
{currentQuote && (currentQuote.bidPrices?.length > 0 || currentQuote.askPrices?.length > 0) ? (
|
||||
<OrderBookPanel
|
||||
bidPrices={currentQuote.bidPrices || []}
|
||||
bidVolumes={currentQuote.bidVolumes || []}
|
||||
askPrices={currentQuote.askPrices || []}
|
||||
askVolumes={currentQuote.askVolumes || []}
|
||||
prevClose={currentQuote.prevClose}
|
||||
upperLimit={'upperLimit' in currentQuote ? currentQuote.upperLimit : undefined}
|
||||
lowerLimit={'lowerLimit' in currentQuote ? currentQuote.lowerLimit : undefined}
|
||||
defaultLevels={5}
|
||||
/>
|
||||
) : (
|
||||
<Center h="200px">
|
||||
<VStack spacing={2}>
|
||||
<Text fontSize="xs" color={darkGoldTheme.textMuted}>
|
||||
{isInTradingHours ? '获取盘口数据中...' : '非交易时间'}
|
||||
</Text>
|
||||
{!isInTradingHours && (
|
||||
<Text fontSize="2xs" color={darkGoldTheme.textMuted}>
|
||||
交易时间: 9:30-11:30, 13:00-15:00
|
||||
</Text>
|
||||
)}
|
||||
</VStack>
|
||||
</Center>
|
||||
)}
|
||||
</Box>
|
||||
</GridItem>
|
||||
)}
|
||||
</Grid>
|
||||
) : (
|
||||
<EmptyState title="暂无分时数据" description="点击刷新按钮获取当日分时数据" />
|
||||
)
|
||||
<MinuteChartWithOrderBook
|
||||
minuteData={minuteData}
|
||||
minuteLoading={minuteLoading}
|
||||
stockCode={stockCode}
|
||||
showOrderBook={showOrderBook}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default KLineModule;
|
||||
export default memo(KLineModule);
|
||||
|
||||
@@ -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<number, RiseAnalysis>;
|
||||
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 }) => (
|
||||
<Center h="300px">
|
||||
<VStack spacing={4}>
|
||||
<Icon as={InfoIcon} color={darkGoldTheme.textMuted} boxSize={12} />
|
||||
<VStack spacing={2}>
|
||||
<Text color={darkGoldTheme.textMuted} fontSize="lg">{title}</Text>
|
||||
<Text color={darkGoldTheme.textMuted} fontSize="sm" textAlign="center">{description}</Text>
|
||||
</VStack>
|
||||
</VStack>
|
||||
</Center>
|
||||
);
|
||||
|
||||
/**
|
||||
* 日K线图表组件
|
||||
*/
|
||||
const DailyKLineChart: React.FC<DailyKLineChartProps> = ({
|
||||
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 <EmptyState title="暂无日K线数据" description="该股票暂无交易数据" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box h="650px">
|
||||
<ReactECharts
|
||||
option={chartOption}
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
theme="dark"
|
||||
notMerge={true}
|
||||
onEvents={onChartClick ? { click: onChartClick } : undefined}
|
||||
opts={{ renderer: 'canvas' }}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(DailyKLineChart);
|
||||
@@ -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<KLineToolbarProps> = ({
|
||||
mode,
|
||||
onModeChange,
|
||||
selectedPeriod,
|
||||
onPeriodChange,
|
||||
showAnalysis,
|
||||
onToggleAnalysis,
|
||||
mainIndicator,
|
||||
onMainIndicatorChange,
|
||||
subIndicator,
|
||||
onSubIndicatorChange,
|
||||
drawingType,
|
||||
onDrawingTypeChange,
|
||||
overlayMetrics,
|
||||
onAddOverlayMetric,
|
||||
onRemoveOverlayMetric,
|
||||
stockDateRange,
|
||||
minuteData,
|
||||
minuteLoading,
|
||||
showOrderBook,
|
||||
onToggleOrderBook,
|
||||
onRefreshMinuteData,
|
||||
}) => {
|
||||
return (
|
||||
<Box py={4} borderBottom="1px solid" borderColor={darkGoldTheme.border}>
|
||||
<HStack justify="space-between" align="center" flexWrap="wrap" gap={2}>
|
||||
{/* 左侧标题区域 */}
|
||||
<HStack spacing={3}>
|
||||
<Box p={2} borderRadius="lg" bg={darkGoldTheme.tagBg}>
|
||||
{mode === 'daily' ? (
|
||||
<TrendingUp size={20} color={darkGoldTheme.gold} />
|
||||
) : (
|
||||
<LineChart size={20} color={darkGoldTheme.gold} />
|
||||
)}
|
||||
</Box>
|
||||
<Text
|
||||
fontSize="lg"
|
||||
fontWeight="bold"
|
||||
bgGradient={`linear(to-r, ${darkGoldTheme.gold}, ${darkGoldTheme.orange})`}
|
||||
bgClip="text"
|
||||
>
|
||||
{mode === 'daily' ? '日K线图' : '分时走势'}
|
||||
</Text>
|
||||
{mode === 'minute' && minuteData?.trade_date && (
|
||||
<Badge
|
||||
bg={darkGoldTheme.tagBg}
|
||||
color={darkGoldTheme.gold}
|
||||
fontSize="xs"
|
||||
px={2}
|
||||
py={1}
|
||||
borderRadius="md"
|
||||
>
|
||||
{minuteData.trade_date}
|
||||
</Badge>
|
||||
)}
|
||||
</HStack>
|
||||
|
||||
{/* 右侧控制按钮区域 */}
|
||||
<HStack spacing={2} flexWrap="wrap">
|
||||
{/* 日K模式下的控制按钮 */}
|
||||
{mode === 'daily' && (
|
||||
<>
|
||||
{/* 时间范围选择器 */}
|
||||
{onPeriodChange && (
|
||||
<HStack spacing={1}>
|
||||
<Icon as={Calendar} boxSize={4} color={darkGoldTheme.textMuted} />
|
||||
<Select
|
||||
size="sm"
|
||||
value={selectedPeriod}
|
||||
onChange={(e) => onPeriodChange(Number(e.target.value))}
|
||||
maxW="85px"
|
||||
{...SELECT_STYLE}
|
||||
>
|
||||
{PERIOD_OPTIONS.map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</HStack>
|
||||
)}
|
||||
|
||||
{/* 隐藏/显示涨幅分析 */}
|
||||
<Tooltip label={showAnalysis ? '隐藏涨幅分析标记' : '显示涨幅分析标记'} placement="top" hasArrow>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
leftIcon={showAnalysis ? <ViewOffIcon /> : <ViewIcon />}
|
||||
onClick={onToggleAnalysis}
|
||||
{...(showAnalysis ? INACTIVE_BUTTON_STYLE : ACTIVE_BUTTON_STYLE)}
|
||||
minW="90px"
|
||||
>
|
||||
{showAnalysis ? '隐藏分析' : '显示分析'}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
{/* 主图指标选择 */}
|
||||
<Menu>
|
||||
<Tooltip label="主图指标" placement="top" hasArrow>
|
||||
<MenuButton
|
||||
as={Button}
|
||||
size="sm"
|
||||
variant="outline"
|
||||
rightIcon={<ChevronDownIcon />}
|
||||
{...INACTIVE_BUTTON_STYLE}
|
||||
minW="90px"
|
||||
>
|
||||
{MAIN_INDICATOR_OPTIONS.find(o => o.value === mainIndicator)?.label || 'MA均线'}
|
||||
</MenuButton>
|
||||
</Tooltip>
|
||||
<MenuList {...MENU_LIST_STYLE}>
|
||||
{MAIN_INDICATOR_OPTIONS.map((option) => (
|
||||
<MenuItem
|
||||
key={option.value}
|
||||
onClick={() => onMainIndicatorChange(option.value)}
|
||||
{...getMenuItemStyle(mainIndicator === option.value)}
|
||||
>
|
||||
<VStack align="start" spacing={0}>
|
||||
<Text fontSize="sm" fontWeight={mainIndicator === option.value ? 'bold' : 'normal'}>
|
||||
{option.label}
|
||||
</Text>
|
||||
<Text fontSize="xs" color={darkGoldTheme.textMuted}>
|
||||
{option.description}
|
||||
</Text>
|
||||
</VStack>
|
||||
</MenuItem>
|
||||
))}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
|
||||
{/* 副图指标选择 */}
|
||||
<Menu>
|
||||
<Tooltip label="副图指标" placement="top" hasArrow>
|
||||
<MenuButton
|
||||
as={Button}
|
||||
size="sm"
|
||||
variant="outline"
|
||||
rightIcon={<ChevronDownIcon />}
|
||||
leftIcon={<Activity size={14} />}
|
||||
{...INACTIVE_BUTTON_STYLE}
|
||||
minW="100px"
|
||||
>
|
||||
{SUB_INDICATOR_OPTIONS.find(o => o.value === subIndicator)?.label || 'MACD'}
|
||||
</MenuButton>
|
||||
</Tooltip>
|
||||
<MenuList {...MENU_LIST_STYLE}>
|
||||
{SUB_INDICATOR_OPTIONS.map((option) => (
|
||||
<MenuItem
|
||||
key={option.value}
|
||||
onClick={() => onSubIndicatorChange(option.value)}
|
||||
{...getMenuItemStyle(subIndicator === option.value)}
|
||||
>
|
||||
<VStack align="start" spacing={0}>
|
||||
<Text fontSize="sm" fontWeight={subIndicator === option.value ? 'bold' : 'normal'}>
|
||||
{option.label}
|
||||
</Text>
|
||||
<Text fontSize="xs" color={darkGoldTheme.textMuted}>
|
||||
{option.description}
|
||||
</Text>
|
||||
</VStack>
|
||||
</MenuItem>
|
||||
))}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
|
||||
{/* 绘图工具选择 */}
|
||||
<Menu>
|
||||
<Tooltip label="绘图工具" placement="top" hasArrow>
|
||||
<MenuButton
|
||||
as={Button}
|
||||
size="sm"
|
||||
variant="outline"
|
||||
rightIcon={<ChevronDownIcon />}
|
||||
leftIcon={<Pencil size={14} />}
|
||||
{...(drawingType !== 'NONE' ? ACTIVE_BUTTON_STYLE : INACTIVE_BUTTON_STYLE)}
|
||||
minW="90px"
|
||||
>
|
||||
{DRAWING_OPTIONS.find(o => o.value === drawingType)?.label || '绘图'}
|
||||
</MenuButton>
|
||||
</Tooltip>
|
||||
<MenuList {...MENU_LIST_STYLE}>
|
||||
{DRAWING_OPTIONS.map((option) => (
|
||||
<MenuItem
|
||||
key={option.value}
|
||||
onClick={() => onDrawingTypeChange(option.value)}
|
||||
{...getMenuItemStyle(drawingType === option.value)}
|
||||
>
|
||||
<VStack align="start" spacing={0}>
|
||||
<Text fontSize="sm" fontWeight={drawingType === option.value ? 'bold' : 'normal'}>
|
||||
{option.label}
|
||||
</Text>
|
||||
<Text fontSize="xs" color={darkGoldTheme.textMuted}>
|
||||
{option.description}
|
||||
</Text>
|
||||
</VStack>
|
||||
</MenuItem>
|
||||
))}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
|
||||
{/* 商品数据叠加搜索 */}
|
||||
<MetricOverlaySearch
|
||||
overlayMetrics={overlayMetrics}
|
||||
onAddMetric={onAddOverlayMetric}
|
||||
onRemoveMetric={onRemoveOverlayMetric}
|
||||
stockDateRange={stockDateRange}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 分时模式下的控制按钮 */}
|
||||
{mode === 'minute' && (
|
||||
<>
|
||||
{/* 显示/隐藏盘口 */}
|
||||
<Tooltip label={showOrderBook ? '隐藏盘口' : '显示盘口'} placement="top" hasArrow>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={onToggleOrderBook}
|
||||
{...(showOrderBook ? ACTIVE_BUTTON_STYLE : INACTIVE_BUTTON_STYLE)}
|
||||
minW="80px"
|
||||
>
|
||||
{showOrderBook ? '隐藏盘口' : '显示盘口'}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
{/* 刷新按钮 */}
|
||||
<Button
|
||||
leftIcon={<RepeatIcon />}
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={onRefreshMinuteData}
|
||||
isLoading={minuteLoading}
|
||||
loadingText="获取中"
|
||||
{...INACTIVE_BUTTON_STYLE}
|
||||
>
|
||||
刷新
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 模式切换按钮组 */}
|
||||
<ButtonGroup size="sm" isAttached>
|
||||
<Button
|
||||
leftIcon={<BarChart2 size={14} />}
|
||||
onClick={() => onModeChange('daily')}
|
||||
{...(mode === 'daily' ? ACTIVE_BUTTON_STYLE : INACTIVE_BUTTON_STYLE)}
|
||||
>
|
||||
日K
|
||||
</Button>
|
||||
<Button
|
||||
leftIcon={<LineChart size={14} />}
|
||||
onClick={() => onModeChange('minute')}
|
||||
{...(mode === 'minute' ? ACTIVE_BUTTON_STYLE : INACTIVE_BUTTON_STYLE)}
|
||||
>
|
||||
分时
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</HStack>
|
||||
</HStack>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(KLineToolbar);
|
||||
@@ -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 }) => (
|
||||
<Center h="300px">
|
||||
<VStack spacing={4}>
|
||||
<Icon as={InfoIcon} color={darkGoldTheme.textMuted} boxSize={12} />
|
||||
<VStack spacing={2}>
|
||||
<Text color={darkGoldTheme.textMuted} fontSize="lg">{title}</Text>
|
||||
<Text color={darkGoldTheme.textMuted} fontSize="sm" textAlign="center">{description}</Text>
|
||||
</VStack>
|
||||
</VStack>
|
||||
</Center>
|
||||
);
|
||||
|
||||
/**
|
||||
* 分时图 + 五档盘口组件
|
||||
*/
|
||||
const MinuteChartWithOrderBook: React.FC<MinuteChartWithOrderBookProps> = ({
|
||||
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 (
|
||||
<Center h="450px">
|
||||
<VStack spacing={4}>
|
||||
<Spinner
|
||||
thickness="4px"
|
||||
speed="0.65s"
|
||||
emptyColor="rgba(212, 175, 55, 0.2)"
|
||||
color={darkGoldTheme.gold}
|
||||
size="lg"
|
||||
/>
|
||||
<Text color={darkGoldTheme.textMuted} fontSize="sm">
|
||||
加载分时数据中...
|
||||
</Text>
|
||||
</VStack>
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
// 无数据状态
|
||||
if (!hasMinuteData) {
|
||||
return <EmptyState title="暂无分时数据" description="点击刷新按钮获取当日分时数据" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid templateColumns={showOrderBook ? '1fr 220px' : '1fr'} gap={4} h="450px">
|
||||
{/* 分时图表 */}
|
||||
<GridItem>
|
||||
<Box h="100%">
|
||||
<ReactECharts
|
||||
option={chartOption}
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
theme="dark"
|
||||
notMerge={true}
|
||||
opts={{ renderer: 'canvas' }}
|
||||
/>
|
||||
</Box>
|
||||
</GridItem>
|
||||
|
||||
{/* 五档盘口 */}
|
||||
{showOrderBook && (
|
||||
<GridItem>
|
||||
<Box
|
||||
h="100%"
|
||||
bg="rgba(0, 0, 0, 0.3)"
|
||||
borderRadius="lg"
|
||||
border="1px solid"
|
||||
borderColor={darkGoldTheme.border}
|
||||
p={3}
|
||||
overflowY="auto"
|
||||
>
|
||||
{/* 盘口标题 */}
|
||||
<HStack justify="space-between" mb={3}>
|
||||
<Text fontSize="sm" fontWeight="bold" color={darkGoldTheme.gold}>
|
||||
五档盘口
|
||||
</Text>
|
||||
{/* 连接状态指示 */}
|
||||
<HStack spacing={1}>
|
||||
{isInTradingHours && (
|
||||
<Badge
|
||||
bg={connected.SSE || connected.SZSE ? 'green.500' : 'gray.500'}
|
||||
color="white"
|
||||
fontSize="2xs"
|
||||
px={1}
|
||||
>
|
||||
{connected.SSE || connected.SZSE ? '实时' : '离线'}
|
||||
</Badge>
|
||||
)}
|
||||
</HStack>
|
||||
</HStack>
|
||||
|
||||
{/* 当前价格信息 */}
|
||||
{currentQuote && (
|
||||
<VStack spacing={1} mb={3} align="stretch">
|
||||
<HStack justify="space-between">
|
||||
<Text fontSize="xs" color={darkGoldTheme.textMuted}>当前价</Text>
|
||||
<Text
|
||||
fontSize="lg"
|
||||
fontWeight="bold"
|
||||
color={
|
||||
currentQuote.changePct > 0 ? '#ff4d4d' :
|
||||
currentQuote.changePct < 0 ? '#22c55e' :
|
||||
darkGoldTheme.textPrimary
|
||||
}
|
||||
>
|
||||
{currentQuote.price?.toFixed(2) || '-'}
|
||||
</Text>
|
||||
</HStack>
|
||||
<HStack justify="space-between">
|
||||
<Text fontSize="xs" color={darkGoldTheme.textMuted}>涨跌幅</Text>
|
||||
<Text
|
||||
fontSize="sm"
|
||||
color={
|
||||
currentQuote.changePct > 0 ? '#ff4d4d' :
|
||||
currentQuote.changePct < 0 ? '#22c55e' :
|
||||
darkGoldTheme.textMuted
|
||||
}
|
||||
>
|
||||
{currentQuote.changePct > 0 ? '+' : ''}{currentQuote.changePct?.toFixed(2) || '0.00'}%
|
||||
</Text>
|
||||
</HStack>
|
||||
</VStack>
|
||||
)}
|
||||
|
||||
{/* 五档盘口面板 */}
|
||||
{currentQuote && (currentQuote.bidPrices?.length > 0 || currentQuote.askPrices?.length > 0) ? (
|
||||
<OrderBookPanel
|
||||
bidPrices={currentQuote.bidPrices || []}
|
||||
bidVolumes={currentQuote.bidVolumes || []}
|
||||
askPrices={currentQuote.askPrices || []}
|
||||
askVolumes={currentQuote.askVolumes || []}
|
||||
prevClose={currentQuote.prevClose}
|
||||
upperLimit={'upperLimit' in currentQuote ? currentQuote.upperLimit : undefined}
|
||||
lowerLimit={'lowerLimit' in currentQuote ? currentQuote.lowerLimit : undefined}
|
||||
defaultLevels={5}
|
||||
/>
|
||||
) : (
|
||||
<Center h="200px">
|
||||
<VStack spacing={2}>
|
||||
<Text fontSize="xs" color={darkGoldTheme.textMuted}>
|
||||
{isInTradingHours ? '获取盘口数据中...' : '非交易时间'}
|
||||
</Text>
|
||||
{!isInTradingHours && (
|
||||
<Text fontSize="2xs" color={darkGoldTheme.textMuted}>
|
||||
交易时间: 9:30-11:30, 13:00-15:00
|
||||
</Text>
|
||||
)}
|
||||
</VStack>
|
||||
</Center>
|
||||
)}
|
||||
</Box>
|
||||
</GridItem>
|
||||
)}
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(MinuteChartWithOrderBook);
|
||||
@@ -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';
|
||||
@@ -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: '显示所有参考线' },
|
||||
];
|
||||
@@ -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<number, RiseAnalysis>;
|
||||
onLoadMinuteData: () => void;
|
||||
onChartClick: (params: { seriesName?: string; data?: [number, number] }) => void;
|
||||
selectedPeriod?: number;
|
||||
onPeriodChange?: (period: number) => void;
|
||||
stockCode?: string;
|
||||
}
|
||||
|
||||
const TradeDataPanel: React.FC<TradeDataPanelProps> = ({
|
||||
theme,
|
||||
tradeData,
|
||||
minuteData,
|
||||
minuteLoading,
|
||||
analysisMap,
|
||||
onLoadMinuteData,
|
||||
onChartClick,
|
||||
selectedPeriod,
|
||||
onPeriodChange,
|
||||
stockCode,
|
||||
}) => {
|
||||
return (
|
||||
<KLineModule
|
||||
theme={theme}
|
||||
tradeData={tradeData}
|
||||
minuteData={minuteData}
|
||||
minuteLoading={minuteLoading}
|
||||
analysisMap={analysisMap}
|
||||
onLoadMinuteData={onLoadMinuteData}
|
||||
onChartClick={onChartClick}
|
||||
selectedPeriod={selectedPeriod}
|
||||
onPeriodChange={onPeriodChange}
|
||||
stockCode={stockCode}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
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';
|
||||
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user