refactor(Company): fetch 请求迁移至 axios

- DeepAnalysis: 4 个 fetch → axios
- DynamicTracking: 3 个 fetch → axios (NewsPanel, ForecastPanel)
- MarketDataView/services: 4 个 fetch → axios
- CompanyOverview/hooks: 9 个 fetch → axios (6 个文件)
- StockQuoteCard/hooks: 1 个 fetch → axios
- ValueChainNodeCard: 1 个 fetch → axios

清理:
- 删除未使用的 useCompanyOverviewData.ts
- 移除所有 getApiBase/API_BASE_URL 引用

总计: 22 个 fetch 调用迁移, 复用项目已有的 axios 拦截器配置

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
zdl
2025-12-17 11:54:32 +08:00
parent c4900bd280
commit c83d239219
13 changed files with 62 additions and 257 deletions

View File

@@ -32,12 +32,10 @@ import {
FaStar,
} from 'react-icons/fa';
import { logger } from '@utils/logger';
import { getApiBase } from '@utils/apiConfig';
import axios from '@utils/axiosConfig';
import RelatedCompaniesModal from './RelatedCompaniesModal';
import type { ValueChainNodeCardProps, RelatedCompany } from '../../types';
const API_BASE_URL = getApiBase();
// 黑金主题配置
const THEME = {
cardBg: 'gray.700',
@@ -120,12 +118,11 @@ const ValueChainNodeCard: React.FC<ValueChainNodeCardProps> = memo(({
const fetchRelatedCompanies = async () => {
setLoadingRelated(true);
try {
const response = await fetch(
`${API_BASE_URL}/api/company/value-chain/related-companies?node_name=${encodeURIComponent(
const { data } = await axios.get(
`/api/company/value-chain/related-companies?node_name=${encodeURIComponent(
node.node_name
)}`
);
const data = await response.json();
if (data.success) {
setRelatedCompanies(data.data || []);
} else {

View File

@@ -3,11 +3,9 @@
import { useState, useEffect, useCallback } from "react";
import { logger } from "@utils/logger";
import { getApiBase } from "@utils/apiConfig";
import axios from "@utils/axiosConfig";
import type { Announcement } from "../types";
const API_BASE_URL = getApiBase();
interface ApiResponse<T> {
success: boolean;
data: T;
@@ -35,10 +33,9 @@ export const useAnnouncementsData = (stockCode?: string): UseAnnouncementsDataRe
setError(null);
try {
const response = await fetch(
`${API_BASE_URL}/api/stock/${stockCode}/announcements?limit=20`
const { data: result } = await axios.get<ApiResponse<Announcement[]>>(
`/api/stock/${stockCode}/announcements?limit=20`
);
const result = (await response.json()) as ApiResponse<Announcement[]>;
if (result.success) {
setAnnouncements(result.data);

View File

@@ -3,11 +3,9 @@
import { useState, useEffect, useCallback } from "react";
import { logger } from "@utils/logger";
import { getApiBase } from "@utils/apiConfig";
import axios from "@utils/axiosConfig";
import type { BasicInfo } from "../types";
const API_BASE_URL = getApiBase();
interface ApiResponse<T> {
success: boolean;
data: T;
@@ -35,8 +33,9 @@ export const useBasicInfo = (stockCode?: string): UseBasicInfoResult => {
setError(null);
try {
const response = await fetch(`${API_BASE_URL}/api/stock/${stockCode}/basic-info`);
const result = (await response.json()) as ApiResponse<BasicInfo>;
const { data: result } = await axios.get<ApiResponse<BasicInfo>>(
`/api/stock/${stockCode}/basic-info`
);
if (result.success) {
setBasicInfo(result.data);

View File

@@ -3,11 +3,9 @@
import { useState, useEffect, useCallback } from "react";
import { logger } from "@utils/logger";
import { getApiBase } from "@utils/apiConfig";
import axios from "@utils/axiosConfig";
import type { Branch } from "../types";
const API_BASE_URL = getApiBase();
interface ApiResponse<T> {
success: boolean;
data: T;
@@ -35,8 +33,9 @@ export const useBranchesData = (stockCode?: string): UseBranchesDataResult => {
setError(null);
try {
const response = await fetch(`${API_BASE_URL}/api/stock/${stockCode}/branches`);
const result = (await response.json()) as ApiResponse<Branch[]>;
const { data: result } = await axios.get<ApiResponse<Branch[]>>(
`/api/stock/${stockCode}/branches`
);
if (result.success) {
setBranches(result.data);

View File

@@ -1,140 +0,0 @@
// src/views/Company/components/CompanyOverview/hooks/useCompanyOverviewData.ts
// 公司概览数据加载 Hook
import { useState, useEffect, useCallback } from "react";
import { logger } from "@utils/logger";
import { getApiBase } from "@utils/apiConfig";
import type {
BasicInfo,
ActualControl,
Concentration,
Management,
Shareholder,
Branch,
Announcement,
DisclosureSchedule,
CompanyOverviewData,
} from "../types";
const API_BASE_URL = getApiBase();
interface ApiResponse<T> {
success: boolean;
data: T;
}
/**
* 公司概览数据加载 Hook
* @param propStockCode - 股票代码
* @returns 公司概览数据
*/
export const useCompanyOverviewData = (propStockCode?: string): CompanyOverviewData => {
const [stockCode, setStockCode] = useState(propStockCode || "000001");
const [loading, setLoading] = useState(false);
const [dataLoaded, setDataLoaded] = useState(false);
// 基本信息数据
const [basicInfo, setBasicInfo] = useState<BasicInfo | null>(null);
const [actualControl, setActualControl] = useState<ActualControl[]>([]);
const [concentration, setConcentration] = useState<Concentration[]>([]);
const [management, setManagement] = useState<Management[]>([]);
const [topCirculationShareholders, setTopCirculationShareholders] = useState<Shareholder[]>([]);
const [topShareholders, setTopShareholders] = useState<Shareholder[]>([]);
const [branches, setBranches] = useState<Branch[]>([]);
const [announcements, setAnnouncements] = useState<Announcement[]>([]);
const [disclosureSchedule, setDisclosureSchedule] = useState<DisclosureSchedule[]>([]);
// 监听 props 中的 stockCode 变化
useEffect(() => {
if (propStockCode && propStockCode !== stockCode) {
setStockCode(propStockCode);
setDataLoaded(false);
}
}, [propStockCode, stockCode]);
// 加载基本信息数据9个接口
const loadBasicInfoData = useCallback(async () => {
if (dataLoaded) return;
setLoading(true);
try {
const [
basicRes,
actualRes,
concentrationRes,
managementRes,
circulationRes,
shareholdersRes,
branchesRes,
announcementsRes,
disclosureRes,
] = await Promise.all([
fetch(`${API_BASE_URL}/api/stock/${stockCode}/basic-info`).then((r) =>
r.json()
) as Promise<ApiResponse<BasicInfo>>,
fetch(`${API_BASE_URL}/api/stock/${stockCode}/actual-control`).then((r) =>
r.json()
) as Promise<ApiResponse<ActualControl[]>>,
fetch(`${API_BASE_URL}/api/stock/${stockCode}/concentration`).then((r) =>
r.json()
) as Promise<ApiResponse<Concentration[]>>,
fetch(`${API_BASE_URL}/api/stock/${stockCode}/management?active_only=true`).then((r) =>
r.json()
) as Promise<ApiResponse<Management[]>>,
fetch(`${API_BASE_URL}/api/stock/${stockCode}/top-circulation-shareholders?limit=10`).then((r) =>
r.json()
) as Promise<ApiResponse<Shareholder[]>>,
fetch(`${API_BASE_URL}/api/stock/${stockCode}/top-shareholders?limit=10`).then((r) =>
r.json()
) as Promise<ApiResponse<Shareholder[]>>,
fetch(`${API_BASE_URL}/api/stock/${stockCode}/branches`).then((r) =>
r.json()
) as Promise<ApiResponse<Branch[]>>,
fetch(`${API_BASE_URL}/api/stock/${stockCode}/announcements?limit=20`).then((r) =>
r.json()
) as Promise<ApiResponse<Announcement[]>>,
fetch(`${API_BASE_URL}/api/stock/${stockCode}/disclosure-schedule`).then((r) =>
r.json()
) as Promise<ApiResponse<DisclosureSchedule[]>>,
]);
if (basicRes.success) setBasicInfo(basicRes.data);
if (actualRes.success) setActualControl(actualRes.data);
if (concentrationRes.success) setConcentration(concentrationRes.data);
if (managementRes.success) setManagement(managementRes.data);
if (circulationRes.success) setTopCirculationShareholders(circulationRes.data);
if (shareholdersRes.success) setTopShareholders(shareholdersRes.data);
if (branchesRes.success) setBranches(branchesRes.data);
if (announcementsRes.success) setAnnouncements(announcementsRes.data);
if (disclosureRes.success) setDisclosureSchedule(disclosureRes.data);
setDataLoaded(true);
} catch (err) {
logger.error("useCompanyOverviewData", "loadBasicInfoData", err, { stockCode });
} finally {
setLoading(false);
}
}, [stockCode, dataLoaded]);
// 首次加载
useEffect(() => {
if (stockCode) {
loadBasicInfoData();
}
}, [stockCode, loadBasicInfoData]);
return {
basicInfo,
actualControl,
concentration,
management,
topCirculationShareholders,
topShareholders,
branches,
announcements,
disclosureSchedule,
loading,
dataLoaded,
};
};

View File

@@ -3,11 +3,9 @@
import { useState, useEffect, useCallback } from "react";
import { logger } from "@utils/logger";
import { getApiBase } from "@utils/apiConfig";
import axios from "@utils/axiosConfig";
import type { DisclosureSchedule } from "../types";
const API_BASE_URL = getApiBase();
interface ApiResponse<T> {
success: boolean;
data: T;
@@ -35,10 +33,9 @@ export const useDisclosureData = (stockCode?: string): UseDisclosureDataResult =
setError(null);
try {
const response = await fetch(
`${API_BASE_URL}/api/stock/${stockCode}/disclosure-schedule`
const { data: result } = await axios.get<ApiResponse<DisclosureSchedule[]>>(
`/api/stock/${stockCode}/disclosure-schedule`
);
const result = (await response.json()) as ApiResponse<DisclosureSchedule[]>;
if (result.success) {
setDisclosureSchedule(result.data);

View File

@@ -3,11 +3,9 @@
import { useState, useEffect, useCallback } from "react";
import { logger } from "@utils/logger";
import { getApiBase } from "@utils/apiConfig";
import axios from "@utils/axiosConfig";
import type { Management } from "../types";
const API_BASE_URL = getApiBase();
interface ApiResponse<T> {
success: boolean;
data: T;
@@ -35,10 +33,9 @@ export const useManagementData = (stockCode?: string): UseManagementDataResult =
setError(null);
try {
const response = await fetch(
`${API_BASE_URL}/api/stock/${stockCode}/management?active_only=true`
const { data: result } = await axios.get<ApiResponse<Management[]>>(
`/api/stock/${stockCode}/management?active_only=true`
);
const result = (await response.json()) as ApiResponse<Management[]>;
if (result.success) {
setManagement(result.data);

View File

@@ -3,11 +3,9 @@
import { useState, useEffect, useCallback } from "react";
import { logger } from "@utils/logger";
import { getApiBase } from "@utils/apiConfig";
import axios from "@utils/axiosConfig";
import type { ActualControl, Concentration, Shareholder } from "../types";
const API_BASE_URL = getApiBase();
interface ApiResponse<T> {
success: boolean;
data: T;
@@ -41,19 +39,16 @@ export const useShareholderData = (stockCode?: string): UseShareholderDataResult
setError(null);
try {
const [actualRes, concentrationRes, shareholdersRes, circulationRes] = await Promise.all([
fetch(`${API_BASE_URL}/api/stock/${stockCode}/actual-control`).then((r) =>
r.json()
) as Promise<ApiResponse<ActualControl[]>>,
fetch(`${API_BASE_URL}/api/stock/${stockCode}/concentration`).then((r) =>
r.json()
) as Promise<ApiResponse<Concentration[]>>,
fetch(`${API_BASE_URL}/api/stock/${stockCode}/top-shareholders?limit=10`).then((r) =>
r.json()
) as Promise<ApiResponse<Shareholder[]>>,
fetch(`${API_BASE_URL}/api/stock/${stockCode}/top-circulation-shareholders?limit=10`).then((r) =>
r.json()
) as Promise<ApiResponse<Shareholder[]>>,
const [
{ data: actualRes },
{ data: concentrationRes },
{ data: shareholdersRes },
{ data: circulationRes },
] = await Promise.all([
axios.get<ApiResponse<ActualControl[]>>(`/api/stock/${stockCode}/actual-control`),
axios.get<ApiResponse<Concentration[]>>(`/api/stock/${stockCode}/concentration`),
axios.get<ApiResponse<Shareholder[]>>(`/api/stock/${stockCode}/top-shareholders?limit=10`),
axios.get<ApiResponse<Shareholder[]>>(`/api/stock/${stockCode}/top-circulation-shareholders?limit=10`),
]);
if (actualRes.success) setActualControl(actualRes.data);

View File

@@ -3,13 +3,11 @@
import React, { useState, useEffect, useCallback, useRef } from "react";
import { logger } from "@utils/logger";
import { getApiBase } from "@utils/apiConfig";
import axios from "@utils/axiosConfig";
// 复用原有的展示组件
import DeepAnalysisTab from "../CompanyOverview/DeepAnalysisTab";
const API_BASE_URL = getApiBase();
/**
* Tab 与 API 接口映射
* - strategy 和 business 共用 comprehensive 接口
@@ -84,9 +82,9 @@ const DeepAnalysis = ({ stockCode }) => {
switch (apiKey) {
case "comprehensive":
setComprehensiveLoading(true);
const comprehensiveRes = await fetch(
`${API_BASE_URL}/api/company/comprehensive-analysis/${stockCode}`
).then((r) => r.json());
const { data: comprehensiveRes } = await axios.get(
`/api/company/comprehensive-analysis/${stockCode}`
);
// 检查 stockCode 是否已变更(防止竞态)
if (currentStockCodeRef.current === stockCode) {
if (comprehensiveRes.success)
@@ -97,9 +95,9 @@ const DeepAnalysis = ({ stockCode }) => {
case "valueChain":
setValueChainLoading(true);
const valueChainRes = await fetch(
`${API_BASE_URL}/api/company/value-chain-analysis/${stockCode}`
).then((r) => r.json());
const { data: valueChainRes } = await axios.get(
`/api/company/value-chain-analysis/${stockCode}`
);
if (currentStockCodeRef.current === stockCode) {
if (valueChainRes.success) setValueChainData(valueChainRes.data);
loadedApisRef.current.valueChain = true;
@@ -108,9 +106,9 @@ const DeepAnalysis = ({ stockCode }) => {
case "keyFactors":
setKeyFactorsLoading(true);
const keyFactorsRes = await fetch(
`${API_BASE_URL}/api/company/key-factors-timeline/${stockCode}`
).then((r) => r.json());
const { data: keyFactorsRes } = await axios.get(
`/api/company/key-factors-timeline/${stockCode}`
);
if (currentStockCodeRef.current === stockCode) {
if (keyFactorsRes.success) setKeyFactorsData(keyFactorsRes.data);
loadedApisRef.current.keyFactors = true;
@@ -119,9 +117,9 @@ const DeepAnalysis = ({ stockCode }) => {
case "industryRank":
setIndustryRankLoading(true);
const industryRankRes = await fetch(
`${API_BASE_URL}/api/financial/industry-rank/${stockCode}`
).then((r) => r.json());
const { data: industryRankRes } = await axios.get(
`/api/financial/industry-rank/${stockCode}`
);
if (currentStockCodeRef.current === stockCode) {
if (industryRankRes.success) setIndustryRankData(industryRankRes.data);
loadedApisRef.current.industryRank = true;

View File

@@ -12,9 +12,7 @@ import {
} from '@chakra-ui/react';
import { Tag } from 'antd';
import { logger } from '@utils/logger';
import { getApiBase } from '@utils/apiConfig';
const API_BASE_URL = getApiBase();
import axios from '@utils/axiosConfig';
// 黑金主题
const THEME = {
@@ -53,10 +51,9 @@ const ForecastPanel = ({ stockCode }) => {
setLoading(true);
try {
const response = await fetch(
`${API_BASE_URL}/api/stock/${stockCode}/forecast`
const { data: result } = await axios.get(
`/api/stock/${stockCode}/forecast`
);
const result = await response.json();
if (result.success && result.data) {
setForecast(result.data);
}

View File

@@ -3,11 +3,9 @@
import React, { useState, useEffect, useCallback } from 'react';
import { logger } from '@utils/logger';
import { getApiBase } from '@utils/apiConfig';
import axios from '@utils/axiosConfig';
import NewsEventsTab from '../../CompanyOverview/NewsEventsTab';
const API_BASE_URL = getApiBase();
const NewsPanel = ({ stockCode }) => {
const [newsEvents, setNewsEvents] = useState([]);
const [loading, setLoading] = useState(false);
@@ -25,10 +23,9 @@ const NewsPanel = ({ stockCode }) => {
// 获取股票名称
const fetchStockName = useCallback(async () => {
try {
const response = await fetch(
`${API_BASE_URL}/api/stock/${stockCode}/basic-info`
const { data: result } = await axios.get(
`/api/stock/${stockCode}/basic-info`
);
const result = await response.json();
if (result.success && result.data) {
const name = result.data.SECNAME || result.data.ORGNAME || stockCode;
setStockName(name);
@@ -47,10 +44,9 @@ const NewsPanel = ({ stockCode }) => {
setLoading(true);
try {
const searchTerm = query || stockName || stockCode;
const response = await fetch(
`${API_BASE_URL}/api/events?q=${encodeURIComponent(searchTerm)}&page=${page}&per_page=10`
const { data: result } = await axios.get(
`/api/events?q=${encodeURIComponent(searchTerm)}&page=${page}&per_page=10`
);
const result = await response.json();
if (result.success) {
setNewsEvents(result.data || []);

View File

@@ -1,7 +1,7 @@
// src/views/Company/components/MarketDataView/services/marketService.ts
// MarketDataView API 服务层
import { getApiBase } from '@utils/apiConfig';
import axios from '@utils/axiosConfig';
import { logger } from '@utils/logger';
import type {
MarketSummary,
@@ -23,21 +23,13 @@ interface ApiResponse<T> {
message?: string;
}
/**
* API 基础 URL
*/
const getBaseUrl = (): string => getApiBase();
/**
* 通用 API 请求函数
*/
const apiRequest = async <T>(url: string): Promise<ApiResponse<T>> => {
try {
const response = await fetch(`${getBaseUrl()}${url}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
const { data } = await axios.get<ApiResponse<T>>(url);
return data;
} catch (error) {
logger.error('marketService', 'apiRequest', error, { url });
throw error;
@@ -80,11 +72,8 @@ export const marketService = {
* @param days 天数,默认 30 天
*/
async getBigDealData(stockCode: string, days: number = 30): Promise<BigDealData> {
const response = await fetch(`${getBaseUrl()}/api/market/bigdeal/${stockCode}?days=${days}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
const { data } = await axios.get<BigDealData>(`/api/market/bigdeal/${stockCode}?days=${days}`);
return data;
},
/**
@@ -93,11 +82,8 @@ export const marketService = {
* @param days 天数,默认 30 天
*/
async getUnusualData(stockCode: string, days: number = 30): Promise<UnusualData> {
const response = await fetch(`${getBaseUrl()}/api/market/unusual/${stockCode}?days=${days}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
const { data } = await axios.get<UnusualData>(`/api/market/unusual/${stockCode}?days=${days}`);
return data;
},
/**
@@ -132,18 +118,8 @@ export const marketService = {
*/
async getMinuteData(stockCode: string): Promise<MinuteData> {
try {
const response = await fetch(`${getBaseUrl()}/api/stock/${stockCode}/latest-minute`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
const { data } = await axios.get<MinuteData>(`/api/stock/${stockCode}/latest-minute`);
if (!response.ok) {
throw new Error('Failed to fetch minute data');
}
const data = await response.json();
if (data.data && Array.isArray(data.data)) {
return data;
}

View File

@@ -7,12 +7,10 @@
import { useState, useEffect, useCallback } from 'react';
import { stockService } from '@services/eventService';
import { logger } from '@utils/logger';
import { getApiBase } from '@utils/apiConfig';
import axios from '@utils/axiosConfig';
import type { StockQuoteCardData } from '../types';
import type { BasicInfo } from '../../CompanyOverview/types';
const API_BASE_URL = getApiBase();
/**
* 将 API 响应数据转换为 StockQuoteCard 所需格式
*/
@@ -114,8 +112,7 @@ export const useStockQuoteData = (stockCode?: string): UseStockQuoteDataResult =
setBasicLoading(true);
try {
const response = await fetch(`${API_BASE_URL}/api/stock/${stockCode}/basic-info`);
const result = await response.json();
const { data: result } = await axios.get(`/api/stock/${stockCode}/basic-info`);
if (result.success) {
setBasicInfo(result.data);