fix(types): 修复 ECharts 类型导出和组件类型冲突
- echarts.ts: 将 EChartsOption 改为 EChartsCoreOption 的类型别名 - FuiCorners: 移除 extends BoxProps,position 重命名为 corner - KLineChartModal/TimelineChartModal/ConcentrationCard: 使用导入的 EChartsOption - LoadingState: 新增骨架屏 variant 支持 - FinancialPanorama: 使用骨架屏加载状态 - useFinancialData/financialService: 优化数据获取逻辑 - Company/index: 简化组件结构 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { useToast } from '@chakra-ui/react';
|
||||
import axios from 'axios';
|
||||
import { logger } from '@utils/logger';
|
||||
import { financialService } from '@services/financialService';
|
||||
import type {
|
||||
@@ -19,6 +20,11 @@ import type {
|
||||
ComparisonData,
|
||||
} from '../types';
|
||||
|
||||
// 判断是否为取消请求的错误
|
||||
const isCancelError = (error: unknown): boolean => {
|
||||
return axios.isCancel(error) || (error instanceof Error && error.name === 'CanceledError');
|
||||
};
|
||||
|
||||
// Tab key 到数据类型的映射
|
||||
export type DataTypeKey =
|
||||
| 'balance'
|
||||
@@ -102,6 +108,10 @@ export const useFinancialData = (
|
||||
const isInitialLoad = useRef(true);
|
||||
const prevPeriods = useRef(selectedPeriods);
|
||||
|
||||
// AbortController refs - 用于取消请求
|
||||
const coreDataControllerRef = useRef<AbortController | null>(null);
|
||||
const tabDataControllerRef = useRef<AbortController | null>(null);
|
||||
|
||||
// 判断 Tab key 对应的数据类型
|
||||
const getDataTypeForTab = (tabKey: DataTypeKey): 'balance' | 'income' | 'cashflow' | 'metrics' => {
|
||||
switch (tabKey) {
|
||||
@@ -120,32 +130,36 @@ export const useFinancialData = (
|
||||
// 按数据类型加载数据
|
||||
const loadDataByType = useCallback(async (
|
||||
dataType: 'balance' | 'income' | 'cashflow' | 'metrics',
|
||||
periods: number
|
||||
periods: number,
|
||||
signal?: AbortSignal
|
||||
) => {
|
||||
const options: { signal?: AbortSignal } = signal ? { signal } : {};
|
||||
try {
|
||||
switch (dataType) {
|
||||
case 'balance': {
|
||||
const res = await financialService.getBalanceSheet(stockCode, periods);
|
||||
const res = await financialService.getBalanceSheet(stockCode, periods, options);
|
||||
if (res.success) setBalanceSheet(res.data);
|
||||
break;
|
||||
}
|
||||
case 'income': {
|
||||
const res = await financialService.getIncomeStatement(stockCode, periods);
|
||||
const res = await financialService.getIncomeStatement(stockCode, periods, options);
|
||||
if (res.success) setIncomeStatement(res.data);
|
||||
break;
|
||||
}
|
||||
case 'cashflow': {
|
||||
const res = await financialService.getCashflow(stockCode, periods);
|
||||
const res = await financialService.getCashflow(stockCode, periods, options);
|
||||
if (res.success) setCashflow(res.data);
|
||||
break;
|
||||
}
|
||||
case 'metrics': {
|
||||
const res = await financialService.getFinancialMetrics(stockCode, periods);
|
||||
const res = await financialService.getFinancialMetrics(stockCode, periods, options);
|
||||
if (res.success) setFinancialMetrics(res.data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
// 取消请求不作为错误处理
|
||||
if (isCancelError(err)) return;
|
||||
logger.error('useFinancialData', 'loadDataByType', err, { dataType, periods });
|
||||
throw err;
|
||||
}
|
||||
@@ -157,6 +171,11 @@ export const useFinancialData = (
|
||||
return;
|
||||
}
|
||||
|
||||
// 取消之前的 Tab 数据请求
|
||||
tabDataControllerRef.current?.abort();
|
||||
const controller = new AbortController();
|
||||
tabDataControllerRef.current = controller;
|
||||
|
||||
const dataType = getDataTypeForTab(tabKey);
|
||||
logger.debug('useFinancialData', '刷新单个 Tab 数据', { tabKey, dataType, selectedPeriods });
|
||||
|
||||
@@ -164,13 +183,18 @@ export const useFinancialData = (
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
await loadDataByType(dataType, selectedPeriods);
|
||||
await loadDataByType(dataType, selectedPeriods, controller.signal);
|
||||
logger.info('useFinancialData', `${tabKey} 数据刷新成功`);
|
||||
} catch (err) {
|
||||
// 取消请求不作为错误处理
|
||||
if (isCancelError(err)) return;
|
||||
const errorMessage = err instanceof Error ? err.message : '未知错误';
|
||||
setError(errorMessage);
|
||||
} finally {
|
||||
setLoadingTab(null);
|
||||
// 只有当前请求没有被取消时才设置 loading 状态
|
||||
if (!controller.signal.aborted) {
|
||||
setLoadingTab(null);
|
||||
}
|
||||
}
|
||||
}, [stockCode, selectedPeriods, loadDataByType]);
|
||||
|
||||
@@ -191,6 +215,12 @@ export const useFinancialData = (
|
||||
return;
|
||||
}
|
||||
|
||||
// 取消之前的核心数据请求
|
||||
coreDataControllerRef.current?.abort();
|
||||
const controller = new AbortController();
|
||||
coreDataControllerRef.current = controller;
|
||||
const options = { signal: controller.signal };
|
||||
|
||||
logger.debug('useFinancialData', '开始加载核心财务数据', { stockCode, selectedPeriods });
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
@@ -203,10 +233,10 @@ export const useFinancialData = (
|
||||
comparisonRes,
|
||||
businessRes,
|
||||
] = await Promise.all([
|
||||
financialService.getStockInfo(stockCode),
|
||||
financialService.getFinancialMetrics(stockCode, selectedPeriods),
|
||||
financialService.getPeriodComparison(stockCode, selectedPeriods),
|
||||
financialService.getMainBusiness(stockCode, 4),
|
||||
financialService.getStockInfo(stockCode, options),
|
||||
financialService.getFinancialMetrics(stockCode, selectedPeriods, options),
|
||||
financialService.getPeriodComparison(stockCode, selectedPeriods, options),
|
||||
financialService.getMainBusiness(stockCode, 4, options),
|
||||
]);
|
||||
|
||||
// 设置数据
|
||||
@@ -217,11 +247,16 @@ export const useFinancialData = (
|
||||
|
||||
logger.info('useFinancialData', '核心财务数据加载成功', { stockCode });
|
||||
} catch (err) {
|
||||
// 取消请求不作为错误处理
|
||||
if (isCancelError(err)) return;
|
||||
const errorMessage = err instanceof Error ? err.message : '未知错误';
|
||||
setError(errorMessage);
|
||||
logger.error('useFinancialData', 'loadCoreFinancialData', err, { stockCode, selectedPeriods });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
// 只有当前请求没有被取消时才设置 loading 状态
|
||||
if (!controller.signal.aborted) {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
}, [stockCode, selectedPeriods, toast]);
|
||||
|
||||
@@ -253,6 +288,14 @@ export const useFinancialData = (
|
||||
}
|
||||
}, [selectedPeriods, activeTab, refetchByTab]);
|
||||
|
||||
// 组件卸载时取消所有进行中的请求
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
coreDataControllerRef.current?.abort();
|
||||
tabDataControllerRef.current?.abort();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return {
|
||||
// 数据状态
|
||||
stockInfo,
|
||||
|
||||
@@ -283,7 +283,7 @@ const FinancialPanorama: React.FC<FinancialPanoramaProps> = ({ stockCode: propSt
|
||||
<VStack spacing={6} align="stretch">
|
||||
{/* 财务全景面板(三列布局:成长能力、盈利与回报、风险与运营) */}
|
||||
{loading ? (
|
||||
<LoadingState message="加载财务数据中..." height="300px" />
|
||||
<LoadingState message="加载财务数据中..." height="300px" variant="skeleton" skeletonRows={6} />
|
||||
) : (
|
||||
<FinancialOverviewPanel
|
||||
stockInfo={stockInfo}
|
||||
|
||||
Reference in New Issue
Block a user