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>
This commit is contained in:
zdl
2025-12-18 18:03:21 +08:00
parent a5bc1e1ce3
commit ae397ac904
2 changed files with 140 additions and 9 deletions

View File

@@ -263,15 +263,15 @@ export const stockHandlers = [
try {
let data;
if (type === 'timeline') {
if (type === 'timeline' || type === 'minute') {
// timeline 和 minute 都使用分时数据
data = generateTimelineData(indexCode);
} else if (type === 'daily') {
data = generateDailyData(indexCode, 30);
} else {
return HttpResponse.json(
{ error: '不支持的类型' },
{ status: 400 }
);
// 其他类型也降级使用 timeline 数据
console.log('[Mock Stock] 未知类型,降级使用 timeline:', type);
data = generateTimelineData(indexCode);
}
return HttpResponse.json({
@@ -558,4 +558,133 @@ export const stockHandlers = [
);
}
}),
// 获取股票详细行情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: '获取成功'
});
}),
];