refactor(TradeDataPanel): 合并 KLineChart 和 MinuteKLineSection 为 KLineModule

- 新增 KLineModule 组件,整合日K线和分钟K线功能
- 右上角 ButtonGroup 切换「日K」/「分钟」模式
- 刷新按钮置于切换按钮组前方
- 切换到分钟模式时自动加载数据
- 删除旧的 KLineChart.tsx 和 MinuteKLineSection.tsx
- 更新 panels/index.ts 导出
- 更新 types.ts,合并类型定义为 KLineModuleProps

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
zdl
2025-12-16 14:52:06 +08:00
parent 7f392619e7
commit 406b951e53
6 changed files with 185 additions and 187 deletions

View File

@@ -1,43 +0,0 @@
// src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/KLineChart.tsx
// 日K线图组件
import React from 'react';
import { Box, CardBody } from '@chakra-ui/react';
import ReactECharts from 'echarts-for-react';
import ThemedCard from '../../ThemedCard';
import { getKLineOption } from '../../../utils/chartOptions';
import type { Theme, TradeDayData, RiseAnalysis } from '../../../types';
export interface KLineChartProps {
theme: Theme;
tradeData: TradeDayData[];
analysisMap: Record<number, RiseAnalysis>;
onChartClick: (params: { seriesName?: string; data?: [number, number] }) => void;
}
const KLineChart: React.FC<KLineChartProps> = ({
theme,
tradeData,
analysisMap,
onChartClick,
}) => {
return (
<ThemedCard theme={theme}>
<CardBody>
{tradeData.length > 0 && (
<Box h="600px">
<ReactECharts
option={getKLineOption(theme, tradeData, analysisMap)}
style={{ height: '100%', width: '100%' }}
theme="light"
onEvents={{ click: onChartClick }}
/>
</Box>
)}
</CardBody>
</ThemedCard>
);
};
export default KLineChart;

View File

@@ -0,0 +1,168 @@
// src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/KLineModule.tsx
// K线模块 - 日K线/分钟K线切换展示
import React, { useState } from 'react';
import {
Box,
Text,
VStack,
HStack,
Button,
ButtonGroup,
Badge,
Center,
Spinner,
CardBody,
CardHeader,
Heading,
} from '@chakra-ui/react';
import { RepeatIcon } from '@chakra-ui/icons';
import { BarChart2, Clock } from 'lucide-react';
import ReactECharts from 'echarts-for-react';
import ThemedCard from '../../ThemedCard';
import { getKLineOption, getMinuteKLineOption } from '../../../utils/chartOptions';
import { MinuteStats, TradeAnalysis, EmptyState } from './atoms';
import type { KLineModuleProps } from '../../../types';
// 重新导出类型供外部使用
export type { KLineModuleProps } from '../../../types';
type ChartMode = 'daily' | 'minute';
const KLineModule: React.FC<KLineModuleProps> = ({
theme,
tradeData,
minuteData,
minuteLoading,
analysisMap,
onLoadMinuteData,
onChartClick,
}) => {
const [mode, setMode] = useState<ChartMode>('daily');
const hasMinuteData = minuteData && minuteData.data && minuteData.data.length > 0;
// 切换到分钟模式时自动加载数据
const handleModeChange = (newMode: ChartMode) => {
setMode(newMode);
if (newMode === 'minute' && !hasMinuteData && !minuteLoading) {
onLoadMinuteData();
}
};
return (
<ThemedCard theme={theme}>
<CardHeader>
<HStack justify="space-between" align="center">
<HStack spacing={3}>
<Heading size="md" color={theme.textSecondary}>
{mode === 'daily' ? '日K线图' : '分钟K线图'}
</Heading>
{mode === 'minute' && minuteData?.trade_date && (
<Badge colorScheme="blue" fontSize="xs">
{minuteData.trade_date}
</Badge>
)}
</HStack>
<HStack spacing={3}>
{/* 分钟模式下的刷新按钮 */}
{mode === 'minute' && (
<Button
leftIcon={<RepeatIcon />}
size="sm"
variant="outline"
colorScheme="blue"
onClick={onLoadMinuteData}
isLoading={minuteLoading}
loadingText="获取中"
>
</Button>
)}
{/* 模式切换按钮组 */}
<ButtonGroup size="sm" isAttached variant="outline">
<Button
leftIcon={<BarChart2 size={16} />}
colorScheme={mode === 'daily' ? 'blue' : 'gray'}
variant={mode === 'daily' ? 'solid' : 'outline'}
onClick={() => handleModeChange('daily')}
>
K
</Button>
<Button
leftIcon={<Clock size={16} />}
colorScheme={mode === 'minute' ? 'blue' : 'gray'}
variant={mode === 'minute' ? 'solid' : 'outline'}
onClick={() => handleModeChange('minute')}
>
</Button>
</ButtonGroup>
</HStack>
</HStack>
</CardHeader>
<CardBody>
{mode === 'daily' ? (
// 日K线图
tradeData.length > 0 ? (
<Box h="600px">
<ReactECharts
option={getKLineOption(theme, tradeData, analysisMap)}
style={{ height: '100%', width: '100%' }}
theme="light"
onEvents={{ click: onChartClick }}
/>
</Box>
) : (
<EmptyState
theme={theme}
title="暂无日K线数据"
description="该股票暂无交易数据"
/>
)
) : (
// 分钟K线图
minuteLoading ? (
<Center h="500px">
<VStack spacing={4}>
<Spinner
thickness="4px"
speed="0.65s"
emptyColor={theme.bgDark}
color={theme.primary}
size="lg"
/>
<Text color={theme.textMuted} fontSize="sm">
...
</Text>
</VStack>
</Center>
) : hasMinuteData ? (
<VStack spacing={6} align="stretch">
<Box h="500px">
<ReactECharts
option={getMinuteKLineOption(theme, minuteData)}
style={{ height: '100%', width: '100%' }}
theme="light"
/>
</Box>
<MinuteStats theme={theme} data={minuteData.data} />
<TradeAnalysis theme={theme} data={minuteData.data} />
</VStack>
) : (
<EmptyState
theme={theme}
title="暂无分钟数据"
description="点击右上角刷新按钮获取当日分钟频数据"
/>
)
)}
</CardBody>
</ThemedCard>
);
};
export default KLineModule;

