refactor(KLineModule): 黑金主题 + 精简组件结构
- KLineModule 应用黑金主题(渐变背景、金色按钮、金色图标) - 删除 TradeTable、MinuteStats、TradeAnalysis 组件 - 删除 atoms 目录,EmptyState 内联到 KLineModule - 更新 types.ts 移除 TradeTableProps - 更新导出文件 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
// src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/KLineModule.tsx
|
// src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/KLineModule.tsx
|
||||||
// K线模块 - 日K线/分钟K线切换展示
|
// K线模块 - 日K线/分钟K线切换展示(黑金主题)
|
||||||
|
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import {
|
import {
|
||||||
@@ -12,19 +12,29 @@ import {
|
|||||||
Badge,
|
Badge,
|
||||||
Center,
|
Center,
|
||||||
Spinner,
|
Spinner,
|
||||||
CardBody,
|
Icon,
|
||||||
CardHeader,
|
|
||||||
Heading,
|
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { RepeatIcon } from '@chakra-ui/icons';
|
import { RepeatIcon, InfoIcon } from '@chakra-ui/icons';
|
||||||
import { BarChart2, Clock } from 'lucide-react';
|
import { BarChart2, Clock, TrendingUp } from 'lucide-react';
|
||||||
import ReactECharts from 'echarts-for-react';
|
import ReactECharts from 'echarts-for-react';
|
||||||
|
|
||||||
import ThemedCard from '../../ThemedCard';
|
import { darkGoldTheme } from '../../../constants';
|
||||||
import { getKLineOption, getMinuteKLineOption } from '../../../utils/chartOptions';
|
import { getKLineOption, getMinuteKLineOption } from '../../../utils/chartOptions';
|
||||||
import { MinuteStats, TradeAnalysis, EmptyState } from './atoms';
|
|
||||||
import type { KLineModuleProps } from '../../../types';
|
import type { KLineModuleProps } from '../../../types';
|
||||||
|
|
||||||
|
// 空状态组件(内联)
|
||||||
|
const EmptyState: React.FC<{ title: string; description: string }> = ({ title, description }) => (
|
||||||
|
<Center h="300px">
|
||||||
|
<VStack spacing={4}>
|
||||||
|
<Icon as={InfoIcon} color={darkGoldTheme.textMuted} boxSize={12} />
|
||||||
|
<VStack spacing={2}>
|
||||||
|
<Text color={darkGoldTheme.textMuted} fontSize="lg">{title}</Text>
|
||||||
|
<Text color={darkGoldTheme.textMuted} fontSize="sm" textAlign="center">{description}</Text>
|
||||||
|
</VStack>
|
||||||
|
</VStack>
|
||||||
|
</Center>
|
||||||
|
);
|
||||||
|
|
||||||
// 重新导出类型供外部使用
|
// 重新导出类型供外部使用
|
||||||
export type { KLineModuleProps } from '../../../types';
|
export type { KLineModuleProps } from '../../../types';
|
||||||
|
|
||||||
@@ -50,16 +60,68 @@ const KLineModule: React.FC<KLineModuleProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 黑金主题按钮样式
|
||||||
|
const activeButtonStyle = {
|
||||||
|
bg: `linear-gradient(135deg, ${darkGoldTheme.gold} 0%, ${darkGoldTheme.orange} 100%)`,
|
||||||
|
color: '#1a1a2e',
|
||||||
|
borderColor: darkGoldTheme.gold,
|
||||||
|
_hover: {
|
||||||
|
bg: `linear-gradient(135deg, ${darkGoldTheme.goldLight} 0%, ${darkGoldTheme.gold} 100%)`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const inactiveButtonStyle = {
|
||||||
|
bg: 'transparent',
|
||||||
|
color: darkGoldTheme.textMuted,
|
||||||
|
borderColor: darkGoldTheme.border,
|
||||||
|
_hover: {
|
||||||
|
bg: 'rgba(212, 175, 55, 0.1)',
|
||||||
|
borderColor: darkGoldTheme.gold,
|
||||||
|
color: darkGoldTheme.gold,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemedCard theme={theme}>
|
<Box
|
||||||
<CardHeader>
|
bg={darkGoldTheme.bgCard}
|
||||||
|
borderRadius="xl"
|
||||||
|
border="1px solid"
|
||||||
|
borderColor={darkGoldTheme.border}
|
||||||
|
overflow="hidden"
|
||||||
|
transition="all 0.3s ease"
|
||||||
|
_hover={{
|
||||||
|
borderColor: darkGoldTheme.borderHover,
|
||||||
|
boxShadow: `0 4px 20px rgba(212, 175, 55, 0.15)`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* 卡片头部 */}
|
||||||
|
<Box px={6} py={4} borderBottom="1px solid" borderColor={darkGoldTheme.border}>
|
||||||
<HStack justify="space-between" align="center">
|
<HStack justify="space-between" align="center">
|
||||||
<HStack spacing={3}>
|
<HStack spacing={3}>
|
||||||
<Heading size="md" color={theme.textSecondary}>
|
<Box
|
||||||
|
p={2}
|
||||||
|
borderRadius="lg"
|
||||||
|
bg={darkGoldTheme.tagBg}
|
||||||
|
>
|
||||||
|
<TrendingUp size={20} color={darkGoldTheme.gold} />
|
||||||
|
</Box>
|
||||||
|
<Text
|
||||||
|
fontSize="lg"
|
||||||
|
fontWeight="bold"
|
||||||
|
bgGradient={`linear(to-r, ${darkGoldTheme.gold}, ${darkGoldTheme.orange})`}
|
||||||
|
bgClip="text"
|
||||||
|
>
|
||||||
{mode === 'daily' ? '日K线图' : '分钟K线图'}
|
{mode === 'daily' ? '日K线图' : '分钟K线图'}
|
||||||
</Heading>
|
</Text>
|
||||||
{mode === 'minute' && minuteData?.trade_date && (
|
{mode === 'minute' && minuteData?.trade_date && (
|
||||||
<Badge colorScheme="blue" fontSize="xs">
|
<Badge
|
||||||
|
bg={darkGoldTheme.tagBg}
|
||||||
|
color={darkGoldTheme.gold}
|
||||||
|
fontSize="xs"
|
||||||
|
px={2}
|
||||||
|
py={1}
|
||||||
|
borderRadius="md"
|
||||||
|
>
|
||||||
{minuteData.trade_date}
|
{minuteData.trade_date}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
@@ -72,39 +134,38 @@ const KLineModule: React.FC<KLineModuleProps> = ({
|
|||||||
leftIcon={<RepeatIcon />}
|
leftIcon={<RepeatIcon />}
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
colorScheme="blue"
|
|
||||||
onClick={onLoadMinuteData}
|
onClick={onLoadMinuteData}
|
||||||
isLoading={minuteLoading}
|
isLoading={minuteLoading}
|
||||||
loadingText="获取中"
|
loadingText="获取中"
|
||||||
|
{...inactiveButtonStyle}
|
||||||
>
|
>
|
||||||
刷新
|
刷新
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 模式切换按钮组 */}
|
{/* 模式切换按钮组 */}
|
||||||
<ButtonGroup size="sm" isAttached variant="outline">
|
<ButtonGroup size="sm" isAttached>
|
||||||
<Button
|
<Button
|
||||||
leftIcon={<BarChart2 size={16} />}
|
leftIcon={<BarChart2 size={14} />}
|
||||||
colorScheme={mode === 'daily' ? 'blue' : 'gray'}
|
|
||||||
variant={mode === 'daily' ? 'solid' : 'outline'}
|
|
||||||
onClick={() => handleModeChange('daily')}
|
onClick={() => handleModeChange('daily')}
|
||||||
|
{...(mode === 'daily' ? activeButtonStyle : inactiveButtonStyle)}
|
||||||
>
|
>
|
||||||
日K
|
日K
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
leftIcon={<Clock size={16} />}
|
leftIcon={<Clock size={14} />}
|
||||||
colorScheme={mode === 'minute' ? 'blue' : 'gray'}
|
|
||||||
variant={mode === 'minute' ? 'solid' : 'outline'}
|
|
||||||
onClick={() => handleModeChange('minute')}
|
onClick={() => handleModeChange('minute')}
|
||||||
|
{...(mode === 'minute' ? activeButtonStyle : inactiveButtonStyle)}
|
||||||
>
|
>
|
||||||
分钟
|
分钟
|
||||||
</Button>
|
</Button>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
</HStack>
|
</HStack>
|
||||||
</HStack>
|
</HStack>
|
||||||
</CardHeader>
|
</Box>
|
||||||
|
|
||||||
<CardBody>
|
{/* 卡片内容 */}
|
||||||
|
<Box p={6}>
|
||||||
{mode === 'daily' ? (
|
{mode === 'daily' ? (
|
||||||
// 日K线图
|
// 日K线图
|
||||||
tradeData.length > 0 ? (
|
tradeData.length > 0 ? (
|
||||||
@@ -112,16 +173,12 @@ const KLineModule: React.FC<KLineModuleProps> = ({
|
|||||||
<ReactECharts
|
<ReactECharts
|
||||||
option={getKLineOption(theme, tradeData, analysisMap)}
|
option={getKLineOption(theme, tradeData, analysisMap)}
|
||||||
style={{ height: '100%', width: '100%' }}
|
style={{ height: '100%', width: '100%' }}
|
||||||
theme="light"
|
theme="dark"
|
||||||
onEvents={{ click: onChartClick }}
|
onEvents={{ click: onChartClick }}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
<EmptyState
|
<EmptyState title="暂无日K线数据" description="该股票暂无交易数据" />
|
||||||
theme={theme}
|
|
||||||
title="暂无日K线数据"
|
|
||||||
description="该股票暂无交易数据"
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
// 分钟K线图
|
// 分钟K线图
|
||||||
@@ -131,37 +188,29 @@ const KLineModule: React.FC<KLineModuleProps> = ({
|
|||||||
<Spinner
|
<Spinner
|
||||||
thickness="4px"
|
thickness="4px"
|
||||||
speed="0.65s"
|
speed="0.65s"
|
||||||
emptyColor={theme.bgDark}
|
emptyColor="rgba(212, 175, 55, 0.2)"
|
||||||
color={theme.primary}
|
color={darkGoldTheme.gold}
|
||||||
size="lg"
|
size="lg"
|
||||||
/>
|
/>
|
||||||
<Text color={theme.textMuted} fontSize="sm">
|
<Text color={darkGoldTheme.textMuted} fontSize="sm">
|
||||||
加载分钟频数据中...
|
加载分钟频数据中...
|
||||||
</Text>
|
</Text>
|
||||||
</VStack>
|
</VStack>
|
||||||
</Center>
|
</Center>
|
||||||
) : hasMinuteData ? (
|
) : hasMinuteData ? (
|
||||||
<VStack spacing={6} align="stretch">
|
<Box h="500px">
|
||||||
<Box h="500px">
|
<ReactECharts
|
||||||
<ReactECharts
|
option={getMinuteKLineOption(theme, minuteData)}
|
||||||
option={getMinuteKLineOption(theme, minuteData)}
|
style={{ height: '100%', width: '100%' }}
|
||||||
style={{ height: '100%', width: '100%' }}
|
theme="dark"
|
||||||
theme="light"
|
/>
|
||||||
/>
|
</Box>
|
||||||
</Box>
|
|
||||||
<MinuteStats theme={theme} data={minuteData.data} />
|
|
||||||
<TradeAnalysis theme={theme} data={minuteData.data} />
|
|
||||||
</VStack>
|
|
||||||
) : (
|
) : (
|
||||||
<EmptyState
|
<EmptyState title="暂无分钟数据" description="点击刷新按钮获取当日分钟频数据" />
|
||||||
theme={theme}
|
|
||||||
title="暂无分钟数据"
|
|
||||||
description="点击右上角刷新按钮获取当日分钟频数据"
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
</CardBody>
|
</Box>
|
||||||
</ThemedCard>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
// src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/TradeTable.tsx
|
|
||||||
// 交易明细表格组件
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
Thead,
|
|
||||||
Tbody,
|
|
||||||
Tr,
|
|
||||||
Th,
|
|
||||||
Td,
|
|
||||||
TableContainer,
|
|
||||||
CardBody,
|
|
||||||
CardHeader,
|
|
||||||
Heading,
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
|
|
||||||
import ThemedCard from '../../ThemedCard';
|
|
||||||
import { formatNumber, formatPercent } from '../../../utils/formatUtils';
|
|
||||||
import type { Theme, TradeDayData } from '../../../types';
|
|
||||||
|
|
||||||
export interface TradeTableProps {
|
|
||||||
theme: Theme;
|
|
||||||
tradeData: TradeDayData[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const TradeTable: React.FC<TradeTableProps> = ({ theme, tradeData }) => {
|
|
||||||
return (
|
|
||||||
<ThemedCard theme={theme}>
|
|
||||||
<CardHeader>
|
|
||||||
<Heading size="md" color={theme.textSecondary}>
|
|
||||||
交易明细
|
|
||||||
</Heading>
|
|
||||||
</CardHeader>
|
|
||||||
<CardBody>
|
|
||||||
<TableContainer>
|
|
||||||
<Table variant="simple" size="sm">
|
|
||||||
<Thead>
|
|
||||||
<Tr>
|
|
||||||
<Th color={theme.textSecondary}>日期</Th>
|
|
||||||
<Th isNumeric color={theme.textSecondary}>开盘</Th>
|
|
||||||
<Th isNumeric color={theme.textSecondary}>最高</Th>
|
|
||||||
<Th isNumeric color={theme.textSecondary}>最低</Th>
|
|
||||||
<Th isNumeric color={theme.textSecondary}>收盘</Th>
|
|
||||||
<Th isNumeric color={theme.textSecondary}>涨跌幅</Th>
|
|
||||||
<Th isNumeric color={theme.textSecondary}>成交量</Th>
|
|
||||||
<Th isNumeric color={theme.textSecondary}>成交额</Th>
|
|
||||||
</Tr>
|
|
||||||
</Thead>
|
|
||||||
<Tbody>
|
|
||||||
{tradeData
|
|
||||||
.slice(-10)
|
|
||||||
.reverse()
|
|
||||||
.map((item, idx) => (
|
|
||||||
<Tr key={idx} _hover={{ bg: theme.bgDark }}>
|
|
||||||
<Td color={theme.textPrimary}>{item.date}</Td>
|
|
||||||
<Td isNumeric color={theme.textPrimary}>{item.open}</Td>
|
|
||||||
<Td isNumeric color={theme.textPrimary}>{item.high}</Td>
|
|
||||||
<Td isNumeric color={theme.textPrimary}>{item.low}</Td>
|
|
||||||
<Td isNumeric color={theme.textPrimary} fontWeight="bold">
|
|
||||||
{item.close}
|
|
||||||
</Td>
|
|
||||||
<Td
|
|
||||||
isNumeric
|
|
||||||
color={item.change_percent >= 0 ? theme.success : theme.danger}
|
|
||||||
fontWeight="bold"
|
|
||||||
>
|
|
||||||
{item.change_percent >= 0 ? '+' : ''}
|
|
||||||
{formatPercent(item.change_percent)}
|
|
||||||
</Td>
|
|
||||||
<Td isNumeric color={theme.textPrimary}>
|
|
||||||
{formatNumber(item.volume, 0)}
|
|
||||||
</Td>
|
|
||||||
<Td isNumeric color={theme.textPrimary}>
|
|
||||||
{formatNumber(item.amount)}
|
|
||||||
</Td>
|
|
||||||
</Tr>
|
|
||||||
))}
|
|
||||||
</Tbody>
|
|
||||||
</Table>
|
|
||||||
</TableContainer>
|
|
||||||
</CardBody>
|
|
||||||
</ThemedCard>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TradeTable;
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
// src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/atoms/EmptyState.tsx
|
|
||||||
// 空状态组件
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import { Center, VStack, Text, Icon } from '@chakra-ui/react';
|
|
||||||
import { InfoIcon } from '@chakra-ui/icons';
|
|
||||||
|
|
||||||
import type { Theme } from '../../../../types';
|
|
||||||
|
|
||||||
export interface EmptyStateProps {
|
|
||||||
theme: Theme;
|
|
||||||
title?: string;
|
|
||||||
description?: string;
|
|
||||||
height?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const EmptyState: React.FC<EmptyStateProps> = ({
|
|
||||||
theme,
|
|
||||||
title = '暂无分钟频数据',
|
|
||||||
description = '点击"获取分钟数据"按钮加载最新的交易日分钟频数据',
|
|
||||||
height = '300px',
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<Center h={height}>
|
|
||||||
<VStack spacing={4}>
|
|
||||||
<Icon as={InfoIcon} color={theme.textMuted} boxSize={12} />
|
|
||||||
<VStack spacing={2}>
|
|
||||||
<Text color={theme.textMuted} fontSize="lg">
|
|
||||||
{title}
|
|
||||||
</Text>
|
|
||||||
<Text color={theme.textMuted} fontSize="sm" textAlign="center">
|
|
||||||
{description}
|
|
||||||
</Text>
|
|
||||||
</VStack>
|
|
||||||
</VStack>
|
|
||||||
</Center>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default EmptyState;
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
// src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/atoms/MinuteStats.tsx
|
|
||||||
// 分钟数据统计组件
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import {
|
|
||||||
SimpleGrid,
|
|
||||||
Stat,
|
|
||||||
StatLabel,
|
|
||||||
StatNumber,
|
|
||||||
StatHelpText,
|
|
||||||
StatArrow,
|
|
||||||
HStack,
|
|
||||||
Text,
|
|
||||||
Icon,
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
import {
|
|
||||||
ChevronDownIcon,
|
|
||||||
ChevronUpIcon,
|
|
||||||
ArrowUpIcon,
|
|
||||||
ArrowDownIcon,
|
|
||||||
} from '@chakra-ui/icons';
|
|
||||||
|
|
||||||
import type { Theme, MinuteDataPoint } from '../../../../types';
|
|
||||||
|
|
||||||
export interface MinuteStatsProps {
|
|
||||||
theme: Theme;
|
|
||||||
data: MinuteDataPoint[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const MinuteStats: React.FC<MinuteStatsProps> = ({ theme, data }) => {
|
|
||||||
if (data.length === 0) return null;
|
|
||||||
|
|
||||||
const firstOpen = data[0]?.open || 0;
|
|
||||||
const lastClose = data[data.length - 1]?.close || 0;
|
|
||||||
const highPrice = Math.max(...data.map((item) => item.high).filter(Boolean));
|
|
||||||
const lowPrice = Math.min(...data.map((item) => item.low).filter(Boolean));
|
|
||||||
const isUp = lastClose >= firstOpen;
|
|
||||||
const changePercent = firstOpen ? Math.abs(((lastClose - firstOpen) / firstOpen) * 100) : 0;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SimpleGrid columns={{ base: 2, md: 4 }} spacing={4}>
|
|
||||||
<Stat>
|
|
||||||
<StatLabel color={theme.textMuted} fontSize="sm">
|
|
||||||
<HStack spacing={1}>
|
|
||||||
<Icon as={ArrowUpIcon} boxSize={3} />
|
|
||||||
<Text>开盘价</Text>
|
|
||||||
</HStack>
|
|
||||||
</StatLabel>
|
|
||||||
<StatNumber color={theme.textPrimary} fontSize="lg">
|
|
||||||
{firstOpen?.toFixed(2) || '-'}
|
|
||||||
</StatNumber>
|
|
||||||
</Stat>
|
|
||||||
|
|
||||||
<Stat>
|
|
||||||
<StatLabel color={theme.textMuted} fontSize="sm">
|
|
||||||
<HStack spacing={1}>
|
|
||||||
<Icon as={ArrowDownIcon} boxSize={3} />
|
|
||||||
<Text>当前价</Text>
|
|
||||||
</HStack>
|
|
||||||
</StatLabel>
|
|
||||||
<StatNumber color={isUp ? theme.success : theme.danger} fontSize="lg">
|
|
||||||
{lastClose?.toFixed(2) || '-'}
|
|
||||||
</StatNumber>
|
|
||||||
<StatHelpText fontSize="xs">
|
|
||||||
<StatArrow type={isUp ? 'increase' : 'decrease'} />
|
|
||||||
{changePercent.toFixed(2)}%
|
|
||||||
</StatHelpText>
|
|
||||||
</Stat>
|
|
||||||
|
|
||||||
<Stat>
|
|
||||||
<StatLabel color={theme.textMuted} fontSize="sm">
|
|
||||||
<HStack spacing={1}>
|
|
||||||
<Icon as={ChevronUpIcon} boxSize={3} />
|
|
||||||
<Text>最高价</Text>
|
|
||||||
</HStack>
|
|
||||||
</StatLabel>
|
|
||||||
<StatNumber color={theme.success} fontSize="lg">
|
|
||||||
{highPrice.toFixed(2)}
|
|
||||||
</StatNumber>
|
|
||||||
</Stat>
|
|
||||||
|
|
||||||
<Stat>
|
|
||||||
<StatLabel color={theme.textMuted} fontSize="sm">
|
|
||||||
<HStack spacing={1}>
|
|
||||||
<Icon as={ChevronDownIcon} boxSize={3} />
|
|
||||||
<Text>最低价</Text>
|
|
||||||
</HStack>
|
|
||||||
</StatLabel>
|
|
||||||
<StatNumber color={theme.danger} fontSize="lg">
|
|
||||||
{lowPrice.toFixed(2)}
|
|
||||||
</StatNumber>
|
|
||||||
</Stat>
|
|
||||||
</SimpleGrid>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MinuteStats;
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
// src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/atoms/TradeAnalysis.tsx
|
|
||||||
// 成交数据分析组件
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import { Box, Text, HStack, Badge, Grid } from '@chakra-ui/react';
|
|
||||||
|
|
||||||
import { formatNumber } from '../../../../utils/formatUtils';
|
|
||||||
import type { Theme, MinuteDataPoint } from '../../../../types';
|
|
||||||
|
|
||||||
export interface TradeAnalysisProps {
|
|
||||||
theme: Theme;
|
|
||||||
data: MinuteDataPoint[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const TradeAnalysis: React.FC<TradeAnalysisProps> = ({ theme, data }) => {
|
|
||||||
if (data.length === 0) return null;
|
|
||||||
|
|
||||||
const totalVolume = data.reduce((sum, item) => sum + item.volume, 0);
|
|
||||||
const totalAmount = data.reduce((sum, item) => sum + item.amount, 0);
|
|
||||||
const avgPrice = data.reduce((sum, item) => sum + item.close, 0) / data.length;
|
|
||||||
const maxVolume = Math.max(...data.map((item) => item.volume));
|
|
||||||
const activeTime = data.find((item) => item.volume === maxVolume);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
p={4}
|
|
||||||
bg={theme.bgDark}
|
|
||||||
borderRadius="lg"
|
|
||||||
border="1px solid"
|
|
||||||
borderColor={theme.border}
|
|
||||||
>
|
|
||||||
<HStack justify="space-between" mb={4}>
|
|
||||||
<Text fontWeight="bold" color={theme.textSecondary}>
|
|
||||||
成交数据分析
|
|
||||||
</Text>
|
|
||||||
<HStack spacing={2}>
|
|
||||||
<Badge colorScheme="purple" fontSize="xs">
|
|
||||||
总成交量: {formatNumber(totalVolume, 0)}
|
|
||||||
</Badge>
|
|
||||||
<Badge colorScheme="orange" fontSize="xs">
|
|
||||||
总成交额: {formatNumber(totalAmount)}
|
|
||||||
</Badge>
|
|
||||||
</HStack>
|
|
||||||
</HStack>
|
|
||||||
|
|
||||||
<Grid templateColumns="repeat(3, 1fr)" gap={4}>
|
|
||||||
<Box>
|
|
||||||
<Text fontSize="sm" color={theme.textMuted} mb={2}>
|
|
||||||
活跃时段
|
|
||||||
</Text>
|
|
||||||
<Text fontSize="sm" color={theme.textPrimary}>
|
|
||||||
{activeTime ? `${activeTime.time} (${formatNumber(maxVolume, 0)})` : '-'}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fontSize="sm" color={theme.textMuted} mb={2}>
|
|
||||||
平均价格
|
|
||||||
</Text>
|
|
||||||
<Text fontSize="sm" color={theme.textPrimary}>
|
|
||||||
{avgPrice.toFixed(2)}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fontSize="sm" color={theme.textMuted} mb={2}>
|
|
||||||
数据点数
|
|
||||||
</Text>
|
|
||||||
<Text fontSize="sm" color={theme.textPrimary}>
|
|
||||||
{data.length} 个分钟
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
</Grid>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TradeAnalysis;
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
// src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/atoms/index.ts
|
|
||||||
// 原子组件统一导出
|
|
||||||
|
|
||||||
export { default as MinuteStats } from './MinuteStats';
|
|
||||||
export { default as TradeAnalysis } from './TradeAnalysis';
|
|
||||||
export { default as EmptyState } from './EmptyState';
|
|
||||||
|
|
||||||
export type { MinuteStatsProps } from './MinuteStats';
|
|
||||||
export type { TradeAnalysisProps } from './TradeAnalysis';
|
|
||||||
export type { EmptyStateProps } from './EmptyState';
|
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
// src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/index.tsx
|
// src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/index.tsx
|
||||||
// 交易数据面板 - K线模块(日K/分钟切换)、交易明细表格
|
// 交易数据面板 - K线模块(日K/分钟切换)
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { VStack } from '@chakra-ui/react';
|
|
||||||
|
|
||||||
import KLineModule from './KLineModule';
|
import KLineModule from './KLineModule';
|
||||||
import TradeTable from './TradeTable';
|
|
||||||
import type { Theme, TradeDayData, MinuteData, RiseAnalysis } from '../../../types';
|
import type { Theme, TradeDayData, MinuteData, RiseAnalysis } from '../../../types';
|
||||||
|
|
||||||
export interface TradeDataPanelProps {
|
export interface TradeDataPanelProps {
|
||||||
@@ -28,19 +26,15 @@ const TradeDataPanel: React.FC<TradeDataPanelProps> = ({
|
|||||||
onChartClick,
|
onChartClick,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<VStack spacing={6} align="stretch">
|
<KLineModule
|
||||||
<KLineModule
|
theme={theme}
|
||||||
theme={theme}
|
tradeData={tradeData}
|
||||||
tradeData={tradeData}
|
minuteData={minuteData}
|
||||||
minuteData={minuteData}
|
minuteLoading={minuteLoading}
|
||||||
minuteLoading={minuteLoading}
|
analysisMap={analysisMap}
|
||||||
analysisMap={analysisMap}
|
onLoadMinuteData={onLoadMinuteData}
|
||||||
onLoadMinuteData={onLoadMinuteData}
|
onChartClick={onChartClick}
|
||||||
onChartClick={onChartClick}
|
/>
|
||||||
/>
|
|
||||||
|
|
||||||
<TradeTable theme={theme} tradeData={tradeData} />
|
|
||||||
</VStack>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -48,6 +42,4 @@ export default TradeDataPanel;
|
|||||||
|
|
||||||
// 导出子组件供外部按需使用
|
// 导出子组件供外部按需使用
|
||||||
export { default as KLineModule } from './KLineModule';
|
export { default as KLineModule } from './KLineModule';
|
||||||
export { default as TradeTable } from './TradeTable';
|
|
||||||
export type { KLineModuleProps } from './KLineModule';
|
export type { KLineModuleProps } from './KLineModule';
|
||||||
export type { TradeTableProps } from './TradeTable';
|
|
||||||
|
|||||||
@@ -15,5 +15,5 @@ export type { UnusualPanelProps } from './UnusualPanel';
|
|||||||
export type { PledgePanelProps } from './PledgePanel';
|
export type { PledgePanelProps } from './PledgePanel';
|
||||||
|
|
||||||
// 导出 TradeDataPanel 子组件
|
// 导出 TradeDataPanel 子组件
|
||||||
export { KLineModule, TradeTable } from './TradeDataPanel';
|
export { KLineModule } from './TradeDataPanel';
|
||||||
export type { KLineModuleProps, TradeTableProps } from './TradeDataPanel';
|
export type { KLineModuleProps } from './TradeDataPanel';
|
||||||
|
|||||||
@@ -299,14 +299,6 @@ export interface KLineModuleProps {
|
|||||||
onChartClick: (params: { seriesName?: string; data?: [number, number] }) => void;
|
onChartClick: (params: { seriesName?: string; data?: [number, number] }) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* TradeTable 组件 Props
|
|
||||||
*/
|
|
||||||
export interface TradeTableProps {
|
|
||||||
theme: Theme;
|
|
||||||
tradeData: TradeDayData[];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FundingTab 组件 Props
|
* FundingTab 组件 Props
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user