Files
vf_react/src/mocks/handlers/stock.js
zdl ae397ac904 feat(mock): 完善 Mock 数据,修复 API 返回格式
- event.js: 修复 /api/events 返回格式,匹配 useEventData 期望的结构
- stock.js: 添加 /api/stock/:code/quote-detail handler(完整行情数据含买卖盘)
- stock.js: 添加 /api/flex-screen/quotes handler(指数行情)
- stock.js: 修复 /api/index/:code/kline 支持 minute 类型

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-18 18:03:21 +08:00

691 lines
29 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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));
// 常用字拼音首字母映射(简化版)
const PINYIN_MAP = {
'平': 'p', '安': 'a', '银': 'y', '行': 'h', '浦': 'p', '发': 'f',
'招': 'z', '商': 's', '兴': 'x', '业': 'y', '北': 'b', '京': 'j',
'农': 'n', '交': 'j', '通': 't', '工': 'g', '光': 'g', '大': 'd',
'建': 'j', '设': 's', '中': 'z', '信': 'x', '证': 'z', '券': 'q',
'国': 'g', '金': 'j', '海': 'h', '华': 'h', '泰': 't', '方': 'f',
'正': 'z', '新': 'x', '保': 'b', '险': 'x', '太': 't', '人': 'r',
'寿': 's', '泸': 'l', '州': 'z', '老': 'l', '窖': 'j', '古': 'g',
'井': 'j', '贡': 'g', '酒': 'j', '五': 'w', '粮': 'l', '液': 'y',
'贵': 'g', '茅': 'm', '台': 't', '青': 'q', '岛': 'd', '啤': 'p',
'水': 's', '坊': 'f', '今': 'j', '世': 's', '缘': 'y', '云': 'y',
'南': 'n', '白': 'b', '药': 'y', '长': 'c', '春': 'c', '高': 'g',
'科': 'k', '伦': 'l', '比': 'b', '亚': 'y', '迪': 'd', '恒': 'h',
'瑞': 'r', '医': 'y', '片': 'p', '仔': 'z', '癀': 'h', '明': 'm',
'康': 'k', '德': 'd', '讯': 'x', '东': 'd', '威': 'w', '视': 's',
'立': 'l', '精': 'j', '密': 'm', '电': 'd', '航': 'h',
'动': 'd', '力': 'l', '韦': 'w', '尔': 'e', '股': 'g', '份': 'f',
'万': 'w', '赣': 'g', '锋': 'f', '锂': 'l', '宁': 'n', '时': 's',
'代': 'd', '隆': 'l', '基': 'j', '绿': 'l', '能': 'n',
'筑': 'z', '汽': 'q', '车': 'c', '宇': 'y', '客': 'k', '上': 's',
'集': 'j', '团': 't', '广': 'g', '城': 'c', '侨': 'q', '夏': 'x',
'幸': 'x', '福': 'f', '地': 'd', '控': 'k', '美': 'm', '格': 'g',
'苏': 's', '智': 'z', '家': 'j', '易': 'y', '购': 'g',
'轩': 'x', '财': 'c', '富': 'f', '石': 's', '化': 'h', '学': 'x',
'山': 's', '黄': 'h', '螺': 'l', '泥': 'n', '神': 's', '油': 'y',
'联': 'l', '移': 'y', '伊': 'y', '利': 'l', '紫': 'z', '矿': 'k',
'天': 't', '味': 'w', '港': 'g', '微': 'w',
'技': 'j', '的': 'd', '器': 'q', '泊': 'b', '铁': 't',
};
// 生成拼音缩写
const generatePinyinAbbr = (name) => {
return name.split('').map(char => PINYIN_MAP[char] || '').join('');
};
// 生成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.map(s => ({
...s,
pinyin_abbr: generatePinyinAbbr(s.name)
}));
};
// 股票相关的 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();
const pinyin = (s.pinyin_abbr || '').toLowerCase();
return code.includes(query) || name.includes(query) || pinyin.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 aPinyin = (a.pinyin_abbr || '').toLowerCase();
const bPinyin = (b.pinyin_abbr || '').toLowerCase();
// 计算匹配分数(包含拼音匹配)
const getScore = (code, name, pinyin) => {
if (code === query || name === query || pinyin === query) return 100; // 完全匹配
if (code.startsWith(query)) return 80; // 代码开头
if (pinyin.startsWith(query)) return 70; // 拼音开头
if (name.startsWith(query)) return 60; // 名称开头
if (code.includes(query)) return 40; // 代码包含
if (pinyin.includes(query)) return 30; // 拼音包含
if (name.includes(query)) return 20; // 名称包含
return 0;
};
return getScore(bCode, bName, bPinyin) - getScore(aCode, aName, aPinyin);
});
// 返回格式化数据
return HttpResponse.json({
success: true,
data: results.slice(0, limit).map(s => ({
stock_code: s.code,
stock_name: s.name,
pinyin_abbr: s.pinyin_abbr,
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' || type === 'minute') {
// timeline 和 minute 都使用分时数据
data = generateTimelineData(indexCode);
} else if (type === 'daily') {
data = generateDailyData(indexCode, 30);
} else {
// 其他类型也降级使用 timeline 数据
console.log('[Mock Stock] 未知类型,降级使用 timeline:', type);
data = generateTimelineData(indexCode);
}
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.get('/api/stock/:stockCode/forecast', async ({ params }) => {
await delay(200);
const { stockCode } = params;
console.log('[Mock Stock] 获取业绩预告:', { stockCode });
// 生成股票列表用于查找名称
const stockList = generateStockList();
const stockInfo = stockList.find(s => s.code === stockCode.replace(/\.(SH|SZ)$/i, ''));
const stockName = stockInfo?.name || `股票${stockCode}`;
// 业绩预告类型列表
const forecastTypes = ['预增', '预减', '略增', '略减', '扭亏', '续亏', '首亏', '续盈'];
// 生成业绩预告数据
const forecasts = [
{
forecast_type: '预增',
report_date: '2024年年报',
content: `${stockName}预计2024年度归属于上市公司股东的净利润为58亿元至62亿元同比增长10%至17%。`,
reason: '报告期内,公司主营业务收入稳步增长,产品结构持续优化,毛利率提升;同时公司加大研发投入,新产品市场表现良好。',
change_range: {
lower: 10,
upper: 17
},
publish_date: '2024-10-15'
},
{
forecast_type: '略增',
report_date: '2024年三季报',
content: `${stockName}预计2024年1-9月归属于上市公司股东的净利润为42亿元至45亿元同比增长5%至12%。`,
reason: '公司积极拓展市场渠道,销售规模持续扩大,经营效益稳步提升。',
change_range: {
lower: 5,
upper: 12
},
publish_date: '2024-07-12'
},
{
forecast_type: forecastTypes[Math.floor(Math.random() * forecastTypes.length)],
report_date: '2024年中报',
content: `${stockName}预计2024年上半年归属于上市公司股东的净利润为28亿元至30亿元。`,
reason: '受益于行业景气度回升及公司降本增效措施效果显现,经营业绩同比有所改善。',
change_range: {
lower: 3,
upper: 8
},
publish_date: '2024-04-20'
}
];
return HttpResponse.json({
success: true,
data: {
stock_code: stockCode,
stock_name: stockName,
forecasts: forecasts
}
});
}),
// 获取股票报价(批量)
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 || [],
// 关键指标
pe: parseFloat((Math.random() * 50 + 5).toFixed(2)),
eps: parseFloat((Math.random() * 5 + 0.1).toFixed(3)),
pb: parseFloat((Math.random() * 8 + 0.5).toFixed(2)),
market_cap: `${(Math.random() * 5000 + 100).toFixed(0)}亿`,
week52_low: parseFloat((basePrice * 0.7).toFixed(2)),
week52_high: parseFloat((basePrice * 1.3).toFixed(2)),
// 主力动态
main_net_inflow: parseFloat((Math.random() * 10 - 5).toFixed(2)),
institution_holding: parseFloat((Math.random() * 50 + 10).toFixed(2)),
buy_ratio: parseFloat((Math.random() * 40 + 30).toFixed(2)),
sell_ratio: parseFloat((100 - (Math.random() * 40 + 30)).toFixed(2))
};
});
return HttpResponse.json({
success: true,
data: quotesData,
message: '获取成功'
});
} catch (error) {
console.error('[Mock Stock] 获取股票报价失败:', error);
return HttpResponse.json(
{ success: false, error: '获取股票报价失败' },
{ status: 500 }
);
}
}),
// 获取股票详细行情quote-detail
http.get('/api/stock/:stockCode/quote-detail', async ({ params }) => {
await delay(200);
const { stockCode } = params;
console.log('[Mock Stock] 获取股票详细行情:', { stockCode });
const stocks = generateStockList();
const codeWithoutSuffix = stockCode.replace(/\.(SH|SZ)$/i, '');
const stockInfo = stocks.find(s => s.code === codeWithoutSuffix);
const stockName = stockInfo?.name || `股票${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));
return HttpResponse.json({
success: true,
data: {
stock_code: stockCode,
stock_name: stockName,
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)),
turnover_rate: parseFloat((Math.random() * 10).toFixed(2)),
amplitude: parseFloat((Math.random() * 8).toFixed(2)),
market: stockCode.startsWith('6') ? 'SH' : 'SZ',
update_time: new Date().toISOString(),
// 买卖盘口
bid1: parseFloat((basePrice * 0.998).toFixed(2)),
bid1_volume: Math.floor(Math.random() * 10000),
bid2: parseFloat((basePrice * 0.996).toFixed(2)),
bid2_volume: Math.floor(Math.random() * 10000),
bid3: parseFloat((basePrice * 0.994).toFixed(2)),
bid3_volume: Math.floor(Math.random() * 10000),
bid4: parseFloat((basePrice * 0.992).toFixed(2)),
bid4_volume: Math.floor(Math.random() * 10000),
bid5: parseFloat((basePrice * 0.990).toFixed(2)),
bid5_volume: Math.floor(Math.random() * 10000),
ask1: parseFloat((basePrice * 1.002).toFixed(2)),
ask1_volume: Math.floor(Math.random() * 10000),
ask2: parseFloat((basePrice * 1.004).toFixed(2)),
ask2_volume: Math.floor(Math.random() * 10000),
ask3: parseFloat((basePrice * 1.006).toFixed(2)),
ask3_volume: Math.floor(Math.random() * 10000),
ask4: parseFloat((basePrice * 1.008).toFixed(2)),
ask4_volume: Math.floor(Math.random() * 10000),
ask5: parseFloat((basePrice * 1.010).toFixed(2)),
ask5_volume: Math.floor(Math.random() * 10000),
// 关键指标
pe: parseFloat((Math.random() * 50 + 5).toFixed(2)),
pb: parseFloat((Math.random() * 8 + 0.5).toFixed(2)),
eps: parseFloat((Math.random() * 5 + 0.1).toFixed(3)),
market_cap: `${(Math.random() * 5000 + 100).toFixed(0)}亿`,
circulating_market_cap: `${(Math.random() * 3000 + 50).toFixed(0)}亿`,
total_shares: `${(Math.random() * 100 + 10).toFixed(2)}亿`,
circulating_shares: `${(Math.random() * 80 + 5).toFixed(2)}亿`,
week52_high: parseFloat((basePrice * 1.3).toFixed(2)),
week52_low: parseFloat((basePrice * 0.7).toFixed(2))
},
message: '获取成功'
});
}),
// FlexScreen 行情数据
http.get('/api/flex-screen/quotes', async ({ request }) => {
await delay(200);
const url = new URL(request.url);
const codes = url.searchParams.get('codes')?.split(',') || [];
console.log('[Mock Stock] 获取 FlexScreen 行情:', { codes });
// 默认主要指数
const defaultIndices = ['000001', '399001', '399006'];
const targetCodes = codes.length > 0 ? codes : defaultIndices;
const indexData = {
'000001': { name: '上证指数', basePrice: 3200 },
'399001': { name: '深证成指', basePrice: 10500 },
'399006': { name: '创业板指', basePrice: 2100 },
'000300': { name: '沪深300', basePrice: 3800 },
'000016': { name: '上证50', basePrice: 2600 },
'000905': { name: '中证500', basePrice: 5800 },
};
const quotesData = {};
targetCodes.forEach(code => {
const codeWithoutSuffix = code.replace(/\.(SH|SZ)$/i, '');
const info = indexData[codeWithoutSuffix] || { name: `指数${code}`, basePrice: 3000 };
const changePercent = parseFloat((Math.random() * 4 - 2).toFixed(2));
const price = parseFloat((info.basePrice * (1 + changePercent / 100)).toFixed(2));
const change = parseFloat((price - info.basePrice).toFixed(2));
quotesData[code] = {
code: code,
name: info.name,
price: price,
change: change,
change_percent: changePercent,
prev_close: info.basePrice,
open: parseFloat((info.basePrice * (1 + (Math.random() * 0.01 - 0.005))).toFixed(2)),
high: parseFloat((price * (1 + Math.random() * 0.01)).toFixed(2)),
low: parseFloat((price * (1 - Math.random() * 0.01)).toFixed(2)),
volume: parseFloat((Math.random() * 5000 + 2000).toFixed(2)), // 亿手
amount: parseFloat((Math.random() * 8000 + 3000).toFixed(2)), // 亿元
update_time: new Date().toISOString()
};
});
return HttpResponse.json({
success: true,
data: quotesData,
message: '获取成功'
});
}),
];