View File

@@ -1,107 +0,0 @@
// src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/MinuteKLineSection.tsx
// 分钟K线数据区域组件
import React from 'react';
import {
Box,
Text,
VStack,
HStack,
Button,
Badge,
Center,
Spinner,
CardBody,
CardHeader,
Icon,
Heading,
} from '@chakra-ui/react';
import { TimeIcon, RepeatIcon } from '@chakra-ui/icons';
import ReactECharts from 'echarts-for-react';
import ThemedCard from '../../ThemedCard';
import { getMinuteKLineOption } from '../../../utils/chartOptions';
import { MinuteStats, TradeAnalysis, EmptyState } from './atoms';
import type { Theme, MinuteData } from '../../../types';
export interface MinuteKLineSectionProps {
theme: Theme;
minuteData: MinuteData | null;
loading: boolean;
onLoadMinuteData: () => void;
}
const MinuteKLineSection: React.FC<MinuteKLineSectionProps> = ({
theme,
minuteData,
loading,
onLoadMinuteData,
}) => {
const hasData = minuteData && minuteData.data && minuteData.data.length > 0;
return (
<ThemedCard theme={theme}>
<CardHeader>
<HStack justify="space-between" align="center">
<HStack spacing={3}>
<Icon as={TimeIcon} color={theme.primary} boxSize={5} />
<Heading size="md" color={theme.textSecondary}>
</Heading>
{minuteData?.trade_date && (
<Badge colorScheme="blue" fontSize="xs">
{minuteData.trade_date}
</Badge>
)}
</HStack>
<Button
leftIcon={<RepeatIcon />}
size="sm"
variant="outline"
colorScheme="blue"
onClick={onLoadMinuteData}
isLoading={loading}
loadingText="获取中"
>
</Button>
</HStack>
</CardHeader>
<CardBody>
{loading ? (
<Center h="400px">
<VStack spacing={4}>
<Spinner
thickness="4px"
speed="0.65s"
emptyColor={theme.bgDark}
color={theme.primary}
size="lg"
/>
<Text color={theme.textMuted} fontSize="sm">
...
</Text>
</VStack>
</Center>
) : hasData ? (
<VStack spacing={6} align="stretch">
<Box h="500px">
<ReactECharts
option={getMinuteKLineOption(theme, minuteData)}
style={{ height: '100%', width: '100%' }}
theme="light"
/>
</Box>
<MinuteStats theme={theme} data={minuteData.data} />
<TradeAnalysis theme={theme} data={minuteData.data} />
</VStack>
) : (
<EmptyState theme={theme} />
)}
</CardBody>
</ThemedCard>
);
};
export default MinuteKLineSection;

