// src/views/Market/MarketDataPro.jsx import React, { useState, useEffect, useMemo } from 'react'; import { logger } from '../../utils/logger'; import { getApiBase } from '../../utils/apiConfig'; import { Box, Container, Tabs, TabList, TabPanels, Tab, TabPanel, Heading, Text, Table, Thead, Tbody, Tr, Th, Td, TableContainer, Stat, StatLabel, StatNumber, StatHelpText, StatArrow, SimpleGrid, Card, CardBody, CardHeader, Spinner, Center, Alert, AlertIcon, Badge, VStack, HStack, Divider, useColorModeValue, Select, Button, Tooltip, Progress, Modal, ModalOverlay, ModalContent, ModalHeader, ModalBody, ModalCloseButton, useDisclosure, Input, Flex, Tag, TagLabel, IconButton, useToast, Skeleton, SkeletonText, Grid, GridItem, ButtonGroup, Stack, useColorMode, Icon, InputGroup, InputLeftElement, Spacer, CircularProgress, CircularProgressLabel, chakra, } from '@chakra-ui/react'; import { ChevronDownIcon, ChevronUpIcon, InfoIcon, DownloadIcon, RepeatIcon, SearchIcon, ViewIcon, TimeIcon, ArrowUpIcon, ArrowDownIcon, StarIcon, WarningIcon, LockIcon, UnlockIcon, BellIcon, CalendarIcon, ExternalLinkIcon, AddIcon, MinusIcon, CheckCircleIcon, SmallCloseIcon, MoonIcon, SunIcon, } from '@chakra-ui/icons'; import ReactECharts from 'echarts-for-react'; import ReactMarkdown from 'react-markdown'; // API服务配置 const API_BASE_URL = getApiBase(); // 主题配置 const themes = { light: { // 日间模式 - 白+蓝 primary: '#2B6CB0', primaryDark: '#1E4E8C', secondary: '#FFFFFF', secondaryDark: '#F7FAFC', success: '#FF4444', // 涨 - 红色 danger: '#00C851', // 跌 - 绿色 warning: '#FF9800', info: '#00BCD4', bgMain: '#F7FAFC', bgCard: '#FFFFFF', bgDark: '#EDF2F7', textPrimary: '#2D3748', textSecondary: '#4A5568', textMuted: '#718096', border: '#CBD5E0', chartBg: '#FFFFFF', }, dark: { // 夜间模式 - 黑+金 primary: '#FFD700', primaryDark: '#FFA500', secondary: '#1A1A1A', secondaryDark: '#000000', success: '#FF4444', // 涨 - 红色 danger: '#00C851', // 跌 - 绿色 warning: '#FFA500', info: '#00BFFF', bgMain: '#0A0A0A', bgCard: '#141414', bgDark: '#000000', textPrimary: '#FFFFFF', textSecondary: '#FFD700', textMuted: '#999999', border: '#333333', chartBg: '#141414', } }; // API服务 const marketService = { async apiRequest(url) { try { const response = await fetch(`${API_BASE_URL}${url}`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); } catch (error) { logger.error('marketService', 'apiRequest', error, { url }); throw error; } }, async getTradeData(stockCode, days = 60) { return this.apiRequest(`/api/market/trade/${stockCode}?days=${days}`); }, async getFundingData(stockCode, days = 30) { return this.apiRequest(`/api/market/funding/${stockCode}?days=${days}`); }, async getBigDealData(stockCode, days = 30) { return this.apiRequest(`/api/market/bigdeal/${stockCode}?days=${days}`); }, async getUnusualData(stockCode, days = 30) { return this.apiRequest(`/api/market/unusual/${stockCode}?days=${days}`); }, async getPledgeData(stockCode) { return this.apiRequest(`/api/market/pledge/${stockCode}`); }, async getMarketSummary(stockCode) { return this.apiRequest(`/api/market/summary/${stockCode}`); }, async getRiseAnalysis(stockCode, startDate, endDate) { let url = `/api/market/rise-analysis/${stockCode}`; if (startDate && endDate) { url += `?start_date=${startDate}&end_date=${endDate}`; } return this.apiRequest(url); } }; // 格式化工具 const formatUtils = { formatNumber(value, decimals = 2) { if (!value && value !== 0) return '-'; const num = parseFloat(value); if (Math.abs(num) >= 100000000) { return (num / 100000000).toFixed(decimals) + '亿'; } else if (Math.abs(num) >= 10000) { return (num / 10000).toFixed(decimals) + '万'; } return num.toFixed(decimals); }, formatPercent(value) { if (!value && value !== 0) return '-'; return value.toFixed(2) + '%'; }, formatDate(dateStr) { if (!dateStr) return '-'; return dateStr.substring(0, 10); } }; // 主题化卡片组件 const ThemedCard = ({ children, theme, ...props }) => { return ( {children} ); }; // Markdown渲染组件 const MarkdownRenderer = ({ children, theme, colorMode }) => { return ( {children} ); }; // 主组件 const MarketDataView = ({ stockCode: propStockCode }) => { const { colorMode } = useColorMode(); const toast = useToast(); const { isOpen, onOpen, onClose } = useDisclosure(); const [modalContent, setModalContent] = useState(null); // 获取当前主题 const theme = colorMode === 'light' ? themes.light : themes.dark; // 状态管理 const [stockCode, setStockCode] = useState(propStockCode || '600000'); const [loading, setLoading] = useState(false); const [activeTab, setActiveTab] = useState(0); const [selectedPeriod, setSelectedPeriod] = useState(60); // 数据状态 const [summary, setSummary] = useState(null); const [tradeData, setTradeData] = useState([]); const [fundingData, setFundingData] = useState([]); const [bigDealData, setBigDealData] = useState({ data: [], daily_stats: [] }); const [unusualData, setUnusualData] = useState({ data: [], grouped_data: [] }); const [pledgeData, setPledgeData] = useState([]); const [riseAnalysisData, setRiseAnalysisData] = useState([]); const [analysisMap, setAnalysisMap] = useState({}); const [minuteData, setMinuteData] = useState([]); const [minuteLoading, setMinuteLoading] = useState(false); // 加载数据 const loadMarketData = async () => { logger.debug('MarketDataView', '开始加载市场数据', { stockCode, selectedPeriod }); setLoading(true); try { const [summaryRes, tradeRes, fundingRes, bigDealRes, unusualRes, pledgeRes, riseAnalysisRes] = await Promise.all([ marketService.getMarketSummary(stockCode), marketService.getTradeData(stockCode, selectedPeriod), marketService.getFundingData(stockCode, 30), marketService.getBigDealData(stockCode, 30), marketService.getUnusualData(stockCode, 30), marketService.getPledgeData(stockCode), marketService.getRiseAnalysis(stockCode) ]); if (summaryRes.success) setSummary(summaryRes.data); if (tradeRes.success) setTradeData(tradeRes.data); if (fundingRes.success) setFundingData(fundingRes.data); if (bigDealRes.success) setBigDealData(bigDealRes); // 设置整个响应对象,包含daily_stats if (unusualRes.success) setUnusualData(unusualRes); // 设置整个响应对象,包含grouped_data if (pledgeRes.success) setPledgeData(pledgeRes.data); if (riseAnalysisRes.success) { setRiseAnalysisData(riseAnalysisRes.data); // 创建分析数据映射 const tempAnalysisMap = {}; if (tradeRes.success && tradeRes.data && riseAnalysisRes.data) { riseAnalysisRes.data.forEach(analysis => { const dateIndex = tradeRes.data.findIndex(item => item.date.substring(0, 10) === analysis.trade_date ); if (dateIndex !== -1) { tempAnalysisMap[dateIndex] = analysis; } }); } setAnalysisMap(tempAnalysisMap); } // ❌ 移除数据加载成功toast logger.info('MarketDataView', '市场数据加载成功', { stockCode }); } catch (error) { logger.error('MarketDataView', 'loadMarketData', error, { stockCode, selectedPeriod }); // ❌ 移除数据加载失败toast // toast({ title: '数据加载失败', description: error.message, status: 'error', duration: 5000, isClosable: true }); } finally { setLoading(false); } }; // 获取分钟频数据 const loadMinuteData = async () => { logger.debug('MarketDataView', '开始加载分钟频数据', { stockCode }); setMinuteLoading(true); try { const response = await fetch( `${API_BASE_URL}/api/stock/${stockCode}/latest-minute`, { method: 'GET', headers: { 'Content-Type': 'application/json', } } ); if (!response.ok) { throw new Error('Failed to fetch minute data'); } const data = await response.json(); if (data.data && Array.isArray(data.data)) { setMinuteData(data); logger.info('MarketDataView', '分钟频数据加载成功', { stockCode, dataPoints: data.data.length }); } else { setMinuteData({ data: [], code: stockCode, name: '', trade_date: '', type: 'minute' }); logger.warn('MarketDataView', '分钟频数据为空', { stockCode }); } } catch (error) { logger.error('MarketDataView', 'loadMinuteData', error, { stockCode }); // ❌ 移除分钟数据加载失败toast // toast({ title: '分钟数据加载失败', description: error.message, status: 'error', duration: 3000, isClosable: true }); setMinuteData({ data: [], code: stockCode, name: '', trade_date: '', type: 'minute' }); } finally { setMinuteLoading(false); } }; // 监听props中的stockCode变化 useEffect(() => { if (propStockCode && propStockCode !== stockCode) { setStockCode(propStockCode); } }, [propStockCode, stockCode]); useEffect(() => { if (stockCode) { loadMarketData(); // 自动加载分钟频数据 loadMinuteData(); } }, [stockCode, selectedPeriod]); // K线图配置 const getKLineOption = () => { if (!tradeData || tradeData.length === 0) return {}; const dates = tradeData.map(item => item.date.substring(5, 10)); const kData = tradeData.map(item => [item.open, item.close, item.low, item.high]); const volumes = tradeData.map(item => item.volume); const ma5 = calculateMA(tradeData.map(item => item.close), 5); const ma10 = calculateMA(tradeData.map(item => item.close), 10); const ma20 = calculateMA(tradeData.map(item => item.close), 20); // 创建涨幅分析标记点 const scatterData = []; // 使用组件级别的 analysisMap Object.keys(analysisMap).forEach(dateIndex => { const idx = parseInt(dateIndex); if (tradeData[idx]) { const value = tradeData[idx].high * 1.02; // 在最高价上方显示 scatterData.push([idx, value]); } }); return { backgroundColor: theme.chartBg, animation: true, legend: { data: ['K线', 'MA5', 'MA10', 'MA20'], top: 10, textStyle: { color: theme.textPrimary } }, tooltip: { trigger: 'axis', axisPointer: { type: 'cross', lineStyle: { color: theme.primary, width: 1, opacity: 0.8 } }, backgroundColor: colorMode === 'light' ? 'rgba(255,255,255,0.9)' : 'rgba(0,0,0,0.8)', borderColor: theme.primary, borderWidth: 1, textStyle: { color: theme.textPrimary }, formatter: function(params) { const dataIndex = params[0]?.dataIndex; let result = `${params[0]?.name || ''}
`; params.forEach(param => { if (param.seriesName === '涨幅分析' && analysisMap[dataIndex]) { const analysis = analysisMap[dataIndex]; result = `
${analysis.stock_name} (${analysis.stock_code})
日期: ${analysis.trade_date}
涨幅: ${analysis.rise_rate}%
收盘价: ${analysis.close_price}

