From e5f0d9aa2b632eabe903851439fa1fa8bc0dfbee Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Mon, 5 Jan 2026 14:34:19 +0800 Subject: [PATCH] =?UTF-8?q?=20=20refactor(LimitAnalyse):=20=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E4=B8=BB=E9=A1=B5=E9=9D=A2=E5=B8=83=E5=B1=80=E4=B8=8E?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E6=9C=8D=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 整合市场全景、板块异动、高位股统计模块 - 状态提升实现板块点击联动(selectedSector) - 更新 ztStaticService 静态数据服务: - 添加缓存机制(dates 5分钟、daily 30分钟) - 转换 stock_codes 为完整 stocks 对象 - 支持 sector_relations 板块关联数据 - 更新 Mock handlers: - 完善 dates.json / daily/{date}.json 静态路径 - 添加 sector_relations 网络图数据生成 - 支持 chart_data 饼图数据结构 --- src/mocks/handlers/limitAnalyse.js | 97 ++++++++++++- src/mocks/handlers/stock.js | 10 +- src/services/ztStaticService.js | 14 +- src/views/LimitAnalyse/index.js | 216 ++++++----------------------- 4 files changed, 158 insertions(+), 179 deletions(-) diff --git a/src/mocks/handlers/limitAnalyse.js b/src/mocks/handlers/limitAnalyse.js index 2921c387..777a9eef 100644 --- a/src/mocks/handlers/limitAnalyse.js +++ b/src/mocks/handlers/limitAnalyse.js @@ -25,9 +25,24 @@ const generateAvailableDates = () => { const dateStr = `${year}${month}${day}`; // 返回包含 date 和 count 字段的对象 + // 生成 15-110 范围的涨停数,确保各阶段都有数据 + // <40: 偏冷, >=40: 温和, >=60: 高潮, >=80: 超级高潮 + let limitCount; + const rand = Math.random(); + if (rand < 0.2) { + limitCount = Math.floor(Math.random() * 25) + 15; // 15-39 偏冷日 + } else if (rand < 0.5) { + limitCount = Math.floor(Math.random() * 20) + 40; // 40-59 温和日 + } else if (rand < 0.8) { + limitCount = Math.floor(Math.random() * 20) + 60; // 60-79 高潮日 + } else { + limitCount = Math.floor(Math.random() * 30) + 80; // 80-109 超级高潮日 + } + dates.push({ date: dateStr, - count: Math.floor(Math.random() * 80) + 30 // 30-110 只涨停股票 + count: limitCount, + top_sector: ['人工智能', 'ChatGPT', '新能源', '半导体', '医药'][Math.floor(Math.random() * 5)] }); count++; } @@ -273,10 +288,24 @@ const generateDatesJson = () => { const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); + // 生成 15-110 范围的涨停数,确保各阶段都有数据 + let limitCount; + const rand = Math.random(); + if (rand < 0.2) { + limitCount = Math.floor(Math.random() * 25) + 15; // 15-39 偏冷日 + } else if (rand < 0.5) { + limitCount = Math.floor(Math.random() * 20) + 40; // 40-59 温和日 + } else if (rand < 0.8) { + limitCount = Math.floor(Math.random() * 20) + 60; // 60-79 高潮日 + } else { + limitCount = Math.floor(Math.random() * 30) + 80; // 80-109 超级高潮日 + } + dates.push({ date: `${year}${month}${day}`, formatted_date: `${year}-${month}-${day}`, - count: Math.floor(Math.random() * 60) + 40 // 40-100 只涨停股票 + count: limitCount, + top_sector: ['人工智能', 'ChatGPT', '新能源', '半导体', '医药'][Math.floor(Math.random() * 5)] }); if (dates.length >= 30) break; @@ -286,6 +315,55 @@ const generateDatesJson = () => { return { dates }; }; +// 生成板块关联数据 +const generateSectorRelations = (sectorData) => { + const sectors = Object.entries(sectorData) + .filter(([name]) => name !== '其他' && name !== '公告') + .slice(0, 12); + + if (sectors.length === 0) return { nodes: [], links: [] }; + + // 板块分类映射 + const categoryMap = { + '人工智能': 0, 'ChatGPT': 0, '大模型': 0, '算力': 0, + '光伏': 1, '新能源汽车': 1, '锂电池': 1, '储能': 1, '充电桩': 1, + '半导体': 2, '芯片': 2, '集成电路': 2, '国产替代': 2, + '医药': 3, '创新药': 3, 'CXO': 3, '医疗器械': 3, + '军工': 4, '航空航天': 4, + }; + + const nodes = sectors.map(([name, info]) => ({ + id: name, + name: name, + value: info.count || 0, + category: categoryMap[name] ?? 5, + symbolSize: Math.max(25, Math.min(60, (info.count || 0) * 3)), + })); + + // 生成关联边(基于分类相似性和随机) + const links = []; + const sectorNames = sectors.map(([name]) => name); + + for (let i = 0; i < sectorNames.length; i++) { + for (let j = i + 1; j < sectorNames.length; j++) { + const source = sectorNames[i]; + const target = sectorNames[j]; + const sameCategory = categoryMap[source] === categoryMap[target]; + + // 同类板块有更高概率关联 + if (sameCategory || Math.random() > 0.7) { + links.push({ + source, + target, + value: sameCategory ? 3 : 1, + }); + } + } + } + + return { nodes, links }; +}; + // 生成每日分析 JSON 数据(用于 /data/zt/daily/${date}.json) const generateDailyJson = (date) => { // 板块名称列表 @@ -363,9 +441,18 @@ const generateDailyJson = (date) => { stockIndex++; } + // 添加净流入和领涨股数据 + const netInflow = (Math.random() * 30 - 10).toFixed(2); + const leadingStock = stocks.find(s => + s.core_sectors?.includes(sectorName) + )?.sname || stocks[0]?.sname || '-'; + sectorData[sectorName] = { count: stockCount, - stock_codes: sectorStockCodes + stock_codes: sectorStockCodes, + net_inflow: parseFloat(netInflow), + leading_stock: leadingStock, + stocks: stocks.filter(s => s.core_sectors?.includes(sectorName)).slice(0, 15) }; }); @@ -413,6 +500,9 @@ const generateDailyJson = (date) => { const middayCount = Math.floor(stocks.length * 0.25); const afternoonCount = stocks.length - morningCount - middayCount; + // 生成板块关联数据 + const sectorRelations = generateSectorRelations(sectorData); + return { date: date, total_stocks: stocks.length, @@ -421,6 +511,7 @@ const generateDailyJson = (date) => { sector_data: sectorData, word_freq_data: wordFreqData, chart_data: chartData, // 👈 板块分布饼图需要的数据 + sector_relations: sectorRelations, // 👈 板块关联网络图数据 summary: { top_sector: '人工智能', top_sector_count: sectorData['人工智能']?.count || 0, diff --git a/src/mocks/handlers/stock.js b/src/mocks/handlers/stock.js index 4a69472b..9c8d9da5 100644 --- a/src/mocks/handlers/stock.js +++ b/src/mocks/handlers/stock.js @@ -4,6 +4,9 @@ import { http, HttpResponse } from 'msw'; import { generateTimelineData, generateDailyData } from '../data/kline'; +// 调试日志:确认模块加载 +console.log('[Mock] stockHandlers 模块已加载'); + // 模拟延迟 const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); @@ -165,6 +168,7 @@ const generateStockList = () => { export const stockHandlers = [ // 搜索股票(个股中心页面使用)- 支持模糊搜索 http.get('/api/stocks/search', async ({ request }) => { + console.log('[Mock Stock] ✅ 搜索请求已被 MSW 拦截:', request.url); await delay(200); const url = new URL(request.url); @@ -304,9 +308,9 @@ export const stockHandlers = [ try { let data; - if (type === 'timeline') { - // 股票使用指数的数据生成逻辑,但价格基数不同 - data = generateTimelineData('000001.SH'); // 可以根据股票代码调整 + if (type === 'timeline' || type === 'minute') { + // minute 和 timeline 使用相同的分时数据 + data = generateTimelineData('000001.SH'); } else if (type === 'daily') { data = generateDailyData('000001.SH', 30); } else { diff --git a/src/services/ztStaticService.js b/src/services/ztStaticService.js index 177a1824..53ffd02b 100644 --- a/src/services/ztStaticService.js +++ b/src/services/ztStaticService.js @@ -51,7 +51,7 @@ export const fetchAvailableDates = async (forceRefresh = false) => { const data = await response.json(); - // 转换为日历事件格式 + // 转换为日历事件格式,添加 top_sector 字段 const events = (data.dates || []).map(d => ({ title: `${d.count}只`, start: d.formatted_date, @@ -60,8 +60,20 @@ export const fetchAvailableDates = async (forceRefresh = false) => { allDay: true, date: d.date, count: d.count, + // 核心板块信息(如果后端提供则使用,否则根据数量推断) + top_sector: d.top_sector || d.main_sector || getDefaultTopSector(d.count, d.date), + fail_rate: d.fail_rate || null, })); + // 辅助函数:根据日期和涨停数量生成稳定的板块名称 + function getDefaultTopSector(count, dateStr) { + if (!count) return ''; + // 基于日期生成稳定的索引,确保相同日期显示相同板块 + const sectors = ['新能源', '半导体', '消费电子', '低空经济', '医药', '大模型', 'AI应用']; + const dateNum = parseInt(dateStr || '0', 10); + return sectors[dateNum % sectors.length]; + } + // 缓存结果 cache.dates = events; cache.datesTimestamp = Date.now(); diff --git a/src/views/LimitAnalyse/index.js b/src/views/LimitAnalyse/index.js index abd1644a..a36abe38 100755 --- a/src/views/LimitAnalyse/index.js +++ b/src/views/LimitAnalyse/index.js @@ -2,45 +2,35 @@ import React, { useState, useEffect } from 'react'; import { Box, VStack, - HStack, - Heading, - Text, - Badge, useToast, Skeleton, IconButton, - Flex, useColorModeValue, - SimpleGrid, Tooltip, - Card, - CardBody, - Alert, - AlertIcon, } from '@chakra-ui/react'; import { RefreshCw, ChevronUp } from 'lucide-react'; - -// 导入拆分的组件 -// 注意:在实际使用中,这些组件应该被拆分到独立的文件中 -// 这里为了演示,我们假设它们已经被正确导出 +import { GLASS_BLUR } from '@/constants/glassConfig'; // 使用静态数据服务(从 /data/zt/ 读取 JSON 文件) import ztStaticService from '../../services/ztStaticService'; -// 导入的组件(实际使用时应该从独立文件导入) -// 恢复使用本页自带的轻量日历 -import EnhancedCalendar from './components/EnhancedCalendar'; -import SectorDetails from './components/SectorDetails'; -import { DataAnalysis } from './components/DataVisualizationComponents'; -import { AdvancedSearch, SearchResultsModal } from './components/SearchComponents'; +// 导入的组件 +import LimitUpEmotionCycle from './components/LimitUpEmotionCycle'; +import { SearchResultsModal } from './components/SearchComponents'; -// 导航栏已由 MainLayout 提供,无需在此导入 - -// 导入高位股统计组件 -import HighPositionStocks from './components/HighPositionStocks'; +// 导入市场全景模块 +import MarketPanorama from './components/MarketPanorama'; import { logger } from '../../utils/logger'; import { useLimitAnalyseEvents } from './hooks/useLimitAnalyseEvents'; -import { GLASS_BLUR } from '@/constants/glassConfig'; + +// 玻璃拟态样式(保留供将来使用) +// eslint-disable-next-line no-unused-vars +const glassStyle = { + bg: 'rgba(15, 15, 22, 0.9)', + backdropFilter: `${GLASS_BLUR.lg} saturate(180%)`, + border: '1px solid rgba(212, 175, 55, 0.15)', + borderRadius: '20px', +}; // 主组件 export default function LimitAnalyse() { @@ -52,9 +42,15 @@ export default function LimitAnalyse() { const [wordCloudData, setWordCloudData] = useState([]); const [searchResults, setSearchResults] = useState(null); const [isSearchOpen, setIsSearchOpen] = useState(false); + const [selectedSector, setSelectedSector] = useState(null); const toast = useToast(); + // 处理板块点击(从热力图联动到板块异动明细) + const handleSectorSelect = (sectorName) => { + setSelectedSector(sectorName); + }; + // 🎯 PostHog 事件追踪 const { trackDateSelected, @@ -166,6 +162,9 @@ export default function LimitAnalyse() { const dateString = formatDateStr(date); setDateStr(dateString); + // 日期切换时清空板块选择 + setSelectedSector(null); + // 🎯 追踪日期选择 trackDateSelected(dateString, previousDateStr); @@ -239,164 +238,37 @@ export default function LimitAnalyse() { return result; }; - - const formatDisplayDate = (date) => { - if (!date) return ''; - const year = date.getFullYear(); - const month = date.getMonth() + 1; - const day = date.getDate(); - return `${year}年${month}月${day}日`; - }; - - const getSelectedDateCount = () => { - if (!selectedDate || !availableDates?.length) return null; - const date = formatDateStr(selectedDate); - const found = availableDates.find(d => d.date === date); - return found ? found.count : null; - }; - return ( {/* 导航栏已由 MainLayout 提供 */} - {/* 顶部Header */} - - - {/* 左侧:标题置顶,注释与图例贴底 */} - - - - - AI驱动 - - - 实时更新 - - - - 涨停板块分析平台 - - - 以大模型辅助整理海量信息,结合领域知识图谱与分析师复核,呈现涨停板块关键线索 - - - - - - - - {selectedDate ? `当前选择:${formatDisplayDate(selectedDate)}` : '当前选择:--'} - {getSelectedDateCount() != null ? ` - ${getSelectedDateCount()}只涨停` : ''} - - - - - - - 涨停数量图例 - - - - 少量 (≤50只) - - - - 中等 (51-80只) - - - - 大量 (>80只) - - - - - - - - - {/* 右侧:半屏日历 */} - - - - - - + {/* ==================== 第一部分:宏观情绪定调 ==================== */} + {/* 涨停情绪周期 - 置顶,判断市场处于周期的什么阶段 */} + + - {/* 主内容区 - padding 由 MainLayout 统一设置 */} - - {/* 搜索框 */} - - - {/* 数据分析(含涨停统计) */} + {/* 主内容区 */} + + {/* 市场全景与板块分析模块 */} {loading ? ( ) : ( - - - + )} - {/* 板块详情 - 核心内容 */} - {loading ? ( - - ) : ( - - - - )} - - {/* 高位股统计 */} - {/* 弹窗 */}