// src/mocks/handlers/stock.js // 股票相关的 Mock Handlers import { http, HttpResponse } from 'msw'; import { generateTimelineData, generateDailyData } from '../data/kline'; // 模拟延迟 const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); // 生成A股主要股票数据(包含各大指数成分股) const generateStockList = () => { const stocks = [ // 银行 { code: '000001', name: '平安银行' }, { code: '600000', name: '浦发银行' }, { code: '600036', name: '招商银行' }, { code: '601166', name: '兴业银行' }, { code: '601169', name: '北京银行' }, { code: '601288', name: '农业银行' }, { code: '601328', name: '交通银行' }, { code: '601398', name: '工商银行' }, { code: '601818', name: '光大银行' }, { code: '601939', name: '建设银行' }, { code: '601998', name: '中信银行' }, // 证券 { code: '600030', name: '中信证券' }, { code: '600109', name: '国金证券' }, { code: '600837', name: '海通证券' }, { code: '600999', name: '招商证券' }, { code: '601688', name: '华泰证券' }, { code: '601901', name: '方正证券' }, // 保险 { code: '601318', name: '中国平安' }, { code: '601336', name: '新华保险' }, { code: '601601', name: '中国太保' }, { code: '601628', name: '中国人寿' }, // 白酒/食品饮料 { code: '000568', name: '泸州老窖' }, { code: '000596', name: '古井贡酒' }, { code: '000858', name: '五粮液' }, { code: '600519', name: '贵州茅台' }, { code: '600600', name: '青岛啤酒' }, { code: '600779', name: '水井坊' }, { code: '603369', name: '今世缘' }, // 医药 { code: '000538', name: '云南白药' }, { code: '000661', name: '长春高新' }, { code: '002422', name: '科伦药业' }, { code: '002594', name: '比亚迪' }, { code: '600276', name: '恒瑞医药' }, { code: '600436', name: '片仔癀' }, { code: '603259', name: '药明康德' }, // 科技/半导体 { code: '000063', name: '中兴通讯' }, { code: '000725', name: '京东方A' }, { code: '002049', name: '紫光国微' }, { code: '002415', name: '海康威视' }, { code: '002475', name: '立讯精密' }, { code: '600584', name: '长电科技' }, { code: '600893', name: '航发动力' }, { code: '603501', name: '韦尔股份' }, // 新能源/电力 { code: '000002', name: '万科A' }, { code: '002460', name: '赣锋锂业' }, { code: '300750', name: '宁德时代' }, { code: '600438', name: '通威股份' }, { code: '601012', name: '隆基绿能' }, { code: '601668', name: '中国建筑' }, // 汽车 { code: '000625', name: '长安汽车' }, { code: '600066', name: '宇通客车' }, { code: '600104', name: '上汽集团' }, { code: '601238', name: '广汽集团' }, { code: '601633', name: '长城汽车' }, // 地产 { code: '000002', name: '万科A' }, { code: '000069', name: '华侨城A' }, { code: '600340', name: '华夏幸福' }, { code: '600606', name: '绿地控股' }, // 家电 { code: '000333', name: '美的集团' }, { code: '000651', name: '格力电器' }, { code: '002032', name: '苏泊尔' }, { code: '600690', name: '海尔智家' }, // 互联网/电商 { code: '002024', name: '苏宁易购' }, { code: '002074', name: '国轩高科' }, { code: '300059', name: '东方财富' }, // 能源/化工 { code: '600028', name: '中国石化' }, { code: '600309', name: '万华化学' }, { code: '600547', name: '山东黄金' }, { code: '600585', name: '海螺水泥' }, { code: '601088', name: '中国神华' }, { code: '601857', name: '中国石油' }, // 电信/运营商 { code: '600050', name: '中国联通' }, { code: '600941', name: '中国移动' }, { code: '601728', name: '中国电信' }, // 其他蓝筹 { code: '600887', name: '伊利股份' }, { code: '601111', name: '中国国航' }, { code: '601390', name: '中国中铁' }, { code: '601899', name: '紫金矿业' }, { code: '603288', name: '海天味业' }, ]; return stocks; }; // 股票相关的 Handlers export const stockHandlers = [ // 搜索股票(个股中心页面使用)- 支持模糊搜索 http.get('/api/stocks/search', async ({ request }) => { await delay(200); const url = new URL(request.url); const query = (url.searchParams.get('q') || '').toLowerCase().trim(); const limit = parseInt(url.searchParams.get('limit') || '10'); console.log('[Mock Stock] 搜索股票:', { query, limit }); const stocks = generateStockList(); // 如果没有搜索词,返回空结果 if (!query) { return HttpResponse.json({ success: true, data: [] }); } // 模糊搜索:代码 + 名称(不区分大小写) const results = stocks.filter(s => { const code = s.code.toLowerCase(); const name = s.name.toLowerCase(); return code.includes(query) || name.includes(query); }); // 按相关性排序:完全匹配 > 开头匹配 > 包含匹配 results.sort((a, b) => { const aCode = a.code.toLowerCase(); const bCode = b.code.toLowerCase(); const aName = a.name.toLowerCase(); const bName = b.name.toLowerCase(); // 计算匹配分数 const getScore = (code, name) => { if (code === query || name === query) return 100; // 完全匹配 if (code.startsWith(query)) return 80; // 代码开头 if (name.startsWith(query)) return 60; // 名称开头 if (code.includes(query)) return 40; // 代码包含 if (name.includes(query)) return 20; // 名称包含 return 0; }; return getScore(bCode, bName) - getScore(aCode, aName); }); // 返回格式化数据 return HttpResponse.json({ success: true, data: results.slice(0, limit).map(s => ({ stock_code: s.code, stock_name: s.name, market: s.code.startsWith('6') ? 'SH' : 'SZ', industry: ['银行', '证券', '保险', '白酒', '医药', '科技', '新能源', '汽车', '地产', '家电'][Math.floor(Math.random() * 10)], change_pct: parseFloat((Math.random() * 10 - 3).toFixed(2)), price: parseFloat((Math.random() * 100 + 5).toFixed(2)) })) }); }), // 获取所有股票列表 http.get('/api/stocklist', async () => { await delay(200); try { const stocks = generateStockList(); // console.log('[Mock Stock] 获取股票列表成功:', { count: stocks.length }); // 已关闭:减少日志 return HttpResponse.json(stocks); } catch (error) { console.error('[Mock Stock] 获取股票列表失败:', error); return HttpResponse.json( { error: '获取股票列表失败' }, { status: 500 } ); } }), // 获取指数K线数据 http.get('/api/index/:indexCode/kline', async ({ params, request }) => { await delay(300); const { indexCode } = params; const url = new URL(request.url); const type = url.searchParams.get('type') || 'timeline'; const eventTime = url.searchParams.get('event_time'); console.log('[Mock Stock] 获取指数K线数据:', { indexCode, type, eventTime }); try { let data; if (type === 'timeline') { data = generateTimelineData(indexCode); } else if (type === 'daily') { data = generateDailyData(indexCode, 30); } else { return HttpResponse.json( { error: '不支持的类型' }, { status: 400 } ); } return HttpResponse.json({ success: true, data: data, index_code: indexCode, type: type, message: '获取成功' }); } catch (error) { console.error('[Mock Stock] 获取K线数据失败:', error); return HttpResponse.json( { error: '获取K线数据失败' }, { status: 500 } ); } }), // 获取股票K线数据 http.get('/api/stock/:stockCode/kline', async ({ params, request }) => { await delay(300); const { stockCode } = params; const url = new URL(request.url); const type = url.searchParams.get('type') || 'timeline'; const eventTime = url.searchParams.get('event_time'); console.log('[Mock Stock] 获取股票K线数据:', { stockCode, type, eventTime }); try { let data; if (type === 'timeline') { // 股票使用指数的数据生成逻辑,但价格基数不同 data = generateTimelineData('000001.SH'); // 可以根据股票代码调整 } else if (type === 'daily') { data = generateDailyData('000001.SH', 30); } else { return HttpResponse.json( { error: '不支持的类型' }, { status: 400 } ); } return HttpResponse.json({ success: true, data: data, stock_code: stockCode, type: type, message: '获取成功' }); } catch (error) { console.error('[Mock Stock] 获取股票K线数据失败:', error); return HttpResponse.json( { error: '获取K线数据失败' }, { status: 500 } ); } }), // 批量获取股票K线数据 http.post('/api/stock/batch-kline', async ({ request }) => { await delay(400); try { const body = await request.json(); const { codes, type = 'timeline', event_time } = body; console.log('[Mock Stock] 批量获取K线数据:', { stockCount: codes?.length, type, eventTime: event_time }); if (!codes || !Array.isArray(codes) || codes.length === 0) { return HttpResponse.json( { error: '股票代码列表不能为空' }, { status: 400 } ); } // 为每只股票生成数据 const batchData = {}; codes.forEach(stockCode => { let data; if (type === 'timeline') { data = generateTimelineData('000001.SH'); } else if (type === 'daily') { data = generateDailyData('000001.SH', 60); } else { data = []; } batchData[stockCode] = { success: true, data: data, stock_code: stockCode }; }); return HttpResponse.json({ success: true, data: batchData, type: type, message: '批量获取成功' }); } catch (error) { console.error('[Mock Stock] 批量获取K线数据失败:', error); return HttpResponse.json( { error: '批量获取K线数据失败' }, { status: 500 } ); } }), // 获取股票报价(批量) http.post('/api/stock/quotes', async ({ request }) => { await delay(200); try { const body = await request.json(); const { codes, event_time } = body; console.log('[Mock Stock] 获取股票报价:', { stockCount: codes?.length, eventTime: event_time }); if (!codes || !Array.isArray(codes) || codes.length === 0) { return HttpResponse.json( { success: false, error: '股票代码列表不能为空' }, { status: 400 } ); } // 生成股票列表用于查找名称 const stockList = generateStockList(); const stockMap = {}; stockList.forEach(s => { stockMap[s.code] = s.name; }); // 行业和指数映射表 const stockIndustryMap = { '000001': { industry_l1: '金融', industry: '银行', index_tags: ['沪深300', '上证180'] }, '600519': { industry_l1: '消费', industry: '白酒', index_tags: ['沪深300', '上证50'] }, '300750': { industry_l1: '工业', industry: '电池', index_tags: ['创业板50'] }, '601318': { industry_l1: '金融', industry: '保险', index_tags: ['沪深300', '上证50'] }, '600036': { industry_l1: '金融', industry: '银行', index_tags: ['沪深300', '上证50'] }, '000858': { industry_l1: '消费', industry: '白酒', index_tags: ['沪深300'] }, '002594': { industry_l1: '汽车', industry: '乘用车', index_tags: ['沪深300', '创业板指'] }, }; const defaultIndustries = [ { industry_l1: '科技', industry: '软件' }, { industry_l1: '医药', industry: '化学制药' }, { industry_l1: '消费', industry: '食品' }, { industry_l1: '金融', industry: '证券' }, { industry_l1: '工业', industry: '机械' }, ]; // 为每只股票生成报价数据 const quotesData = {}; codes.forEach(stockCode => { // 生成基础价格(10-200之间) const basePrice = parseFloat((Math.random() * 190 + 10).toFixed(2)); // 涨跌幅(-10% 到 +10%) const changePercent = parseFloat((Math.random() * 20 - 10).toFixed(2)); // 涨跌额 const change = parseFloat((basePrice * changePercent / 100).toFixed(2)); // 昨收 const prevClose = parseFloat((basePrice - change).toFixed(2)); // 获取行业和指数信息 const codeWithoutSuffix = stockCode.replace(/\.(SH|SZ)$/i, ''); const industryInfo = stockIndustryMap[codeWithoutSuffix] || defaultIndustries[Math.floor(Math.random() * defaultIndustries.length)]; quotesData[stockCode] = { code: stockCode, name: stockMap[stockCode] || `股票${stockCode}`, price: basePrice, change: change, change_percent: changePercent, prev_close: prevClose, open: parseFloat((prevClose * (1 + (Math.random() * 0.02 - 0.01))).toFixed(2)), high: parseFloat((basePrice * (1 + Math.random() * 0.05)).toFixed(2)), low: parseFloat((basePrice * (1 - Math.random() * 0.05)).toFixed(2)), volume: Math.floor(Math.random() * 100000000), amount: parseFloat((Math.random() * 10000000000).toFixed(2)), market: stockCode.startsWith('6') ? 'SH' : 'SZ', update_time: new Date().toISOString(), // 行业和指数标签 industry_l1: industryInfo.industry_l1, industry: industryInfo.industry, index_tags: industryInfo.index_tags || [] }; }); return HttpResponse.json({ success: true, data: quotesData, message: '获取成功' }); } catch (error) { console.error('[Mock Stock] 获取股票报价失败:', error); return HttpResponse.json( { success: false, error: '获取股票报价失败' }, { status: 500 } ); } }), ];