fix(SubTabContainer): 移除外层 Suspense,Tab 内容直接展示
- SubTabContainer 内部为每个 Tab 添加 Suspense fallback={null}
- 移除 Company/index.tsx 外层 Suspense 和 TabLoadingFallback
- 切换一级 Tab 时不再显示整体 loading,直接展示内容
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -19,7 +19,7 @@
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState, useCallback, memo } from 'react';
|
import React, { useState, useCallback, memo, Suspense } from 'react';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Tabs,
|
Tabs,
|
||||||
@@ -311,7 +311,9 @@ const SubTabContainer: React.FC<SubTabContainerProps> = memo(({
|
|||||||
return (
|
return (
|
||||||
<TabPanel key={tab.key} p={0}>
|
<TabPanel key={tab.key} p={0}>
|
||||||
{shouldRender && Component ? (
|
{shouldRender && Component ? (
|
||||||
|
<Suspense fallback={null}>
|
||||||
<Component {...componentProps} />
|
<Component {...componentProps} />
|
||||||
|
</Suspense>
|
||||||
) : null}
|
) : null}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,53 +1,35 @@
|
|||||||
import { getApiBase } from '../utils/apiConfig';
|
|
||||||
// src/services/financialService.js
|
// src/services/financialService.js
|
||||||
/**
|
/**
|
||||||
* 完整的财务数据服务层
|
* 完整的财务数据服务层
|
||||||
* 对应Flask后端的所有财务API接口
|
* 对应Flask后端的所有财务API接口
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { logger } from '../utils/logger';
|
import axios from '@utils/axiosConfig';
|
||||||
|
|
||||||
const isProduction = process.env.NODE_ENV === 'production';
|
|
||||||
const API_BASE_URL = getApiBase();
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一的 API 请求函数
|
||||||
|
* axios 拦截器已自动处理日志记录
|
||||||
|
*/
|
||||||
const apiRequest = async (url, options = {}) => {
|
const apiRequest = async (url, options = {}) => {
|
||||||
try {
|
const { method = 'GET', body, ...rest } = options;
|
||||||
logger.debug('financialService', 'API请求', {
|
|
||||||
url: `${API_BASE_URL}${url}`,
|
|
||||||
method: options.method || 'GET'
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await fetch(`${API_BASE_URL}${url}`, {
|
const config = {
|
||||||
...options,
|
method,
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
...options.headers,
|
|
||||||
},
|
|
||||||
credentials: 'include', // 包含 cookies,以便后端识别登录状态
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorText = await response.text();
|
|
||||||
logger.error('financialService', 'apiRequest', new Error(`HTTP ${response.status}`), {
|
|
||||||
url,
|
url,
|
||||||
status: response.status,
|
...rest,
|
||||||
errorText: errorText.substring(0, 200)
|
};
|
||||||
});
|
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
// 如果有 body,根据方法设置 data 或 params
|
||||||
|
if (body) {
|
||||||
|
if (method === 'GET') {
|
||||||
|
config.params = typeof body === 'string' ? JSON.parse(body) : body;
|
||||||
|
} else {
|
||||||
|
config.data = typeof body === 'string' ? JSON.parse(body) : body;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const response = await axios(config);
|
||||||
logger.debug('financialService', 'API响应', {
|
return response.data;
|
||||||
url,
|
|
||||||
success: data.success,
|
|
||||||
hasData: !!data.data
|
|
||||||
});
|
|
||||||
|
|
||||||
return data;
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('financialService', 'apiRequest', error, { url });
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const financialService = {
|
export const financialService = {
|
||||||
|
|||||||
@@ -1,53 +1,36 @@
|
|||||||
import { getApiBase } from '../utils/apiConfig';
|
|
||||||
// src/services/marketService.js
|
// src/services/marketService.js
|
||||||
/**
|
/**
|
||||||
* 完整的市场行情数据服务层
|
* 完整的市场行情数据服务层
|
||||||
* 对应Flask后端的所有市场API接口
|
* 对应Flask后端的所有市场API接口
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import axios from '@utils/axiosConfig';
|
||||||
import { logger } from '../utils/logger';
|
import { logger } from '../utils/logger';
|
||||||
|
|
||||||
const isProduction = process.env.NODE_ENV === 'production';
|
/**
|
||||||
const API_BASE_URL = getApiBase();
|
* 统一的 API 请求函数
|
||||||
|
* axios 拦截器已自动处理日志记录
|
||||||
|
*/
|
||||||
const apiRequest = async (url, options = {}) => {
|
const apiRequest = async (url, options = {}) => {
|
||||||
try {
|
const { method = 'GET', body, ...rest } = options;
|
||||||
logger.debug('marketService', 'API请求', {
|
|
||||||
url: `${API_BASE_URL}${url}`,
|
|
||||||
method: options.method || 'GET'
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await fetch(`${API_BASE_URL}${url}`, {
|
const config = {
|
||||||
...options,
|
method,
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
...options.headers,
|
|
||||||
},
|
|
||||||
credentials: 'include', // 包含 cookies,以便后端识别登录状态
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorText = await response.text();
|
|
||||||
logger.error('marketService', 'apiRequest', new Error(`HTTP ${response.status}`), {
|
|
||||||
url,
|
url,
|
||||||
status: response.status,
|
...rest,
|
||||||
errorText: errorText.substring(0, 200)
|
};
|
||||||
});
|
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
// 如果有 body,根据方法设置 data 或 params
|
||||||
|
if (body) {
|
||||||
|
if (method === 'GET') {
|
||||||
|
config.params = typeof body === 'string' ? JSON.parse(body) : body;
|
||||||
|
} else {
|
||||||
|
config.data = typeof body === 'string' ? JSON.parse(body) : body;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const response = await axios(config);
|
||||||
logger.debug('marketService', 'API响应', {
|
return response.data;
|
||||||
url,
|
|
||||||
success: data.success,
|
|
||||||
hasData: !!data.data
|
|
||||||
});
|
|
||||||
|
|
||||||
return data;
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('marketService', 'apiRequest', error, { url });
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const marketService = {
|
export const marketService = {
|
||||||
|
|||||||
@@ -9,12 +9,12 @@
|
|||||||
* - HeroUI 现代组件风格
|
* - HeroUI 现代组件风格
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { memo, useCallback, useRef, useEffect, Suspense } from 'react';
|
import React, { memo, useCallback, useRef, useEffect } from 'react';
|
||||||
|
|
||||||
// FUI 动画样式
|
// FUI 动画样式
|
||||||
import './theme/fui-animations.css';
|
import './theme/fui-animations.css';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { Box, Spinner, Center } from '@chakra-ui/react';
|
import { Box } from '@chakra-ui/react';
|
||||||
import SubTabContainer from '@components/SubTabContainer';
|
import SubTabContainer from '@components/SubTabContainer';
|
||||||
import { useCompanyEvents } from './hooks/useCompanyEvents';
|
import { useCompanyEvents } from './hooks/useCompanyEvents';
|
||||||
import { useCompanyData } from './hooks/useCompanyData';
|
import { useCompanyData } from './hooks/useCompanyData';
|
||||||
@@ -22,18 +22,6 @@ import CompanyHeader from './components/CompanyHeader';
|
|||||||
import StockQuoteCard from './components/StockQuoteCard';
|
import StockQuoteCard from './components/StockQuoteCard';
|
||||||
import { THEME, TAB_CONFIG } from './config';
|
import { THEME, TAB_CONFIG } from './config';
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// 加载状态组件
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
const TabLoadingFallback = memo(() => (
|
|
||||||
<Center py={20}>
|
|
||||||
<Spinner size="xl" color={THEME.gold} thickness="3px" />
|
|
||||||
</Center>
|
|
||||||
));
|
|
||||||
|
|
||||||
TabLoadingFallback.displayName = 'TabLoadingFallback';
|
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// 主内容区组件 - FUI 风格
|
// 主内容区组件 - FUI 风格
|
||||||
// ============================================
|
// ============================================
|
||||||
@@ -121,7 +109,6 @@ const CompanyContent = memo<CompanyContentProps>(({
|
|||||||
opacity={0.6}
|
opacity={0.6}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Suspense fallback={<TabLoadingFallback />}>
|
|
||||||
<SubTabContainer
|
<SubTabContainer
|
||||||
tabs={TAB_CONFIG}
|
tabs={TAB_CONFIG}
|
||||||
componentProps={{ stockCode }}
|
componentProps={{ stockCode }}
|
||||||
@@ -130,7 +117,6 @@ const CompanyContent = memo<CompanyContentProps>(({
|
|||||||
contentPadding={0}
|
contentPadding={0}
|
||||||
isLazy={true}
|
isLazy={true}
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
));
|
));
|
||||||
|
|||||||
Reference in New Issue
Block a user