Compare commits
5 Commits
ac7e627b2d
...
c4900bd280
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c4900bd280 | ||
|
|
7736212235 | ||
|
|
348d8a0ec3 | ||
|
|
5a0d6e1569 | ||
|
|
bc2b6ae41c |
@@ -1,6 +1,6 @@
|
||||
# Company 目录结构说明
|
||||
|
||||
> 最后更新:2025-12-16
|
||||
> 最后更新:2025-12-17(StockQuoteCard 数据下沉优化)
|
||||
|
||||
## 目录结构
|
||||
|
||||
@@ -11,24 +11,40 @@ src/views/Company/
|
||||
│
|
||||
├── components/ # UI 组件
|
||||
│ │
|
||||
│ ├── LoadingState.tsx # 通用加载状态组件
|
||||
│ │
|
||||
│ ├── CompanyHeader/ # 页面头部
|
||||
│ │ ├── index.js # 组合导出
|
||||
│ │ └── SearchBar.js # 股票搜索栏
|
||||
│ │
|
||||
│ ├── CompanyTabs/ # Tab 切换容器
|
||||
│ │ ├── index.js # Tab 容器(状态管理 + 内容渲染)
|
||||
│ │ └── TabNavigation.js # Tab 导航栏
|
||||
│ │ └── index.js # Tab 容器(状态管理 + 内容渲染)
|
||||
│ │
|
||||
│ ├── StockQuoteCard/ # 股票行情卡片(TypeScript)
|
||||
│ │ ├── index.tsx # 主组件
|
||||
│ ├── StockQuoteCard/ # 股票行情卡片(TypeScript,数据已下沉)
|
||||
│ │ ├── index.tsx # 主组件(Props 从 11 个精简为 4 个)
|
||||
│ │ ├── types.ts # 类型定义
|
||||
│ │ └── mockData.ts # Mock 数据
|
||||
│ │ ├── mockData.ts # Mock 数据
|
||||
│ │ ├── hooks/ # 内部数据 Hooks(2025-12-17 新增)
|
||||
│ │ │ ├── index.ts # hooks 导出索引
|
||||
│ │ │ ├── useStockQuoteData.ts # 行情数据+基本信息获取
|
||||
│ │ │ └── useStockCompare.ts # 股票对比逻辑
|
||||
│ │ └── components/ # 子组件
|
||||
│ │ ├── index.ts # 组件导出
|
||||
│ │ ├── theme.ts # 主题配置
|
||||
│ │ ├── formatters.ts # 格式化工具
|
||||
│ │ ├── StockHeader.tsx # 股票头部(名称、代码、收藏按钮)
|
||||
│ │ ├── PriceDisplay.tsx # 价格显示组件
|
||||
│ │ ├── CompanyInfo.tsx # 公司信息(行业、市值等)
|
||||
│ │ ├── KeyMetrics.tsx # 关键指标(PE、PB、换手率等)
|
||||
│ │ ├── MainForceInfo.tsx # 主力资金信息
|
||||
│ │ ├── SecondaryQuote.tsx # 副行情(对比股票)
|
||||
│ │ ├── CompareStockInput.tsx # 对比股票输入
|
||||
│ │ └── StockCompareModal.tsx # 股票对比弹窗
|
||||
│ │
|
||||
│ ├── CompanyOverview/ # Tab: 公司概览(TypeScript)
|
||||
│ │ ├── index.tsx # 主组件(组合层)
|
||||
│ │ ├── types.ts # 类型定义
|
||||
│ │ ├── utils.ts # 格式化工具
|
||||
│ │ ├── DeepAnalysisTab/ # 深度分析 Tab(21 个 TS 文件)
|
||||
│ │ ├── NewsEventsTab.js # 新闻事件 Tab
|
||||
│ │ │
|
||||
│ │ ├── hooks/ # 数据 Hooks
|
||||
@@ -47,29 +63,69 @@ src/views/Company/
|
||||
│ │ │ ├── ConcentrationCard.tsx # 股权集中度卡片
|
||||
│ │ │ └── ShareholdersTable.tsx # 股东表格
|
||||
│ │ │
|
||||
│ │ └── BasicInfoTab/ # 基本信息 Tab(可配置化)
|
||||
│ │ ├── index.tsx # 主组件(可配置)
|
||||
│ │ ├── config.ts # Tab 配置 + 黑金主题
|
||||
│ │ ├── utils.ts # 格式化工具函数
|
||||
│ │ └── components/ # 子组件
|
||||
│ │ ├── index.ts # 组件统一导出
|
||||
│ │ ├── LoadingState.tsx # 加载状态组件
|
||||
│ │ ├── ShareholderPanel.tsx # 股权结构面板
|
||||
│ │ ├── AnnouncementsPanel.tsx # 公告信息面板
|
||||
│ │ ├── BranchesPanel.tsx # 分支机构面板
|
||||
│ │ ├── BusinessInfoPanel.tsx # 工商信息面板
|
||||
│ │ ├── DisclosureSchedulePanel.tsx # 披露日程面板
|
||||
│ │ └── management/ # 管理团队模块
|
||||
│ │ ├── index.ts # 模块导出
|
||||
│ │ ├── types.ts # 类型定义
|
||||
│ │ ├── ManagementPanel.tsx # 主组件(useMemo)
|
||||
│ │ ├── CategorySection.tsx # 分类区块(memo)
|
||||
│ │ └── ManagementCard.tsx # 人员卡片(memo)
|
||||
│ │ ├── BasicInfoTab/ # 基本信息 Tab(可配置化)
|
||||
│ │ │ ├── index.tsx # 主组件(可配置)
|
||||
│ │ │ ├── config.ts # Tab 配置 + 黑金主题
|
||||
│ │ │ ├── utils.ts # 格式化工具函数
|
||||
│ │ │ └── components/ # 子组件
|
||||
│ │ │ ├── index.ts # 组件统一导出
|
||||
│ │ │ ├── LoadingState.tsx # 加载状态组件
|
||||
│ │ │ ├── ShareholderPanel.tsx # 股权结构面板
|
||||
│ │ │ ├── AnnouncementsPanel.tsx # 公告信息面板
|
||||
│ │ │ ├── BranchesPanel.tsx # 分支机构面板
|
||||
│ │ │ ├── BusinessInfoPanel.tsx # 工商信息面板
|
||||
│ │ │ ├── DisclosureSchedulePanel.tsx # 披露日程面板
|
||||
│ │ │ └── management/ # 管理团队模块
|
||||
│ │ │ ├── index.ts # 模块导出
|
||||
│ │ │ ├── types.ts # 类型定义
|
||||
│ │ │ ├── ManagementPanel.tsx # 主组件(useMemo)
|
||||
│ │ │ ├── CategorySection.tsx # 分类区块(memo)
|
||||
│ │ │ └── ManagementCard.tsx # 人员卡片(memo)
|
||||
│ │ │
|
||||
│ │ └── DeepAnalysisTab/ # 深度分析 Tab(原子设计模式)
|
||||
│ │ ├── index.tsx # 主入口组件
|
||||
│ │ ├── types.ts # 类型定义
|
||||
│ │ ├── atoms/ # 原子组件
|
||||
│ │ │ ├── index.ts
|
||||
│ │ │ ├── DisclaimerBox.tsx # 免责声明
|
||||
│ │ │ ├── ScoreBar.tsx # 评分进度条
|
||||
│ │ │ ├── BusinessTreeItem.tsx # 业务树形项
|
||||
│ │ │ ├── KeyFactorCard.tsx # 关键因素卡片
|
||||
│ │ │ ├── ProcessNavigation.tsx # 流程导航
|
||||
│ │ │ └── ValueChainFilterBar.tsx # 产业链筛选栏
|
||||
│ │ ├── components/ # Card 组件
|
||||
│ │ │ ├── index.ts
|
||||
│ │ │ ├── CorePositioningCard/ # 核心定位卡片(含 atoms)
|
||||
│ │ │ │ ├── index.tsx
|
||||
│ │ │ │ ├── theme.ts
|
||||
│ │ │ │ └── atoms/
|
||||
│ │ │ ├── CompetitiveAnalysisCard.tsx
|
||||
│ │ │ ├── BusinessStructureCard.tsx
|
||||
│ │ │ ├── BusinessSegmentsCard.tsx
|
||||
│ │ │ ├── ValueChainCard.tsx
|
||||
│ │ │ ├── KeyFactorsCard.tsx
|
||||
│ │ │ ├── TimelineCard.tsx
|
||||
│ │ │ └── StrategyAnalysisCard.tsx
|
||||
│ │ ├── organisms/ # 复杂交互组件
|
||||
│ │ │ ├── ValueChainNodeCard/
|
||||
│ │ │ │ ├── index.tsx
|
||||
│ │ │ │ └── RelatedCompaniesModal.tsx
|
||||
│ │ │ └── TimelineComponent/
|
||||
│ │ │ ├── index.tsx
|
||||
│ │ │ └── EventDetailModal.tsx
|
||||
│ │ ├── tabs/ # Tab 面板
|
||||
│ │ │ ├── index.ts
|
||||
│ │ │ ├── BusinessTab.tsx
|
||||
│ │ │ ├── DevelopmentTab.tsx
|
||||
│ │ │ ├── StrategyTab.tsx
|
||||
│ │ │ └── ValueChainTab.tsx
|
||||
│ │ └── utils/
|
||||
│ │ └── chartOptions.ts
|
||||
│ │
|
||||
│ ├── MarketDataView/ # Tab: 股票行情(TypeScript)
|
||||
│ │ ├── index.tsx # 主组件入口(~285 行,Tab 容器)
|
||||
│ │ ├── index.tsx # 主组件入口
|
||||
│ │ ├── types.ts # 类型定义
|
||||
│ │ ├── constants.ts # 主题配置、常量(含黑金主题 darkGoldTheme)
|
||||
│ │ ├── constants.ts # 主题配置(含黑金主题 darkGoldTheme)
|
||||
│ │ ├── services/
|
||||
│ │ │ └── marketService.ts # API 服务层
|
||||
│ │ ├── hooks/
|
||||
@@ -83,71 +139,90 @@ src/views/Company/
|
||||
│ │ ├── MarkdownRenderer.tsx # Markdown 渲染
|
||||
│ │ ├── 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 # 状态标签(活跃/健康等)
|
||||
│ │ │ ├── index.tsx
|
||||
│ │ │ ├── StockHeaderCard.tsx
|
||||
│ │ │ ├── MetricCard.tsx
|
||||
│ │ │ ├── utils.ts
|
||||
│ │ │ └── atoms/
|
||||
│ │ │ ├── index.ts
|
||||
│ │ │ ├── DarkGoldCard.tsx
|
||||
│ │ │ ├── CardTitle.tsx
|
||||
│ │ │ ├── MetricValue.tsx
|
||||
│ │ │ ├── PriceDisplay.tsx
|
||||
│ │ │ └── StatusTag.tsx
|
||||
│ │ └── panels/ # Tab 面板组件
|
||||
│ │ ├── index.ts # 面板组件统一导出
|
||||
│ │ ├── 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 # 龙虎榜面板
|
||||
│ │ └── PledgePanel.tsx # 股权质押面板
|
||||
│ │ ├── index.ts
|
||||
│ │ ├── TradeDataPanel/
|
||||
│ │ │ ├── index.tsx
|
||||
│ │ │ └── KLineModule.tsx
|
||||
│ │ ├── FundingPanel.tsx
|
||||
│ │ ├── BigDealPanel.tsx
|
||||
│ │ ├── UnusualPanel.tsx
|
||||
│ │ └── PledgePanel.tsx
|
||||
│ │
|
||||
│ ├── DeepAnalysis/ # Tab: 深度分析
|
||||
│ ├── DeepAnalysis/ # Tab: 深度分析(入口)
|
||||
│ │ └── index.js
|
||||
│ │
|
||||
│ ├── DynamicTracking/ # Tab: 动态跟踪
|
||||
│ │ └── index.js
|
||||
│ │ ├── index.js # 主组件
|
||||
│ │ └── components/
|
||||
│ │ ├── index.js # 组件导出
|
||||
│ │ ├── NewsPanel.js # 新闻面板
|
||||
│ │ └── ForecastPanel.js # 业绩预告面板
|
||||
│ │
|
||||
│ ├── FinancialPanorama/ # Tab: 财务全景(TypeScript 模块化)
|
||||
│ │ ├── index.tsx # 主组件入口(~400 行)
|
||||
│ │ ├── index.tsx # 主组件入口
|
||||
│ │ ├── types.ts # TypeScript 类型定义
|
||||
│ │ ├── constants.ts # 常量配置(颜色、指标定义)
|
||||
│ │ ├── hooks/
|
||||
│ │ │ ├── index.ts # Hook 统一导出
|
||||
│ │ │ └── useFinancialData.ts # 财务数据加载 Hook
|
||||
│ │ │ ├── index.ts
|
||||
│ │ │ └── useFinancialData.ts
|
||||
│ │ ├── utils/
|
||||
│ │ │ ├── index.ts # 工具函数统一导出
|
||||
│ │ │ ├── calculations.ts # 计算工具(同比变化、单元格颜色)
|
||||
│ │ │ └── chartOptions.ts # ECharts 图表配置生成器
|
||||
│ │ │ ├── index.ts
|
||||
│ │ │ ├── calculations.ts
|
||||
│ │ │ └── chartOptions.ts
|
||||
│ │ ├── tabs/ # Tab 面板组件
|
||||
│ │ │ ├── index.ts
|
||||
│ │ │ ├── BalanceSheetTab.tsx
|
||||
│ │ │ ├── CashflowTab.tsx
|
||||
│ │ │ ├── FinancialMetricsTab.tsx
|
||||
│ │ │ ├── IncomeStatementTab.tsx
|
||||
│ │ │ └── MetricsCategoryTab.tsx
|
||||
│ │ └── components/
|
||||
│ │ ├── index.ts # 组件统一导出
|
||||
│ │ ├── StockInfoHeader.tsx # 股票信息头部
|
||||
│ │ ├── BalanceSheetTable.tsx # 资产负债表
|
||||
│ │ ├── IncomeStatementTable.tsx # 利润表
|
||||
│ │ ├── CashflowTable.tsx # 现金流量表
|
||||
│ │ ├── FinancialMetricsTable.tsx # 财务指标表
|
||||
│ │ ├── MainBusinessAnalysis.tsx # 主营业务分析
|
||||
│ │ ├── IndustryRankingView.tsx # 行业排名
|
||||
│ │ ├── StockComparison.tsx # 股票对比
|
||||
│ │ └── ComparisonAnalysis.tsx # 综合对比分析
|
||||
│ │ ├── index.ts
|
||||
│ │ ├── StockInfoHeader.tsx
|
||||
│ │ ├── FinancialTable.tsx # 通用财务表格
|
||||
│ │ ├── FinancialOverviewPanel.tsx # 财务概览面板
|
||||
│ │ ├── KeyMetricsOverview.tsx # 关键指标概览
|
||||
│ │ ├── PeriodSelector.tsx # 期数选择器
|
||||
│ │ ├── BalanceSheetTable.tsx
|
||||
│ │ ├── IncomeStatementTable.tsx
|
||||
│ │ ├── CashflowTable.tsx
|
||||
│ │ ├── FinancialMetricsTable.tsx
|
||||
│ │ ├── MainBusinessAnalysis.tsx
|
||||
│ │ ├── IndustryRankingView.tsx
|
||||
│ │ ├── StockComparison.tsx
|
||||
│ │ └── ComparisonAnalysis.tsx
|
||||
│ │
|
||||
│ └── ForecastReport/ # Tab: 盈利预测(待拆分)
|
||||
│ └── index.js
|
||||
│ └── ForecastReport/ # Tab: 盈利预测(TypeScript,已模块化)
|
||||
│ ├── index.tsx # 主组件入口
|
||||
│ ├── types.ts # 类型定义
|
||||
│ ├── constants.ts # 配色、图表配置常量
|
||||
│ └── components/
|
||||
│ ├── index.ts
|
||||
│ ├── ChartCard.tsx # 图表卡片容器
|
||||
│ ├── IncomeProfitGrowthChart.tsx # 营收与利润趋势图
|
||||
│ ├── IncomeProfitChart.tsx # 营收利润图(备用)
|
||||
│ ├── GrowthChart.tsx # 增长率图(备用)
|
||||
│ ├── EpsChart.tsx # EPS 趋势图
|
||||
│ ├── PePegChart.tsx # PE/PEG 分析图
|
||||
│ └── DetailTable.tsx # 详细数据表格
|
||||
│
|
||||
├── hooks/ # 页面级 Hooks
|
||||
│ ├── useCompanyStock.js # 股票代码管理(URL 同步)
|
||||
│ ├── useCompanyWatchlist.js # 自选股管理(Redux 集成)
|
||||
│ ├── useCompanyEvents.js # PostHog 事件追踪
|
||||
│ └── useStockQuote.js # 股票行情数据 Hook
|
||||
│ └── useCompanyEvents.js # PostHog 事件追踪
|
||||
│ # 注:useStockQuote.js 已下沉到 StockQuoteCard/hooks/useStockQuoteData.ts
|
||||
│
|
||||
└── constants/ # 常量定义
|
||||
└── index.js # Tab 配置、Toast 消息、默认值
|
||||
@@ -161,13 +236,18 @@ src/views/Company/
|
||||
|
||||
#### `index.js` - 页面入口
|
||||
- **职责**:纯组合层,协调 Hooks 和 Components
|
||||
- **代码行数**:95 行
|
||||
- **代码行数**:~105 行(2025-12-17 优化后精简)
|
||||
- **依赖**:
|
||||
- `useCompanyStock` - 股票代码状态
|
||||
- `useCompanyWatchlist` - 自选股状态
|
||||
- `useCompanyEvents` - 事件追踪
|
||||
- `CompanyHeader` - 页面头部
|
||||
- `StockQuoteCard` - 股票行情卡片(内部自行获取数据)
|
||||
- `CompanyTabs` - Tab 切换区
|
||||
- **已移除**(2025-12-17):
|
||||
- `useStockQuote` - 已下沉到 StockQuoteCard
|
||||
- `useBasicInfo` - 已下沉到 StockQuoteCard
|
||||
- 股票对比逻辑 - 已下沉到 StockQuoteCard
|
||||
|
||||
---
|
||||
|
||||
@@ -999,4 +1079,98 @@ index.tsx
|
||||
- **原子设计模式**:atoms(MinuteStats、TradeAnalysis、EmptyState)→ 业务组件(KLineChart、MinuteKLineSection、TradeTable)→ 主组件
|
||||
- **职责分离**:图表、统计、表格各自独立
|
||||
- **组件复用**:EmptyState 可在其他场景复用
|
||||
- **类型安全**:完整的 Props 类型定义和导出
|
||||
- **类型安全**:完整的 Props 类型定义和导出
|
||||
|
||||
### 2025-12-17 StockQuoteCard 数据下沉优化
|
||||
|
||||
**改动概述**:
|
||||
- StockQuoteCard Props 从 **11 个** 精简至 **4 个**(减少 64%)
|
||||
- 行情数据、基本信息、股票对比逻辑全部下沉到组件内部
|
||||
- Company/index.js 移除约 **40 行** 数据获取代码
|
||||
- 删除 `Company/hooks/useStockQuote.js`
|
||||
|
||||
**创建的文件**:
|
||||
```
|
||||
StockQuoteCard/hooks/
|
||||
├── index.ts # hooks 导出索引
|
||||
├── useStockQuoteData.ts # 行情数据 + 基本信息获取(~152 行)
|
||||
└── useStockCompare.ts # 股票对比逻辑(~91 行)
|
||||
```
|
||||
|
||||
**Props 对比**:
|
||||
|
||||
**优化前(11 个 Props)**:
|
||||
```tsx
|
||||
<StockQuoteCard
|
||||
data={quoteData}
|
||||
isLoading={isQuoteLoading}
|
||||
basicInfo={basicInfo}
|
||||
currentStockInfo={currentStockInfo}
|
||||
compareStockInfo={compareStockInfo}
|
||||
isCompareLoading={isCompareLoading}
|
||||
onCompare={handleCompare}
|
||||
onCloseCompare={handleCloseCompare}
|
||||
isInWatchlist={isInWatchlist}
|
||||
isWatchlistLoading={isWatchlistLoading}
|
||||
onWatchlistToggle={handleWatchlistToggle}
|
||||
/>
|
||||
```
|
||||
|
||||
**优化后(4 个 Props)**:
|
||||
```tsx
|
||||
<StockQuoteCard
|
||||
stockCode={stockCode}
|
||||
isInWatchlist={isInWatchlist}
|
||||
isWatchlistLoading={isWatchlistLoading}
|
||||
onWatchlistToggle={handleWatchlistToggle}
|
||||
/>
|
||||
```
|
||||
|
||||
**Hook 返回值**:
|
||||
|
||||
`useStockQuoteData(stockCode)`:
|
||||
```typescript
|
||||
{
|
||||
quoteData: StockQuoteCardData | null; // 行情数据
|
||||
basicInfo: BasicInfo | null; // 基本信息
|
||||
isLoading: boolean; // 加载状态
|
||||
error: string | null; // 错误信息
|
||||
refetch: () => void; // 手动刷新
|
||||
}
|
||||
```
|
||||
|
||||
`useStockCompare(stockCode)`:
|
||||
```typescript
|
||||
{
|
||||
currentStockInfo: StockInfo | null; // 当前股票财务信息
|
||||
compareStockInfo: StockInfo | null; // 对比股票财务信息
|
||||
isCompareLoading: boolean; // 对比数据加载中
|
||||
handleCompare: (code: string) => Promise<void>; // 触发对比
|
||||
clearCompare: () => void; // 清除对比
|
||||
}
|
||||
```
|
||||
|
||||
**修改的文件**:
|
||||
| 文件 | 操作 | 说明 |
|
||||
|------|------|------|
|
||||
| `StockQuoteCard/hooks/useStockQuoteData.ts` | 新建 | 合并行情+基本信息获取 |
|
||||
| `StockQuoteCard/hooks/useStockCompare.ts` | 新建 | 股票对比逻辑 |
|
||||
| `StockQuoteCard/hooks/index.ts` | 新建 | hooks 导出索引 |
|
||||
| `StockQuoteCard/index.tsx` | 修改 | 使用内部 hooks,减少 props |
|
||||
| `StockQuoteCard/types.ts` | 修改 | Props 从 11 个精简为 4 个 |
|
||||
| `Company/index.js` | 修改 | 移除下沉的数据获取逻辑 |
|
||||
| `Company/hooks/useStockQuote.js` | 删除 | 已移到 StockQuoteCard |
|
||||
|
||||
**优化收益**:
|
||||
| 指标 | 优化前 | 优化后 | 改善 |
|
||||
|------|--------|--------|------|
|
||||
| Props 数量 | 11 | 4 | -64% |
|
||||
| Company/index.js 行数 | ~172 | ~105 | -39% |
|
||||
| 数据获取位置 | 页面层 | 组件内部 | 就近原则 |
|
||||
| 可复用性 | 依赖父组件 | 独立可用 | 提升 |
|
||||
|
||||
**设计原则**:
|
||||
- **数据就近获取**:组件自己获取自己需要的数据
|
||||
- **Props 最小化**:只传递真正需要外部控制的状态
|
||||
- **职责清晰**:自选股状态保留在页面层(涉及 Redux 和事件追踪)
|
||||
- **可复用性**:StockQuoteCard 可独立在其他页面使用
|
||||
@@ -5,10 +5,7 @@ import React, { useState, useEffect, ReactNode, useMemo, useCallback } from 'rea
|
||||
import {
|
||||
Box,
|
||||
Container,
|
||||
CardBody,
|
||||
Center,
|
||||
VStack,
|
||||
Text,
|
||||
useDisclosure,
|
||||
} from '@chakra-ui/react';
|
||||
import {
|
||||
@@ -161,11 +158,14 @@ const MarketDataView: React.FC<MarketDataViewProps> = ({ stockCode: propStockCod
|
||||
|
||||
{/* 主要内容区域 - Tab */}
|
||||
{loading ? (
|
||||
<ThemedCard theme={theme}>
|
||||
<CardBody>
|
||||
<LoadingState message="数据加载中..." height="400px" />
|
||||
</CardBody>
|
||||
</ThemedCard>
|
||||
<Box
|
||||
bg="gray.900"
|
||||
border="1px solid"
|
||||
borderColor="rgba(212, 175, 55, 0.3)"
|
||||
borderRadius="xl"
|
||||
>
|
||||
<LoadingState message="数据加载中..." height="400px" />
|
||||
</Box>
|
||||
) : (
|
||||
<SubTabContainer
|
||||
tabs={tabConfigs}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* StockQuoteCard Hooks 导出索引
|
||||
*/
|
||||
|
||||
export { useStockQuoteData } from './useStockQuoteData';
|
||||
export { useStockCompare } from './useStockCompare';
|
||||
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* useStockCompare - 股票对比逻辑 Hook
|
||||
*
|
||||
* 管理股票对比所需的数据获取和状态
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useToast } from '@chakra-ui/react';
|
||||
import { financialService } from '@services/financialService';
|
||||
import { logger } from '@utils/logger';
|
||||
import type { StockInfo } from '../../FinancialPanorama/types';
|
||||
|
||||
interface UseStockCompareResult {
|
||||
currentStockInfo: StockInfo | null;
|
||||
compareStockInfo: StockInfo | null;
|
||||
isCompareLoading: boolean;
|
||||
handleCompare: (compareCode: string) => Promise<void>;
|
||||
clearCompare: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 股票对比 Hook
|
||||
*
|
||||
* @param stockCode - 当前股票代码
|
||||
*/
|
||||
export const useStockCompare = (stockCode?: string): UseStockCompareResult => {
|
||||
const toast = useToast();
|
||||
const [currentStockInfo, setCurrentStockInfo] = useState<StockInfo | null>(null);
|
||||
const [compareStockInfo, setCompareStockInfo] = useState<StockInfo | null>(null);
|
||||
const [isCompareLoading, setIsCompareLoading] = useState(false);
|
||||
|
||||
// 加载当前股票财务信息(用于对比)
|
||||
useEffect(() => {
|
||||
const loadCurrentStockInfo = async () => {
|
||||
if (!stockCode) {
|
||||
setCurrentStockInfo(null);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await financialService.getStockInfo(stockCode);
|
||||
setCurrentStockInfo(res.data);
|
||||
} catch (error) {
|
||||
logger.error('useStockCompare', 'loadCurrentStockInfo', error, { stockCode });
|
||||
}
|
||||
};
|
||||
|
||||
loadCurrentStockInfo();
|
||||
// 股票代码变化时清除对比数据
|
||||
setCompareStockInfo(null);
|
||||
}, [stockCode]);
|
||||
|
||||
// 处理股票对比
|
||||
const handleCompare = useCallback(async (compareCode: string) => {
|
||||
if (!compareCode) return;
|
||||
|
||||
logger.debug('useStockCompare', '开始加载对比数据', { stockCode, compareCode });
|
||||
setIsCompareLoading(true);
|
||||
|
||||
try {
|
||||
const res = await financialService.getStockInfo(compareCode);
|
||||
setCompareStockInfo(res.data);
|
||||
logger.info('useStockCompare', '对比数据加载成功', { stockCode, compareCode });
|
||||
} catch (error) {
|
||||
logger.error('useStockCompare', 'handleCompare', error, { stockCode, compareCode });
|
||||
toast({
|
||||
title: '加载对比数据失败',
|
||||
description: '请检查股票代码是否正确',
|
||||
status: 'error',
|
||||
duration: 3000,
|
||||
});
|
||||
} finally {
|
||||
setIsCompareLoading(false);
|
||||
}
|
||||
}, [stockCode, toast]);
|
||||
|
||||
// 清除对比数据
|
||||
const clearCompare = useCallback(() => {
|
||||
setCompareStockInfo(null);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
currentStockInfo,
|
||||
compareStockInfo,
|
||||
isCompareLoading,
|
||||
handleCompare,
|
||||
clearCompare,
|
||||
};
|
||||
};
|
||||
|
||||
export default useStockCompare;
|
||||
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* useStockQuoteData - 股票行情数据获取 Hook
|
||||
*
|
||||
* 合并获取行情数据和基本信息,供 StockQuoteCard 内部使用
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { stockService } from '@services/eventService';
|
||||
import { logger } from '@utils/logger';
|
||||
import { getApiBase } from '@utils/apiConfig';
|
||||
import type { StockQuoteCardData } from '../types';
|
||||
import type { BasicInfo } from '../../CompanyOverview/types';
|
||||
|
||||
const API_BASE_URL = getApiBase();
|
||||
|
||||
/**
|
||||
* 将 API 响应数据转换为 StockQuoteCard 所需格式
|
||||
*/
|
||||
const transformQuoteData = (apiData: any, stockCode: string): StockQuoteCardData | null => {
|
||||
if (!apiData) return null;
|
||||
|
||||
return {
|
||||
// 基础信息
|
||||
name: apiData.name || apiData.stock_name || '未知',
|
||||
code: apiData.code || apiData.stock_code || stockCode,
|
||||
indexTags: apiData.index_tags || apiData.indexTags || [],
|
||||
industry: apiData.industry || apiData.sw_industry_l2 || '',
|
||||
industryL1: apiData.industry_l1 || apiData.sw_industry_l1 || '',
|
||||
|
||||
// 价格信息
|
||||
currentPrice: apiData.current_price || apiData.currentPrice || apiData.close || 0,
|
||||
changePercent: apiData.change_percent || apiData.changePercent || apiData.pct_chg || 0,
|
||||
todayOpen: apiData.today_open || apiData.todayOpen || apiData.open || 0,
|
||||
yesterdayClose: apiData.yesterday_close || apiData.yesterdayClose || apiData.pre_close || 0,
|
||||
todayHigh: apiData.today_high || apiData.todayHigh || apiData.high || 0,
|
||||
todayLow: apiData.today_low || apiData.todayLow || apiData.low || 0,
|
||||
|
||||
// 关键指标
|
||||
pe: apiData.pe || apiData.pe_ttm || 0,
|
||||
eps: apiData.eps || apiData.basic_eps || undefined,
|
||||
pb: apiData.pb || apiData.pb_mrq || 0,
|
||||
marketCap: apiData.market_cap || apiData.marketCap || apiData.circ_mv || '0',
|
||||
week52Low: apiData.week52_low || apiData.week52Low || 0,
|
||||
week52High: apiData.week52_high || apiData.week52High || 0,
|
||||
|
||||
// 主力动态
|
||||
mainNetInflow: apiData.main_net_inflow || apiData.mainNetInflow || 0,
|
||||
institutionHolding: apiData.institution_holding || apiData.institutionHolding || 0,
|
||||
buyRatio: apiData.buy_ratio || apiData.buyRatio || 50,
|
||||
sellRatio: apiData.sell_ratio || apiData.sellRatio || 50,
|
||||
|
||||
// 更新时间
|
||||
updateTime: apiData.update_time || apiData.updateTime || new Date().toLocaleString(),
|
||||
};
|
||||
};
|
||||
|
||||
interface UseStockQuoteDataResult {
|
||||
quoteData: StockQuoteCardData | null;
|
||||
basicInfo: BasicInfo | null;
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
refetch: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 股票行情数据获取 Hook
|
||||
* 合并获取行情数据和基本信息
|
||||
*
|
||||
* @param stockCode - 股票代码
|
||||
*/
|
||||
export const useStockQuoteData = (stockCode?: string): UseStockQuoteDataResult => {
|
||||
const [quoteData, setQuoteData] = useState<StockQuoteCardData | null>(null);
|
||||
const [basicInfo, setBasicInfo] = useState<BasicInfo | null>(null);
|
||||
const [quoteLoading, setQuoteLoading] = useState(false);
|
||||
const [basicLoading, setBasicLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// 获取行情数据
|
||||
const fetchQuote = useCallback(async () => {
|
||||
if (!stockCode) {
|
||||
setQuoteData(null);
|
||||
return;
|
||||
}
|
||||
|
||||
setQuoteLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
logger.debug('useStockQuoteData', '获取股票行情', { stockCode });
|
||||
const quotes = await stockService.getQuotes([stockCode]);
|
||||
|
||||
// API 返回格式: { [stockCode]: quoteData }
|
||||
const quoteResult = quotes?.[stockCode] || quotes;
|
||||
const transformedData = transformQuoteData(quoteResult, stockCode);
|
||||
|
||||
logger.debug('useStockQuoteData', '行情数据转换完成', { stockCode, hasData: !!transformedData });
|
||||
setQuoteData(transformedData);
|
||||
} catch (err) {
|
||||
logger.error('useStockQuoteData', '获取行情失败', err);
|
||||
setError('获取行情数据失败');
|
||||
setQuoteData(null);
|
||||
} finally {
|
||||
setQuoteLoading(false);
|
||||
}
|
||||
}, [stockCode]);
|
||||
|
||||
// 获取基本信息
|
||||
const fetchBasicInfo = useCallback(async () => {
|
||||
if (!stockCode) {
|
||||
setBasicInfo(null);
|
||||
return;
|
||||
}
|
||||
|
||||
setBasicLoading(true);
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/api/stock/${stockCode}/basic-info`);
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
setBasicInfo(result.data);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('useStockQuoteData', '获取基本信息失败', err);
|
||||
// 基本信息获取失败不影响主流程,只记录日志
|
||||
} finally {
|
||||
setBasicLoading(false);
|
||||
}
|
||||
}, [stockCode]);
|
||||
|
||||
// stockCode 变化时重新获取数据
|
||||
useEffect(() => {
|
||||
fetchQuote();
|
||||
fetchBasicInfo();
|
||||
}, [fetchQuote, fetchBasicInfo]);
|
||||
|
||||
// 手动刷新
|
||||
const refetch = useCallback(() => {
|
||||
fetchQuote();
|
||||
fetchBasicInfo();
|
||||
}, [fetchQuote, fetchBasicInfo]);
|
||||
|
||||
return {
|
||||
quoteData,
|
||||
basicInfo,
|
||||
isLoading: quoteLoading || basicLoading,
|
||||
error,
|
||||
refetch,
|
||||
};
|
||||
};
|
||||
|
||||
export default useStockQuoteData;
|
||||
@@ -3,6 +3,8 @@
|
||||
*
|
||||
* 展示股票的实时行情、关键指标和主力动态
|
||||
* 采用原子组件拆分,提高可维护性和复用性
|
||||
*
|
||||
* 优化:数据获取已下沉到组件内部,Props 从 11 个精简为 4 个
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
@@ -26,42 +28,46 @@ import {
|
||||
StockCompareModal,
|
||||
STOCK_CARD_THEME,
|
||||
} from './components';
|
||||
import { useStockQuoteData, useStockCompare } from './hooks';
|
||||
import type { StockQuoteCardProps } from './types';
|
||||
|
||||
const StockQuoteCard: React.FC<StockQuoteCardProps> = ({
|
||||
data,
|
||||
isLoading = false,
|
||||
stockCode,
|
||||
isInWatchlist = false,
|
||||
isWatchlistLoading = false,
|
||||
onWatchlistToggle,
|
||||
onShare,
|
||||
basicInfo,
|
||||
// 对比相关
|
||||
currentStockInfo,
|
||||
compareStockInfo,
|
||||
isCompareLoading = false,
|
||||
onCompare,
|
||||
onCloseCompare,
|
||||
}) => {
|
||||
// 内部获取行情数据和基本信息
|
||||
const { quoteData, basicInfo, isLoading } = useStockQuoteData(stockCode);
|
||||
|
||||
// 内部管理股票对比逻辑
|
||||
const {
|
||||
currentStockInfo,
|
||||
compareStockInfo,
|
||||
isCompareLoading,
|
||||
handleCompare: triggerCompare,
|
||||
clearCompare,
|
||||
} = useStockCompare(stockCode);
|
||||
|
||||
// 对比弹窗控制
|
||||
const { isOpen: isCompareModalOpen, onOpen: openCompareModal, onClose: closeCompareModal } = useDisclosure();
|
||||
|
||||
// 处理对比按钮点击
|
||||
const handleCompare = (stockCode: string) => {
|
||||
onCompare?.(stockCode);
|
||||
const handleCompare = (compareCode: string) => {
|
||||
triggerCompare(compareCode);
|
||||
openCompareModal();
|
||||
};
|
||||
|
||||
// 处理关闭对比弹窗
|
||||
const handleCloseCompare = () => {
|
||||
closeCompareModal();
|
||||
onCloseCompare?.();
|
||||
clearCompare();
|
||||
};
|
||||
|
||||
const { cardBg, borderColor } = STOCK_CARD_THEME;
|
||||
|
||||
// 加载中或无数据时显示骨架屏
|
||||
if (isLoading || !data) {
|
||||
if (isLoading || !quoteData) {
|
||||
return (
|
||||
<Card bg={cardBg} shadow="sm" borderWidth="1px" borderColor={borderColor}>
|
||||
<CardBody>
|
||||
@@ -80,16 +86,15 @@ const StockQuoteCard: React.FC<StockQuoteCardProps> = ({
|
||||
<CardBody>
|
||||
{/* 顶部:股票名称 + 关注/分享按钮 + 更新时间 */}
|
||||
<StockHeader
|
||||
name={data.name}
|
||||
code={data.code}
|
||||
industryL1={data.industryL1}
|
||||
industry={data.industry}
|
||||
indexTags={data.indexTags}
|
||||
updateTime={data.updateTime}
|
||||
name={quoteData.name}
|
||||
code={quoteData.code}
|
||||
industryL1={quoteData.industryL1}
|
||||
industry={quoteData.industry}
|
||||
indexTags={quoteData.indexTags}
|
||||
updateTime={quoteData.updateTime}
|
||||
isInWatchlist={isInWatchlist}
|
||||
isWatchlistLoading={isWatchlistLoading}
|
||||
onWatchlistToggle={onWatchlistToggle}
|
||||
onShare={onShare}
|
||||
isCompareLoading={isCompareLoading}
|
||||
onCompare={handleCompare}
|
||||
/>
|
||||
@@ -98,7 +103,7 @@ const StockQuoteCard: React.FC<StockQuoteCardProps> = ({
|
||||
<StockCompareModal
|
||||
isOpen={isCompareModalOpen}
|
||||
onClose={handleCloseCompare}
|
||||
currentStock={data.code}
|
||||
currentStock={quoteData.code}
|
||||
currentStockInfo={currentStockInfo || null}
|
||||
compareStock={compareStockInfo?.stock_code || ''}
|
||||
compareStockInfo={compareStockInfo || null}
|
||||
@@ -110,32 +115,32 @@ const StockQuoteCard: React.FC<StockQuoteCardProps> = ({
|
||||
{/* 左栏:价格信息 (flex=1) */}
|
||||
<Box flex="1" minWidth="0">
|
||||
<PriceDisplay
|
||||
currentPrice={data.currentPrice}
|
||||
changePercent={data.changePercent}
|
||||
currentPrice={quoteData.currentPrice}
|
||||
changePercent={quoteData.changePercent}
|
||||
/>
|
||||
<SecondaryQuote
|
||||
todayOpen={data.todayOpen}
|
||||
yesterdayClose={data.yesterdayClose}
|
||||
todayHigh={data.todayHigh}
|
||||
todayLow={data.todayLow}
|
||||
todayOpen={quoteData.todayOpen}
|
||||
yesterdayClose={quoteData.yesterdayClose}
|
||||
todayHigh={quoteData.todayHigh}
|
||||
todayLow={quoteData.todayLow}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* 右栏:关键指标 + 主力动态 (flex=2) */}
|
||||
<Flex flex="2" minWidth="0" gap={8} borderLeftWidth="1px" borderColor={borderColor} pl={8}>
|
||||
<KeyMetrics
|
||||
pe={data.pe}
|
||||
eps={data.eps}
|
||||
pb={data.pb}
|
||||
marketCap={data.marketCap}
|
||||
week52Low={data.week52Low}
|
||||
week52High={data.week52High}
|
||||
pe={quoteData.pe}
|
||||
eps={quoteData.eps}
|
||||
pb={quoteData.pb}
|
||||
marketCap={quoteData.marketCap}
|
||||
week52Low={quoteData.week52Low}
|
||||
week52High={quoteData.week52High}
|
||||
/>
|
||||
<MainForceInfo
|
||||
mainNetInflow={data.mainNetInflow}
|
||||
institutionHolding={data.institutionHolding}
|
||||
buyRatio={data.buyRatio}
|
||||
sellRatio={data.sellRatio}
|
||||
mainNetInflow={quoteData.mainNetInflow}
|
||||
institutionHolding={quoteData.institutionHolding}
|
||||
buyRatio={quoteData.buyRatio}
|
||||
sellRatio={quoteData.sellRatio}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
* StockQuoteCard 组件类型定义
|
||||
*/
|
||||
|
||||
import type { BasicInfo } from '../CompanyOverview/types';
|
||||
import type { StockInfo } from '../FinancialPanorama/types';
|
||||
// 注:BasicInfo 和 StockInfo 类型由内部 hooks 使用,不再在 Props 中传递
|
||||
export type { StockInfo } from '../FinancialPanorama/types';
|
||||
|
||||
/**
|
||||
* 股票行情卡片数据
|
||||
@@ -46,26 +46,18 @@ export interface StockQuoteCardData {
|
||||
}
|
||||
|
||||
/**
|
||||
* StockQuoteCard 组件 Props
|
||||
* StockQuoteCard 组件 Props(优化后)
|
||||
*
|
||||
* 行情数据、基本信息、对比逻辑已下沉到组件内部 hooks 获取
|
||||
* Props 从 11 个精简为 4 个
|
||||
*/
|
||||
export interface StockQuoteCardProps {
|
||||
data?: StockQuoteCardData;
|
||||
isLoading?: boolean;
|
||||
// 自选股相关(与 WatchlistButton 接口保持一致)
|
||||
isInWatchlist?: boolean; // 是否在自选股中
|
||||
isWatchlistLoading?: boolean; // 自选股操作加载中
|
||||
onWatchlistToggle?: () => void; // 自选股切换回调
|
||||
// 分享
|
||||
onShare?: () => void; // 分享回调
|
||||
// 公司基本信息
|
||||
basicInfo?: BasicInfo;
|
||||
// 股票对比相关
|
||||
currentStockInfo?: StockInfo; // 当前股票财务信息(用于对比)
|
||||
compareStockInfo?: StockInfo; // 对比股票财务信息
|
||||
isCompareLoading?: boolean; // 对比数据加载中
|
||||
onCompare?: (stockCode: string) => void; // 触发对比回调
|
||||
onCloseCompare?: () => void; // 关闭对比弹窗回调
|
||||
/** 股票代码 - 用于内部数据获取 */
|
||||
stockCode?: string;
|
||||
/** 是否在自选股中(保留:涉及 Redux 和事件追踪回调) */
|
||||
isInWatchlist?: boolean;
|
||||
/** 自选股操作加载中 */
|
||||
isWatchlistLoading?: boolean;
|
||||
/** 自选股切换回调 */
|
||||
onWatchlistToggle?: () => void;
|
||||
}
|
||||
|
||||
// 重新导出 StockInfo 类型以便外部使用
|
||||
export type { StockInfo };
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
// src/views/Company/hooks/useStockQuote.js
|
||||
// 股票行情数据获取 Hook
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { stockService } from '@services/eventService';
|
||||
import { logger } from '@utils/logger';
|
||||
|
||||
/**
|
||||
* 将 API 响应数据转换为 StockQuoteCard 所需格式
|
||||
*/
|
||||
const transformQuoteData = (apiData, stockCode) => {
|
||||
if (!apiData) return null;
|
||||
|
||||
return {
|
||||
// 基础信息
|
||||
name: apiData.name || apiData.stock_name || '未知',
|
||||
code: apiData.code || apiData.stock_code || stockCode,
|
||||
indexTags: apiData.index_tags || apiData.indexTags || [],
|
||||
industry: apiData.industry || apiData.sw_industry_l2 || '',
|
||||
industryL1: apiData.industry_l1 || apiData.sw_industry_l1 || '',
|
||||
|
||||
// 价格信息
|
||||
currentPrice: apiData.current_price || apiData.currentPrice || apiData.close || 0,
|
||||
changePercent: apiData.change_percent || apiData.changePercent || apiData.pct_chg || 0,
|
||||
todayOpen: apiData.today_open || apiData.todayOpen || apiData.open || 0,
|
||||
yesterdayClose: apiData.yesterday_close || apiData.yesterdayClose || apiData.pre_close || 0,
|
||||
todayHigh: apiData.today_high || apiData.todayHigh || apiData.high || 0,
|
||||
todayLow: apiData.today_low || apiData.todayLow || apiData.low || 0,
|
||||
|
||||
// 关键指标
|
||||
pe: apiData.pe || apiData.pe_ttm || 0,
|
||||
eps: apiData.eps || apiData.basic_eps || undefined,
|
||||
pb: apiData.pb || apiData.pb_mrq || 0,
|
||||
marketCap: apiData.market_cap || apiData.marketCap || apiData.circ_mv || '0',
|
||||
week52Low: apiData.week52_low || apiData.week52Low || 0,
|
||||
week52High: apiData.week52_high || apiData.week52High || 0,
|
||||
|
||||
// 主力动态
|
||||
mainNetInflow: apiData.main_net_inflow || apiData.mainNetInflow || 0,
|
||||
institutionHolding: apiData.institution_holding || apiData.institutionHolding || 0,
|
||||
buyRatio: apiData.buy_ratio || apiData.buyRatio || 50,
|
||||
sellRatio: apiData.sell_ratio || apiData.sellRatio || 50,
|
||||
|
||||
// 更新时间
|
||||
updateTime: apiData.update_time || apiData.updateTime || new Date().toLocaleString(),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 股票行情数据获取 Hook
|
||||
*
|
||||
* @param {string} stockCode - 股票代码
|
||||
* @returns {Object} { data, isLoading, error, refetch }
|
||||
*/
|
||||
export const useStockQuote = (stockCode) => {
|
||||
const [data, setData] = useState(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!stockCode) {
|
||||
setData(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const fetchQuote = async () => {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
logger.debug('useStockQuote', '获取股票行情', { stockCode });
|
||||
const quotes = await stockService.getQuotes([stockCode]);
|
||||
|
||||
// API 返回格式: { [stockCode]: quoteData }
|
||||
const quoteData = quotes?.[stockCode] || quotes;
|
||||
const transformedData = transformQuoteData(quoteData, stockCode);
|
||||
|
||||
logger.debug('useStockQuote', '行情数据转换完成', { stockCode, hasData: !!transformedData });
|
||||
setData(transformedData);
|
||||
} catch (err) {
|
||||
logger.error('useStockQuote', '获取行情失败', err);
|
||||
setError(err);
|
||||
setData(null);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchQuote();
|
||||
}, [stockCode]);
|
||||
|
||||
// 手动刷新
|
||||
const refetch = () => {
|
||||
if (stockCode) {
|
||||
setData(null);
|
||||
// 触发 useEffect 重新执行
|
||||
}
|
||||
};
|
||||
|
||||
return { data, isLoading, error, refetch };
|
||||
};
|
||||
|
||||
export default useStockQuote;
|
||||
@@ -1,19 +1,17 @@
|
||||
// src/views/Company/index.js
|
||||
// 公司详情页面入口 - 纯组合层
|
||||
//
|
||||
// 优化:行情数据、基本信息、对比逻辑已下沉到 StockQuoteCard 内部
|
||||
|
||||
import React, { useEffect, useRef, useState, useCallback } from 'react';
|
||||
import { Container, VStack, useToast } from '@chakra-ui/react';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { Container, VStack } from '@chakra-ui/react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { loadAllStocks } from '@store/slices/stockSlice';
|
||||
import { financialService } from '@services/financialService';
|
||||
import { logger } from '@utils/logger';
|
||||
|
||||
// 自定义 Hooks
|
||||
import { useCompanyStock } from './hooks/useCompanyStock';
|
||||
import { useCompanyWatchlist } from './hooks/useCompanyWatchlist';
|
||||
import { useCompanyEvents } from './hooks/useCompanyEvents';
|
||||
import { useStockQuote } from './hooks/useStockQuote';
|
||||
import { useBasicInfo } from './components/CompanyOverview/hooks/useBasicInfo';
|
||||
|
||||
// 页面组件
|
||||
import CompanyHeader from './components/CompanyHeader';
|
||||
@@ -31,9 +29,8 @@ import CompanyTabs from './components/CompanyTabs';
|
||||
*/
|
||||
const CompanyIndex = () => {
|
||||
const dispatch = useDispatch();
|
||||
const toast = useToast();
|
||||
|
||||
// 1. 先获取股票代码(不带追踪回调)
|
||||
// 1. 获取股票代码(不带追踪回调)
|
||||
const {
|
||||
stockCode,
|
||||
inputCode,
|
||||
@@ -47,64 +44,7 @@ const CompanyIndex = () => {
|
||||
dispatch(loadAllStocks());
|
||||
}, [dispatch]);
|
||||
|
||||
// 2. 获取股票行情数据
|
||||
const { data: quoteData, isLoading: isQuoteLoading } = useStockQuote(stockCode);
|
||||
|
||||
// 2.1 获取公司基本信息
|
||||
const { basicInfo } = useBasicInfo(stockCode);
|
||||
|
||||
// 5. 股票对比状态管理
|
||||
const [currentStockInfo, setCurrentStockInfo] = useState(null);
|
||||
const [compareStockInfo, setCompareStockInfo] = useState(null);
|
||||
const [isCompareLoading, setIsCompareLoading] = useState(false);
|
||||
|
||||
// 加载当前股票财务信息(用于对比)
|
||||
useEffect(() => {
|
||||
const loadCurrentStockInfo = async () => {
|
||||
if (!stockCode) return;
|
||||
try {
|
||||
const res = await financialService.getStockInfo(stockCode);
|
||||
setCurrentStockInfo(res.data);
|
||||
} catch (error) {
|
||||
logger.error('CompanyIndex', 'loadCurrentStockInfo', error, { stockCode });
|
||||
}
|
||||
};
|
||||
loadCurrentStockInfo();
|
||||
// 清除对比数据
|
||||
setCompareStockInfo(null);
|
||||
}, [stockCode]);
|
||||
|
||||
// 处理股票对比
|
||||
const handleCompare = useCallback(async (compareCode) => {
|
||||
if (!compareCode) return;
|
||||
|
||||
logger.debug('CompanyIndex', '开始加载对比数据', { stockCode, compareCode });
|
||||
setIsCompareLoading(true);
|
||||
|
||||
try {
|
||||
const res = await financialService.getStockInfo(compareCode);
|
||||
setCompareStockInfo(res.data);
|
||||
logger.info('CompanyIndex', '对比数据加载成功', { stockCode, compareCode });
|
||||
} catch (error) {
|
||||
logger.error('CompanyIndex', 'handleCompare', error, { stockCode, compareCode });
|
||||
toast({
|
||||
title: '加载对比数据失败',
|
||||
description: '请检查股票代码是否正确',
|
||||
status: 'error',
|
||||
duration: 3000,
|
||||
});
|
||||
} finally {
|
||||
setIsCompareLoading(false);
|
||||
}
|
||||
}, [stockCode, toast]);
|
||||
|
||||
// 关闭对比弹窗
|
||||
const handleCloseCompare = useCallback(() => {
|
||||
// 可选:清除对比数据
|
||||
// setCompareStockInfo(null);
|
||||
}, []);
|
||||
|
||||
// 3. 再初始化事件追踪(传入 stockCode)
|
||||
// 2. 初始化事件追踪(传入 stockCode)
|
||||
const {
|
||||
trackStockSearched,
|
||||
trackTabChanged,
|
||||
@@ -147,19 +87,12 @@ const CompanyIndex = () => {
|
||||
/>
|
||||
|
||||
{/* 股票行情卡片:价格、关键指标、主力动态、公司信息、股票对比 */}
|
||||
{/* 优化:数据获取已下沉到组件内部,Props 从 11 个精简为 4 个 */}
|
||||
<StockQuoteCard
|
||||
data={quoteData}
|
||||
isLoading={isQuoteLoading}
|
||||
stockCode={stockCode}
|
||||
isInWatchlist={isInWatchlist}
|
||||
isWatchlistLoading={isWatchlistLoading}
|
||||
onWatchlistToggle={handleWatchlistToggle}
|
||||
basicInfo={basicInfo}
|
||||
// 股票对比相关
|
||||
currentStockInfo={currentStockInfo}
|
||||
compareStockInfo={compareStockInfo}
|
||||
isCompareLoading={isCompareLoading}
|
||||
onCompare={handleCompare}
|
||||
onCloseCompare={handleCloseCompare}
|
||||
/>
|
||||
|
||||
{/* Tab 切换区域:概览、行情、财务、预测 */}
|
||||
|
||||
Reference in New Issue
Block a user