// src/mocks/handlers/limitAnalyse.js // 涨停分析相关的 Mock Handlers import { http, HttpResponse } from 'msw'; // 模拟延迟 const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); // 生成可用日期列表(最近30个交易日) const generateAvailableDates = () => { const dates = []; const today = new Date(); let count = 0; for (let i = 0; i < 60 && count < 30; i++) { const date = new Date(today); date.setDate(date.getDate() - i); const dayOfWeek = date.getDay(); // 跳过周末 if (dayOfWeek !== 0 && dayOfWeek !== 6) { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); const dateStr = `${year}${month}${day}`; // 返回包含 date 和 count 字段的对象 dates.push({ date: dateStr, count: Math.floor(Math.random() * 80) + 30 // 30-110 只涨停股票 }); count++; } } return dates; }; // 生成板块数据 const generateSectors = (count = 8) => { const sectorNames = [ '人工智能', 'ChatGPT', '数字经济', '新能源汽车', '光伏', '锂电池', '半导体', '芯片', '5G通信', '医疗器械', '创新药', '中药', '白酒', '食品饮料', '消费电子', '军工', '航空航天', '新材料' ]; const sectors = []; for (let i = 0; i < Math.min(count, sectorNames.length); i++) { const stockCount = Math.floor(Math.random() * 15) + 5; const stocks = []; for (let j = 0; j < stockCount; j++) { stocks.push({ code: `${Math.random() > 0.5 ? '6' : '0'}${String(Math.floor(Math.random() * 100000)).padStart(5, '0')}`, name: `${sectorNames[i]}股票${j + 1}`, latest_limit_time: `${Math.floor(Math.random() * 4) + 9}:${String(Math.floor(Math.random() * 60)).padStart(2, '0')}:${String(Math.floor(Math.random() * 60)).padStart(2, '0')}`, limit_up_count: Math.floor(Math.random() * 3) + 1, price: (Math.random() * 100 + 10).toFixed(2), change_pct: (Math.random() * 5 + 5).toFixed(2), turnover_rate: (Math.random() * 30 + 5).toFixed(2), volume: Math.floor(Math.random() * 100000000 + 10000000), amount: (Math.random() * 1000000000 + 100000000).toFixed(2), limit_type: Math.random() > 0.7 ? '一字板' : (Math.random() > 0.5 ? 'T字板' : '普通涨停'), 封单金额: (Math.random() * 500000000).toFixed(2), }); } sectors.push({ sector_name: sectorNames[i], stock_count: stockCount, avg_limit_time: `${Math.floor(Math.random() * 2) + 10}:${String(Math.floor(Math.random() * 60)).padStart(2, '0')}`, stocks: stocks, }); } return sectors; }; // 生成高位股数据(用于 HighPositionStocks 组件) const generateHighPositionStocks = () => { const stocks = []; const stockNames = [ '宁德时代', '比亚迪', '隆基绿能', '东方财富', '中际旭创', '京东方A', '海康威视', '立讯精密', '三一重工', '恒瑞医药', '三六零', '东方通信', '贵州茅台', '五粮液', '中国平安' ]; const industries = [ '锂电池', '新能源汽车', '光伏', '金融科技', '通信设备', '显示器件', '安防设备', '电子元件', '工程机械', '医药制造', '网络安全', '通信服务', '白酒', '食品饮料', '保险' ]; for (let i = 0; i < stockNames.length; i++) { const code = `${Math.random() > 0.5 ? '6' : '0'}${String(Math.floor(Math.random() * 100000)).padStart(5, '0')}`; const continuousDays = Math.floor(Math.random() * 8) + 2; // 2-9连板 const price = parseFloat((Math.random() * 100 + 20).toFixed(2)); const increaseRate = parseFloat((Math.random() * 3 + 8).toFixed(2)); // 8%-11% const turnoverRate = parseFloat((Math.random() * 20 + 5).toFixed(2)); // 5%-25% stocks.push({ stock_code: code, stock_name: stockNames[i], price: price, increase_rate: increaseRate, continuous_limit_up: continuousDays, industry: industries[i], turnover_rate: turnoverRate, }); } // 按连板天数降序排序 stocks.sort((a, b) => b.continuous_limit_up - a.continuous_limit_up); return stocks; }; // 生成高位股统计数据 const generateHighPositionStatistics = (stocks) => { if (!stocks || stocks.length === 0) { return { total_count: 0, avg_continuous_days: 0, max_continuous_days: 0, }; } const totalCount = stocks.length; const sumDays = stocks.reduce((sum, stock) => sum + stock.continuous_limit_up, 0); const maxDays = Math.max(...stocks.map(s => s.continuous_limit_up)); return { total_count: totalCount, avg_continuous_days: parseFloat((sumDays / totalCount).toFixed(1)), max_continuous_days: maxDays, }; }; // 生成词云数据 const generateWordCloudData = () => { const keywords = [ '人工智能', 'ChatGPT', 'AI芯片', '大模型', '算力', '新能源', '光伏', '锂电池', '储能', '充电桩', '半导体', '芯片', 'EDA', '国产替代', '集成电路', '医疗', '创新药', 'CXO', '医疗器械', '生物医药', '消费', '白酒', '食品', '零售', '餐饮', '金融', '券商', '保险', '银行', '金融科技' ]; return keywords.map(keyword => ({ text: keyword, value: Math.floor(Math.random() * 50) + 10, category: ['科技', '新能源', '医疗', '消费', '金融'][Math.floor(Math.random() * 5)], })); }; // 生成每日分析数据 const generateDailyAnalysis = (date) => { const sectorNames = [ '公告', '人工智能', 'ChatGPT', '数字经济', '新能源汽车', '光伏', '锂电池', '半导体', '芯片', '5G通信', '医疗器械', '创新药', '其他' ]; const stockNameTemplates = [ '龙头', '科技', '新能源', '智能', '数字', '云计算', '创新', '生物', '医疗', '通信', '电子', '材料', '能源', '互联' ]; // 生成 sector_data(SectorDetails 组件需要的格式) const sectorData = {}; let totalStocks = 0; sectorNames.forEach((sectorName, sectorIdx) => { const stockCount = Math.floor(Math.random() * 12) + 3; // 每个板块 3-15 只股票 const stocks = []; for (let i = 0; i < stockCount; i++) { const code = `${Math.random() > 0.5 ? '6' : '0'}${String(Math.floor(Math.random() * 100000)).padStart(5, '0')}`; const continuousDays = Math.floor(Math.random() * 6) + 1; // 1-6连板 const ztHour = Math.floor(Math.random() * 5) + 9; // 9-13点 const ztMinute = Math.floor(Math.random() * 60); const ztSecond = Math.floor(Math.random() * 60); const ztTime = `2024-10-28 ${String(ztHour).padStart(2, '0')}:${String(ztMinute).padStart(2, '0')}:${String(ztSecond).padStart(2, '0')}`; const stockName = `${stockNameTemplates[i % stockNameTemplates.length]}${sectorName === '公告' ? '公告' : ''}股份${i + 1}`; stocks.push({ scode: code, sname: stockName, zt_time: ztTime, formatted_time: `${String(ztHour).padStart(2, '0')}:${String(ztMinute).padStart(2, '0')}`, continuous_days: continuousDays === 1 ? '首板' : `${continuousDays}连板`, brief: `${sectorName}板块异动,${stockName}因${sectorName === '公告' ? '重大公告利好' : '板块热点'}涨停。公司是${sectorName}行业龙头企业之一。`, summary: `${sectorName}概念持续活跃`, first_time: `2024-10-${String(28 - (continuousDays - 1)).padStart(2, '0')}`, change_pct: parseFloat((Math.random() * 2 + 9).toFixed(2)), // 9%-11% core_sectors: [ sectorName, sectorNames[Math.floor(Math.random() * sectorNames.length)], sectorNames[Math.floor(Math.random() * sectorNames.length)] ].filter((v, i, a) => a.indexOf(v) === i) // 去重 }); } sectorData[sectorName] = { count: stockCount, stocks: stocks.sort((a, b) => a.zt_time.localeCompare(b.zt_time)) // 按涨停时间排序 }; totalStocks += stockCount; }); // 统计数据 const morningCount = Math.floor(totalStocks * 0.35); // 早盘涨停 const announcementCount = sectorData['公告']?.count || 0; const topSector = sectorNames.filter(s => s !== '公告' && s !== '其他') .reduce((max, name) => (sectorData[name]?.count || 0) > (sectorData[max]?.count || 0) ? name : max , '人工智能'); return { date: date, total_stocks: totalStocks, total_sectors: Object.keys(sectorData).length, sector_data: sectorData, // 👈 SectorDetails 组件需要的数据 summary: { top_sector: topSector, top_sector_count: sectorData[topSector]?.count || 0, announcement_stocks: announcementCount, zt_time_distribution: { morning: morningCount, afternoon: totalStocks - morningCount, } } }; }; // ==================== 静态文件 Mock Handlers ==================== // 这些 handlers 用于拦截 /data/zt/* 静态文件请求 // 生成 dates.json 数据 const generateDatesJson = () => { const dates = []; const today = new Date(); for (let i = 0; i < 60; i++) { const date = new Date(today); date.setDate(date.getDate() - i); const dayOfWeek = date.getDay(); // 跳过周末 if (dayOfWeek !== 0 && dayOfWeek !== 6) { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); dates.push({ date: `${year}${month}${day}`, formatted_date: `${year}-${month}-${day}`, count: Math.floor(Math.random() * 60) + 40 // 40-100 只涨停股票 }); if (dates.length >= 30) break; } } return { dates }; }; // 生成每日分析 JSON 数据(用于 /data/zt/daily/${date}.json) const generateDailyJson = (date) => { // 板块名称列表 const sectorNames = [ '公告', '人工智能', 'ChatGPT', '大模型', '算力', '光伏', '新能源汽车', '锂电池', '储能', '充电桩', '半导体', '芯片', '集成电路', '国产替代', '医药', '创新药', 'CXO', '医疗器械', '军工', '航空航天', '其他' ]; // 股票名称模板 const stockPrefixes = [ '龙头', '科技', '新能', '智能', '数字', '云计', '创新', '生物', '医疗', '通信', '电子', '材料', '能源', '互联', '天马', '华鑫', '中科', '东方', '西部', '南方', '北方', '金龙', '银河', '星辰', '宏达', '盛世', '鹏程', '万里' ]; const stockSuffixes = [ '股份', '科技', '电子', '信息', '新材', '能源', '医药', '通讯', '智造', '集团', '实业', '控股', '产业', '发展' ]; // 生成所有股票 const stocks = []; const sectorData = {}; let stockIndex = 0; sectorNames.forEach((sectorName, sectorIdx) => { const stockCount = sectorName === '公告' ? Math.floor(Math.random() * 5) + 8 // 公告板块 8-12 只 : sectorName === '其他' ? Math.floor(Math.random() * 4) + 3 // 其他板块 3-6 只 : Math.floor(Math.random() * 8) + 3; // 普通板块 3-10 只 const sectorStockCodes = []; for (let i = 0; i < stockCount; i++) { const code = `${Math.random() > 0.6 ? '6' : Math.random() > 0.3 ? '0' : '3'}${String(Math.floor(Math.random() * 100000)).padStart(5, '0')}`; const continuousDays = Math.floor(Math.random() * 6) + 1; const ztHour = Math.floor(Math.random() * 4) + 9; const ztMinute = Math.floor(Math.random() * 60); const ztSecond = Math.floor(Math.random() * 60); const prefix = stockPrefixes[Math.floor(Math.random() * stockPrefixes.length)]; const suffix = stockSuffixes[Math.floor(Math.random() * stockSuffixes.length)]; const stockName = `${prefix}${suffix}`; // 生成关联板块 const coreSectors = [sectorName]; if (Math.random() > 0.5) { const otherSector = sectorNames[Math.floor(Math.random() * (sectorNames.length - 1))]; if (otherSector !== sectorName && otherSector !== '其他' && otherSector !== '公告') { coreSectors.push(otherSector); } } stocks.push({ scode: code, sname: stockName, zt_time: `${date.slice(0,4)}-${date.slice(4,6)}-${date.slice(6,8)} ${String(ztHour).padStart(2,'0')}:${String(ztMinute).padStart(2,'0')}:${String(ztSecond).padStart(2,'0')}`, formatted_time: `${String(ztHour).padStart(2,'0')}:${String(ztMinute).padStart(2,'0')}`, continuous_days: continuousDays === 1 ? '首板' : `${continuousDays}连板`, brief: sectorName === '公告' ? `${stockName}发布重大公告,公司拟收购资产/重组/增发等利好消息。` : `${sectorName}板块异动,${stockName}因板块热点涨停。公司是${sectorName}行业核心标的。`, summary: `${sectorName}概念活跃`, first_time: `${date.slice(0,4)}-${date.slice(4,6)}-${String(parseInt(date.slice(6,8)) - (continuousDays - 1)).padStart(2,'0')}`, change_pct: parseFloat((Math.random() * 1.5 + 9.5).toFixed(2)), core_sectors: coreSectors }); sectorStockCodes.push(code); stockIndex++; } sectorData[sectorName] = { count: stockCount, stock_codes: sectorStockCodes }; }); // 生成词频数据 const wordFreqData = [ { name: '人工智能', value: Math.floor(Math.random() * 30) + 20 }, { name: 'ChatGPT', value: Math.floor(Math.random() * 25) + 15 }, { name: '大模型', value: Math.floor(Math.random() * 20) + 12 }, { name: '算力', value: Math.floor(Math.random() * 18) + 10 }, { name: '光伏', value: Math.floor(Math.random() * 15) + 10 }, { name: '新能源', value: Math.floor(Math.random() * 15) + 8 }, { name: '锂电池', value: Math.floor(Math.random() * 12) + 8 }, { name: '储能', value: Math.floor(Math.random() * 12) + 6 }, { name: '半导体', value: Math.floor(Math.random() * 15) + 10 }, { name: '芯片', value: Math.floor(Math.random() * 15) + 8 }, { name: '集成电路', value: Math.floor(Math.random() * 10) + 5 }, { name: '国产替代', value: Math.floor(Math.random() * 10) + 5 }, { name: '医药', value: Math.floor(Math.random() * 12) + 6 }, { name: '创新药', value: Math.floor(Math.random() * 10) + 5 }, { name: '医疗器械', value: Math.floor(Math.random() * 8) + 4 }, { name: '军工', value: Math.floor(Math.random() * 10) + 5 }, { name: '航空航天', value: Math.floor(Math.random() * 8) + 4 }, { name: '数字经济', value: Math.floor(Math.random() * 12) + 6 }, { name: '工业4.0', value: Math.floor(Math.random() * 8) + 4 }, { name: '机器人', value: Math.floor(Math.random() * 10) + 5 }, { name: '自动驾驶', value: Math.floor(Math.random() * 8) + 4 }, { name: '元宇宙', value: Math.floor(Math.random() * 6) + 3 }, { name: 'Web3.0', value: Math.floor(Math.random() * 5) + 2 }, { name: '区块链', value: Math.floor(Math.random() * 5) + 2 }, ]; return { date: date, total_stocks: stocks.length, total_sectors: Object.keys(sectorData).length, stocks: stocks, sector_data: sectorData, word_freq_data: wordFreqData, summary: { top_sector: '人工智能', top_sector_count: sectorData['人工智能']?.count || 0, announcement_stocks: sectorData['公告']?.count || 0, zt_time_distribution: { morning: Math.floor(stocks.length * 0.4), afternoon: Math.floor(stocks.length * 0.6), } } }; }; // 生成 stocks.jsonl 数据 const generateStocksJsonl = () => { const stocks = []; const today = new Date(); // 生成 200 只历史涨停股票记录 for (let i = 0; i < 200; i++) { const daysAgo = Math.floor(Math.random() * 30); const date = new Date(today); date.setDate(date.getDate() - daysAgo); // 跳过周末 while (date.getDay() === 0 || date.getDay() === 6) { date.setDate(date.getDate() - 1); } const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); stocks.push({ scode: `${Math.random() > 0.6 ? '6' : Math.random() > 0.3 ? '0' : '3'}${String(Math.floor(Math.random() * 100000)).padStart(5, '0')}`, sname: ['龙头', '科技', '新能', '智能', '数字', '云计'][Math.floor(Math.random() * 6)] + ['股份', '科技', '电子', '信息', '新材'][Math.floor(Math.random() * 5)], date: `${year}${month}${day}`, formatted_date: `${year}-${month}-${day}`, continuous_days: Math.floor(Math.random() * 5) + 1, core_sectors: [['人工智能', 'ChatGPT', '光伏', '锂电池', '芯片'][Math.floor(Math.random() * 5)]] }); } return stocks; }; // Mock Handlers export const limitAnalyseHandlers = [ // ==================== 静态文件路径 Handlers ==================== // 1. /data/zt/dates.json - 可用日期列表 http.get('/data/zt/dates.json', async () => { await delay(200); console.log('[Mock LimitAnalyse] 获取 dates.json'); const data = generateDatesJson(); return HttpResponse.json(data); }), // 2. /data/zt/daily/:date.json - 每日分析数据 http.get('/data/zt/daily/:date', async ({ params }) => { await delay(300); // 移除 .json 后缀和查询参数 const dateParam = params.date.replace('.json', '').split('?')[0]; console.log('[Mock LimitAnalyse] 获取每日数据:', dateParam); const data = generateDailyJson(dateParam); return HttpResponse.json(data); }), // 3. /data/zt/stocks.jsonl - 股票列表(用于搜索) http.get('/data/zt/stocks.jsonl', async () => { await delay(200); console.log('[Mock LimitAnalyse] 获取 stocks.jsonl'); const stocks = generateStocksJsonl(); // JSONL 格式:每行一个 JSON const jsonl = stocks.map(s => JSON.stringify(s)).join('\n'); return new HttpResponse(jsonl, { headers: { 'Content-Type': 'text/plain' } }); }), // ==================== API 路径 Handlers (兼容旧版本) ==================== // 1. 获取可用日期列表 http.get('http://111.198.58.126:5001/api/v1/dates/available', async () => { await delay(300); const availableDates = generateAvailableDates(); return HttpResponse.json({ success: true, events: availableDates, message: '可用日期列表获取成功', }); }), // 2. 获取每日分析数据 http.get('http://111.198.58.126:5001/api/v1/analysis/daily/:date', async ({ params }) => { await delay(500); const { date } = params; const data = generateDailyAnalysis(date); return HttpResponse.json({ success: true, data: data, message: `${date} 每日分析数据获取成功`, }); }), // 3. 获取词云数据 http.get('http://111.198.58.126:5001/api/v1/analysis/wordcloud/:date', async ({ params }) => { await delay(300); const { date } = params; const wordCloudData = generateWordCloudData(); return HttpResponse.json({ success: true, data: wordCloudData, message: `${date} 词云数据获取成功`, }); }), // 4. 混合搜索(POST) http.post('http://111.198.58.126:5001/api/v1/stocks/search/hybrid', async ({ request }) => { await delay(400); const body = await request.json(); const { query, type = 'all', mode = 'hybrid' } = body; // 生成模拟搜索结果 const results = []; const count = Math.floor(Math.random() * 10) + 5; for (let i = 0; i < count; i++) { results.push({ code: `${Math.random() > 0.5 ? '6' : '0'}${String(Math.floor(Math.random() * 100000)).padStart(5, '0')}`, name: `${query || '搜索'}相关股票${i + 1}`, sector: ['人工智能', 'ChatGPT', '新能源'][Math.floor(Math.random() * 3)], limit_date: new Date().toISOString().split('T')[0].replace(/-/g, ''), limit_time: `${Math.floor(Math.random() * 4) + 9}:${String(Math.floor(Math.random() * 60)).padStart(2, '0')}`, price: (Math.random() * 100 + 10).toFixed(2), change_pct: (Math.random() * 10).toFixed(2), match_score: (Math.random() * 0.5 + 0.5).toFixed(2), }); } return HttpResponse.json({ success: true, data: { query: query, type: type, mode: mode, results: results, total: results.length, }, message: '搜索完成', }); }), // 5. 获取高位股列表(涨停股票列表) http.get('http://111.198.58.126:5001/api/limit-analyse/high-position-stocks', async ({ request }) => { await delay(400); const url = new URL(request.url); const date = url.searchParams.get('date'); console.log('[Mock LimitAnalyse] 获取高位股列表:', { date }); const stocks = generateHighPositionStocks(); const statistics = generateHighPositionStatistics(stocks); return HttpResponse.json({ success: true, data: { stocks: stocks, statistics: statistics, date: date, }, message: '高位股数据获取成功', }); }), ];