345 lines
13 KiB
JavaScript
345 lines
13 KiB
JavaScript
// 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
|
||
export const limitAnalyseHandlers = [
|
||
// 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: '高位股数据获取成功',
|
||
});
|
||
}),
|
||
];
|