perf(MarketDataView): 优化加载状态,使用骨架屏避免布局跳动
- useMarketData: 新增 hasLoaded 状态,优化首次加载 loading 逻辑 - 导出 SummaryCardSkeleton 组件用于概览卡片占位 - MarketDataView: 使用骨架屏替代空白占位 - DeepAnalysisTab: 使用 skeleton 变体替代 spinner 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -77,7 +77,7 @@ const DeepAnalysisTab: React.FC<DeepAnalysisTabProps> = ({
|
||||
themePreset="blackGold"
|
||||
size="sm"
|
||||
/>
|
||||
<LoadingState message="加载数据中..." height="200px" />
|
||||
<LoadingState variant="skeleton" height="300px" skeletonRows={6} />
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -136,5 +136,5 @@ const MarketDataSkeleton: React.FC = memo(() => (
|
||||
|
||||
MarketDataSkeleton.displayName = 'MarketDataSkeleton';
|
||||
|
||||
export { MarketDataSkeleton };
|
||||
export { MarketDataSkeleton, SummaryCardSkeleton };
|
||||
export default MarketDataSkeleton;
|
||||
|
||||
@@ -5,4 +5,4 @@ export { default as ThemedCard } from './ThemedCard';
|
||||
export { default as MarkdownRenderer } from './MarkdownRenderer';
|
||||
export { default as StockSummaryCard } from './StockSummaryCard';
|
||||
export { default as AnalysisModal, AnalysisContent } from './AnalysisModal';
|
||||
export { MarketDataSkeleton } from './MarketDataSkeleton';
|
||||
export { MarketDataSkeleton, SummaryCardSkeleton } from './MarketDataSkeleton';
|
||||
|
||||
@@ -33,8 +33,9 @@ export const useMarketData = (
|
||||
period: number = DEFAULT_PERIOD
|
||||
): UseMarketDataReturn => {
|
||||
// 主数据状态
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [tradeLoading, setTradeLoading] = useState(false);
|
||||
const [hasLoaded, setHasLoaded] = useState(false);
|
||||
const [summary, setSummary] = useState<MarketSummary | null>(null);
|
||||
const [tradeData, setTradeData] = useState<TradeDayData[]>([]);
|
||||
const [fundingData, setFundingData] = useState<FundingDayData[]>([]);
|
||||
@@ -153,15 +154,17 @@ export const useMarketData = (
|
||||
if (loadedTradeData.length > 0) {
|
||||
loadRiseAnalysis(loadedTradeData);
|
||||
}
|
||||
} catch (error) {
|
||||
// 取消请求不作为错误处理
|
||||
if (isCancelError(error)) return;
|
||||
logger.error('useMarketData', 'loadCoreData', error, { stockCode, period });
|
||||
} finally {
|
||||
// 只有当前请求没有被取消时才设置 loading 状态
|
||||
if (!controller.signal.aborted) {
|
||||
|
||||
setLoading(false);
|
||||
setHasLoaded(true);
|
||||
} catch (error) {
|
||||
// 请求被取消时,不更新任何状态
|
||||
if (isCancelError(error)) {
|
||||
return;
|
||||
}
|
||||
logger.error('useMarketData', 'loadCoreData', error, { stockCode, period });
|
||||
setLoading(false);
|
||||
setHasLoaded(true);
|
||||
}
|
||||
}, [stockCode, period, loadRiseAnalysis]);
|
||||
|
||||
@@ -363,8 +366,11 @@ export const useMarketData = (
|
||||
};
|
||||
}, []);
|
||||
|
||||
// 派生 loading 状态:stockCode 存在但尚未完成首次加载时,视为 loading
|
||||
const isLoading = loading || (!!stockCode && !hasLoaded);
|
||||
|
||||
return {
|
||||
loading,
|
||||
loading: isLoading,
|
||||
tradeLoading,
|
||||
summary,
|
||||
tradeData,
|
||||
|
||||
@@ -22,6 +22,7 @@ import { useMarketData } from './hooks/useMarketData';
|
||||
import {
|
||||
ThemedCard,
|
||||
StockSummaryCard,
|
||||
SummaryCardSkeleton,
|
||||
AnalysisModal,
|
||||
AnalysisContent,
|
||||
} from './components';
|
||||
@@ -89,13 +90,12 @@ const MarketDataView: React.FC<MarketDataViewProps> = ({ stockCode: propStockCod
|
||||
}
|
||||
}, [propStockCode, stockCode]);
|
||||
|
||||
// 首次渲染时加载默认 Tab(融资融券)的数据
|
||||
// 首次挂载时加载默认 Tab(融资融券)的数据
|
||||
// 注意:SubTabContainer 的 onChange 只在切换时触发,首次渲染不会触发
|
||||
useEffect(() => {
|
||||
// 默认 Tab 是融资融券(index 0)
|
||||
if (activeTab === 0) {
|
||||
loadDataByType('funding');
|
||||
}
|
||||
}, [loadDataByType, activeTab]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []); // 只在首次挂载时执行
|
||||
|
||||
// 处理图表点击事件
|
||||
const handleChartClick = useCallback(
|
||||
@@ -137,8 +137,8 @@ const MarketDataView: React.FC<MarketDataViewProps> = ({ stockCode: propStockCod
|
||||
<Box bg={'#1A202C'} minH="100vh" color={theme.textPrimary}>
|
||||
<Container maxW="container.xl" py={4}>
|
||||
<VStack align="stretch" spacing={4}>
|
||||
{/* 股票概览 */}
|
||||
{summary && <StockSummaryCard summary={summary} theme={theme} />}
|
||||
{/* 股票概览 - 未加载时显示骨架屏占位,避免布局跳动 */}
|
||||
{summary ? <StockSummaryCard summary={summary} theme={theme} /> : <SummaryCardSkeleton />}
|
||||
|
||||
{/* 交易数据 - 日K/分钟K线(独立显示在 Tab 上方) */}
|
||||
<TradeDataPanel
|
||||
|
||||
Reference in New Issue
Block a user