From 7f392619e7dbfc989bb96e32b76b511789194f25 Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Tue, 16 Dec 2025 14:34:45 +0800 Subject: [PATCH] =?UTF-8?q?refactor(TradeDataPanel):=20=E5=8E=9F=E5=AD=90?= =?UTF-8?q?=E8=AE=BE=E8=AE=A1=E6=A8=A1=E5=BC=8F=E6=8B=86=E5=88=86=E9=87=8D?= =?UTF-8?q?=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 TradeDataPanel.tsx (382行) 拆分为 8 个 TypeScript 文件 - 创建 3 个原子组件: MinuteStats、TradeAnalysis、EmptyState - 创建 3 个业务组件: KLineChart、MinuteKLineSection、TradeTable - 主入口组件精简至 ~50 行,降低 87% - 更新 panels/index.ts 导出子组件 - 更新 STRUCTURE.md 文档 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/views/Company/STRUCTURE.md | 175 +++++++- .../components/panels/TradeDataPanel.tsx | 381 ------------------ .../panels/TradeDataPanel/KLineChart.tsx | 43 ++ .../TradeDataPanel/MinuteKLineSection.tsx | 107 +++++ .../panels/TradeDataPanel/TradeTable.tsx | 87 ++++ .../TradeDataPanel/atoms/EmptyState.tsx | 40 ++ .../TradeDataPanel/atoms/MinuteStats.tsx | 97 +++++ .../TradeDataPanel/atoms/TradeAnalysis.tsx | 76 ++++ .../panels/TradeDataPanel/atoms/index.ts | 10 + .../panels/TradeDataPanel/index.tsx | 58 +++ .../MarketDataView/components/panels/index.ts | 12 + 11 files changed, 700 insertions(+), 386 deletions(-) delete mode 100644 src/views/Company/components/MarketDataView/components/panels/TradeDataPanel.tsx create mode 100644 src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/KLineChart.tsx create mode 100644 src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/MinuteKLineSection.tsx create mode 100644 src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/TradeTable.tsx create mode 100644 src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/atoms/EmptyState.tsx create mode 100644 src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/atoms/MinuteStats.tsx create mode 100644 src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/atoms/TradeAnalysis.tsx create mode 100644 src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/atoms/index.ts create mode 100644 src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/index.tsx diff --git a/src/views/Company/STRUCTURE.md b/src/views/Company/STRUCTURE.md index ec7d9dba..1e06cbdf 100644 --- a/src/views/Company/STRUCTURE.md +++ b/src/views/Company/STRUCTURE.md @@ -1,6 +1,6 @@ # Company 目录结构说明 -> 最后更新:2025-12-12 +> 最后更新:2025-12-16 ## 目录结构 @@ -69,7 +69,7 @@ src/views/Company/ │ ├── MarketDataView/ # Tab: 股票行情(TypeScript) │ │ ├── index.tsx # 主组件入口(~285 行,Tab 容器) │ │ ├── types.ts # 类型定义 -│ │ ├── constants.ts # 主题配置、常量 +│ │ ├── constants.ts # 主题配置、常量(含黑金主题 darkGoldTheme) │ │ ├── services/ │ │ │ └── marketService.ts # API 服务层 │ │ ├── hooks/ @@ -81,11 +81,31 @@ src/views/Company/ │ │ ├── index.ts # 组件导出 │ │ ├── ThemedCard.tsx # 主题化卡片 │ │ ├── MarkdownRenderer.tsx # Markdown 渲染 -│ │ ├── StockSummaryCard.tsx # 股票概览卡片 │ │ ├── AnalysisModal.tsx # 涨幅分析模态框 +│ │ ├── StockSummaryCard/ # 股票概览卡片(黑金主题 4 列布局) +│ │ │ ├── index.tsx # 主组件(4 列 SimpleGrid 布局) +│ │ │ ├── StockHeaderCard.tsx # 股票信息卡片(名称、价格、涨跌幅) +│ │ │ ├── MetricCard.tsx # 指标卡片模板 +│ │ │ ├── utils.ts # 状态计算工具函数 +│ │ │ └── atoms/ # 原子组件 +│ │ │ ├── index.ts # 原子组件导出 +│ │ │ ├── DarkGoldCard.tsx # 黑金主题卡片容器 +│ │ │ ├── CardTitle.tsx # 卡片标题(图标+标题+副标题) +│ │ │ ├── MetricValue.tsx # 核心数值展示 +│ │ │ ├── PriceDisplay.tsx # 价格显示(价格+涨跌箭头) +│ │ │ └── StatusTag.tsx # 状态标签(活跃/健康等) │ │ └── panels/ # Tab 面板组件 │ │ ├── index.ts # 面板组件统一导出 -│ │ ├── TradeDataPanel.tsx # 交易数据(K线图、分钟图、表格) +│ │ ├── TradeDataPanel/ # 交易数据面板(原子设计模式) +│ │ │ ├── index.tsx # 主入口组件(~50 行) +│ │ │ ├── KLineChart.tsx # 日K线图组件(~40 行) +│ │ │ ├── MinuteKLineSection.tsx # 分钟K线区域(~95 行) +│ │ │ ├── TradeTable.tsx # 交易明细表格(~75 行) +│ │ │ └── atoms/ # 原子组件 +│ │ │ ├── index.ts # 统一导出 +│ │ │ ├── MinuteStats.tsx # 分钟数据统计(~80 行) +│ │ │ ├── TradeAnalysis.tsx # 成交分析(~65 行) +│ │ │ └── EmptyState.tsx # 空状态组件(~35 行) │ │ ├── FundingPanel.tsx # 融资融券面板 │ │ ├── BigDealPanel.tsx # 大宗交易面板 │ │ ├── UnusualPanel.tsx # 龙虎榜面板 @@ -834,4 +854,149 @@ MarketDataView/components/panels/ **设计原则**: - **职责分离**:主组件只负责 Tab 容器和状态管理 - **组件复用**:面板组件可独立测试和维护 -- **类型安全**:每个面板组件有独立的 Props 类型定义 \ No newline at end of file +- **类型安全**:每个面板组件有独立的 Props 类型定义 + +### 2025-12-16 StockSummaryCard 黑金主题重构 + +**改动概述**: +- `StockSummaryCard.tsx` 从单文件重构为**原子设计模式**的目录结构 +- 布局从 **1+3**(头部+三卡片)改为 **4 列横向排列** +- 新增**黑金主题**(`darkGoldTheme`) +- 提取 **5 个原子组件** + **2 个业务组件** + +**拆分后文件结构**: +``` +StockSummaryCard/ +├── index.tsx # 主组件(4 列 SimpleGrid 布局) +├── StockHeaderCard.tsx # 股票信息卡片(名称、价格、涨跌幅、走势) +├── MetricCard.tsx # 指标卡片模板组件 +├── utils.ts # 状态计算工具函数 +└── atoms/ # 原子组件 + ├── index.ts # 统一导出 + ├── DarkGoldCard.tsx # 黑金主题卡片容器(渐变背景、金色边框) + ├── CardTitle.tsx # 卡片标题(图标+标题+副标题) + ├── MetricValue.tsx # 核心数值展示(标签+数值+后缀) + ├── PriceDisplay.tsx # 价格显示(价格+涨跌箭头+百分比) + └── StatusTag.tsx # 状态标签(活跃/健康/警惕等) +``` + +**4 列布局设计**: +``` +┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ +│ 股票信息 │ │ 交易热度 │ │ 估值VS安全 │ │ 情绪与风险 │ +│ 平安银行 │ │ (流动性) │ │ (便宜否) │ │ (资金面) │ +│ (000001) │ │ │ │ │ │ │ +│ 13.50 ↗+1.89%│ │ 成交额 46.79亿│ │ PE 4.96 │ │ 融资 58.23亿 │ +│ 走势:小幅上涨 │ │ 成交量|换手率 │ │ 质押率(健康) │ │ 融券 1.26亿 │ +└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ +``` + +**黑金主题配置**(`constants.ts`): +```typescript +export const darkGoldTheme = { + bgCard: 'linear-gradient(135deg, #1a1a2e 0%, #0f0f1a 100%)', + border: 'rgba(212, 175, 55, 0.3)', + gold: '#D4AF37', + orange: '#FF9500', + green: '#00C851', + red: '#FF4444', + textPrimary: '#FFFFFF', + textMuted: 'rgba(255, 255, 255, 0.6)', +}; +``` + +**状态计算工具**(`utils.ts`): +| 函数 | 功能 | +|------|------| +| `getTrendDescription` | 根据涨跌幅返回走势描述(强势上涨/小幅下跌等) | +| `getTurnoverStatus` | 换手率状态(≥3% 活跃, ≥1% 正常, <1% 冷清) | +| `getPEStatus` | 市盈率估值评级(极低估值/合理/偏高/泡沫风险) | +| `getPledgeStatus` | 质押率健康状态(<10% 健康, <30% 正常, <50% 偏高, ≥50% 警惕) | +| `getPriceColor` | 根据涨跌返回颜色(红涨绿跌) | + +**原子组件说明**: +| 组件 | 行数 | 用途 | 可复用场景 | +|------|------|------|-----------| +| `DarkGoldCard` | ~40 | 黑金主题卡片容器 | 任何需要黑金风格的卡片 | +| `CardTitle` | ~30 | 卡片标题行 | 带图标的标题展示 | +| `MetricValue` | ~45 | 核心数值展示 | 各种指标数值展示 | +| `PriceDisplay` | ~55 | 价格+涨跌幅 | 股票价格展示 | +| `StatusTag` | ~20 | 状态标签 | 各种状态文字标签 | + +**响应式断点**: +- `lg` (≥992px): 4 列 +- `md` (≥768px): 2 列 +- `base` (<768px): 1 列 + +**类型定义更新**(`types.ts`): +- `StockSummaryCardProps.theme` 改为可选参数,组件内置使用 `darkGoldTheme` + +**优化效果**: +| 指标 | 优化前 | 优化后 | 改善 | +|------|--------|--------|------| +| 主文件行数 | ~350 | ~115 | -67% | +| 文件数量 | 1 | 8 | 原子设计模式 | +| 可复用组件 | 0 | 5 原子 + 2 业务 | 提升 | +| 主题支持 | 依赖传入 | 内置黑金主题 | 独立 | + +**设计原则**: +- **原子设计模式**:atoms(基础元素)→ 业务组件(MetricCard、StockHeaderCard)→ 页面组件(index.tsx) +- **主题独立**:StockSummaryCard 使用内置黑金主题,不依赖外部传入 +- **职责分离**:状态计算逻辑提取到 `utils.ts`,UI 与逻辑解耦 +- **组件复用**:原子组件可在其他黑金主题场景复用 + +### 2025-12-16 TradeDataPanel 原子设计模式拆分 + +**改动概述**: +- `TradeDataPanel.tsx` 从 **382 行** 拆分为 **8 个 TypeScript 文件** +- 采用**原子设计模式**组织代码 +- 提取 **3 个原子组件** + **3 个业务组件** + +**拆分后文件结构**: +``` +TradeDataPanel/ +├── index.tsx # 主入口组件(~50 行,组合 3 个子组件) +├── KLineChart.tsx # 日K线图组件(~40 行) +├── MinuteKLineSection.tsx # 分钟K线区域(~95 行,含加载/空状态处理) +├── TradeTable.tsx # 交易明细表格(~75 行) +└── atoms/ # 原子组件 + ├── index.ts # 统一导出 + ├── MinuteStats.tsx # 分钟数据统计(~80 行,4 个 Stat 卡片) + ├── TradeAnalysis.tsx # 成交分析(~65 行,活跃时段/平均价格等) + └── EmptyState.tsx # 空状态组件(~35 行,可复用) +``` + +**组件依赖关系**: +``` +index.tsx +├── KLineChart # 日K线图(ECharts) +├── MinuteKLineSection # 分钟K线区域 +│ ├── MinuteStats (atom) # 开盘/当前/最高/最低价统计 +│ ├── TradeAnalysis (atom) # 成交数据分析 +│ └── EmptyState (atom) # 空状态提示 +└── TradeTable # 交易明细表格(最近 10 天) +``` + +**组件职责**: +| 组件 | 行数 | 功能 | +|------|------|------| +| `index.tsx` | ~50 | 主入口,组合 3 个子组件 | +| `KLineChart` | ~40 | 日K线图渲染,支持图表点击事件 | +| `MinuteKLineSection` | ~95 | 分钟K线区域,含加载状态、空状态、统计数据 | +| `TradeTable` | ~75 | 最近 10 天交易明细表格 | +| `MinuteStats` | ~80 | 分钟数据四宫格统计(开盘/当前/最高/最低价) | +| `TradeAnalysis` | ~65 | 成交数据分析(活跃时段、平均价格、数据点数) | +| `EmptyState` | ~35 | 通用空状态组件(可配置标题和描述) | + +**优化效果**: +| 指标 | 优化前 | 优化后 | 改善 | +|------|--------|--------|------| +| 主文件行数 | 382 | ~50 | -87% | +| 文件数量 | 1 | 8 | 原子设计模式 | +| 可复用组件 | 0 | 3 原子 + 3 业务 | 提升 | + +**设计原则**: +- **原子设计模式**:atoms(MinuteStats、TradeAnalysis、EmptyState)→ 业务组件(KLineChart、MinuteKLineSection、TradeTable)→ 主组件 +- **职责分离**:图表、统计、表格各自独立 +- **组件复用**:EmptyState 可在其他场景复用 +- **类型安全**:完整的 Props 类型定义和导出 \ No newline at end of file diff --git a/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel.tsx b/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel.tsx deleted file mode 100644 index 59c5ed38..00000000 --- a/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel.tsx +++ /dev/null @@ -1,381 +0,0 @@ -// src/views/Company/components/MarketDataView/components/panels/TradeDataPanel.tsx -// 交易数据面板 - K线图、分钟图、交易明细表格 - -import React from 'react'; -import { - Box, - Text, - Table, - Thead, - Tbody, - Tr, - Th, - Td, - TableContainer, - Stat, - StatLabel, - StatNumber, - StatHelpText, - StatArrow, - SimpleGrid, - CardBody, - CardHeader, - Spinner, - Center, - Badge, - VStack, - HStack, - Button, - Grid, - Icon, - Heading, -} from '@chakra-ui/react'; -import { - ChevronDownIcon, - ChevronUpIcon, - InfoIcon, - RepeatIcon, - TimeIcon, - ArrowUpIcon, - ArrowDownIcon, -} from '@chakra-ui/icons'; -import ReactECharts from 'echarts-for-react'; - -import ThemedCard from '../ThemedCard'; -import { formatNumber, formatPercent } from '../../utils/formatUtils'; -import { getKLineOption, getMinuteKLineOption } from '../../utils/chartOptions'; -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; -} - -const TradeDataPanel: React.FC = ({ - theme, - tradeData, - minuteData, - minuteLoading, - analysisMap, - onLoadMinuteData, - onChartClick, -}) => { - return ( - - {/* K线图 */} - - - {tradeData.length > 0 && ( - - - - )} - - - - {/* 分钟K线数据 */} - - - - - - - 当日分钟频数据 - - {minuteData && minuteData.trade_date && ( - - {minuteData.trade_date} - - )} - - - - - - {minuteLoading ? ( -
- - - - 加载分钟频数据中... - - -
- ) : minuteData && minuteData.data && minuteData.data.length > 0 ? ( - - - - - - {/* 分钟数据统计 */} - - - - - - 开盘价 - - - - {minuteData.data[0]?.open?.toFixed(2) || '-'} - - - - - - - 当前价 - - - = - (minuteData.data[0]?.open || 0) - ? theme.success - : theme.danger - } - fontSize="lg" - > - {minuteData.data[minuteData.data.length - 1]?.close?.toFixed(2) || '-'} - - - = - (minuteData.data[0]?.open || 0) - ? 'increase' - : 'decrease' - } - /> - {(() => { - const lastClose = minuteData.data[minuteData.data.length - 1]?.close; - const firstOpen = minuteData.data[0]?.open; - if (lastClose && firstOpen) { - return Math.abs(((lastClose - firstOpen) / firstOpen) * 100).toFixed(2); - } - return '0.00'; - })()} - % - - - - - - - 最高价 - - - - {Math.max(...minuteData.data.map((item) => item.high).filter(Boolean)).toFixed( - 2 - )} - - - - - - - 最低价 - - - - {Math.min(...minuteData.data.map((item) => item.low).filter(Boolean)).toFixed(2)} - - - - - {/* 成交数据分析 */} - - - - 成交数据分析 - - - - 总成交量:{' '} - {formatNumber( - minuteData.data.reduce((sum, item) => sum + item.volume, 0), - 0 - )} - - - 总成交额:{' '} - {formatNumber(minuteData.data.reduce((sum, item) => sum + item.amount, 0))} - - - - - - - - 活跃时段 - - - {(() => { - const maxVolume = Math.max(...minuteData.data.map((item) => item.volume)); - const activeTime = minuteData.data.find( - (item) => item.volume === maxVolume - ); - return activeTime - ? `${activeTime.time} (${formatNumber(maxVolume, 0)})` - : '-'; - })()} - - - - - 平均价格 - - - {( - minuteData.data.reduce((sum, item) => sum + item.close, 0) / - minuteData.data.length - ).toFixed(2)} - - - - - 数据点数 - - - {minuteData.data.length} 个分钟 - - - - - - ) : ( -
- - - - - 暂无分钟频数据 - - - 点击"获取分钟数据"按钮加载最新的交易日分钟频数据 - - - -
- )} -
-
- - {/* 交易明细表格 */} - - - - 交易明细 - - - - - - - - - - - - - - - - - - - {tradeData - .slice(-10) - .reverse() - .map((item, idx) => ( - - - - - - - - - - - ))} - -
日期 - 开盘 - - 最高 - - 最低 - - 收盘 - - 涨跌幅 - - 成交量 - - 成交额 -
{item.date} - {item.open} - - {item.high} - - {item.low} - - {item.close} - = 0 ? theme.success : theme.danger} - fontWeight="bold" - > - {item.change_percent >= 0 ? '+' : ''} - {formatPercent(item.change_percent)} - - {formatNumber(item.volume, 0)} - - {formatNumber(item.amount)} -
-
-
-
-
- ); -}; - -export default TradeDataPanel; diff --git a/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/KLineChart.tsx b/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/KLineChart.tsx new file mode 100644 index 00000000..0264d70b --- /dev/null +++ b/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/KLineChart.tsx @@ -0,0 +1,43 @@ +// src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/KLineChart.tsx +// 日K线图组件 + +import React from 'react'; +import { Box, CardBody } from '@chakra-ui/react'; +import ReactECharts from 'echarts-for-react'; + +import ThemedCard from '../../ThemedCard'; +import { getKLineOption } from '../../../utils/chartOptions'; +import type { Theme, TradeDayData, RiseAnalysis } from '../../../types'; + +export interface KLineChartProps { + theme: Theme; + tradeData: TradeDayData[]; + analysisMap: Record; + onChartClick: (params: { seriesName?: string; data?: [number, number] }) => void; +} + +const KLineChart: React.FC = ({ + theme, + tradeData, + analysisMap, + onChartClick, +}) => { + return ( + + + {tradeData.length > 0 && ( + + + + )} + + + ); +}; + +export default KLineChart; diff --git a/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/MinuteKLineSection.tsx b/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/MinuteKLineSection.tsx new file mode 100644 index 00000000..06cb6ae3 --- /dev/null +++ b/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/MinuteKLineSection.tsx @@ -0,0 +1,107 @@ +// src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/MinuteKLineSection.tsx +// 分钟K线数据区域组件 + +import React from 'react'; +import { + Box, + Text, + VStack, + HStack, + Button, + Badge, + Center, + Spinner, + CardBody, + CardHeader, + Icon, + Heading, +} from '@chakra-ui/react'; +import { TimeIcon, RepeatIcon } from '@chakra-ui/icons'; +import ReactECharts from 'echarts-for-react'; + +import ThemedCard from '../../ThemedCard'; +import { getMinuteKLineOption } from '../../../utils/chartOptions'; +import { MinuteStats, TradeAnalysis, EmptyState } from './atoms'; +import type { Theme, MinuteData } from '../../../types'; + +export interface MinuteKLineSectionProps { + theme: Theme; + minuteData: MinuteData | null; + loading: boolean; + onLoadMinuteData: () => void; +} + +const MinuteKLineSection: React.FC = ({ + theme, + minuteData, + loading, + onLoadMinuteData, +}) => { + const hasData = minuteData && minuteData.data && minuteData.data.length > 0; + + return ( + + + + + + + 当日分钟频数据 + + {minuteData?.trade_date && ( + + {minuteData.trade_date} + + )} + + + + + + + {loading ? ( +
+ + + + 加载分钟频数据中... + + +
+ ) : hasData ? ( + + + + + + + + ) : ( + + )} +
+
+ ); +}; + +export default MinuteKLineSection; diff --git a/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/TradeTable.tsx b/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/TradeTable.tsx new file mode 100644 index 00000000..c3ccef81 --- /dev/null +++ b/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/TradeTable.tsx @@ -0,0 +1,87 @@ +// src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/TradeTable.tsx +// 交易明细表格组件 + +import React from 'react'; +import { + Table, + Thead, + Tbody, + Tr, + Th, + Td, + TableContainer, + CardBody, + CardHeader, + Heading, +} from '@chakra-ui/react'; + +import ThemedCard from '../../ThemedCard'; +import { formatNumber, formatPercent } from '../../../utils/formatUtils'; +import type { Theme, TradeDayData } from '../../../types'; + +export interface TradeTableProps { + theme: Theme; + tradeData: TradeDayData[]; +} + +const TradeTable: React.FC = ({ theme, tradeData }) => { + return ( + + + + 交易明细 + + + + + + + + + + + + + + + + + + + {tradeData + .slice(-10) + .reverse() + .map((item, idx) => ( + + + + + + + + + + + ))} + +
日期开盘最高最低收盘涨跌幅成交量成交额
{item.date}{item.open}{item.high}{item.low} + {item.close} + = 0 ? theme.success : theme.danger} + fontWeight="bold" + > + {item.change_percent >= 0 ? '+' : ''} + {formatPercent(item.change_percent)} + + {formatNumber(item.volume, 0)} + + {formatNumber(item.amount)} +
+
+
+
+ ); +}; + +export default TradeTable; diff --git a/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/atoms/EmptyState.tsx b/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/atoms/EmptyState.tsx new file mode 100644 index 00000000..279b3a33 --- /dev/null +++ b/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/atoms/EmptyState.tsx @@ -0,0 +1,40 @@ +// src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/atoms/EmptyState.tsx +// 空状态组件 + +import React from 'react'; +import { Center, VStack, Text, Icon } from '@chakra-ui/react'; +import { InfoIcon } from '@chakra-ui/icons'; + +import type { Theme } from '../../../../types'; + +export interface EmptyStateProps { + theme: Theme; + title?: string; + description?: string; + height?: string; +} + +const EmptyState: React.FC = ({ + theme, + title = '暂无分钟频数据', + description = '点击"获取分钟数据"按钮加载最新的交易日分钟频数据', + height = '300px', +}) => { + return ( +
+ + + + + {title} + + + {description} + + + +
+ ); +}; + +export default EmptyState; diff --git a/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/atoms/MinuteStats.tsx b/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/atoms/MinuteStats.tsx new file mode 100644 index 00000000..94a203fd --- /dev/null +++ b/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/atoms/MinuteStats.tsx @@ -0,0 +1,97 @@ +// src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/atoms/MinuteStats.tsx +// 分钟数据统计组件 + +import React from 'react'; +import { + SimpleGrid, + Stat, + StatLabel, + StatNumber, + StatHelpText, + StatArrow, + HStack, + Text, + Icon, +} from '@chakra-ui/react'; +import { + ChevronDownIcon, + ChevronUpIcon, + ArrowUpIcon, + ArrowDownIcon, +} from '@chakra-ui/icons'; + +import type { Theme, MinuteDataPoint } from '../../../../types'; + +export interface MinuteStatsProps { + theme: Theme; + data: MinuteDataPoint[]; +} + +const MinuteStats: React.FC = ({ theme, data }) => { + if (data.length === 0) return null; + + const firstOpen = data[0]?.open || 0; + const lastClose = data[data.length - 1]?.close || 0; + const highPrice = Math.max(...data.map((item) => item.high).filter(Boolean)); + const lowPrice = Math.min(...data.map((item) => item.low).filter(Boolean)); + const isUp = lastClose >= firstOpen; + const changePercent = firstOpen ? Math.abs(((lastClose - firstOpen) / firstOpen) * 100) : 0; + + return ( + + + + + + 开盘价 + + + + {firstOpen?.toFixed(2) || '-'} + + + + + + + + 当前价 + + + + {lastClose?.toFixed(2) || '-'} + + + + {changePercent.toFixed(2)}% + + + + + + + + 最高价 + + + + {highPrice.toFixed(2)} + + + + + + + + 最低价 + + + + {lowPrice.toFixed(2)} + + + + ); +}; + +export default MinuteStats; diff --git a/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/atoms/TradeAnalysis.tsx b/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/atoms/TradeAnalysis.tsx new file mode 100644 index 00000000..c5e0bf9b --- /dev/null +++ b/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/atoms/TradeAnalysis.tsx @@ -0,0 +1,76 @@ +// src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/atoms/TradeAnalysis.tsx +// 成交数据分析组件 + +import React from 'react'; +import { Box, Text, HStack, Badge, Grid } from '@chakra-ui/react'; + +import { formatNumber } from '../../../../utils/formatUtils'; +import type { Theme, MinuteDataPoint } from '../../../../types'; + +export interface TradeAnalysisProps { + theme: Theme; + data: MinuteDataPoint[]; +} + +const TradeAnalysis: React.FC = ({ theme, data }) => { + if (data.length === 0) return null; + + const totalVolume = data.reduce((sum, item) => sum + item.volume, 0); + const totalAmount = data.reduce((sum, item) => sum + item.amount, 0); + const avgPrice = data.reduce((sum, item) => sum + item.close, 0) / data.length; + const maxVolume = Math.max(...data.map((item) => item.volume)); + const activeTime = data.find((item) => item.volume === maxVolume); + + return ( + + + + 成交数据分析 + + + + 总成交量: {formatNumber(totalVolume, 0)} + + + 总成交额: {formatNumber(totalAmount)} + + + + + + + + 活跃时段 + + + {activeTime ? `${activeTime.time} (${formatNumber(maxVolume, 0)})` : '-'} + + + + + 平均价格 + + + {avgPrice.toFixed(2)} + + + + + 数据点数 + + + {data.length} 个分钟 + + + + + ); +}; + +export default TradeAnalysis; diff --git a/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/atoms/index.ts b/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/atoms/index.ts new file mode 100644 index 00000000..0ae41f51 --- /dev/null +++ b/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/atoms/index.ts @@ -0,0 +1,10 @@ +// src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/atoms/index.ts +// 原子组件统一导出 + +export { default as MinuteStats } from './MinuteStats'; +export { default as TradeAnalysis } from './TradeAnalysis'; +export { default as EmptyState } from './EmptyState'; + +export type { MinuteStatsProps } from './MinuteStats'; +export type { TradeAnalysisProps } from './TradeAnalysis'; +export type { EmptyStateProps } from './EmptyState'; diff --git a/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/index.tsx b/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/index.tsx new file mode 100644 index 00000000..c52fffcf --- /dev/null +++ b/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/index.tsx @@ -0,0 +1,58 @@ +// src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/index.tsx +// 交易数据面板 - K线图、分钟图、交易明细表格 + +import React from 'react'; +import { VStack } from '@chakra-ui/react'; + +import KLineChart from './KLineChart'; +import MinuteKLineSection from './MinuteKLineSection'; +import TradeTable from './TradeTable'; +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; +} + +const TradeDataPanel: React.FC = ({ + theme, + tradeData, + minuteData, + minuteLoading, + analysisMap, + onLoadMinuteData, + onChartClick, +}) => { + return ( + + + + + + + + ); +}; + +export default TradeDataPanel; + +// 导出子组件供外部按需使用 +export { KLineChart, MinuteKLineSection, TradeTable }; +export type { KLineChartProps } from './KLineChart'; +export type { MinuteKLineSectionProps } from './MinuteKLineSection'; +export type { TradeTableProps } from './TradeTable'; diff --git a/src/views/Company/components/MarketDataView/components/panels/index.ts b/src/views/Company/components/MarketDataView/components/panels/index.ts index 4b6510d5..3cf06498 100644 --- a/src/views/Company/components/MarketDataView/components/panels/index.ts +++ b/src/views/Company/components/MarketDataView/components/panels/index.ts @@ -13,3 +13,15 @@ export type { FundingPanelProps } from './FundingPanel'; export type { BigDealPanelProps } from './BigDealPanel'; export type { UnusualPanelProps } from './UnusualPanel'; export type { PledgePanelProps } from './PledgePanel'; + +// 导出 TradeDataPanel 子组件 +export { + KLineChart, + MinuteKLineSection, + TradeTable, +} from './TradeDataPanel'; +export type { + KLineChartProps, + MinuteKLineSectionProps, + TradeTableProps, +} from './TradeDataPanel';