diff --git a/src/views/Company/components/CompanyOverview/hooks/useAnnouncementsData.ts b/src/views/Company/components/CompanyOverview/hooks/useAnnouncementsData.ts index bde0c708..ca26c6a0 100644 --- a/src/views/Company/components/CompanyOverview/hooks/useAnnouncementsData.ts +++ b/src/views/Company/components/CompanyOverview/hooks/useAnnouncementsData.ts @@ -1,7 +1,7 @@ // src/views/Company/components/CompanyOverview/hooks/useAnnouncementsData.ts // 公告数据 Hook - 用于公司公告 Tab -import { useState, useEffect, useCallback } from "react"; +import { useState, useEffect } from "react"; import { logger } from "@utils/logger"; import axios from "@utils/axiosConfig"; import type { Announcement } from "../types"; @@ -26,33 +26,38 @@ export const useAnnouncementsData = (stockCode?: string): UseAnnouncementsDataRe const [loading, setLoading] = useState(false); const [error, setError] = useState(null); - const loadData = useCallback(async () => { + useEffect(() => { if (!stockCode) return; - setLoading(true); - setError(null); + const controller = new AbortController(); - try { - const { data: result } = await axios.get>( - `/api/stock/${stockCode}/announcements?limit=20` - ); + const loadData = async () => { + setLoading(true); + setError(null); - if (result.success) { - setAnnouncements(result.data); - } else { - setError("加载公告数据失败"); + try { + const { data: result } = await axios.get>( + `/api/stock/${stockCode}/announcements?limit=20`, + { signal: controller.signal } + ); + + if (result.success) { + setAnnouncements(result.data); + } else { + setError("加载公告数据失败"); + } + } catch (err: any) { + if (err.name === "CanceledError") return; + logger.error("useAnnouncementsData", "loadData", err, { stockCode }); + setError("网络请求失败"); + } finally { + setLoading(false); } - } catch (err) { - logger.error("useAnnouncementsData", "loadData", err, { stockCode }); - setError("网络请求失败"); - } finally { - setLoading(false); - } - }, [stockCode]); + }; - useEffect(() => { loadData(); - }, [loadData]); + return () => controller.abort(); + }, [stockCode]); return { announcements, loading, error }; }; diff --git a/src/views/Company/components/CompanyOverview/hooks/useBasicInfo.ts b/src/views/Company/components/CompanyOverview/hooks/useBasicInfo.ts index d602101e..476b52d6 100644 --- a/src/views/Company/components/CompanyOverview/hooks/useBasicInfo.ts +++ b/src/views/Company/components/CompanyOverview/hooks/useBasicInfo.ts @@ -1,7 +1,7 @@ // src/views/Company/components/CompanyOverview/hooks/useBasicInfo.ts // 公司基本信息 Hook - 用于 CompanyHeaderCard -import { useState, useEffect, useCallback } from "react"; +import { useState, useEffect } from "react"; import { logger } from "@utils/logger"; import axios from "@utils/axiosConfig"; import type { BasicInfo } from "../types"; @@ -26,33 +26,38 @@ export const useBasicInfo = (stockCode?: string): UseBasicInfoResult => { const [loading, setLoading] = useState(false); const [error, setError] = useState(null); - const loadData = useCallback(async () => { + useEffect(() => { if (!stockCode) return; - setLoading(true); - setError(null); + const controller = new AbortController(); - try { - const { data: result } = await axios.get>( - `/api/stock/${stockCode}/basic-info` - ); + const loadData = async () => { + setLoading(true); + setError(null); - if (result.success) { - setBasicInfo(result.data); - } else { - setError("加载基本信息失败"); + try { + const { data: result } = await axios.get>( + `/api/stock/${stockCode}/basic-info`, + { signal: controller.signal } + ); + + if (result.success) { + setBasicInfo(result.data); + } else { + setError("加载基本信息失败"); + } + } catch (err: any) { + if (err.name === "CanceledError") return; + logger.error("useBasicInfo", "loadData", err, { stockCode }); + setError("网络请求失败"); + } finally { + setLoading(false); } - } catch (err) { - logger.error("useBasicInfo", "loadData", err, { stockCode }); - setError("网络请求失败"); - } finally { - setLoading(false); - } - }, [stockCode]); + }; - useEffect(() => { loadData(); - }, [loadData]); + return () => controller.abort(); + }, [stockCode]); return { basicInfo, loading, error }; }; diff --git a/src/views/Company/components/CompanyOverview/hooks/useBranchesData.ts b/src/views/Company/components/CompanyOverview/hooks/useBranchesData.ts index d7c0a58e..f50b72fb 100644 --- a/src/views/Company/components/CompanyOverview/hooks/useBranchesData.ts +++ b/src/views/Company/components/CompanyOverview/hooks/useBranchesData.ts @@ -1,7 +1,7 @@ // src/views/Company/components/CompanyOverview/hooks/useBranchesData.ts // 分支机构数据 Hook - 用于分支机构 Tab -import { useState, useEffect, useCallback } from "react"; +import { useState, useEffect } from "react"; import { logger } from "@utils/logger"; import axios from "@utils/axiosConfig"; import type { Branch } from "../types"; @@ -26,33 +26,38 @@ export const useBranchesData = (stockCode?: string): UseBranchesDataResult => { const [loading, setLoading] = useState(false); const [error, setError] = useState(null); - const loadData = useCallback(async () => { + useEffect(() => { if (!stockCode) return; - setLoading(true); - setError(null); + const controller = new AbortController(); - try { - const { data: result } = await axios.get>( - `/api/stock/${stockCode}/branches` - ); + const loadData = async () => { + setLoading(true); + setError(null); - if (result.success) { - setBranches(result.data); - } else { - setError("加载分支机构数据失败"); + try { + const { data: result } = await axios.get>( + `/api/stock/${stockCode}/branches`, + { signal: controller.signal } + ); + + if (result.success) { + setBranches(result.data); + } else { + setError("加载分支机构数据失败"); + } + } catch (err: any) { + if (err.name === "CanceledError") return; + logger.error("useBranchesData", "loadData", err, { stockCode }); + setError("网络请求失败"); + } finally { + setLoading(false); } - } catch (err) { - logger.error("useBranchesData", "loadData", err, { stockCode }); - setError("网络请求失败"); - } finally { - setLoading(false); - } - }, [stockCode]); + }; - useEffect(() => { loadData(); - }, [loadData]); + return () => controller.abort(); + }, [stockCode]); return { branches, loading, error }; }; diff --git a/src/views/Company/components/CompanyOverview/hooks/useDisclosureData.ts b/src/views/Company/components/CompanyOverview/hooks/useDisclosureData.ts index a573cced..31a9e185 100644 --- a/src/views/Company/components/CompanyOverview/hooks/useDisclosureData.ts +++ b/src/views/Company/components/CompanyOverview/hooks/useDisclosureData.ts @@ -1,7 +1,7 @@ // src/views/Company/components/CompanyOverview/hooks/useDisclosureData.ts // 披露日程数据 Hook - 用于工商信息 Tab -import { useState, useEffect, useCallback } from "react"; +import { useState, useEffect } from "react"; import { logger } from "@utils/logger"; import axios from "@utils/axiosConfig"; import type { DisclosureSchedule } from "../types"; @@ -26,33 +26,38 @@ export const useDisclosureData = (stockCode?: string): UseDisclosureDataResult = const [loading, setLoading] = useState(false); const [error, setError] = useState(null); - const loadData = useCallback(async () => { + useEffect(() => { if (!stockCode) return; - setLoading(true); - setError(null); + const controller = new AbortController(); - try { - const { data: result } = await axios.get>( - `/api/stock/${stockCode}/disclosure-schedule` - ); + const loadData = async () => { + setLoading(true); + setError(null); - if (result.success) { - setDisclosureSchedule(result.data); - } else { - setError("加载披露日程数据失败"); + try { + const { data: result } = await axios.get>( + `/api/stock/${stockCode}/disclosure-schedule`, + { signal: controller.signal } + ); + + if (result.success) { + setDisclosureSchedule(result.data); + } else { + setError("加载披露日程数据失败"); + } + } catch (err: any) { + if (err.name === "CanceledError") return; + logger.error("useDisclosureData", "loadData", err, { stockCode }); + setError("网络请求失败"); + } finally { + setLoading(false); } - } catch (err) { - logger.error("useDisclosureData", "loadData", err, { stockCode }); - setError("网络请求失败"); - } finally { - setLoading(false); - } - }, [stockCode]); + }; - useEffect(() => { loadData(); - }, [loadData]); + return () => controller.abort(); + }, [stockCode]); return { disclosureSchedule, loading, error }; }; diff --git a/src/views/Company/components/CompanyOverview/hooks/useManagementData.ts b/src/views/Company/components/CompanyOverview/hooks/useManagementData.ts index 420aa2ca..8dca8d70 100644 --- a/src/views/Company/components/CompanyOverview/hooks/useManagementData.ts +++ b/src/views/Company/components/CompanyOverview/hooks/useManagementData.ts @@ -1,7 +1,7 @@ // src/views/Company/components/CompanyOverview/hooks/useManagementData.ts // 管理团队数据 Hook - 用于管理团队 Tab -import { useState, useEffect, useCallback } from "react"; +import { useState, useEffect } from "react"; import { logger } from "@utils/logger"; import axios from "@utils/axiosConfig"; import type { Management } from "../types"; @@ -26,33 +26,38 @@ export const useManagementData = (stockCode?: string): UseManagementDataResult = const [loading, setLoading] = useState(false); const [error, setError] = useState(null); - const loadData = useCallback(async () => { + useEffect(() => { if (!stockCode) return; - setLoading(true); - setError(null); + const controller = new AbortController(); - try { - const { data: result } = await axios.get>( - `/api/stock/${stockCode}/management?active_only=true` - ); + const loadData = async () => { + setLoading(true); + setError(null); - if (result.success) { - setManagement(result.data); - } else { - setError("加载管理团队数据失败"); + try { + const { data: result } = await axios.get>( + `/api/stock/${stockCode}/management?active_only=true`, + { signal: controller.signal } + ); + + if (result.success) { + setManagement(result.data); + } else { + setError("加载管理团队数据失败"); + } + } catch (err: any) { + if (err.name === "CanceledError") return; + logger.error("useManagementData", "loadData", err, { stockCode }); + setError("网络请求失败"); + } finally { + setLoading(false); } - } catch (err) { - logger.error("useManagementData", "loadData", err, { stockCode }); - setError("网络请求失败"); - } finally { - setLoading(false); - } - }, [stockCode]); + }; - useEffect(() => { loadData(); - }, [loadData]); + return () => controller.abort(); + }, [stockCode]); return { management, loading, error }; }; diff --git a/src/views/Company/components/CompanyOverview/hooks/useShareholderData.ts b/src/views/Company/components/CompanyOverview/hooks/useShareholderData.ts index 24fba1f0..17b65f1e 100644 --- a/src/views/Company/components/CompanyOverview/hooks/useShareholderData.ts +++ b/src/views/Company/components/CompanyOverview/hooks/useShareholderData.ts @@ -1,7 +1,7 @@ // src/views/Company/components/CompanyOverview/hooks/useShareholderData.ts // 股权结构数据 Hook - 用于股权结构 Tab -import { useState, useEffect, useCallback } from "react"; +import { useState, useEffect } from "react"; import { logger } from "@utils/logger"; import axios from "@utils/axiosConfig"; import type { ActualControl, Concentration, Shareholder } from "../types"; @@ -32,40 +32,44 @@ export const useShareholderData = (stockCode?: string): UseShareholderDataResult const [loading, setLoading] = useState(false); const [error, setError] = useState(null); - const loadData = useCallback(async () => { + useEffect(() => { if (!stockCode) return; - setLoading(true); - setError(null); + const controller = new AbortController(); - try { - const [ - { data: actualRes }, - { data: concentrationRes }, - { data: shareholdersRes }, - { data: circulationRes }, - ] = await Promise.all([ - axios.get>(`/api/stock/${stockCode}/actual-control`), - axios.get>(`/api/stock/${stockCode}/concentration`), - axios.get>(`/api/stock/${stockCode}/top-shareholders?limit=10`), - axios.get>(`/api/stock/${stockCode}/top-circulation-shareholders?limit=10`), - ]); + const loadData = async () => { + setLoading(true); + setError(null); - if (actualRes.success) setActualControl(actualRes.data); - if (concentrationRes.success) setConcentration(concentrationRes.data); - if (shareholdersRes.success) setTopShareholders(shareholdersRes.data); - if (circulationRes.success) setTopCirculationShareholders(circulationRes.data); - } catch (err) { - logger.error("useShareholderData", "loadData", err, { stockCode }); - setError("加载股权结构数据失败"); - } finally { - setLoading(false); - } - }, [stockCode]); + try { + const [ + { data: actualRes }, + { data: concentrationRes }, + { data: shareholdersRes }, + { data: circulationRes }, + ] = await Promise.all([ + axios.get>(`/api/stock/${stockCode}/actual-control`, { signal: controller.signal }), + axios.get>(`/api/stock/${stockCode}/concentration`, { signal: controller.signal }), + axios.get>(`/api/stock/${stockCode}/top-shareholders?limit=10`, { signal: controller.signal }), + axios.get>(`/api/stock/${stockCode}/top-circulation-shareholders?limit=10`, { signal: controller.signal }), + ]); + + if (actualRes.success) setActualControl(actualRes.data); + if (concentrationRes.success) setConcentration(concentrationRes.data); + if (shareholdersRes.success) setTopShareholders(shareholdersRes.data); + if (circulationRes.success) setTopCirculationShareholders(circulationRes.data); + } catch (err: any) { + if (err.name === "CanceledError") return; + logger.error("useShareholderData", "loadData", err, { stockCode }); + setError("加载股权结构数据失败"); + } finally { + setLoading(false); + } + }; - useEffect(() => { loadData(); - }, [loadData]); + return () => controller.abort(); + }, [stockCode]); return { actualControl, diff --git a/src/views/Company/components/StockQuoteCard/hooks/useStockQuoteData.ts b/src/views/Company/components/StockQuoteCard/hooks/useStockQuoteData.ts index 232c9bfc..3d1e28a9 100644 --- a/src/views/Company/components/StockQuoteCard/hooks/useStockQuoteData.ts +++ b/src/views/Company/components/StockQuoteCard/hooks/useStockQuoteData.ts @@ -73,24 +73,18 @@ export const useStockQuoteData = (stockCode?: string): UseStockQuoteDataResult = const [basicLoading, setBasicLoading] = useState(false); const [error, setError] = useState(null); - // 获取行情数据 - const fetchQuote = useCallback(async () => { - if (!stockCode) { - setQuoteData(null); - return; - } + // 用于手动刷新的 ref + const refetchRef = useCallback(async () => { + if (!stockCode) 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) { @@ -100,49 +94,85 @@ export const useStockQuoteData = (stockCode?: string): UseStockQuoteDataResult = } finally { setQuoteLoading(false); } - }, [stockCode]); - - // 获取基本信息 - const fetchBasicInfo = useCallback(async () => { - if (!stockCode) { - setBasicInfo(null); - return; - } + // 获取基本信息 setBasicLoading(true); - try { const { data: result } = await axios.get(`/api/stock/${stockCode}/basic-info`); - if (result.success) { setBasicInfo(result.data); } } catch (err) { logger.error('useStockQuoteData', '获取基本信息失败', err); - // 基本信息获取失败不影响主流程,只记录日志 } finally { setBasicLoading(false); } }, [stockCode]); - // stockCode 变化时重新获取数据 + // stockCode 变化时重新获取数据(带取消支持) useEffect(() => { - fetchQuote(); - fetchBasicInfo(); - }, [fetchQuote, fetchBasicInfo]); + if (!stockCode) { + setQuoteData(null); + setBasicInfo(null); + return; + } - // 手动刷新 - const refetch = useCallback(() => { - fetchQuote(); - fetchBasicInfo(); - }, [fetchQuote, fetchBasicInfo]); + const controller = new AbortController(); + let isCancelled = false; + + const fetchData = async () => { + // 获取行情数据 + setQuoteLoading(true); + setError(null); + try { + logger.debug('useStockQuoteData', '获取股票行情', { stockCode }); + const quotes = await stockService.getQuotes([stockCode]); + if (isCancelled) return; + const quoteResult = quotes?.[stockCode] || quotes; + const transformedData = transformQuoteData(quoteResult, stockCode); + logger.debug('useStockQuoteData', '行情数据转换完成', { stockCode, hasData: !!transformedData }); + setQuoteData(transformedData); + } catch (err: any) { + if (isCancelled || err.name === 'CanceledError') return; + logger.error('useStockQuoteData', '获取行情失败', err); + setError('获取行情数据失败'); + setQuoteData(null); + } finally { + if (!isCancelled) setQuoteLoading(false); + } + + // 获取基本信息 + setBasicLoading(true); + try { + const { data: result } = await axios.get(`/api/stock/${stockCode}/basic-info`, { + signal: controller.signal, + }); + if (isCancelled) return; + if (result.success) { + setBasicInfo(result.data); + } + } catch (err: any) { + if (isCancelled || err.name === 'CanceledError') return; + logger.error('useStockQuoteData', '获取基本信息失败', err); + } finally { + if (!isCancelled) setBasicLoading(false); + } + }; + + fetchData(); + + return () => { + isCancelled = true; + controller.abort(); + }; + }, [stockCode]); return { quoteData, basicInfo, isLoading: quoteLoading || basicLoading, error, - refetch, + refetch: refetchRef, }; };