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:
zdl
2025-12-18 18:23:16 +08:00
parent 79572fcc98
commit eaa65b2328
4 changed files with 54 additions and 101 deletions

View File

@@ -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>
); );

View File

@@ -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 = {

View File

@@ -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 = {

View File

@@ -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>
)); ));