View File

@@ -1,11 +1,10 @@
// src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/index.tsx // src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/index.tsx
// 交易数据面板 - K线图、分钟图、交易明细表格 // 交易数据面板 - K线模块日K/分钟切换)、交易明细表格
import React from 'react'; import React from 'react';
import { VStack } from '@chakra-ui/react'; import { VStack } from '@chakra-ui/react';
import KLineChart from './KLineChart'; import KLineModule from './KLineModule';
import MinuteKLineSection from './MinuteKLineSection';
import TradeTable from './TradeTable'; import TradeTable from './TradeTable';
import type { Theme, TradeDayData, MinuteData, RiseAnalysis } from '../../../types'; import type { Theme, TradeDayData, MinuteData, RiseAnalysis } from '../../../types';
@@ -30,18 +29,14 @@ const TradeDataPanel: React.FC<TradeDataPanelProps> = ({
}) => { }) => {
return ( return (
<VStack spacing={6} align="stretch"> <VStack spacing={6} align="stretch">
<KLineChart <KLineModule
theme={theme} theme={theme}
tradeData={tradeData} tradeData={tradeData}
analysisMap={analysisMap}
onChartClick={onChartClick}
/>
<MinuteKLineSection
theme={theme}
minuteData={minuteData} minuteData={minuteData}
loading={minuteLoading} minuteLoading={minuteLoading}
analysisMap={analysisMap}
onLoadMinuteData={onLoadMinuteData} onLoadMinuteData={onLoadMinuteData}
onChartClick={onChartClick}
/> />
<TradeTable theme={theme} tradeData={tradeData} /> <TradeTable theme={theme} tradeData={tradeData} />
@@ -52,7 +47,7 @@ const TradeDataPanel: React.FC<TradeDataPanelProps> = ({
export default TradeDataPanel; export default TradeDataPanel;
// 导出子组件供外部按需使用 // 导出子组件供外部按需使用
export { KLineChart, MinuteKLineSection, TradeTable }; export { default as KLineModule } from './KLineModule';
export type { KLineChartProps } from './KLineChart'; export { default as TradeTable } from './TradeTable';
export type { MinuteKLineSectionProps } from './MinuteKLineSection'; export type { KLineModuleProps } from './KLineModule';
export type { TradeTableProps } from './TradeTable'; export type { TradeTableProps } from './TradeTable';

View File

@@ -15,13 +15,5 @@ export type { UnusualPanelProps } from './UnusualPanel';
export type { PledgePanelProps } from './PledgePanel'; export type { PledgePanelProps } from './PledgePanel';
// 导出 TradeDataPanel 子组件 // 导出 TradeDataPanel 子组件
export { export { KLineModule, TradeTable } from './TradeDataPanel';
KLineChart, export type { KLineModuleProps, TradeTableProps } from './TradeDataPanel';
MinuteKLineSection,
TradeTable,
} from './TradeDataPanel';
export type {
KLineChartProps,
MinuteKLineSectionProps,
TradeTableProps,
} from './TradeDataPanel';

View File

@@ -287,23 +287,16 @@ export interface TradeDataTabProps {
} }
/** /**
* KLineChart 组件 Props * KLineModule 组件 Props日K/分钟K线切换模块
*/ */
export interface KLineChartProps { export interface KLineModuleProps {
theme: Theme; theme: Theme;
tradeData: TradeDayData[]; tradeData: TradeDayData[];
analysisMap: Record<number, RiseAnalysis>;
onAnalysisClick: (analysis: RiseAnalysis) => void;
}
/**
* MinuteKLineChart 组件 Props
*/
export interface MinuteKLineChartProps {
theme: Theme;
minuteData: MinuteData | null; minuteData: MinuteData | null;
loading: boolean; minuteLoading: boolean;
onRefresh: () => void; analysisMap: Record<number, RiseAnalysis>;
onLoadMinuteData: () => void;
onChartClick: (params: { seriesName?: string; data?: [number, number] }) => void;
} }
/** /**