涨幅原因:
${analysis.rise_reason_brief || '暂无分析'}

点击查看详细分析
`; } else if (param.seriesName === 'K线') { const [open, close, low, high] = param.data; result += `${param.marker} ${param.seriesName}
`; result += `开盘: ${open}
`; result += `收盘: ${close}
`; result += `最低: ${low}
`; result += `最高: ${high}
`; } else if (param.value != null) { result += `${param.marker} ${param.seriesName}: ${param.value}
`; } }); return result; } }, xAxis: [ { type: 'category', data: dates, boundaryGap: false, axisLine: { lineStyle: { color: theme.textMuted } }, axisLabel: { color: theme.textMuted } }, { type: 'category', gridIndex: 1, data: dates, boundaryGap: false, axisLine: { onZero: false, lineStyle: { color: theme.textMuted } }, axisTick: { show: false }, splitLine: { show: false }, axisLabel: { show: false } } ], yAxis: [ { scale: true, splitLine: { show: true, lineStyle: { color: theme.border } }, axisLine: { lineStyle: { color: theme.textMuted } }, axisLabel: { color: theme.textMuted } }, { scale: true, gridIndex: 1, splitNumber: 2, axisLabel: { show: false }, axisLine: { show: false }, axisTick: { show: false }, splitLine: { show: false } } ], grid: [ { left: '10%', right: '10%', height: '50%' }, { left: '10%', right: '10%', top: '65%', height: '20%' } ], series: [ { name: 'K线', type: 'candlestick', data: kData, itemStyle: { color: theme.success, // 涨 - 红色 color0: theme.danger, // 跌 - 绿色 borderColor: theme.success, borderColor0: theme.danger } }, { name: 'MA5', type: 'line', data: ma5, smooth: true, lineStyle: { color: theme.primary, width: 1 }, itemStyle: { color: theme.primary } }, { name: 'MA10', type: 'line', data: ma10, smooth: true, lineStyle: { color: theme.info, width: 1 }, itemStyle: { color: theme.info } }, { name: 'MA20', type: 'line', data: ma20, smooth: true, lineStyle: { color: theme.warning, width: 1 }, itemStyle: { color: theme.warning } }, { name: '涨幅分析', type: 'scatter', data: scatterData, symbolSize: 30, symbol: 'pin', itemStyle: { color: '#FFD700', shadowBlur: 10, shadowColor: 'rgba(255, 215, 0, 0.5)' }, label: { show: true, formatter: '★', fontSize: 20, position: 'inside', color: '#FF6B6B' }, emphasis: { scale: 1.5, itemStyle: { color: '#FFA500' } }, z: 100, // 确保显示在最上层 cursor: 'pointer' // 显示为可点击 }, { name: '成交量', type: 'bar', xAxisIndex: 1, yAxisIndex: 1, data: volumes, itemStyle: { color: (params) => { const item = tradeData[params.dataIndex]; return item.change_percent >= 0 ? 'rgba(255, 68, 68, 0.6)' : 'rgba(0, 200, 81, 0.6)'; } } } ] }; }; // 分钟频K线图配置 const getMinuteKLineOption = () => { if (!minuteData || !minuteData.data || minuteData.data.length === 0) return {}; const times = minuteData.data.map(item => item.time); const kData = minuteData.data.map(item => [item.open, item.close, item.low, item.high]); const volumes = minuteData.data.map(item => item.volume); const avgPrice = calculateMA(minuteData.data.map(item => item.close), 5); // 5分钟均价 // 计算开盘价基准线(用于涨跌判断) const openPrice = minuteData.data.length > 0 ? minuteData.data[0].open : 0; return { backgroundColor: theme.chartBg, title: { text: `${minuteData.name} 分钟K线 (${minuteData.trade_date})`, left: 'center', textStyle: { color: theme.textPrimary, fontSize: 16, fontWeight: 'bold' }, subtextStyle: { color: theme.textMuted } }, tooltip: { trigger: 'axis', axisPointer: { type: 'cross' }, backgroundColor: colorMode === 'light' ? 'rgba(255,255,255,0.95)' : 'rgba(0,0,0,0.85)', borderColor: theme.primary, borderWidth: 1, textStyle: { color: theme.textPrimary, fontSize: 12 }, formatter: (params) => { let result = params[0].name + '
'; params.forEach(param => { if (param.seriesName === '分钟K线') { const [open, close, low, high] = param.data; const changePercent = openPrice > 0 ? ((close - openPrice) / openPrice * 100).toFixed(2) : '0.00'; result += `${param.marker} ${param.seriesName}
`; result += `开盘: ${open.toFixed(2)}
`; result += `收盘: ${close.toFixed(2)}
`; result += `最高: ${high.toFixed(2)}
`; result += `最低: ${low.toFixed(2)}
`; result += `涨跌: ${changePercent}%
`; } else if (param.seriesName === '均价线') { result += `${param.marker} ${param.seriesName}: ${param.value.toFixed(2)}
`; } else if (param.seriesName === '成交量') { result += `${param.marker} ${param.seriesName}: ${formatUtils.formatNumber(param.value, 0)}
`; } }); return result; } }, legend: { data: ['分钟K线', '均价线', '成交量'], top: 35, textStyle: { color: theme.textPrimary, fontSize: 12 }, itemWidth: 25, itemHeight: 14 }, grid: [ { left: '8%', right: '8%', top: '20%', height: '60%' }, { left: '8%', right: '8%', top: '83%', height: '12%' } ], xAxis: [ { type: 'category', data: times, scale: true, boundaryGap: false, axisLine: { lineStyle: { color: theme.textMuted } }, axisLabel: { color: theme.textMuted, fontSize: 10, interval: 'auto' }, splitLine: { show: false } }, { type: 'category', gridIndex: 1, data: times, scale: true, boundaryGap: false, axisLine: { lineStyle: { color: theme.textMuted } }, axisLabel: { color: theme.textMuted, fontSize: 10 }, splitLine: { show: false } } ], yAxis: [ { scale: true, axisLine: { lineStyle: { color: theme.textMuted } }, axisLabel: { color: theme.textMuted, fontSize: 10 }, splitLine: { lineStyle: { color: theme.border, type: 'dashed' } } }, { gridIndex: 1, scale: true, axisLine: { lineStyle: { color: theme.textMuted } }, axisLabel: { color: theme.textMuted, fontSize: 10 }, splitLine: { show: false } } ], dataZoom: [ { type: 'inside', xAxisIndex: [0, 1], start: 70, end: 100, minValueSpan: 20 }, { show: true, xAxisIndex: [0, 1], type: 'slider', top: '95%', start: 70, end: 100, height: 20, handleSize: '100%', handleStyle: { color: theme.primary }, textStyle: { color: theme.textMuted } } ], series: [ { name: '分钟K线', type: 'candlestick', data: kData, itemStyle: { color: theme.success, color0: theme.danger, borderColor: theme.success, borderColor0: theme.danger, borderWidth: 1 }, barWidth: '60%' }, { name: '均价线', type: 'line', data: avgPrice, smooth: true, symbol: 'none', lineStyle: { color: theme.info, width: 2, opacity: 0.8 } }, { name: '成交量', type: 'bar', xAxisIndex: 1, yAxisIndex: 1, data: volumes, barWidth: '50%', itemStyle: { color: (params) => { const item = minuteData.data[params.dataIndex]; return item.close >= item.open ? 'rgba(255, 68, 68, 0.6)' : 'rgba(0, 200, 81, 0.6)'; } } } ] }; }; // 计算移动平均线 const calculateMA = (data, period) => { const result = []; for (let i = 0; i < data.length; i++) { if (i < period - 1) { result.push(null); continue; } let sum = 0; for (let j = 0; j < period; j++) { sum += data[i - j]; } result.push(sum / period); } return result; }; // 融资融券图表配置 const getFundingOption = () => { if (!fundingData || fundingData.length === 0) return {}; const dates = fundingData.map(item => item.date.substring(5, 10)); const financing = fundingData.map(item => item.financing.balance / 100000000); const securities = fundingData.map(item => item.securities.balance_amount / 100000000); return { backgroundColor: theme.chartBg, title: { text: '融资融券余额走势', left: 'center', textStyle: { color: theme.textPrimary, fontSize: 16 } }, tooltip: { trigger: 'axis', backgroundColor: colorMode === 'light' ? 'rgba(255,255,255,0.9)' : 'rgba(0,0,0,0.8)', borderColor: theme.primary, borderWidth: 1, textStyle: { color: theme.textPrimary }, formatter: (params) => { let result = params[0].name + '
'; params.forEach(param => { result += `${param.marker} ${param.seriesName}: ${param.value.toFixed(2)}亿
`; }); return result; } }, legend: { data: ['融资余额', '融券余额'], bottom: 10, textStyle: { color: theme.textPrimary } }, grid: { left: '3%', right: '4%', bottom: '15%', containLabel: true }, xAxis: { type: 'category', boundaryGap: false, data: dates, axisLine: { lineStyle: { color: theme.textMuted } }, axisLabel: { color: theme.textMuted } }, yAxis: { type: 'value', name: '金额(亿)', nameTextStyle: { color: theme.textMuted }, splitLine: { lineStyle: { color: theme.border } }, axisLine: { lineStyle: { color: theme.textMuted } }, axisLabel: { color: theme.textMuted } }, series: [ { name: '融资余额', type: 'line', smooth: true, symbol: 'circle', symbolSize: 8, areaStyle: { color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [{ offset: 0, color: 'rgba(255, 68, 68, 0.3)' }, { offset: 1, color: 'rgba(255, 68, 68, 0.05)' }] } }, lineStyle: { color: theme.success, width: 2 }, itemStyle: { color: theme.success, borderColor: theme.success, borderWidth: 2 }, data: financing }, { name: '融券余额', type: 'line', smooth: true, symbol: 'diamond', symbolSize: 8, areaStyle: { color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [{ offset: 0, color: 'rgba(0, 200, 81, 0.3)' }, { offset: 1, color: 'rgba(0, 200, 81, 0.05)' }] } }, lineStyle: { color: theme.danger, width: 2 }, itemStyle: { color: theme.danger, borderColor: theme.danger, borderWidth: 2 }, data: securities } ] }; }; // 股权质押图表配置 const getPledgeOption = () => { if (!pledgeData || pledgeData.length === 0) return {}; const dates = pledgeData.map(item => item.end_date.substring(5, 10)); const ratios = pledgeData.map(item => item.pledge_ratio); const counts = pledgeData.map(item => item.pledge_count); return { backgroundColor: theme.chartBg, title: { text: '股权质押趋势', left: 'center', textStyle: { color: theme.textPrimary, fontSize: 16 } }, tooltip: { trigger: 'axis', backgroundColor: colorMode === 'light' ? 'rgba(255,255,255,0.9)' : 'rgba(0,0,0,0.8)', borderColor: theme.primary, borderWidth: 1, textStyle: { color: theme.textPrimary } }, legend: { data: ['质押比例', '质押笔数'], bottom: 10, textStyle: { color: theme.textPrimary } }, grid: { left: '3%', right: '4%', bottom: '15%', containLabel: true }, xAxis: { type: 'category', data: dates, axisLine: { lineStyle: { color: theme.textMuted } }, axisLabel: { color: theme.textMuted } }, yAxis: [ { type: 'value', name: '质押比例(%)', nameTextStyle: { color: theme.textMuted }, splitLine: { lineStyle: { color: theme.border } }, axisLine: { lineStyle: { color: theme.textMuted } }, axisLabel: { color: theme.textMuted } }, { type: 'value', name: '质押笔数', nameTextStyle: { color: theme.textMuted }, axisLine: { lineStyle: { color: theme.textMuted } }, axisLabel: { color: theme.textMuted } } ], series: [ { name: '质押比例', type: 'line', smooth: true, symbol: 'circle', symbolSize: 8, lineStyle: { color: theme.warning, width: 2, shadowBlur: 10, shadowColor: theme.warning }, itemStyle: { color: theme.warning, borderColor: theme.bgCard, borderWidth: 2 }, data: ratios }, { name: '质押笔数', type: 'bar', yAxisIndex: 1, barWidth: '50%', itemStyle: { color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [{ offset: 0, color: theme.primary }, { offset: 1, color: theme.primaryDark }] }, barBorderRadius: [5, 5, 0, 0] }, data: counts } ] }; }; return ( {/* 股票概览 */} {summary && ( {summary.stock_name} {summary.stock_code} {summary.latest_trade && ( {summary.latest_trade.close} = 0 ? 'increase' : 'decrease'} color={summary.latest_trade.change_percent >= 0 ? theme.success : theme.danger} /> {Math.abs(summary.latest_trade.change_percent).toFixed(2)}% )} {summary.latest_trade && ( <> 成交量 {formatUtils.formatNumber(summary.latest_trade.volume, 0)} 成交额 {formatUtils.formatNumber(summary.latest_trade.amount)} 换手率 {formatUtils.formatPercent(summary.latest_trade.turnover_rate)} 市盈率 {summary.latest_trade.pe_ratio || '-'} )} {summary.latest_funding && ( 融资余额 {formatUtils.formatNumber(summary.latest_funding.financing_balance)} 融券余额 {formatUtils.formatNumber(summary.latest_funding.securities_balance)} {summary.latest_pledge && ( 质押比例 {formatUtils.formatPercent(summary.latest_pledge.pledge_ratio)} )} )} )} {/* 主要内容区域 */} {loading ? (
数据加载中...
) : ( 交易数据 融资融券 大宗交易 龙虎榜 股权质押 时间范围: {/* 交易数据 */} {tradeData.length > 0 && ( { if (params.seriesName === '涨幅分析' && params.data) { const dataIndex = params.data[0]; // scatter数据格式是[x, y] const analysis = analysisMap[dataIndex]; if (analysis) { setModalContent( {analysis.stock_name} ({analysis.stock_code}) 日期: {analysis.trade_date} 涨幅: {analysis.rise_rate}% 收盘价: {analysis.close_price} {analysis.main_business && ( 主营业务 {analysis.main_business} )} {analysis.rise_reason_detail && ( 详细分析 {analysis.rise_reason_detail} )} {analysis.announcements && analysis.announcements !== '[]' && ( 相关公告 {analysis.announcements} )} {/* 研报引用展示 */} {analysis.verification_reports && analysis.verification_reports.length > 0 && ( 研报引用 ({analysis.verification_reports.length}) {analysis.verification_reports.map((report, reportIdx) => ( {report.publisher || '未知机构'} {report.match_score && ( 匹配度: {report.match_score} )} {report.match_ratio != null && report.match_ratio > 0 && ( {(report.match_ratio * 100).toFixed(0)}% )} {report.declare_date && ( {report.declare_date.substring(0, 10)} )} {report.report_title && ( 《{report.report_title}》 )} {report.author && ( 分析师: {report.author} )} {report.verification_item && ( 验证项: {report.verification_item} )} {report.content && ( {report.content} )} ))} )} 成交量: {formatUtils.formatNumber(analysis.volume)} | 成交额: {formatUtils.formatNumber(analysis.amount)} | 更新时间: {analysis.update_time || analysis.create_time || '-'} ); onOpen(); } } } }} /> )} {/* 当日分钟频数据 */} 当日分钟频数据 {minuteData && minuteData.trade_date && ( {minuteData.trade_date} )} {minuteLoading ? (
加载分钟频数据中...
) : minuteData && minuteData.data && minuteData.data.length > 0 ? ( {/* 分钟K线图 */} {/* 分钟数据统计 */} 开盘价 {minuteData.data[0]?.open != null ? minuteData.data[0].open.toFixed(2) : '-'} 当前价 = minuteData.data[0]?.open ? theme.success : theme.danger} fontSize="lg" > {minuteData.data[minuteData.data.length - 1]?.close != null ? minuteData.data[minuteData.data.length - 1].close.toFixed(2) : '-'} = minuteData.data[0]?.open ? 'increase' : 'decrease'} /> {(minuteData.data[minuteData.data.length - 1]?.close != null && minuteData.data[0]?.open != null) ? Math.abs(((minuteData.data[minuteData.data.length - 1].close - minuteData.data[0].open) / minuteData.data[0].open * 100)).toFixed(2) : '0.00'}% 最高价 {(() => { const highs = minuteData.data.map(item => item.high).filter(h => h != null); return highs.length > 0 ? Math.max(...highs).toFixed(2) : '-'; })()} 最低价 {(() => { const lows = minuteData.data.map(item => item.low).filter(l => l != null); return lows.length > 0 ? Math.min(...lows).toFixed(2) : '-'; })()} {/* 成交量分析 */} 成交数据分析 总成交量: {formatUtils.formatNumber(minuteData.data.reduce((sum, item) => sum + item.volume, 0), 0)} 总成交额: {formatUtils.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} (${formatUtils.formatNumber(maxVolume, 0)})` : '-'; })()} 平均价格 {(() => { const closes = minuteData.data.map(item => item.close).filter(c => c != null); return closes.length > 0 ? (closes.reduce((sum, c) => sum + c, 0) / closes.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 ? '+' : ''}{formatUtils.formatPercent(item.change_percent)} {formatUtils.formatNumber(item.volume, 0)} {formatUtils.formatNumber(item.amount)}
{/* 融资融券 */} {fundingData.length > 0 && ( )} 融资数据 {fundingData.slice(-5).reverse().map((item, idx) => ( {item.date} {formatUtils.formatNumber(item.financing.balance)} 买入{formatUtils.formatNumber(item.financing.buy)} / 偿还{formatUtils.formatNumber(item.financing.repay)} ))} 融券数据 {fundingData.slice(-5).reverse().map((item, idx) => ( {item.date} {formatUtils.formatNumber(item.securities.balance)} 卖出{formatUtils.formatNumber(item.securities.sell)} / 偿还{formatUtils.formatNumber(item.securities.repay)} ))} {/* 大宗交易 */} 大宗交易记录 {bigDealData && bigDealData.daily_stats && bigDealData.daily_stats.length > 0 ? ( {bigDealData.daily_stats.map((dayStats, idx) => ( {dayStats.date} 交易笔数: {dayStats.count} 成交量: {formatUtils.formatNumber(dayStats.total_volume)}万股 成交额: {formatUtils.formatNumber(dayStats.total_amount)}万元 均价: {dayStats.avg_price != null ? dayStats.avg_price.toFixed(2) : '-'}元 {/* 显示当日交易明细 */} {dayStats.deals && dayStats.deals.length > 0 && ( {dayStats.deals.map((deal, i) => ( ))}
买方营业部 卖方营业部 成交价 成交量(万股) 成交额(万元)
{deal.buyer_dept || '-'} {deal.seller_dept || '-'} {deal.price != null ? deal.price.toFixed(2) : '-'} {deal.volume != null ? deal.volume.toFixed(2) : '-'} {deal.amount != null ? deal.amount.toFixed(2) : '-'}
)}
))}
) : (
暂无大宗交易数据
)}
{/* 龙虎榜 */} 龙虎榜数据 {unusualData && unusualData.grouped_data && unusualData.grouped_data.length > 0 ? ( {unusualData.grouped_data.map((dayData, idx) => ( {dayData.date} 买入: {formatUtils.formatNumber(dayData.total_buy)} 卖出: {formatUtils.formatNumber(dayData.total_sell)} 0 ? 'red' : 'green'} fontSize="md"> 净额: {formatUtils.formatNumber(dayData.net_amount)} 买入前五 {dayData.buyers && dayData.buyers.length > 0 ? ( dayData.buyers.slice(0, 5).map((buyer, i) => ( {buyer.dept_name} {formatUtils.formatNumber(buyer.buy_amount)} )) ) : ( 暂无数据 )} 卖出前五 {dayData.sellers && dayData.sellers.length > 0 ? ( dayData.sellers.slice(0, 5).map((seller, i) => ( {seller.dept_name} {formatUtils.formatNumber(seller.sell_amount)} )) ) : ( 暂无数据 )} {/* 信息类型标签 */} 类型: {dayData.info_types && dayData.info_types.map((type, i) => ( {type} ))} ))} ) : (
暂无龙虎榜数据
)}
{/* 股权质押 */} {pledgeData.length > 0 && ( )} 质押明细 {Array.isArray(pledgeData) && pledgeData.length > 0 ? ( pledgeData.map((item, idx) => ( )) ) : ( )}
日期 无限售质押(万股) 限售质押(万股) 质押总量(万股) 总股本(万股) 质押比例 质押笔数
{item.end_date} {formatUtils.formatNumber(item.unrestricted_pledge, 0)} {formatUtils.formatNumber(item.restricted_pledge, 0)} {formatUtils.formatNumber(item.total_pledge, 0)} {formatUtils.formatNumber(item.total_shares, 0)} {formatUtils.formatPercent(item.pledge_ratio)} {item.pledge_count}
暂无数据
)}
{/* 模态框 */} 详细信息 {modalContent}
); }; export default MarketDataView;