fix: 优化加载状态和布局
MarketDataView: - 移除重复的 LoadingState,改用 KLineModule 内部骨架屏 - 修复点击股票行情后数据不显示的问题 FinancialPanorama: - 移除表格右上角"显示 6 期"标签 - 优化 loadingTab 状态处理 SubTabContainer: - 重构布局:Tab 区域可滚动,右侧元素固定 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,7 @@
|
|||||||
import React, { useState, useCallback, memo, Suspense } from 'react';
|
import React, { useState, useCallback, memo, Suspense } from 'react';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
|
Flex,
|
||||||
Tabs,
|
Tabs,
|
||||||
TabList,
|
TabList,
|
||||||
TabPanels,
|
TabPanels,
|
||||||
@@ -30,7 +31,6 @@ import {
|
|||||||
Icon,
|
Icon,
|
||||||
HStack,
|
HStack,
|
||||||
Text,
|
Text,
|
||||||
Spacer,
|
|
||||||
Center,
|
Center,
|
||||||
Spinner,
|
Spinner,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
@@ -202,8 +202,8 @@ const SubTabContainer: React.FC<SubTabContainerProps> = memo(({
|
|||||||
index={currentIndex}
|
index={currentIndex}
|
||||||
onChange={handleTabChange}
|
onChange={handleTabChange}
|
||||||
>
|
>
|
||||||
{/* TabList - 玻璃态导航栏 */}
|
{/* 导航栏容器:左侧 Tab 可滚动,右侧元素固定 */}
|
||||||
<TabList
|
<Flex
|
||||||
bg={theme.bg}
|
bg={theme.bg}
|
||||||
backdropFilter="blur(20px)"
|
backdropFilter="blur(20px)"
|
||||||
borderBottom="1px solid"
|
borderBottom="1px solid"
|
||||||
@@ -211,18 +211,9 @@ const SubTabContainer: React.FC<SubTabContainerProps> = memo(({
|
|||||||
borderRadius={compact ? 0 : DEEP_SPACE.radiusLG}
|
borderRadius={compact ? 0 : DEEP_SPACE.radiusLG}
|
||||||
mx={compact ? 0 : 2}
|
mx={compact ? 0 : 2}
|
||||||
mb={compact ? 0 : 2}
|
mb={compact ? 0 : 2}
|
||||||
px={3}
|
|
||||||
py={compact ? 2 : 3}
|
|
||||||
flexWrap="nowrap"
|
|
||||||
gap={2}
|
|
||||||
alignItems="center"
|
|
||||||
overflowX="auto"
|
|
||||||
position="relative"
|
position="relative"
|
||||||
boxShadow={compact ? 'none' : DEEP_SPACE.innerGlow}
|
boxShadow={compact ? 'none' : DEEP_SPACE.innerGlow}
|
||||||
css={{
|
alignItems="center"
|
||||||
'&::-webkit-scrollbar': { display: 'none' },
|
|
||||||
scrollbarWidth: 'none',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{/* 顶部金色光条 */}
|
{/* 顶部金色光条 */}
|
||||||
<Box
|
<Box
|
||||||
@@ -235,75 +226,102 @@ const SubTabContainer: React.FC<SubTabContainerProps> = memo(({
|
|||||||
background={`linear-gradient(90deg, transparent, rgba(212, 175, 55, 0.4), transparent)`}
|
background={`linear-gradient(90deg, transparent, rgba(212, 175, 55, 0.4), transparent)`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{tabs.map((tab, idx) => {
|
{/* 左侧:可滚动的 Tab 区域 */}
|
||||||
const isSelected = idx === currentIndex;
|
<Box
|
||||||
|
flex="1"
|
||||||
|
minW={0}
|
||||||
|
overflowX="auto"
|
||||||
|
css={{
|
||||||
|
'&::-webkit-scrollbar': { display: 'none' },
|
||||||
|
scrollbarWidth: 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TabList
|
||||||
|
border="none"
|
||||||
|
px={3}
|
||||||
|
py={compact ? 2 : 3}
|
||||||
|
flexWrap="nowrap"
|
||||||
|
gap={2}
|
||||||
|
>
|
||||||
|
{tabs.map((tab, idx) => {
|
||||||
|
const isSelected = idx === currentIndex;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tab
|
<Tab
|
||||||
key={tab.key}
|
key={tab.key}
|
||||||
color={theme.tabUnselectedColor}
|
color={theme.tabUnselectedColor}
|
||||||
borderRadius={DEEP_SPACE.radius}
|
borderRadius={DEEP_SPACE.radius}
|
||||||
px={6}
|
px={6}
|
||||||
py={3}
|
py={3}
|
||||||
fontSize="15px"
|
fontSize="15px"
|
||||||
fontWeight="500"
|
fontWeight="500"
|
||||||
whiteSpace="nowrap"
|
whiteSpace="nowrap"
|
||||||
flexShrink={0}
|
flexShrink={0}
|
||||||
border="1px solid transparent"
|
border="1px solid transparent"
|
||||||
position="relative"
|
position="relative"
|
||||||
letterSpacing="0.03em"
|
letterSpacing="0.03em"
|
||||||
transition={DEEP_SPACE.transition}
|
transition={DEEP_SPACE.transition}
|
||||||
_before={{
|
_before={{
|
||||||
content: '""',
|
content: '""',
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
bottom: '-1px',
|
bottom: '-1px',
|
||||||
left: '50%',
|
left: '50%',
|
||||||
transform: 'translateX(-50%)',
|
transform: 'translateX(-50%)',
|
||||||
width: isSelected ? '70%' : '0%',
|
width: isSelected ? '70%' : '0%',
|
||||||
height: '2px',
|
height: '2px',
|
||||||
bg: '#D4AF37',
|
bg: '#D4AF37',
|
||||||
borderRadius: 'full',
|
borderRadius: 'full',
|
||||||
transition: 'width 0.3s ease',
|
transition: 'width 0.3s ease',
|
||||||
boxShadow: isSelected ? '0 0 10px rgba(212, 175, 55, 0.5)' : 'none',
|
boxShadow: isSelected ? '0 0 10px rgba(212, 175, 55, 0.5)' : 'none',
|
||||||
}}
|
}}
|
||||||
_selected={{
|
_selected={{
|
||||||
bg: theme.tabSelectedBg,
|
bg: theme.tabSelectedBg,
|
||||||
color: theme.tabSelectedColor,
|
color: theme.tabSelectedColor,
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
boxShadow: DEEP_SPACE.glowGold,
|
boxShadow: DEEP_SPACE.glowGold,
|
||||||
border: `1px solid ${DEEP_SPACE.borderGoldHover}`,
|
border: `1px solid ${DEEP_SPACE.borderGoldHover}`,
|
||||||
transform: 'translateY(-2px)',
|
transform: 'translateY(-2px)',
|
||||||
}}
|
}}
|
||||||
_hover={{
|
_hover={{
|
||||||
bg: isSelected ? undefined : theme.tabHoverBg,
|
bg: isSelected ? undefined : theme.tabHoverBg,
|
||||||
border: isSelected ? undefined : `1px solid ${DEEP_SPACE.borderGold}`,
|
border: isSelected ? undefined : `1px solid ${DEEP_SPACE.borderGold}`,
|
||||||
transform: 'translateY(-1px)',
|
transform: 'translateY(-1px)',
|
||||||
}}
|
}}
|
||||||
_active={{
|
_active={{
|
||||||
transform: 'translateY(0)',
|
transform: 'translateY(0)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<HStack spacing={2}>
|
<HStack spacing={2}>
|
||||||
{tab.icon && (
|
{tab.icon && (
|
||||||
<Icon
|
<Icon
|
||||||
as={tab.icon}
|
as={tab.icon}
|
||||||
boxSize={4}
|
boxSize={4}
|
||||||
opacity={isSelected ? 1 : 0.7}
|
opacity={isSelected ? 1 : 0.7}
|
||||||
transition="opacity 0.2s"
|
transition="opacity 0.2s"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Text>{tab.name}</Text>
|
<Text>{tab.name}</Text>
|
||||||
</HStack>
|
</HStack>
|
||||||
</Tab>
|
</Tab>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
</TabList>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* 右侧:固定的自定义元素(如期数选择器) */}
|
||||||
{rightElement && (
|
{rightElement && (
|
||||||
<>
|
<Box
|
||||||
<Spacer />
|
flexShrink={0}
|
||||||
<Box flexShrink={0}>{rightElement}</Box>
|
pr={3}
|
||||||
</>
|
pl={2}
|
||||||
|
py={compact ? 2 : 3}
|
||||||
|
borderLeft="1px solid"
|
||||||
|
borderColor={DEEP_SPACE.borderGold}
|
||||||
|
>
|
||||||
|
{rightElement}
|
||||||
|
</Box>
|
||||||
)}
|
)}
|
||||||
</TabList>
|
</Flex>
|
||||||
|
|
||||||
<TabPanels p={contentPadding}>
|
<TabPanels p={contentPadding}>
|
||||||
{tabs.map((tab, idx) => {
|
{tabs.map((tab, idx) => {
|
||||||
|
|||||||
@@ -345,12 +345,6 @@ const UnifiedFinancialTableInner: React.FC<UnifiedFinancialTableProps> = ({
|
|||||||
return (
|
return (
|
||||||
<Box className={TABLE_CLASS_NAME}>
|
<Box className={TABLE_CLASS_NAME}>
|
||||||
<style>{extendedTableStyles}</style>
|
<style>{extendedTableStyles}</style>
|
||||||
{/* 右上角显示期数标签 */}
|
|
||||||
<HStack justify="flex-end" mb={1}>
|
|
||||||
<Text fontSize="2xs" color="gray.500">
|
|
||||||
显示 {displayData.length} 期
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
|
||||||
<ConfigProvider theme={BLACK_GOLD_TABLE_THEME}>
|
<ConfigProvider theme={BLACK_GOLD_TABLE_THEME}>
|
||||||
<Table
|
<Table
|
||||||
columns={columns}
|
columns={columns}
|
||||||
|
|||||||
@@ -216,26 +216,26 @@ const BALANCE_SHEET_SECTIONS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
/** 资产负债表 Tab */
|
/** 资产负债表 Tab */
|
||||||
export const BalanceSheetTab = memo<BalanceSheetTabProps>(({ balanceSheet, loading, showMetricChart }) => (
|
export const BalanceSheetTab = memo<BalanceSheetTabProps>(({ balanceSheet, loading, loadingTab, showMetricChart }) => (
|
||||||
<UnifiedFinancialTable
|
<UnifiedFinancialTable
|
||||||
type="statement"
|
type="statement"
|
||||||
data={balanceSheet as unknown as FinancialDataItem[]}
|
data={balanceSheet as unknown as FinancialDataItem[]}
|
||||||
sections={BALANCE_SHEET_SECTIONS}
|
sections={BALANCE_SHEET_SECTIONS}
|
||||||
showMetricChart={showMetricChart}
|
showMetricChart={showMetricChart}
|
||||||
loading={loading}
|
loading={loading || loadingTab === 'balance'}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
BalanceSheetTab.displayName = 'BalanceSheetTab';
|
BalanceSheetTab.displayName = 'BalanceSheetTab';
|
||||||
|
|
||||||
/** 利润表 Tab */
|
/** 利润表 Tab */
|
||||||
export const IncomeStatementTab = memo<IncomeStatementTabProps>(({ incomeStatement, loading, showMetricChart }) => (
|
export const IncomeStatementTab = memo<IncomeStatementTabProps>(({ incomeStatement, loading, loadingTab, showMetricChart }) => (
|
||||||
<UnifiedFinancialTable
|
<UnifiedFinancialTable
|
||||||
type="statement"
|
type="statement"
|
||||||
data={incomeStatement as unknown as FinancialDataItem[]}
|
data={incomeStatement as unknown as FinancialDataItem[]}
|
||||||
sections={INCOME_STATEMENT_SECTIONS}
|
sections={INCOME_STATEMENT_SECTIONS}
|
||||||
hideTotalSectionTitle={false}
|
hideTotalSectionTitle={false}
|
||||||
showMetricChart={showMetricChart}
|
showMetricChart={showMetricChart}
|
||||||
loading={loading}
|
loading={loading || loadingTab === 'income'}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
IncomeStatementTab.displayName = 'IncomeStatementTab';
|
IncomeStatementTab.displayName = 'IncomeStatementTab';
|
||||||
@@ -251,14 +251,14 @@ const CASHFLOW_SECTIONS = [{
|
|||||||
}];
|
}];
|
||||||
|
|
||||||
/** 现金流量表 Tab */
|
/** 现金流量表 Tab */
|
||||||
export const CashflowTab = memo<CashflowTabProps>(({ cashflow, loading, showMetricChart }) => (
|
export const CashflowTab = memo<CashflowTabProps>(({ cashflow, loading, loadingTab, showMetricChart }) => (
|
||||||
<UnifiedFinancialTable
|
<UnifiedFinancialTable
|
||||||
type="statement"
|
type="statement"
|
||||||
data={cashflow as unknown as FinancialDataItem[]}
|
data={cashflow as unknown as FinancialDataItem[]}
|
||||||
sections={CASHFLOW_SECTIONS}
|
sections={CASHFLOW_SECTIONS}
|
||||||
hideTotalSectionTitle
|
hideTotalSectionTitle
|
||||||
showMetricChart={showMetricChart}
|
showMetricChart={showMetricChart}
|
||||||
loading={loading}
|
loading={loading || loadingTab === 'cashflow'}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
CashflowTab.displayName = 'CashflowTab';
|
CashflowTab.displayName = 'CashflowTab';
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// K线模块 - 日K线/分时图切换展示(黑金主题 + 专业技术指标 + 商品数据叠加 + 分时盘口)
|
// K线模块 - 日K线/分时图切换展示(黑金主题 + 专业技术指标 + 商品数据叠加 + 分时盘口)
|
||||||
|
|
||||||
import React, { useState, useMemo, useCallback, memo } from 'react';
|
import React, { useState, useMemo, useCallback, memo } from 'react';
|
||||||
import { Box } from '@chakra-ui/react';
|
import { Box, HStack, Skeleton, Card, CardBody } from '@chakra-ui/react';
|
||||||
|
|
||||||
import { darkGoldTheme } from '../../../constants';
|
import { darkGoldTheme } from '../../../constants';
|
||||||
import type { IndicatorType, MainIndicatorType, DrawingType } from '../../../utils/chartOptions';
|
import type { IndicatorType, MainIndicatorType, DrawingType } from '../../../utils/chartOptions';
|
||||||
@@ -30,6 +30,7 @@ const KLineModule: React.FC<KLineModuleProps> = ({
|
|||||||
selectedPeriod,
|
selectedPeriod,
|
||||||
onPeriodChange,
|
onPeriodChange,
|
||||||
stockCode,
|
stockCode,
|
||||||
|
loading = false,
|
||||||
}) => {
|
}) => {
|
||||||
// ========== 状态管理 ==========
|
// ========== 状态管理 ==========
|
||||||
const [mode, setMode] = useState<ChartMode>('daily');
|
const [mode, setMode] = useState<ChartMode>('daily');
|
||||||
@@ -127,7 +128,19 @@ const KLineModule: React.FC<KLineModuleProps> = ({
|
|||||||
|
|
||||||
{/* 图表内容区域 */}
|
{/* 图表内容区域 */}
|
||||||
<Box pt={4}>
|
<Box pt={4}>
|
||||||
{mode === 'daily' ? (
|
{/* 加载中骨架屏 */}
|
||||||
|
{loading && tradeData.length === 0 ? (
|
||||||
|
<Card bg="gray.900" border="1px solid" borderColor="rgba(212, 175, 55, 0.3)">
|
||||||
|
<CardBody p={4}>
|
||||||
|
<Skeleton
|
||||||
|
height="600px"
|
||||||
|
borderRadius="md"
|
||||||
|
startColor="rgba(26, 32, 44, 0.6)"
|
||||||
|
endColor="rgba(212, 175, 55, 0.2)"
|
||||||
|
/>
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
) : mode === 'daily' ? (
|
||||||
// 日K线图
|
// 日K线图
|
||||||
<DailyKLineChart
|
<DailyKLineChart
|
||||||
tradeData={tradeData}
|
tradeData={tradeData}
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import {
|
|||||||
UnusualPanel,
|
UnusualPanel,
|
||||||
PledgePanel,
|
PledgePanel,
|
||||||
} from './components/panels';
|
} from './components/panels';
|
||||||
import LoadingState from '../LoadingState';
|
|
||||||
import type { MarketDataViewProps } from './types';
|
import type { MarketDataViewProps } from './types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -61,7 +60,6 @@ const MarketDataView: React.FC<MarketDataViewProps> = ({ stockCode: propStockCod
|
|||||||
minuteData,
|
minuteData,
|
||||||
minuteLoading,
|
minuteLoading,
|
||||||
analysisMap,
|
analysisMap,
|
||||||
refetch,
|
|
||||||
loadMinuteData,
|
loadMinuteData,
|
||||||
loadDataByType,
|
loadDataByType,
|
||||||
} = useMarketData(stockCode, selectedPeriod);
|
} = useMarketData(stockCode, selectedPeriod);
|
||||||
@@ -133,41 +131,29 @@ const MarketDataView: React.FC<MarketDataViewProps> = ({ stockCode: propStockCod
|
|||||||
{summary && <StockSummaryCard summary={summary} theme={theme} />}
|
{summary && <StockSummaryCard summary={summary} theme={theme} />}
|
||||||
|
|
||||||
{/* 交易数据 - 日K/分钟K线(独立显示在 Tab 上方) */}
|
{/* 交易数据 - 日K/分钟K线(独立显示在 Tab 上方) */}
|
||||||
{!loading && (
|
<TradeDataPanel
|
||||||
<TradeDataPanel
|
theme={theme}
|
||||||
theme={theme}
|
tradeData={tradeData}
|
||||||
tradeData={tradeData}
|
minuteData={minuteData}
|
||||||
minuteData={minuteData}
|
minuteLoading={minuteLoading}
|
||||||
minuteLoading={minuteLoading}
|
analysisMap={analysisMap}
|
||||||
analysisMap={analysisMap}
|
onLoadMinuteData={loadMinuteData}
|
||||||
onLoadMinuteData={loadMinuteData}
|
onChartClick={handleChartClick}
|
||||||
onChartClick={handleChartClick}
|
selectedPeriod={selectedPeriod}
|
||||||
selectedPeriod={selectedPeriod}
|
onPeriodChange={setSelectedPeriod}
|
||||||
onPeriodChange={setSelectedPeriod}
|
stockCode={stockCode}
|
||||||
stockCode={stockCode}
|
loading={loading}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 主要内容区域 - Tab */}
|
{/* 主要内容区域 - Tab */}
|
||||||
{loading ? (
|
<SubTabContainer
|
||||||
<Box
|
tabs={tabConfigs}
|
||||||
bg="gray.900"
|
componentProps={componentProps}
|
||||||
border="1px solid"
|
themePreset="blackGold"
|
||||||
borderColor="rgba(212, 175, 55, 0.3)"
|
index={activeTab}
|
||||||
borderRadius="xl"
|
onTabChange={handleTabChange}
|
||||||
>
|
isLazy
|
||||||
<LoadingState message="数据加载中..." height="400px" />
|
/>
|
||||||
</Box>
|
|
||||||
) : (
|
|
||||||
<SubTabContainer
|
|
||||||
tabs={tabConfigs}
|
|
||||||
componentProps={componentProps}
|
|
||||||
themePreset="blackGold"
|
|
||||||
index={activeTab}
|
|
||||||
onTabChange={handleTabChange}
|
|
||||||
isLazy
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</VStack>
|
</VStack>
|
||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
|
|||||||
@@ -300,6 +300,7 @@ export interface KLineModuleProps {
|
|||||||
selectedPeriod?: number;
|
selectedPeriod?: number;
|
||||||
onPeriodChange?: (period: number) => void;
|
onPeriodChange?: (period: number) => void;
|
||||||
stockCode?: string; // 股票代码,用于获取实时盘口数据
|
stockCode?: string; // 股票代码,用于获取实时盘口数据
|
||||||
|
loading?: boolean; // 日K线数据加载中
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user