- 拦截 /data/concept/latest.json 请求 - 返回 mock 生成的热门概念数据 - 修复 HeroPanel 热门概念模块无数据问题 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
866 lines
39 KiB
JavaScript
866 lines
39 KiB
JavaScript
// src/mocks/handlers/concept.js
|
||
// 概念相关的 Mock Handlers
|
||
|
||
import { http, HttpResponse } from 'msw';
|
||
|
||
// 模拟延迟
|
||
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
||
|
||
// 生成历史触发时间(3-5个历史日期)
|
||
const generateHappenedTimes = (seed) => {
|
||
const times = [];
|
||
const count = 3 + (seed % 3); // 3-5个时间点
|
||
for (let i = 0; i < count; i++) {
|
||
const daysAgo = 30 + (seed * 7 + i * 11) % 330; // 30-360天前
|
||
const date = new Date();
|
||
date.setDate(date.getDate() - daysAgo);
|
||
times.push(date.toISOString().split('T')[0]);
|
||
}
|
||
return times.sort().reverse(); // 降序排列
|
||
};
|
||
|
||
// 生成核心相关股票
|
||
const generateStocksForConcept = (seed, count = 4) => {
|
||
const stockPool = [
|
||
{ name: '贵州茅台', code: '600519' },
|
||
{ name: '宁德时代', code: '300750' },
|
||
{ name: '中国平安', code: '601318' },
|
||
{ name: '比亚迪', code: '002594' },
|
||
{ name: '隆基绿能', code: '601012' },
|
||
{ name: '阳光电源', code: '300274' },
|
||
{ name: '三一重工', code: '600031' },
|
||
{ name: '中芯国际', code: '688981' },
|
||
{ name: '京东方A', code: '000725' },
|
||
{ name: '立讯精密', code: '002475' }
|
||
];
|
||
|
||
const stocks = [];
|
||
for (let i = 0; i < count; i++) {
|
||
const stockIndex = (seed + i * 7) % stockPool.length;
|
||
const stock = stockPool[stockIndex];
|
||
stocks.push({
|
||
stock_name: stock.name,
|
||
stock_code: stock.code,
|
||
reason: `作为行业龙头企业,${stock.name}在该领域具有核心竞争优势,市场份额领先,技术实力雄厚。`,
|
||
change_pct: parseFloat((Math.random() * 15 - 5).toFixed(2)) // -5% ~ +10%
|
||
});
|
||
}
|
||
return stocks;
|
||
};
|
||
|
||
// 生成热门概念数据
|
||
export const generatePopularConcepts = (size = 20) => {
|
||
const concepts = [
|
||
'人工智能', '新能源汽车', '半导体', '光伏', '锂电池',
|
||
'储能', '氢能源', '风电', '特高压', '工业母机',
|
||
'军工', '航空航天', '卫星导航', '量子科技', '数字货币',
|
||
'云计算', '大数据', '物联网', '5G', '6G',
|
||
'元宇宙', '虚拟现实', 'AIGC', 'ChatGPT', '算力',
|
||
'芯片设计', '芯片制造', '半导体设备', '半导体材料', 'EDA',
|
||
'新能源', '风光储', '充电桩', '智能电网', '特斯拉',
|
||
'比亚迪', '宁德时代', '华为', '苹果产业链', '鸿蒙',
|
||
'国产软件', '信创', '网络安全', '数据安全', '量子通信',
|
||
'医疗器械', '创新药', '医美', 'CXO', '生物医药',
|
||
'疫苗', '中药', '医疗信息化', '智慧医疗', '基因测序'
|
||
];
|
||
|
||
const conceptDescriptions = {
|
||
'人工智能': '人工智能是"技术突破+政策扶持"双轮驱动的硬科技主题。随着大模型技术的突破,AI应用场景不断拓展,预计将催化算力、数据、应用三大产业链。',
|
||
'新能源汽车': '新能源汽车行业景气度持续向好,渗透率不断提升。政策支持力度大,产业链上下游企业均受益明显。',
|
||
'半导体': '国产半导体替代加速,自主可控需求强烈。政策和资金支持力度大,行业迎来黄金发展期。',
|
||
'光伏': '光伏装机量快速增长,成本持续下降,行业景气度维持高位。双碳目标下,光伏行业前景广阔。',
|
||
'锂电池': '锂电池技术进步,成本优势扩大,下游应用领域持续扩张。新能源汽车和储能需求旺盛。',
|
||
'储能': '储能市场爆发式增长,政策支持力度大,应用场景不断拓展。未来市场空间巨大。',
|
||
'默认': '该概念市场关注度较高,具有一定的投资价值。相关企业技术实力雄厚,市场前景广阔。'
|
||
};
|
||
|
||
const matchTypes = ['hybrid_knn', 'keyword', 'semantic'];
|
||
|
||
const results = [];
|
||
for (let i = 0; i < Math.min(size, concepts.length); i++) {
|
||
const changePct = (Math.random() * 12 - 2).toFixed(2); // -2% 到 +10%
|
||
const stockCount = Math.floor(Math.random() * 50) + 10; // 10-60 只股票
|
||
const score = parseFloat((Math.random() * 5 + 3).toFixed(2)); // 3-8 分数范围
|
||
|
||
results.push({
|
||
concept: concepts[i],
|
||
concept_id: `CONCEPT_${1000 + i}`,
|
||
stock_count: stockCount,
|
||
score: score, // 相关度分数
|
||
match_type: matchTypes[i % 3], // 匹配类型
|
||
description: conceptDescriptions[concepts[i]] || conceptDescriptions['默认'],
|
||
price_info: {
|
||
avg_change_pct: parseFloat(changePct),
|
||
avg_price: parseFloat((Math.random() * 100 + 10).toFixed(2)),
|
||
total_market_cap: parseFloat((Math.random() * 1000 + 100).toFixed(2))
|
||
},
|
||
happened_times: generateHappenedTimes(i), // 历史触发时间
|
||
stocks: generateStocksForConcept(i, 4), // 核心相关股票
|
||
hot_score: Math.floor(Math.random() * 100)
|
||
});
|
||
}
|
||
|
||
// 按涨跌幅降序排序
|
||
results.sort((a, b) => b.price_info.avg_change_pct - a.price_info.avg_change_pct);
|
||
|
||
return results;
|
||
};
|
||
|
||
// 生成完整的概念统计数据(用于 ConceptStatsPanel)
|
||
const generateConceptStats = () => {
|
||
// 热门概念(涨幅最大的前5)
|
||
const hot_concepts = [
|
||
{ name: '小米大模型', change_pct: 18.76, stock_count: 12, news_count: 35 },
|
||
{ name: '人工智能', change_pct: 15.67, stock_count: 45, news_count: 23 },
|
||
{ name: '新能源汽车', change_pct: 12.34, stock_count: 38, news_count: 18 },
|
||
{ name: '芯片概念', change_pct: 9.87, stock_count: 52, news_count: 31 },
|
||
{ name: '5G通信', change_pct: 8.45, stock_count: 29, news_count: 15 },
|
||
];
|
||
|
||
// 冷门概念(跌幅最大的前5)
|
||
const cold_concepts = [
|
||
{ name: '房地产', change_pct: -8.76, stock_count: 33, news_count: 12 },
|
||
{ name: '煤炭开采', change_pct: -6.54, stock_count: 25, news_count: 8 },
|
||
{ name: '传统零售', change_pct: -5.43, stock_count: 19, news_count: 6 },
|
||
{ name: '钢铁冶炼', change_pct: -4.21, stock_count: 28, news_count: 9 },
|
||
{ name: '纺织服装', change_pct: -3.98, stock_count: 15, news_count: 4 },
|
||
];
|
||
|
||
// 活跃概念(新闻+研报最多的前5)
|
||
const active_concepts = [
|
||
{ name: '人工智能', news_count: 89, report_count: 15, total_mentions: 104 },
|
||
{ name: '芯片概念', news_count: 76, report_count: 12, total_mentions: 88 },
|
||
{ name: '新能源汽车', news_count: 65, report_count: 18, total_mentions: 83 },
|
||
{ name: '生物医药', news_count: 54, report_count: 9, total_mentions: 63 },
|
||
{ name: '量子科技', news_count: 41, report_count: 7, total_mentions: 48 },
|
||
];
|
||
|
||
// 波动最大的概念(前5)
|
||
const volatile_concepts = [
|
||
{ name: '区块链', volatility: 23.45, avg_change: 3.21, max_change: 12.34 },
|
||
{ name: '元宇宙', volatility: 21.87, avg_change: 2.98, max_change: 11.76 },
|
||
{ name: '虚拟现实', volatility: 19.65, avg_change: -1.23, max_change: 9.87 },
|
||
{ name: '游戏概念', volatility: 18.32, avg_change: 4.56, max_change: 10.45 },
|
||
{ name: '在线教育', volatility: 17.89, avg_change: -2.11, max_change: 8.76 },
|
||
];
|
||
|
||
// 动量概念(连续上涨的前5)
|
||
const momentum_concepts = [
|
||
{ name: '数字经济', consecutive_days: 5, total_change: 18.76, avg_daily: 3.75 },
|
||
{ name: '云计算', consecutive_days: 4, total_change: 14.32, avg_daily: 3.58 },
|
||
{ name: '物联网', consecutive_days: 4, total_change: 12.89, avg_daily: 3.22 },
|
||
{ name: '大数据', consecutive_days: 3, total_change: 11.45, avg_daily: 3.82 },
|
||
{ name: '工业互联网', consecutive_days: 3, total_change: 9.87, avg_daily: 3.29 },
|
||
];
|
||
|
||
return {
|
||
hot_concepts,
|
||
cold_concepts,
|
||
active_concepts,
|
||
volatile_concepts,
|
||
momentum_concepts
|
||
};
|
||
};
|
||
|
||
// 概念相关的 Handlers
|
||
export const conceptHandlers = [
|
||
// 搜索概念(热门概念)
|
||
http.post('/concept-api/search', async ({ request }) => {
|
||
await delay(300);
|
||
|
||
try {
|
||
const body = await request.json();
|
||
const { query = '', size = 20, page = 1, sort_by = 'change_pct' } = body;
|
||
|
||
console.log('[Mock Concept] 搜索概念:', { query, size, page, sort_by });
|
||
|
||
// 生成数据(不过滤,模拟真实 API 的语义搜索返回热门概念)
|
||
let results = generatePopularConcepts(size);
|
||
console.log('[Mock Concept] 生成概念数量:', results.length);
|
||
|
||
// Mock 环境下不做过滤,直接返回热门概念
|
||
// 真实环境会根据 query 进行语义搜索
|
||
|
||
// 根据排序字段排序
|
||
if (sort_by === 'change_pct') {
|
||
results.sort((a, b) => b.price_info.avg_change_pct - a.price_info.avg_change_pct);
|
||
} else if (sort_by === 'stock_count') {
|
||
results.sort((a, b) => b.stock_count - a.stock_count);
|
||
} else if (sort_by === 'hot_score') {
|
||
results.sort((a, b) => b.hot_score - a.hot_score);
|
||
}
|
||
|
||
return HttpResponse.json({
|
||
results,
|
||
total: results.length,
|
||
page,
|
||
size,
|
||
message: '搜索成功'
|
||
});
|
||
} catch (error) {
|
||
console.error('[Mock Concept] 搜索概念失败:', error);
|
||
return HttpResponse.json(
|
||
{
|
||
results: [],
|
||
total: 0,
|
||
error: '搜索失败'
|
||
},
|
||
{ status: 500 }
|
||
);
|
||
}
|
||
}),
|
||
|
||
// 获取单个概念详情
|
||
http.get('/concept-api/concepts/:conceptId', async ({ params }) => {
|
||
await delay(300);
|
||
|
||
const { conceptId } = params;
|
||
console.log('[Mock Concept] 获取概念详情:', conceptId);
|
||
|
||
const concepts = generatePopularConcepts(50);
|
||
const concept = concepts.find(c => c.concept_id === conceptId || c.concept === conceptId);
|
||
|
||
if (concept) {
|
||
return HttpResponse.json({
|
||
...concept,
|
||
related_stocks: [
|
||
{ stock_code: '600519', stock_name: '贵州茅台', change_pct: 2.34 },
|
||
{ stock_code: '000858', stock_name: '五粮液', change_pct: 1.89 },
|
||
{ stock_code: '000568', stock_name: '泸州老窖', change_pct: 3.12 }
|
||
],
|
||
news: [
|
||
{ title: `${concept.concept}板块异动`, date: '2024-10-24', source: '财经新闻' }
|
||
]
|
||
});
|
||
} else {
|
||
return HttpResponse.json(
|
||
{ error: '概念不存在' },
|
||
{ status: 404 }
|
||
);
|
||
}
|
||
}),
|
||
|
||
// 获取概念相关股票(concept-api 路由)
|
||
http.get('/concept-api/concepts/:conceptId/stocks', async ({ params, request }) => {
|
||
await delay(300);
|
||
|
||
const { conceptId } = params;
|
||
const url = new URL(request.url);
|
||
const limit = parseInt(url.searchParams.get('limit') || '20');
|
||
|
||
console.log('[Mock Concept] 获取概念相关股票:', { conceptId, limit });
|
||
|
||
// 生成模拟股票数据(确保 change_pct 是数字类型)
|
||
const stocks = [];
|
||
for (let i = 0; i < limit; i++) {
|
||
const code = 600000 + i;
|
||
stocks.push({
|
||
stock_code: `${code}.SH`,
|
||
code: `${code}.SH`,
|
||
stock_name: `股票${i + 1}`,
|
||
name: `股票${i + 1}`,
|
||
change_pct: parseFloat((Math.random() * 10 - 2).toFixed(2)),
|
||
price: parseFloat((Math.random() * 100 + 10).toFixed(2)),
|
||
market_cap: parseFloat((Math.random() * 1000 + 100).toFixed(2)),
|
||
reason: '板块核心成分股'
|
||
});
|
||
}
|
||
|
||
return HttpResponse.json({
|
||
stocks,
|
||
total: stocks.length,
|
||
concept_id: conceptId
|
||
});
|
||
}),
|
||
|
||
// 获取概念相关股票(/api/concept 路由 - 热点概览异动列表使用)
|
||
http.get('/api/concept/:conceptId/stocks', async ({ params, request }) => {
|
||
await delay(200);
|
||
|
||
const { conceptId } = params;
|
||
const url = new URL(request.url);
|
||
const limit = parseInt(url.searchParams.get('limit') || '15');
|
||
|
||
console.log('[Mock Concept] /api/concept/:conceptId/stocks:', { conceptId, limit });
|
||
|
||
// 股票池
|
||
const stockPool = [
|
||
{ code: '600519', name: '贵州茅台' },
|
||
{ code: '300750', name: '宁德时代' },
|
||
{ code: '601318', name: '中国平安' },
|
||
{ code: '002594', name: '比亚迪' },
|
||
{ code: '601012', name: '隆基绿能' },
|
||
{ code: '300274', name: '阳光电源' },
|
||
{ code: '688981', name: '中芯国际' },
|
||
{ code: '000725', name: '京东方A' },
|
||
{ code: '002230', name: '科大讯飞' },
|
||
{ code: '300124', name: '汇川技术' },
|
||
{ code: '002049', name: '紫光国微' },
|
||
{ code: '688012', name: '中微公司' },
|
||
{ code: '603501', name: '韦尔股份' },
|
||
{ code: '600036', name: '招商银行' },
|
||
{ code: '000858', name: '五粮液' },
|
||
];
|
||
|
||
// 生成模拟股票数据(确保 change_pct 是数字类型)
|
||
const stocks = [];
|
||
const count = Math.min(limit, stockPool.length);
|
||
for (let i = 0; i < count; i++) {
|
||
const stock = stockPool[i];
|
||
// 根据股票代码判断交易所后缀
|
||
let suffix = '.SZ';
|
||
if (stock.code.startsWith('6')) {
|
||
suffix = '.SH';
|
||
} else if (stock.code.startsWith('8') || stock.code.startsWith('9') || stock.code.startsWith('4')) {
|
||
suffix = '.BJ';
|
||
}
|
||
stocks.push({
|
||
stock_code: `${stock.code}${suffix}`,
|
||
code: `${stock.code}${suffix}`,
|
||
stock_name: stock.name,
|
||
name: stock.name,
|
||
change_pct: parseFloat((Math.random() * 12 - 3).toFixed(2)), // -3% ~ +9%
|
||
price: parseFloat((Math.random() * 100 + 10).toFixed(2)),
|
||
market_cap: parseFloat((Math.random() * 1000 + 100).toFixed(2)),
|
||
reason: '板块核心标的'
|
||
});
|
||
}
|
||
|
||
return HttpResponse.json({
|
||
success: true,
|
||
data: {
|
||
stocks,
|
||
total: stocks.length,
|
||
concept_id: conceptId
|
||
}
|
||
});
|
||
}),
|
||
|
||
// 获取最新交易日期
|
||
http.get('http://111.198.58.126:16801/price/latest', async () => {
|
||
await delay(200);
|
||
|
||
const today = new Date();
|
||
const dateStr = today.toISOString().split('T')[0].replace(/-/g, '');
|
||
|
||
console.log('[Mock Concept] 获取最新交易日期:', dateStr);
|
||
|
||
return HttpResponse.json({
|
||
latest_date: dateStr,
|
||
timestamp: today.toISOString()
|
||
});
|
||
}),
|
||
|
||
// 搜索概念(硬编码 URL)
|
||
http.post('http://111.198.58.126:16801/search', async ({ request }) => {
|
||
await delay(300);
|
||
|
||
try {
|
||
const body = await request.json();
|
||
const { query = '', size = 20, page = 1, sort_by = 'change_pct' } = body;
|
||
|
||
console.log('[Mock Concept] 搜索概念 (硬编码URL):', { query, size, page, sort_by });
|
||
|
||
let results = generatePopularConcepts(size);
|
||
|
||
if (query) {
|
||
results = results.filter(item =>
|
||
item.concept.toLowerCase().includes(query.toLowerCase())
|
||
);
|
||
}
|
||
|
||
if (sort_by === 'change_pct') {
|
||
results.sort((a, b) => b.price_info.avg_change_pct - a.price_info.avg_change_pct);
|
||
} else if (sort_by === 'stock_count') {
|
||
results.sort((a, b) => b.stock_count - a.stock_count);
|
||
} else if (sort_by === 'hot_score') {
|
||
results.sort((a, b) => b.hot_score - a.hot_score);
|
||
}
|
||
|
||
return HttpResponse.json({
|
||
results,
|
||
total: results.length,
|
||
page,
|
||
size,
|
||
message: '搜索成功'
|
||
});
|
||
} catch (error) {
|
||
console.error('[Mock Concept] 搜索失败:', error);
|
||
return HttpResponse.json(
|
||
{ results: [], total: 0, error: '搜索失败' },
|
||
{ status: 500 }
|
||
);
|
||
}
|
||
}),
|
||
|
||
// 获取统计数据(直接访问外部 API)
|
||
http.get('http://111.198.58.126:16801/statistics', async ({ request }) => {
|
||
await delay(300);
|
||
|
||
const url = new URL(request.url);
|
||
const minStockCount = parseInt(url.searchParams.get('min_stock_count') || '3');
|
||
const days = parseInt(url.searchParams.get('days') || '7');
|
||
const startDate = url.searchParams.get('start_date');
|
||
const endDate = url.searchParams.get('end_date');
|
||
|
||
console.log('[Mock Concept] 获取统计数据 (直接API):', { minStockCount, days, startDate, endDate });
|
||
|
||
// 生成完整的统计数据
|
||
const statsData = generateConceptStats();
|
||
|
||
return HttpResponse.json({
|
||
success: true,
|
||
data: statsData,
|
||
note: 'Mock 数据',
|
||
params: {
|
||
min_stock_count: minStockCount,
|
||
days: days,
|
||
start_date: startDate,
|
||
end_date: endDate
|
||
},
|
||
updated_at: new Date().toISOString()
|
||
});
|
||
}),
|
||
|
||
// 获取统计数据(通过 nginx 代理)
|
||
http.get('/concept-api/statistics', async ({ request }) => {
|
||
await delay(300);
|
||
|
||
const url = new URL(request.url);
|
||
const minStockCount = parseInt(url.searchParams.get('min_stock_count') || '3');
|
||
const days = parseInt(url.searchParams.get('days') || '7');
|
||
const startDate = url.searchParams.get('start_date');
|
||
const endDate = url.searchParams.get('end_date');
|
||
|
||
console.log('[Mock Concept] 获取统计数据 (nginx代理):', { minStockCount, days, startDate, endDate });
|
||
|
||
// 生成完整的统计数据
|
||
const statsData = generateConceptStats();
|
||
|
||
return HttpResponse.json({
|
||
success: true,
|
||
data: statsData,
|
||
note: 'Mock 数据(通过 nginx 代理)',
|
||
params: {
|
||
min_stock_count: minStockCount,
|
||
days: days,
|
||
start_date: startDate,
|
||
end_date: endDate
|
||
},
|
||
updated_at: new Date().toISOString()
|
||
});
|
||
}),
|
||
|
||
// 获取概念价格时间序列
|
||
http.get('http://111.198.58.126:16801/concept/:conceptId/price-timeseries', async ({ params, request }) => {
|
||
await delay(300);
|
||
|
||
const { conceptId } = params;
|
||
const url = new URL(request.url);
|
||
const startDate = url.searchParams.get('start_date');
|
||
const endDate = url.searchParams.get('end_date');
|
||
|
||
console.log('[Mock Concept] 获取价格时间序列:', { conceptId, startDate, endDate });
|
||
|
||
// 生成时间序列数据
|
||
const timeseries = [];
|
||
const start = new Date(startDate || '2024-01-01');
|
||
const end = new Date(endDate || new Date());
|
||
const daysDiff = Math.ceil((end - start) / (1000 * 60 * 60 * 24));
|
||
|
||
for (let i = 0; i <= daysDiff; i++) {
|
||
const date = new Date(start);
|
||
date.setDate(date.getDate() + i);
|
||
|
||
// 跳过周末
|
||
if (date.getDay() !== 0 && date.getDay() !== 6) {
|
||
timeseries.push({
|
||
trade_date: date.toISOString().split('T')[0], // 改为 trade_date
|
||
avg_change_pct: parseFloat((Math.random() * 8 - 2).toFixed(2)), // 转为数值
|
||
stock_count: Math.floor(Math.random() * 30) + 10,
|
||
volume: Math.floor(Math.random() * 1000000000)
|
||
});
|
||
}
|
||
}
|
||
|
||
return HttpResponse.json({
|
||
concept_id: conceptId,
|
||
timeseries: timeseries,
|
||
start_date: startDate,
|
||
end_date: endDate
|
||
});
|
||
}),
|
||
|
||
// 获取概念相关新闻 (search_china_news)
|
||
http.get('http://111.198.58.126:21891/search_china_news', async ({ request }) => {
|
||
await delay(300);
|
||
|
||
const url = new URL(request.url);
|
||
const query = url.searchParams.get('query');
|
||
const exactMatch = url.searchParams.get('exact_match');
|
||
const startDate = url.searchParams.get('start_date');
|
||
const endDate = url.searchParams.get('end_date');
|
||
const topK = parseInt(url.searchParams.get('top_k') || '100');
|
||
|
||
console.log('[Mock Concept] 搜索中国新闻:', { query, exactMatch, startDate, endDate, topK });
|
||
|
||
// 生成新闻数据
|
||
const news = [];
|
||
const newsCount = Math.min(topK, Math.floor(Math.random() * 15) + 5); // 5-20 条新闻
|
||
|
||
for (let i = 0; i < newsCount; i++) {
|
||
const daysAgo = Math.floor(Math.random() * 100); // 0-100 天前
|
||
const date = new Date();
|
||
date.setDate(date.getDate() - daysAgo);
|
||
|
||
const hour = Math.floor(Math.random() * 24);
|
||
const minute = Math.floor(Math.random() * 60);
|
||
const publishedTime = `${date.toISOString().split('T')[0]} ${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}:00`;
|
||
|
||
news.push({
|
||
id: `news_${i}`,
|
||
title: `${query || '概念'}板块动态:${['利好政策发布', '行业景气度提升', '龙头企业业绩超预期', '技术突破进展', '市场需求旺盛'][i % 5]}`,
|
||
detail: `${query || '概念'}相关新闻详细内容。近期${query || '概念'}板块表现活跃,市场关注度持续上升。多家券商研报指出,${query || '概念'}行业前景广阔,建议重点关注龙头企业投资机会。`,
|
||
description: `${query || '概念'}板块最新动态摘要...`,
|
||
source: ['新浪财经', '东方财富网', '财联社', '证券时报', '中国证券报', '上海证券报'][Math.floor(Math.random() * 6)],
|
||
published_time: publishedTime,
|
||
url: `https://finance.sina.com.cn/stock/news/${date.getFullYear()}${String(date.getMonth() + 1).padStart(2, '0')}${String(date.getDate()).padStart(2, '0')}/news_${i}.html`
|
||
});
|
||
}
|
||
|
||
// 按时间降序排序
|
||
news.sort((a, b) => new Date(b.published_time) - new Date(a.published_time));
|
||
|
||
// 返回数组(不是对象)
|
||
return HttpResponse.json(news);
|
||
}),
|
||
|
||
// 获取概念相关研报 (search)
|
||
http.get('http://111.198.58.126:8811/search', async ({ request }) => {
|
||
await delay(300);
|
||
|
||
const url = new URL(request.url);
|
||
const query = url.searchParams.get('query');
|
||
const mode = url.searchParams.get('mode');
|
||
const exactMatch = url.searchParams.get('exact_match');
|
||
const size = parseInt(url.searchParams.get('size') || '30');
|
||
const startDate = url.searchParams.get('start_date');
|
||
|
||
console.log('[Mock Concept] 搜索研报:', { query, mode, exactMatch, size, startDate });
|
||
|
||
// 生成研报数据
|
||
const reports = [];
|
||
const reportCount = Math.min(size, Math.floor(Math.random() * 10) + 3); // 3-12 份研报
|
||
|
||
const publishers = ['中信证券', '国泰君安', '华泰证券', '招商证券', '海通证券', '广发证券', '申万宏源', '兴业证券'];
|
||
const authors = ['张明', '李华', '王强', '刘洋', '陈杰', '赵敏'];
|
||
const ratings = ['买入', '增持', '中性', '减持', '强烈推荐'];
|
||
const securityNames = ['行业研究', '公司研究', '策略研究', '宏观研究', '固收研究'];
|
||
|
||
for (let i = 0; i < reportCount; i++) {
|
||
const daysAgo = Math.floor(Math.random() * 100); // 0-100 天前
|
||
const date = new Date();
|
||
date.setDate(date.getDate() - daysAgo);
|
||
|
||
const declareDate = `${date.toISOString().split('T')[0]} ${String(Math.floor(Math.random() * 24)).padStart(2, '0')}:${String(Math.floor(Math.random() * 60)).padStart(2, '0')}:00`;
|
||
|
||
reports.push({
|
||
id: `report_${i}`,
|
||
report_title: `${query || '概念'}行业${['深度研究报告', '投资策略分析', '行业景气度跟踪', '估值分析报告', '竞争格局研究'][i % 5]}`,
|
||
content: `${query || '概念'}行业研究报告内容摘要。\n\n核心观点:\n1. ${query || '概念'}行业景气度持续向好,市场规模预计将保持高速增长。\n2. 龙头企业凭借技术优势和规模效应,市场份额有望进一步提升。\n3. 政策支持力度加大,为行业发展提供有力保障。\n\n投资建议:建议重点关注行业龙头企业,给予"${ratings[Math.floor(Math.random() * ratings.length)]}"评级。`,
|
||
abstract: `本报告深入分析了${query || '概念'}行业的发展趋势、竞争格局和投资机会,认为行业具备良好的成长性...`,
|
||
publisher: publishers[Math.floor(Math.random() * publishers.length)],
|
||
author: authors[Math.floor(Math.random() * authors.length)],
|
||
declare_date: declareDate,
|
||
rating: ratings[Math.floor(Math.random() * ratings.length)],
|
||
security_name: securityNames[Math.floor(Math.random() * securityNames.length)],
|
||
content_url: `https://pdf.dfcfw.com/pdf/H3_${1000000 + i}_1_${date.getFullYear()}${String(date.getMonth() + 1).padStart(2, '0')}${String(date.getDate()).padStart(2, '0')}.pdf`
|
||
});
|
||
}
|
||
|
||
// 按时间降序排序
|
||
reports.sort((a, b) => new Date(b.declare_date) - new Date(a.declare_date));
|
||
|
||
// 返回符合组件期望的格式
|
||
return HttpResponse.json({
|
||
results: reports,
|
||
total: reports.length,
|
||
query: query,
|
||
mode: mode
|
||
});
|
||
}),
|
||
|
||
// ============ 层级结构 API ============
|
||
|
||
// 获取完整层级结构
|
||
http.get('/concept-api/hierarchy', async () => {
|
||
await delay(300);
|
||
|
||
console.log('[Mock Concept] 获取层级结构');
|
||
|
||
// 模拟层级结构数据
|
||
const hierarchy = [
|
||
{
|
||
id: 'lv1_1',
|
||
name: '人工智能',
|
||
concept_count: 98,
|
||
children: [
|
||
{
|
||
id: 'lv2_1_1',
|
||
name: 'AI基础设施',
|
||
concept_count: 52,
|
||
children: [
|
||
{ id: 'lv3_1_1_1', name: 'AI算力硬件', concept_count: 16, concepts: ['AI芯片', 'GPU概念股', '服务器', 'AI一体机'] },
|
||
{ id: 'lv3_1_1_2', name: 'AI关键组件', concept_count: 24, concepts: ['HBM', 'PCB', '光通信', '存储芯片'] },
|
||
{ id: 'lv3_1_1_3', name: 'AI配套设施', concept_count: 12, concepts: ['数据中心', '液冷', '电力设备'] }
|
||
]
|
||
},
|
||
{
|
||
id: 'lv2_1_2',
|
||
name: 'AI模型与软件',
|
||
concept_count: 13,
|
||
concepts: ['DeepSeek', 'KIMI', 'SORA概念', '国产大模型']
|
||
},
|
||
{
|
||
id: 'lv2_1_3',
|
||
name: 'AI应用',
|
||
concept_count: 17,
|
||
children: [
|
||
{ id: 'lv3_1_3_1', name: '智能体与陪伴', concept_count: 11, concepts: ['AI伴侣', 'AI智能体', 'AI陪伴'] },
|
||
{ id: 'lv3_1_3_2', name: '行业应用', concept_count: 6, concepts: ['AI编程', '低代码'] }
|
||
]
|
||
}
|
||
]
|
||
},
|
||
{
|
||
id: 'lv1_2',
|
||
name: '半导体',
|
||
concept_count: 45,
|
||
children: [
|
||
{ id: 'lv2_2_1', name: '半导体设备', concept_count: 10, concepts: ['光刻机', 'EDA', '半导体设备'] },
|
||
{ id: 'lv2_2_2', name: '半导体材料', concept_count: 8, concepts: ['光刻胶', '半导体材料', '石英砂'] },
|
||
{ id: 'lv2_2_3', name: '芯片设计与制造', concept_count: 10, concepts: ['第三代半导体', '碳化硅', '功率半导体'] },
|
||
{ id: 'lv2_2_4', name: '先进封装', concept_count: 5, concepts: ['玻璃基板', '半导体封测'] }
|
||
]
|
||
},
|
||
{
|
||
id: 'lv1_3',
|
||
name: '机器人',
|
||
concept_count: 42,
|
||
children: [
|
||
{ id: 'lv2_3_1', name: '人形机器人整机', concept_count: 20, concepts: ['特斯拉机器人', '人形机器人', '智元机器人'] },
|
||
{ id: 'lv2_3_2', name: '机器人核心零部件', concept_count: 12, concepts: ['滚柱丝杆', '电子皮肤', '轴向磁通电机'] },
|
||
{ id: 'lv2_3_3', name: '其他类型机器人', concept_count: 10, concepts: ['工业机器人', '机器狗', '外骨骼机器人'] }
|
||
]
|
||
},
|
||
{
|
||
id: 'lv1_4',
|
||
name: '消费电子',
|
||
concept_count: 38,
|
||
children: [
|
||
{ id: 'lv2_4_1', name: '智能终端', concept_count: 8, concepts: ['AI PC', 'AI手机'] },
|
||
{ id: 'lv2_4_2', name: 'XR与空间计算', concept_count: 14, concepts: ['AR眼镜', 'MR', '智能眼镜'] },
|
||
{ id: 'lv2_4_3', name: '华为产业链', concept_count: 16, concepts: ['华为Mate70', '鸿蒙', '华为昇腾'] }
|
||
]
|
||
},
|
||
{
|
||
id: 'lv1_5',
|
||
name: '智能驾驶与汽车',
|
||
concept_count: 35,
|
||
children: [
|
||
{ id: 'lv2_5_1', name: '自动驾驶解决方案', concept_count: 12, concepts: ['Robotaxi', '无人驾驶', '特斯拉FSD'] },
|
||
{ id: 'lv2_5_2', name: '智能汽车产业链', concept_count: 15, concepts: ['比亚迪产业链', '小米汽车产业链'] },
|
||
{ id: 'lv2_5_3', name: '车路协同', concept_count: 8, concepts: ['车路云一体化', '车路协同'] }
|
||
]
|
||
},
|
||
{
|
||
id: 'lv1_6',
|
||
name: '新能源与电力',
|
||
concept_count: 52,
|
||
children: [
|
||
{ id: 'lv2_6_1', name: '新型电池技术', concept_count: 18, concepts: ['固态电池', '钠离子电池', '硅基负极'] },
|
||
{ id: 'lv2_6_2', name: '电力设备与电网', concept_count: 20, concepts: ['电力', '变压器出海', '燃料电池'] },
|
||
{ id: 'lv2_6_3', name: '清洁能源', concept_count: 14, concepts: ['光伏', '核电', '可控核聚变'] }
|
||
]
|
||
},
|
||
{
|
||
id: 'lv1_7',
|
||
name: '空天经济',
|
||
concept_count: 28,
|
||
children: [
|
||
{ id: 'lv2_7_1', name: '低空经济', concept_count: 14, concepts: ['低空经济', 'eVTOL', '飞行汽车'] },
|
||
{ id: 'lv2_7_2', name: '商业航天', concept_count: 14, concepts: ['卫星互联网', '商业航天', '北斗导航'] }
|
||
]
|
||
},
|
||
{
|
||
id: 'lv1_8',
|
||
name: '国防军工',
|
||
concept_count: 25,
|
||
children: [
|
||
{ id: 'lv2_8_1', name: '无人作战与信息化', concept_count: 10, concepts: ['AI军工', '无人机蜂群', '军工信息化'] },
|
||
{ id: 'lv2_8_2', name: '海军装备', concept_count: 8, concepts: ['国产航母', '电磁弹射'] },
|
||
{ id: 'lv2_8_3', name: '军贸出海', concept_count: 7, concepts: ['军贸', '巴黎航展'] }
|
||
]
|
||
}
|
||
];
|
||
|
||
return HttpResponse.json({
|
||
hierarchy,
|
||
total_lv1: hierarchy.length,
|
||
total_concepts: hierarchy.reduce((acc, h) => acc + h.concept_count, 0)
|
||
});
|
||
}),
|
||
|
||
// 获取层级统计数据(包含涨跌幅)
|
||
http.get('/concept-api/statistics/hierarchy', async () => {
|
||
await delay(300);
|
||
|
||
console.log('[Mock Concept] 获取层级统计数据');
|
||
|
||
const statistics = [
|
||
{ lv1: '人工智能', concept_count: 98, avg_change_pct: 3.56, top_gainer: 'DeepSeek', top_gainer_change: 15.23 },
|
||
{ lv1: '半导体', concept_count: 45, avg_change_pct: 2.12, top_gainer: '光刻机', top_gainer_change: 8.76 },
|
||
{ lv1: '机器人', concept_count: 42, avg_change_pct: 4.28, top_gainer: '人形机器人', top_gainer_change: 12.45 },
|
||
{ lv1: '消费电子', concept_count: 38, avg_change_pct: 1.45, top_gainer: 'AR眼镜', top_gainer_change: 6.78 },
|
||
{ lv1: '智能驾驶与汽车', concept_count: 35, avg_change_pct: 2.89, top_gainer: 'Robotaxi', top_gainer_change: 9.32 },
|
||
{ lv1: '新能源与电力', concept_count: 52, avg_change_pct: -0.56, top_gainer: '固态电池', top_gainer_change: 5.67 },
|
||
{ lv1: '空天经济', concept_count: 28, avg_change_pct: 3.12, top_gainer: '低空经济', top_gainer_change: 11.23 },
|
||
{ lv1: '国防军工', concept_count: 25, avg_change_pct: 1.78, top_gainer: 'AI军工', top_gainer_change: 7.89 }
|
||
];
|
||
|
||
return HttpResponse.json({
|
||
statistics,
|
||
total_lv1: statistics.length,
|
||
total_concepts: statistics.reduce((acc, s) => acc + s.concept_count, 0),
|
||
market_avg_change: 2.34,
|
||
update_time: new Date().toISOString()
|
||
});
|
||
}),
|
||
|
||
// 获取层级涨跌幅数据(实时价格)
|
||
http.get('/concept-api/hierarchy/price', async ({ request }) => {
|
||
await delay(200);
|
||
|
||
const url = new URL(request.url);
|
||
const tradeDate = url.searchParams.get('trade_date');
|
||
|
||
console.log('[Mock Concept] 获取层级涨跌幅数据:', { tradeDate });
|
||
|
||
// 模拟 lv1 层级涨跌幅数据
|
||
const lv1_concepts = [
|
||
{ concept_name: '人工智能', avg_change_pct: 3.56, stock_count: 245 },
|
||
{ concept_name: '半导体', avg_change_pct: 2.12, stock_count: 156 },
|
||
{ concept_name: '机器人', avg_change_pct: 4.28, stock_count: 128 },
|
||
{ concept_name: '消费电子', avg_change_pct: 1.45, stock_count: 98 },
|
||
{ concept_name: '智能驾驶与汽车', avg_change_pct: 2.89, stock_count: 112 },
|
||
{ concept_name: '新能源与电力', avg_change_pct: -0.56, stock_count: 186 },
|
||
{ concept_name: '空天经济', avg_change_pct: 3.12, stock_count: 76 },
|
||
{ concept_name: '国防军工', avg_change_pct: 1.78, stock_count: 89 }
|
||
];
|
||
|
||
// 模拟 lv2 层级涨跌幅数据
|
||
const lv2_concepts = [
|
||
// 人工智能下的 lv2
|
||
{ concept_name: 'AI基础设施', avg_change_pct: 4.12, stock_count: 85 },
|
||
{ concept_name: 'AI模型与软件', avg_change_pct: 5.67, stock_count: 42 },
|
||
{ concept_name: 'AI应用', avg_change_pct: 2.34, stock_count: 65 },
|
||
// 半导体下的 lv2
|
||
{ concept_name: '半导体设备', avg_change_pct: 3.21, stock_count: 38 },
|
||
{ concept_name: '半导体材料', avg_change_pct: 1.89, stock_count: 32 },
|
||
{ concept_name: '芯片设计与制造', avg_change_pct: 2.45, stock_count: 56 },
|
||
{ concept_name: '先进封装', avg_change_pct: 1.23, stock_count: 22 },
|
||
// 机器人下的 lv2
|
||
{ concept_name: '人形机器人整机', avg_change_pct: 5.89, stock_count: 45 },
|
||
{ concept_name: '机器人核心零部件', avg_change_pct: 3.45, stock_count: 52 },
|
||
{ concept_name: '其他类型机器人', avg_change_pct: 2.12, stock_count: 31 },
|
||
// 消费电子下的 lv2
|
||
{ concept_name: '智能终端', avg_change_pct: 1.78, stock_count: 28 },
|
||
{ concept_name: 'XR与空间计算', avg_change_pct: 2.56, stock_count: 36 },
|
||
{ concept_name: '华为产业链', avg_change_pct: 0.89, stock_count: 48 },
|
||
// 智能驾驶下的 lv2
|
||
{ concept_name: '自动驾驶解决方案', avg_change_pct: 4.23, stock_count: 35 },
|
||
{ concept_name: '智能汽车产业链', avg_change_pct: 2.45, stock_count: 52 },
|
||
{ concept_name: '车路协同', avg_change_pct: 1.56, stock_count: 25 },
|
||
// 新能源下的 lv2
|
||
{ concept_name: '新型电池技术', avg_change_pct: 0.67, stock_count: 62 },
|
||
{ concept_name: '电力设备与电网', avg_change_pct: -1.23, stock_count: 78 },
|
||
{ concept_name: '清洁能源', avg_change_pct: -0.45, stock_count: 46 },
|
||
// 空天经济下的 lv2
|
||
{ concept_name: '低空经济', avg_change_pct: 4.56, stock_count: 42 },
|
||
{ concept_name: '商业航天', avg_change_pct: 1.89, stock_count: 34 },
|
||
// 国防军工下的 lv2
|
||
{ concept_name: '无人作战与信息化', avg_change_pct: 2.34, stock_count: 28 },
|
||
{ concept_name: '海军装备', avg_change_pct: 1.45, stock_count: 32 },
|
||
{ concept_name: '军贸出海', avg_change_pct: 1.12, stock_count: 18 }
|
||
];
|
||
|
||
// 模拟 lv3 层级涨跌幅数据
|
||
const lv3_concepts = [
|
||
// AI基础设施下的 lv3
|
||
{ concept_name: 'AI算力硬件', avg_change_pct: 5.23, stock_count: 32 },
|
||
{ concept_name: 'AI关键组件', avg_change_pct: 3.89, stock_count: 45 },
|
||
{ concept_name: 'AI配套设施', avg_change_pct: 2.67, stock_count: 28 },
|
||
// AI应用下的 lv3
|
||
{ concept_name: '智能体与陪伴', avg_change_pct: 3.12, stock_count: 24 },
|
||
{ concept_name: '行业应用', avg_change_pct: 1.56, stock_count: 18 }
|
||
];
|
||
|
||
// 计算交易日期(如果没有传入则使用今天)
|
||
const today = tradeDate ? new Date(tradeDate) : new Date();
|
||
const tradeDateStr = today.toISOString().split('T')[0];
|
||
|
||
return HttpResponse.json({
|
||
trade_date: tradeDateStr,
|
||
lv1_concepts,
|
||
lv2_concepts,
|
||
lv3_concepts,
|
||
update_time: new Date().toISOString()
|
||
});
|
||
}),
|
||
|
||
// 获取指定层级的概念列表
|
||
http.get('/concept-api/hierarchy/:lv1Id', async ({ params, request }) => {
|
||
await delay(300);
|
||
|
||
const { lv1Id } = params;
|
||
const url = new URL(request.url);
|
||
const lv2Id = url.searchParams.get('lv2_id');
|
||
|
||
console.log('[Mock Concept] 获取层级概念列表:', { lv1Id, lv2Id });
|
||
|
||
// 返回该层级下的概念列表
|
||
let concepts = generatePopularConcepts(20);
|
||
|
||
// 添加层级信息
|
||
concepts = concepts.map(c => ({
|
||
...c,
|
||
hierarchy: {
|
||
lv1: '人工智能',
|
||
lv1_id: lv1Id,
|
||
lv2: lv2Id ? 'AI基础设施' : null,
|
||
lv2_id: lv2Id
|
||
}
|
||
}));
|
||
|
||
return HttpResponse.json({
|
||
concepts,
|
||
total: concepts.length,
|
||
lv1_id: lv1Id,
|
||
lv2_id: lv2Id
|
||
});
|
||
}),
|
||
|
||
// 热门概念静态数据文件(HeroPanel 使用)
|
||
http.get('/data/concept/latest.json', async () => {
|
||
await delay(200);
|
||
console.log('[Mock Concept] 获取热门概念静态数据');
|
||
|
||
const concepts = generatePopularConcepts(30);
|
||
|
||
return HttpResponse.json({
|
||
date: new Date().toISOString().split('T')[0],
|
||
results: concepts
|
||
});
|
||
})
|
||
];
|