Files
vf_react/src/views/Company/components/StockQuoteCard/hooks/useStockQuoteData.ts
zdl 1730a59ca2 refactor(Company): 简化 CompanyHeader,添加详细代码注释
- CompanyHeader: 移除冗余的股票信息展示(已在 StockQuoteCard 中)
- index.tsx: 添加完整的 JSDoc 注释和架构说明
- types.ts: 简化 CompanyHeaderProps,移除不再需要的属性
- useStockQuoteData: 优化数据获取逻辑

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-19 10:58:49 +08:00

196 lines
6.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* useStockQuoteData - 股票行情数据获取 Hook
*
* 使用 /api/stock/{code}/quote-detail 接口获取完整行情数据
* 供 StockQuoteCard 内部使用
*/
import { useState, useEffect, useCallback } from 'react';
import { logger } from '@utils/logger';
import axios from '@utils/axiosConfig';
import type { StockQuoteCardData } from '../types';
import type { BasicInfo } from '../../CompanyOverview/types';
/**
* 将 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,
marketCap: apiData.market_cap || apiData.marketCap || apiData.circ_mv || '0',
totalShares: apiData.total_shares || apiData.totalShares || undefined,
floatShares: apiData.float_shares || apiData.floatShares || undefined,
turnoverRate: apiData.turnover_rate || apiData.turnoverRate || undefined,
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);
// 用于手动刷新的 ref并行请求
const refetchRef = useCallback(async () => {
if (!stockCode) return;
// 标准化股票代码(去除后缀)
const baseCode = stockCode.split('.')[0];
// 并行获取行情详情和基本信息
setQuoteLoading(true);
setBasicLoading(true);
setError(null);
logger.debug('useStockQuoteData', '刷新股票数据', { stockCode, baseCode });
try {
const [quoteResult, basicResult] = await Promise.all([
axios.get(`/api/stock/${baseCode}/quote-detail`),
axios.get(`/api/stock/${baseCode}/basic-info`),
]);
// 处理行情数据
if (quoteResult.data.success && quoteResult.data.data) {
const transformedData = transformQuoteData(quoteResult.data.data, stockCode);
logger.debug('useStockQuoteData', '行情数据转换完成', { stockCode, hasData: !!transformedData });
setQuoteData(transformedData);
} else {
setError('获取行情数据失败');
setQuoteData(null);
}
// 处理基本信息
if (basicResult.data.success) {
setBasicInfo(basicResult.data.data);
}
} catch (err) {
logger.error('useStockQuoteData', '刷新数据失败', err);
setError('刷新数据失败');
setQuoteData(null);
} finally {
setQuoteLoading(false);
setBasicLoading(false);
}
}, [stockCode]);
// stockCode 变化时重新获取数据(带取消支持)
useEffect(() => {
if (!stockCode) {
setQuoteData(null);
setBasicInfo(null);
return;
}
const controller = new AbortController();
let isCancelled = false;
// 标准化股票代码(去除后缀)
const baseCode = stockCode.split('.')[0];
const fetchData = async () => {
// 并行获取行情详情和基本信息(优化:原串行改为并行,节省 ~120ms
setQuoteLoading(true);
setBasicLoading(true);
setError(null);
logger.debug('useStockQuoteData', '并行获取股票数据', { stockCode, baseCode });
try {
const [quoteResult, basicResult] = await Promise.all([
axios.get(`/api/stock/${baseCode}/quote-detail`, { signal: controller.signal }),
axios.get(`/api/stock/${baseCode}/basic-info`, { signal: controller.signal }),
]);
if (isCancelled) return;
// 处理行情数据
if (quoteResult.data.success && quoteResult.data.data) {
const transformedData = transformQuoteData(quoteResult.data.data, stockCode);
logger.debug('useStockQuoteData', '行情数据转换完成', { stockCode, hasData: !!transformedData });
setQuoteData(transformedData);
} else {
setError('获取行情数据失败');
setQuoteData(null);
}
// 处理基本信息
if (basicResult.data.success) {
setBasicInfo(basicResult.data.data);
}
} catch (err: any) {
if (isCancelled || err.name === 'CanceledError') return;
logger.error('useStockQuoteData', '获取数据失败', err);
setError('获取数据失败');
setQuoteData(null);
} finally {
if (!isCancelled) {
setQuoteLoading(false);
setBasicLoading(false);
}
}
};
fetchData();
return () => {
isCancelled = true;
controller.abort();
};
}, [stockCode]);
return {
quoteData,
basicInfo,
isLoading: quoteLoading || basicLoading,
error,
refetch: refetchRef,
};
};
export default useStockQuoteData;