diff --git a/src/views/Company/components/MarketDataView/components/panels/BigDealPanel.tsx b/src/views/Company/components/MarketDataView/components/panels/BigDealPanel.tsx new file mode 100644 index 00000000..f7bd8b6c --- /dev/null +++ b/src/views/Company/components/MarketDataView/components/panels/BigDealPanel.tsx @@ -0,0 +1,143 @@ +// src/views/Company/components/MarketDataView/components/panels/BigDealPanel.tsx +// 大宗交易面板 - 大宗交易记录表格 + +import React from 'react'; +import { + Box, + Text, + Table, + Thead, + Tbody, + Tr, + Th, + Td, + TableContainer, + CardBody, + CardHeader, + Center, + Badge, + VStack, + HStack, + Tooltip, + Heading, +} from '@chakra-ui/react'; + +import ThemedCard from '../ThemedCard'; +import { formatNumber } from '../../utils/formatUtils'; +import type { Theme, BigDealData } from '../../types'; + +export interface BigDealPanelProps { + theme: Theme; + bigDealData: BigDealData; +} + +const BigDealPanel: React.FC = ({ theme, bigDealData }) => { + return ( + + + + 大宗交易记录 + + + + {bigDealData?.daily_stats && bigDealData.daily_stats.length > 0 ? ( + + {bigDealData.daily_stats.map((dayStats, idx) => ( + + + + {dayStats.date} + + + + 交易笔数: {dayStats.count} + + + 成交量: {formatNumber(dayStats.total_volume)}万股 + + + 成交额: {formatNumber(dayStats.total_amount)}万元 + + + 均价: {dayStats.avg_price?.toFixed(2) || '-'}元 + + + + + {dayStats.deals && dayStats.deals.length > 0 && ( + + + + + + + + + + + + + {dayStats.deals.map((deal, i) => ( + + + + + + + + ))} + +
买方营业部卖方营业部 + 成交价 + + 成交量(万股) + + 成交额(万元) +
+ + {deal.buyer_dept || '-'} + + + + {deal.seller_dept || '-'} + + + {deal.price?.toFixed(2) || '-'} + + {deal.volume?.toFixed(2) || '-'} + + {deal.amount?.toFixed(2) || '-'} +
+
+ )} +
+ ))} +
+ ) : ( +
+ 暂无大宗交易数据 +
+ )} +
+
+ ); +}; + +export default BigDealPanel; diff --git a/src/views/Company/components/MarketDataView/components/panels/FundingPanel.tsx b/src/views/Company/components/MarketDataView/components/panels/FundingPanel.tsx new file mode 100644 index 00000000..81352497 --- /dev/null +++ b/src/views/Company/components/MarketDataView/components/panels/FundingPanel.tsx @@ -0,0 +1,113 @@ +// src/views/Company/components/MarketDataView/components/panels/FundingPanel.tsx +// 融资融券面板 - 融资融券数据图表和卡片 + +import React from 'react'; +import { + Box, + Text, + CardBody, + CardHeader, + VStack, + HStack, + Grid, + Heading, +} from '@chakra-ui/react'; +import ReactECharts from 'echarts-for-react'; + +import ThemedCard from '../ThemedCard'; +import { formatNumber } from '../../utils/formatUtils'; +import { getFundingOption } from '../../utils/chartOptions'; +import type { Theme, FundingDayData } from '../../types'; + +export interface FundingPanelProps { + theme: Theme; + fundingData: FundingDayData[]; +} + +const FundingPanel: React.FC = ({ theme, fundingData }) => { + return ( + + + + {fundingData.length > 0 && ( + + + + )} + + + + + {/* 融资数据 */} + + + + 融资数据 + + + + + {fundingData + .slice(-5) + .reverse() + .map((item, idx) => ( + + + {item.date} + + + {formatNumber(item.financing.balance)} + + + 买入{formatNumber(item.financing.buy)} / 偿还 + {formatNumber(item.financing.repay)} + + + + + ))} + + + + + {/* 融券数据 */} + + + + 融券数据 + + + + + {fundingData + .slice(-5) + .reverse() + .map((item, idx) => ( + + + {item.date} + + + {formatNumber(item.securities.balance)} + + + 卖出{formatNumber(item.securities.sell)} / 偿还 + {formatNumber(item.securities.repay)} + + + + + ))} + + + + + + ); +}; + +export default FundingPanel; diff --git a/src/views/Company/components/MarketDataView/components/panels/PledgePanel.tsx b/src/views/Company/components/MarketDataView/components/panels/PledgePanel.tsx new file mode 100644 index 00000000..13f85d5e --- /dev/null +++ b/src/views/Company/components/MarketDataView/components/panels/PledgePanel.tsx @@ -0,0 +1,124 @@ +// src/views/Company/components/MarketDataView/components/panels/PledgePanel.tsx +// 股权质押面板 - 质押图表和表格 + +import React from 'react'; +import { + Box, + Text, + Table, + Thead, + Tbody, + Tr, + Th, + Td, + TableContainer, + CardBody, + CardHeader, + VStack, + Heading, +} from '@chakra-ui/react'; +import ReactECharts from 'echarts-for-react'; + +import ThemedCard from '../ThemedCard'; +import { formatNumber, formatPercent } from '../../utils/formatUtils'; +import { getPledgeOption } from '../../utils/chartOptions'; +import type { Theme, PledgeData } from '../../types'; + +export interface PledgePanelProps { + theme: Theme; + pledgeData: PledgeData[]; +} + +const PledgePanel: React.FC = ({ theme, pledgeData }) => { + return ( + + + + {pledgeData.length > 0 && ( + + + + )} + + + + + + + 质押明细 + + + + + + + + + + + + + + + + + + {pledgeData.length > 0 ? ( + pledgeData.map((item, idx) => ( + + + + + + + + + + )) + ) : ( + + + + )} + +
日期 + 无限售质押(万股) + + 限售质押(万股) + + 质押总量(万股) + + 总股本(万股) + + 质押比例 + + 质押笔数 +
{item.end_date} + {formatNumber(item.unrestricted_pledge, 0)} + + {formatNumber(item.restricted_pledge, 0)} + + {formatNumber(item.total_pledge, 0)} + + {formatNumber(item.total_shares, 0)} + + {formatPercent(item.pledge_ratio)} + + {item.pledge_count} +
+ + 暂无数据 + +
+
+
+
+
+ ); +}; + +export default PledgePanel; diff --git a/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel.tsx b/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel.tsx new file mode 100644 index 00000000..59c5ed38 --- /dev/null +++ b/src/views/Company/components/MarketDataView/components/panels/TradeDataPanel.tsx @@ -0,0 +1,381 @@ +// src/views/Company/components/MarketDataView/components/panels/TradeDataPanel.tsx +// 交易数据面板 - K线图、分钟图、交易明细表格 + +import React from 'react'; +import { + Box, + Text, + Table, + Thead, + Tbody, + Tr, + Th, + Td, + TableContainer, + Stat, + StatLabel, + StatNumber, + StatHelpText, + StatArrow, + SimpleGrid, + CardBody, + CardHeader, + Spinner, + Center, + Badge, + VStack, + HStack, + Button, + Grid, + Icon, + Heading, +} from '@chakra-ui/react'; +import { + ChevronDownIcon, + ChevronUpIcon, + InfoIcon, + RepeatIcon, + TimeIcon, + ArrowUpIcon, + ArrowDownIcon, +} from '@chakra-ui/icons'; +import ReactECharts from 'echarts-for-react'; + +import ThemedCard from '../ThemedCard'; +import { formatNumber, formatPercent } from '../../utils/formatUtils'; +import { getKLineOption, getMinuteKLineOption } from '../../utils/chartOptions'; +import type { Theme, TradeDayData, MinuteData, RiseAnalysis } from '../../types'; + +export interface TradeDataPanelProps { + theme: Theme; + tradeData: TradeDayData[]; + minuteData: MinuteData | null; + minuteLoading: boolean; + analysisMap: Record; + onLoadMinuteData: () => void; + onChartClick: (params: { seriesName?: string; data?: [number, number] }) => void; +} + +const TradeDataPanel: React.FC = ({ + theme, + tradeData, + minuteData, + minuteLoading, + analysisMap, + onLoadMinuteData, + onChartClick, +}) => { + return ( + + {/* K线图 */} + + + {tradeData.length > 0 && ( + + + + )} + + + + {/* 分钟K线数据 */} + + + + + + + 当日分钟频数据 + + {minuteData && minuteData.trade_date && ( + + {minuteData.trade_date} + + )} + + + + + + {minuteLoading ? ( +
+ + + + 加载分钟频数据中... + + +
+ ) : minuteData && minuteData.data && minuteData.data.length > 0 ? ( + + + + + + {/* 分钟数据统计 */} + + + + + + 开盘价 + + + + {minuteData.data[0]?.open?.toFixed(2) || '-'} + + + + + + + 当前价 + + + = + (minuteData.data[0]?.open || 0) + ? theme.success + : theme.danger + } + fontSize="lg" + > + {minuteData.data[minuteData.data.length - 1]?.close?.toFixed(2) || '-'} + + + = + (minuteData.data[0]?.open || 0) + ? 'increase' + : 'decrease' + } + /> + {(() => { + const lastClose = minuteData.data[minuteData.data.length - 1]?.close; + const firstOpen = minuteData.data[0]?.open; + if (lastClose && firstOpen) { + return Math.abs(((lastClose - firstOpen) / firstOpen) * 100).toFixed(2); + } + return '0.00'; + })()} + % + + + + + + + 最高价 + + + + {Math.max(...minuteData.data.map((item) => item.high).filter(Boolean)).toFixed( + 2 + )} + + + + + + + 最低价 + + + + {Math.min(...minuteData.data.map((item) => item.low).filter(Boolean)).toFixed(2)} + + + + + {/* 成交数据分析 */} + + + + 成交数据分析 + + + + 总成交量:{' '} + {formatNumber( + minuteData.data.reduce((sum, item) => sum + item.volume, 0), + 0 + )} + + + 总成交额:{' '} + {formatNumber(minuteData.data.reduce((sum, item) => sum + item.amount, 0))} + + + + + + + + 活跃时段 + + + {(() => { + const maxVolume = Math.max(...minuteData.data.map((item) => item.volume)); + const activeTime = minuteData.data.find( + (item) => item.volume === maxVolume + ); + return activeTime + ? `${activeTime.time} (${formatNumber(maxVolume, 0)})` + : '-'; + })()} + + + + + 平均价格 + + + {( + minuteData.data.reduce((sum, item) => sum + item.close, 0) / + minuteData.data.length + ).toFixed(2)} + + + + + 数据点数 + + + {minuteData.data.length} 个分钟 + + + + + + ) : ( +
+ + + + + 暂无分钟频数据 + + + 点击"获取分钟数据"按钮加载最新的交易日分钟频数据 + + + +
+ )} +
+
+ + {/* 交易明细表格 */} + + + + 交易明细 + + + + + + + + + + + + + + + + + + + {tradeData + .slice(-10) + .reverse() + .map((item, idx) => ( + + + + + + + + + + + ))} + +
日期 + 开盘 + + 最高 + + 最低 + + 收盘 + + 涨跌幅 + + 成交量 + + 成交额 +
{item.date} + {item.open} + + {item.high} + + {item.low} + + {item.close} + = 0 ? theme.success : theme.danger} + fontWeight="bold" + > + {item.change_percent >= 0 ? '+' : ''} + {formatPercent(item.change_percent)} + + {formatNumber(item.volume, 0)} + + {formatNumber(item.amount)} +
+
+
+
+
+ ); +}; + +export default TradeDataPanel; diff --git a/src/views/Company/components/MarketDataView/components/panels/UnusualPanel.tsx b/src/views/Company/components/MarketDataView/components/panels/UnusualPanel.tsx new file mode 100644 index 00000000..6cc1c3b2 --- /dev/null +++ b/src/views/Company/components/MarketDataView/components/panels/UnusualPanel.tsx @@ -0,0 +1,163 @@ +// src/views/Company/components/MarketDataView/components/panels/UnusualPanel.tsx +// 龙虎榜面板 - 龙虎榜数据展示 + +import React from 'react'; +import { + Box, + Text, + CardBody, + CardHeader, + Center, + Badge, + VStack, + HStack, + Grid, + Heading, +} from '@chakra-ui/react'; + +import ThemedCard from '../ThemedCard'; +import { formatNumber } from '../../utils/formatUtils'; +import type { Theme, UnusualData } from '../../types'; + +export interface UnusualPanelProps { + theme: Theme; + unusualData: UnusualData; +} + +const UnusualPanel: React.FC = ({ theme, unusualData }) => { + return ( + + + + 龙虎榜数据 + + + + {unusualData?.grouped_data && unusualData.grouped_data.length > 0 ? ( + + {unusualData.grouped_data.map((dayData, idx) => ( + + + + {dayData.date} + + + + 买入: {formatNumber(dayData.total_buy)} + + + 卖出: {formatNumber(dayData.total_sell)} + + 0 ? 'red' : 'green'} + fontSize="md" + > + 净额: {formatNumber(dayData.net_amount)} + + + + + + + + 买入前五 + + + {dayData.buyers && dayData.buyers.length > 0 ? ( + dayData.buyers.slice(0, 5).map((buyer, i) => ( + + + {buyer.dept_name} + + + {formatNumber(buyer.buy_amount)} + + + )) + ) : ( + + 暂无数据 + + )} + + + + + + 卖出前五 + + + {dayData.sellers && dayData.sellers.length > 0 ? ( + dayData.sellers.slice(0, 5).map((seller, i) => ( + + + {seller.dept_name} + + + {formatNumber(seller.sell_amount)} + + + )) + ) : ( + + 暂无数据 + + )} + + + + + {/* 信息类型标签 */} + + + 类型: + + {dayData.info_types?.map((type, i) => ( + + {type} + + ))} + + + ))} + + ) : ( +
+ 暂无龙虎榜数据 +
+ )} +
+
+ ); +}; + +export default UnusualPanel;