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 {
Box,
Tabs,
@@ -311,7 +311,9 @@ const SubTabContainer: React.FC<SubTabContainerProps> = memo(({
return (
<TabPanel key={tab.key} p={0}>
{shouldRender && Component ? (
<Component {...componentProps} />
<Suspense fallback={null}>
<Component {...componentProps} />
</Suspense>
) : null}
</TabPanel>
);

View File

@@ -1,53 +1,35 @@
import { getApiBase } from '../utils/apiConfig';
// src/services/financialService.js
/**
* 完整的财务数据服务层
* 对应Flask后端的所有财务API接口
*/
import { logger } from '../utils/logger';
const isProduction = process.env.NODE_ENV === 'production';
const API_BASE_URL = getApiBase();
import axios from '@utils/axiosConfig';
/**
* 统一的 API 请求函数
* axios 拦截器已自动处理日志记录
*/
const apiRequest = async (url, options = {}) => {
try {
logger.debug('financialService', 'API请求', {
url: `${API_BASE_URL}${url}`,
method: options.method || 'GET'
});
const { method = 'GET', body, ...rest } = options;
const response = await fetch(`${API_BASE_URL}${url}`, {
...options,
headers: {
'Content-Type': 'application/json',
...options.headers,
},
credentials: 'include', // 包含 cookies以便后端识别登录状态
});
const config = {
method,
url,
...rest,
};
if (!response.ok) {
const errorText = await response.text();
logger.error('financialService', 'apiRequest', new Error(`HTTP ${response.status}`), {
url,
status: response.status,
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();
logger.debug('financialService', 'API响应', {
url,
success: data.success,
hasData: !!data.data
});
return data;
} catch (error) {
logger.error('financialService', 'apiRequest', error, { url });
throw error;
}
const response = await axios(config);
return response.data;
};
export const financialService = {

View File

@@ -1,53 +1,36 @@
import { getApiBase } from '../utils/apiConfig';
// src/services/marketService.js
/**
* 完整的市场行情数据服务层
* 对应Flask后端的所有市场API接口
*/
import axios from '@utils/axiosConfig';
import { logger } from '../utils/logger';
const isProduction = process.env.NODE_ENV === 'production';
const API_BASE_URL = getApiBase();
/**
* 统一的 API 请求函数
* axios 拦截器已自动处理日志记录
*/
const apiRequest = async (url, options = {}) => {
try {
logger.debug('marketService', 'API请求', {
url: `${API_BASE_URL}${url}`,
method: options.method || 'GET'
});
const { method = 'GET', body, ...rest } = options;
const response = await fetch(`${API_BASE_URL}${url}`, {
...options,
headers: {
'Content-Type': 'application/json',
...options.headers,
},
credentials: 'include', // 包含 cookies以便后端识别登录状态
});
const config = {
method,
url,
...rest,
};
if (!response.ok) {
const errorText = await response.text();
logger.error('marketService', 'apiRequest', new Error(`HTTP ${response.status}`), {
url,
status: response.status,
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();
logger.debug('marketService', 'API响应', {
url,
success: data.success,
hasData: !!data.data
});
return data;
} catch (error) {
logger.error('marketService', 'apiRequest', error, { url });
throw error;
}
const response = await axios(config);
return response.data;
};
export const marketService = {

View File

@@ -9,12 +9,12 @@
* - HeroUI 现代组件风格
*/
import React, { memo, useCallback, useRef, useEffect, Suspense } from 'react';
import React, { memo, useCallback, useRef, useEffect } from 'react';
// FUI 动画样式
import './theme/fui-animations.css';
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 { useCompanyEvents } from './hooks/useCompanyEvents';
import { useCompanyData } from './hooks/useCompanyData';
@@ -22,18 +22,6 @@ import CompanyHeader from './components/CompanyHeader';
import StockQuoteCard from './components/StockQuoteCard';
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 风格
// ============================================
@@ -121,16 +109,14 @@ const CompanyContent = memo<CompanyContentProps>(({
opacity={0.6}
/>
<Suspense fallback={<TabLoadingFallback />}>
<SubTabContainer
tabs={TAB_CONFIG}
componentProps={{ stockCode }}
onTabChange={onTabChange}
themePreset="blackGold"
contentPadding={0}
isLazy={true}
/>
</Suspense>
<SubTabContainer
tabs={TAB_CONFIG}
componentProps={{ stockCode }}
onTabChange={onTabChange}
themePreset="blackGold"
contentPadding={0}
isLazy={true}
/>
</Box>
</Box>
));