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:
@@ -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;
|
|
||||||
@@ -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;
|
||||||
@@ -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;
|
|
||||||
@@ -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';
|
||||||
|
|||||||
@@ -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';
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user