- TAB_API_MAP 改为数组形式,支持一个 Tab 加载多个 API - strategy Tab 现在同时加载 comprehensive 和 industryRank 数据 - loadTabData 更新为遍历加载所有映射的 API - currentLoading 计算改为检查任一相关 API 的 loading 状态 - 初始加载逻辑更新为加载 strategy Tab 的所有数据 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
158 lines
3.9 KiB
TypeScript
158 lines
3.9 KiB
TypeScript
/**
|
||
* useDeepAnalysisData Hook
|
||
*
|
||
* 管理深度分析模块的数据获取逻辑:
|
||
* - 按 Tab 懒加载数据
|
||
* - 已加载数据缓存,避免重复请求
|
||
* - 竞态条件处理
|
||
*/
|
||
|
||
import { useState, useCallback, useRef, useEffect } from "react";
|
||
import axios from "@utils/axiosConfig";
|
||
import { logger } from "@utils/logger";
|
||
import type {
|
||
ApiKey,
|
||
ApiLoadingState,
|
||
DataState,
|
||
UseDeepAnalysisDataReturn,
|
||
} from "../types";
|
||
import { TAB_API_MAP } from "../types";
|
||
|
||
/** API 端点映射 */
|
||
const API_ENDPOINTS: Record<ApiKey, string> = {
|
||
comprehensive: "/api/company/comprehensive-analysis",
|
||
valueChain: "/api/company/value-chain-analysis",
|
||
keyFactors: "/api/company/key-factors-timeline",
|
||
industryRank: "/api/financial/industry-rank",
|
||
};
|
||
|
||
/** 初始数据状态 */
|
||
const initialDataState: DataState = {
|
||
comprehensive: null,
|
||
valueChain: null,
|
||
keyFactors: null,
|
||
industryRank: null,
|
||
};
|
||
|
||
/** 初始 loading 状态 */
|
||
const initialLoadingState: ApiLoadingState = {
|
||
comprehensive: false,
|
||
valueChain: false,
|
||
keyFactors: false,
|
||
industryRank: false,
|
||
};
|
||
|
||
/**
|
||
* 深度分析数据 Hook
|
||
*
|
||
* @param stockCode 股票代码
|
||
* @returns 数据、loading 状态、加载函数
|
||
*/
|
||
export const useDeepAnalysisData = (
|
||
stockCode: string
|
||
): UseDeepAnalysisDataReturn => {
|
||
// 数据状态
|
||
const [data, setData] = useState<DataState>(initialDataState);
|
||
|
||
// Loading 状态
|
||
const [loading, setLoading] = useState<ApiLoadingState>(initialLoadingState);
|
||
|
||
// 已加载的接口记录
|
||
const loadedApisRef = useRef<Record<ApiKey, boolean>>({
|
||
comprehensive: false,
|
||
valueChain: false,
|
||
keyFactors: false,
|
||
industryRank: false,
|
||
});
|
||
|
||
// 当前 stockCode(用于竞态条件检测)
|
||
const currentStockCodeRef = useRef(stockCode);
|
||
|
||
/**
|
||
* 加载指定 API 数据
|
||
*/
|
||
const loadApiData = useCallback(
|
||
async (apiKey: ApiKey) => {
|
||
if (!stockCode) return;
|
||
|
||
// 已加载则跳过
|
||
if (loadedApisRef.current[apiKey]) return;
|
||
|
||
// 设置 loading
|
||
setLoading((prev) => ({ ...prev, [apiKey]: true }));
|
||
|
||
try {
|
||
const endpoint = `${API_ENDPOINTS[apiKey]}/${stockCode}`;
|
||
const { data: response } = await axios.get(endpoint);
|
||
|
||
// 检查 stockCode 是否已变更(防止竞态)
|
||
if (currentStockCodeRef.current !== stockCode) return;
|
||
|
||
if (response.success) {
|
||
setData((prev) => ({ ...prev, [apiKey]: response.data }));
|
||
loadedApisRef.current[apiKey] = true;
|
||
}
|
||
} catch (err) {
|
||
logger.error("DeepAnalysis", `loadApiData:${apiKey}`, err, {
|
||
stockCode,
|
||
});
|
||
} finally {
|
||
// 清除 loading(再次检查 stockCode)
|
||
if (currentStockCodeRef.current === stockCode) {
|
||
setLoading((prev) => ({ ...prev, [apiKey]: false }));
|
||
}
|
||
}
|
||
},
|
||
[stockCode]
|
||
);
|
||
|
||
/**
|
||
* 根据 Tab 加载对应数据(支持一个 Tab 对应多个 API)
|
||
*/
|
||
const loadTabData = useCallback(
|
||
(tabKey: string) => {
|
||
const apiKeys = TAB_API_MAP[tabKey];
|
||
if (apiKeys && apiKeys.length > 0) {
|
||
apiKeys.forEach((apiKey) => loadApiData(apiKey));
|
||
}
|
||
},
|
||
[loadApiData]
|
||
);
|
||
|
||
/**
|
||
* 重置所有数据
|
||
*/
|
||
const resetData = useCallback(() => {
|
||
setData(initialDataState);
|
||
setLoading(initialLoadingState);
|
||
loadedApisRef.current = {
|
||
comprehensive: false,
|
||
valueChain: false,
|
||
keyFactors: false,
|
||
industryRank: false,
|
||
};
|
||
}, []);
|
||
|
||
// stockCode 变更时重置并加载默认数据
|
||
useEffect(() => {
|
||
if (stockCode) {
|
||
currentStockCodeRef.current = stockCode;
|
||
resetData();
|
||
// 加载默认 Tab (strategy) 所需的所有数据
|
||
const defaultTabApis = TAB_API_MAP["strategy"];
|
||
if (defaultTabApis) {
|
||
defaultTabApis.forEach((apiKey) => loadApiData(apiKey));
|
||
}
|
||
}
|
||
}, [stockCode, loadApiData, resetData]);
|
||
|
||
return {
|
||
data,
|
||
loading,
|
||
loadTabData,
|
||
resetData,
|
||
};
|
||
};
|
||
|
||
export default useDeepAnalysisData;
|