- useMarketData: 使用 Map 替代 findIndex,O(n*m) → O(n+m) 性能优化 - useMarketData: 修复 React StrictMode 下请求被意外取消的问题 - config.ts: 添加 CompanyOverview 和 DynamicTracking 的骨架屏 fallback 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
182 lines
5.4 KiB
TypeScript
182 lines
5.4 KiB
TypeScript
// src/views/Company/components/MarketDataView/index.tsx
|
||
// MarketDataView 主组件 - 股票市场数据综合展示
|
||
|
||
import React, { useState, useEffect, ReactNode, useMemo, useCallback, memo } from 'react';
|
||
import {
|
||
Box,
|
||
Card,
|
||
CardBody,
|
||
Container,
|
||
VStack,
|
||
useDisclosure,
|
||
} from '@chakra-ui/react';
|
||
import { Unlock, ArrowUp, Star, Lock } from 'lucide-react';
|
||
|
||
// 通用组件
|
||
import SubTabContainer from '@components/SubTabContainer';
|
||
import type { SubTabConfig } from '@components/SubTabContainer';
|
||
|
||
// 内部模块导入
|
||
import { themes, DEFAULT_PERIOD } from './constants';
|
||
import { useMarketData } from './hooks/useMarketData';
|
||
import {
|
||
ThemedCard,
|
||
StockSummaryCard,
|
||
AnalysisModal,
|
||
AnalysisContent,
|
||
} from './components';
|
||
import {
|
||
TradeDataPanel,
|
||
FundingPanel,
|
||
BigDealPanel,
|
||
UnusualPanel,
|
||
PledgePanel,
|
||
} from './components/panels';
|
||
import type { MarketDataViewProps } from './types';
|
||
|
||
/**
|
||
* MarketDataView 主组件
|
||
* 展示股票的市场数据:交易数据、融资融券、大宗交易、龙虎榜、股权质押
|
||
*/
|
||
const MarketDataView: React.FC<MarketDataViewProps> = ({ stockCode: propStockCode }) => {
|
||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||
const [modalContent, setModalContent] = useState<ReactNode>(null);
|
||
|
||
// 获取当前主题
|
||
const theme = themes.light;
|
||
|
||
// 状态管理
|
||
const [stockCode, setStockCode] = useState(propStockCode || '600000');
|
||
const [activeTab, setActiveTab] = useState(0);
|
||
const [selectedPeriod, setSelectedPeriod] = useState(DEFAULT_PERIOD);
|
||
|
||
// 使用自定义 Hook 获取数据
|
||
const {
|
||
loading,
|
||
summary,
|
||
tradeData,
|
||
fundingData,
|
||
bigDealData,
|
||
unusualData,
|
||
pledgeData,
|
||
minuteData,
|
||
minuteLoading,
|
||
analysisMap,
|
||
loadMinuteData,
|
||
loadDataByType,
|
||
} = useMarketData(stockCode, selectedPeriod);
|
||
|
||
// Tab 切换时按需加载数据
|
||
const handleTabChange = useCallback((index: number) => {
|
||
setActiveTab(index);
|
||
// 根据 tab index 加载对应数据
|
||
const tabDataMap: Record<number, 'funding' | 'bigDeal' | 'unusual' | 'pledge'> = {
|
||
0: 'funding',
|
||
1: 'bigDeal',
|
||
2: 'unusual',
|
||
3: 'pledge',
|
||
};
|
||
const dataType = tabDataMap[index];
|
||
if (dataType) {
|
||
loadDataByType(dataType);
|
||
}
|
||
}, [loadDataByType]);
|
||
|
||
// 监听 props 中的 stockCode 变化
|
||
useEffect(() => {
|
||
if (propStockCode && propStockCode !== stockCode) {
|
||
setStockCode(propStockCode);
|
||
}
|
||
}, [propStockCode, stockCode]);
|
||
|
||
// 首次渲染时加载默认 Tab(融资融券)的数据
|
||
useEffect(() => {
|
||
// 默认 Tab 是融资融券(index 0)
|
||
if (activeTab === 0) {
|
||
loadDataByType('funding');
|
||
}
|
||
}, [loadDataByType, activeTab]);
|
||
|
||
// 处理图表点击事件
|
||
const handleChartClick = useCallback(
|
||
(params: { seriesName?: string; data?: [number, number] }) => {
|
||
if (params.seriesName === '涨幅分析' && params.data) {
|
||
const dataIndex = params.data[0];
|
||
const analysis = analysisMap[dataIndex];
|
||
|
||
if (analysis) {
|
||
setModalContent(<AnalysisContent analysis={analysis} theme={theme} />);
|
||
onOpen();
|
||
}
|
||
}
|
||
},
|
||
[analysisMap, theme, onOpen]
|
||
);
|
||
|
||
// Tab 配置 - 使用通用 SubTabContainer(不含交易数据,交易数据单独显示在上方)
|
||
const tabConfigs: SubTabConfig[] = [
|
||
{ key: 'funding', name: '融资融券', icon: Unlock, component: FundingPanel },
|
||
{ key: 'bigDeal', name: '大宗交易', icon: ArrowUp, component: BigDealPanel },
|
||
{ key: 'unusual', name: '龙虎榜', icon: Star, component: UnusualPanel },
|
||
{ key: 'pledge', name: '股权质押', icon: Lock, component: PledgePanel },
|
||
];
|
||
|
||
// 传递给 Tab 组件的 props - 只传递各 Tab 需要的数据
|
||
const componentProps = useMemo(
|
||
() => ({
|
||
// 各 Tab 只使用自己需要的数据
|
||
fundingData,
|
||
bigDealData,
|
||
unusualData,
|
||
pledgeData,
|
||
}),
|
||
[fundingData, bigDealData, unusualData, pledgeData]
|
||
);
|
||
|
||
return (
|
||
<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} />}
|
||
|
||
{/* 交易数据 - 日K/分钟K线(独立显示在 Tab 上方) */}
|
||
<TradeDataPanel
|
||
theme={theme}
|
||
tradeData={tradeData}
|
||
minuteData={minuteData}
|
||
minuteLoading={minuteLoading}
|
||
analysisMap={analysisMap}
|
||
onLoadMinuteData={loadMinuteData}
|
||
onChartClick={handleChartClick}
|
||
selectedPeriod={selectedPeriod}
|
||
onPeriodChange={setSelectedPeriod}
|
||
stockCode={stockCode}
|
||
loading={loading}
|
||
/>
|
||
|
||
{/* 主要内容区域 - Tab */}
|
||
<Card bg="gray.900" shadow="md" border="1px solid" borderColor="rgba(212, 175, 55, 0.3)">
|
||
<CardBody p={0}>
|
||
<SubTabContainer
|
||
tabs={tabConfigs}
|
||
componentProps={componentProps}
|
||
themePreset="blackGold"
|
||
index={activeTab}
|
||
onTabChange={handleTabChange}
|
||
isLazy
|
||
size="sm"
|
||
/>
|
||
</CardBody>
|
||
</Card>
|
||
</VStack>
|
||
</Container>
|
||
|
||
{/* 涨幅分析模态框 */}
|
||
<AnalysisModal isOpen={isOpen} onClose={onClose} content={modalContent} theme={theme} />
|
||
</Box>
|
||
);
|
||
};
|
||
|
||
export default memo(MarketDataView);
|