fix(mock): 修复主线数据不显示问题

- 调整 MSW handler 顺序,确保 /api/events/mainline 在 :eventId 之前匹配
  - 修复 generateDynamicNewsEvents 函数调用参数顺序错误
  - 添加主线事件模板,确保生成的事件能匹配主线关键词
  - 删除重复的 mainline handler 代码
  - 清理调试日志
This commit is contained in:
zdl
2025-12-23 17:34:20 +08:00
parent 145b6575d8
commit 4cae6fe5b6
3 changed files with 324 additions and 246 deletions

View File

@@ -1142,6 +1142,138 @@ function generateTransmissionChain(industry, index) {
return { nodes, edges }; return { nodes, edges };
} }
// 主线事件标题模板 - 确保生成的事件能够匹配主线定义的关键词
// 每个主线定义至少有 2-3 个事件模板,确保数据充足
const mainlineEventTemplates = [
// ==================== TMT (科技/媒体/通信) ====================
// AI基础设施 - 算力/芯片 (L3_AI_CHIP)
{ title: '英伟达发布新一代GPUAI算力大幅提升', keywords: ['算力', 'AI芯片', 'GPU', '英伟达'], industry: '人工智能' },
{ title: '华为昇腾芯片出货量创新高国产AI算力加速', keywords: ['华为昇腾', 'AI芯片', '算力'], industry: '人工智能' },
{ title: '寒武纪发布新一代AI训练芯片性能提升50%', keywords: ['寒武纪', 'AI芯片', '算力'], industry: '人工智能' },
{ title: 'AI芯片需求激增GPU供不应求价格上涨', keywords: ['AI芯片', 'GPU', '算力'], industry: '人工智能' },
// AI基础设施 - 服务器与数据中心 (L3_AI_SERVER)
{ title: '智算中心建设加速,多地启动算力基础设施项目', keywords: ['智算中心', '算力', '数据中心'], industry: '人工智能' },
{ title: '液冷技术成数据中心标配,服务器散热升级', keywords: ['液冷', '数据中心', '服务器'], industry: '人工智能' },
{ title: 'AI服务器订单暴增数据中心扩容需求旺盛', keywords: ['服务器', '数据中心', '智算中心'], industry: '人工智能' },
// AI基础设施 - 光通信 (L3_OPTICAL)
{ title: 'CPO技术迎来突破光模块成本大幅下降', keywords: ['CPO', '光模块', '光通信'], industry: '通信' },
{ title: '800G光模块量产加速AI训练网络升级', keywords: ['光模块', '光通信', '光芯片'], industry: '通信' },
{ title: '光芯片技术突破CPO方案渗透率提升', keywords: ['光芯片', 'CPO', '光通信', '光模块'], industry: '通信' },
// AI基础设施 - PCB与封装 (L3_PCB)
{ title: 'AI PCB需求激增高多层板产能紧张', keywords: ['PCB', 'AI PCB', '封装'], industry: '电子' },
{ title: '先进封装技术突破PCB产业链升级', keywords: ['封装', 'PCB'], industry: '电子' },
// AI应用与大模型 (L3_AI_APP)
{ title: 'DeepSeek发布最新大模型推理能力超越GPT-4', keywords: ['DeepSeek', '大模型', 'AI', '人工智能'], industry: '人工智能' },
{ title: 'KIMI月活突破1亿国产大模型竞争白热化', keywords: ['KIMI', '大模型', 'AI', '人工智能'], industry: '人工智能' },
{ title: 'ChatGPT新版本发布AI Agent智能体能力升级', keywords: ['ChatGPT', '大模型', '智能体', 'AI'], industry: '人工智能' },
{ title: '人工智能+医疗深度融合AI辅助诊断准确率超90%', keywords: ['人工智能', 'AI', '医疗'], industry: '人工智能' },
{ title: '多模态大模型技术突破AI应用场景扩展', keywords: ['大模型', 'AI', '人工智能'], industry: '人工智能' },
// 半导体 - 芯片设计 (L3_CHIP_DESIGN)
{ title: '芯片设计企业扩产IC设计产能大幅提升', keywords: ['芯片设计', 'IC设计', '半导体'], industry: '半导体' },
{ title: '国产芯片设计工具取得突破EDA软件自主可控', keywords: ['芯片设计', 'IC设计', '半导体'], industry: '半导体' },
// 半导体 - 芯片制造 (L3_CHIP_MFG)
{ title: '中芯国际N+1工艺量产芯片制造技术再突破', keywords: ['中芯国际', '芯片制造', '晶圆'], industry: '半导体' },
{ title: '国产光刻机实现技术突破,半导体设备自主可控', keywords: ['光刻', '半导体', '芯片制造'], industry: '半导体' },
{ title: '晶圆产能利用率回升,半导体行业景气度上行', keywords: ['晶圆', '半导体', '芯片制造'], industry: '半导体' },
// 机器人 - 人形机器人 (L3_HUMANOID)
{ title: '特斯拉人形机器人Optimus量产提速成本降至2万美元', keywords: ['人形机器人', '特斯拉机器人', '具身智能'], industry: '人工智能' },
{ title: '具身智能迎来突破,人形机器人商业化加速', keywords: ['具身智能', '人形机器人', '机器人'], industry: '人工智能' },
{ title: '人形机器人产业链爆发,核心零部件需求激增', keywords: ['人形机器人', '具身智能'], industry: '人工智能' },
// 机器人 - 工业机器人 (L3_INDUSTRIAL_ROBOT)
{ title: '工业机器人出货量同比增长30%,自动化渗透率提升', keywords: ['工业机器人', '自动化', '机器人'], industry: '机械' },
{ title: '智能制造升级,机器人自动化需求持续增长', keywords: ['机器人', '自动化', '工业机器人'], industry: '机械' },
// 消费电子 - 智能手机 (L3_MOBILE)
{ title: '华为Mate系列销量火爆折叠屏手机市场爆发', keywords: ['华为', '手机', '折叠屏'], industry: '消费电子' },
{ title: '小米可穿戴设备出货量全球第一,智能手表市场扩张', keywords: ['小米', '可穿戴', '手机'], industry: '消费电子' },
{ title: '手机市场复苏5G手机换机潮来临', keywords: ['手机', '华为', '小米'], industry: '消费电子' },
// 消费电子 - XR与可穿戴 (L3_XR)
{ title: '苹果Vision Pro销量不及预期XR设备面临挑战', keywords: ['苹果', 'Vision Pro', 'XR', 'MR'], industry: '消费电子' },
{ title: 'AR眼镜成新风口VR/AR设备渗透率提升', keywords: ['AR', 'VR', 'XR'], industry: '消费电子' },
{ title: 'Meta发布新一代VR头显XR市场竞争加剧', keywords: ['VR', 'XR', 'MR', '可穿戴'], industry: '消费电子' },
// 通信 - 5G/6G通信 (L3_5G)
{ title: '5G基站建设加速运营商资本开支超预期', keywords: ['5G', '基站', '通信'], industry: '通信' },
{ title: '6G技术标准制定启动下一代通信网络布局', keywords: ['6G', '5G', '通信'], industry: '通信' },
// 通信 - 云计算与软件 (L3_CLOUD)
{ title: '云计算市场规模突破万亿SaaS企业业绩增长', keywords: ['云计算', 'SaaS', '软件', '互联网'], industry: '互联网' },
{ title: '数字化转型加速,企业级软件需求旺盛', keywords: ['数字化', '软件', '互联网'], industry: '互联网' },
{ title: '国产软件替代加速,信创产业迎来发展机遇', keywords: ['软件', '数字化', '互联网'], industry: '互联网' },
// ==================== 新能源与智能汽车 ====================
// 新能源 - 光伏 (L3_PV)
{ title: '光伏装机量创新高,太阳能发电成本持续下降', keywords: ['光伏', '太阳能', '硅片', '组件'], industry: '新能源' },
{ title: '光伏组件价格企稳,行业出清接近尾声', keywords: ['光伏', '组件', '硅片'], industry: '新能源' },
{ title: '分布式光伏爆发,太阳能产业链受益', keywords: ['光伏', '太阳能'], industry: '新能源' },
// 新能源 - 储能与电池 (L3_STORAGE)
{ title: '储能市场爆发式增长,电池需求大幅提升', keywords: ['储能', '电池', '锂电', '新能源'], industry: '新能源' },
{ title: '固态电池技术突破,新能源汽车续航大幅提升', keywords: ['固态电池', '电池', '锂电', '新能源'], industry: '新能源' },
{ title: '钠电池产业化加速,储能成本有望大幅下降', keywords: ['电池', '储能', '新能源'], industry: '新能源' },
// 智能汽车 - 新能源整车 (L3_EV_OEM)
{ title: '比亚迪月销量突破50万辆新能源汽车市占率第一', keywords: ['比亚迪', '新能源汽车', '电动车'], industry: '新能源' },
{ title: '新能源整车出口创新高,中国汽车品牌走向全球', keywords: ['新能源汽车', '整车', '电动车'], industry: '新能源' },
{ title: '电动车价格战持续新能源汽车渗透率突破50%', keywords: ['电动车', '新能源汽车', '整车'], industry: '新能源' },
// 智能汽车 - 智能驾驶 (L3_AUTO_DRIVE)
{ title: '特斯拉FSD自动驾驶进入中国市场智能驾驶加速', keywords: ['特斯拉', '自动驾驶', '智能驾驶', '智能网联'], industry: '新能源' },
{ title: '车路协同试点扩大,智能网联汽车基建提速', keywords: ['车路协同', '智能网联', '智能驾驶'], industry: '新能源' },
{ title: 'L3级自动驾驶获批智能驾驶产业化加速', keywords: ['自动驾驶', '智能驾驶', '智能网联'], industry: '新能源' },
// ==================== 先进制造 ====================
// 低空经济 - 无人机 (L3_DRONE)
{ title: '低空经济政策密集出台,无人机产业迎来风口', keywords: ['低空', '无人机', '空域'], industry: '航空' },
{ title: '无人机应用场景拓展,低空经济市场规模扩大', keywords: ['无人机', '低空', '空域'], industry: '航空' },
// 低空经济 - eVTOL (L3_EVTOL)
{ title: 'eVTOL飞行汽车完成首飞空中出租车商业化在即', keywords: ['eVTOL', '飞行汽车', '空中出租车'], industry: '航空' },
{ title: '飞行汽车获适航认证eVTOL商业运营启动', keywords: ['飞行汽车', 'eVTOL', '空中出租车'], industry: '航空' },
// 军工 - 航空航天 (L3_AEROSPACE)
{ title: '商业航天发射成功,卫星互联网建设加速', keywords: ['航天', '卫星', '火箭'], industry: '军工' },
{ title: '航空发动机国产化取得突破,航空产业链升级', keywords: ['航空', '军工'], industry: '军工' },
{ title: '卫星通信需求爆发,航天发射频次创新高', keywords: ['卫星', '航天', '火箭'], industry: '军工' },
// 军工 - 国防军工 (L3_DEFENSE)
{ title: '军工订单饱满,国防装备现代化提速', keywords: ['军工', '国防', '军工装备'], industry: '军工' },
{ title: '国防预算增长,军工装备需求持续提升', keywords: ['国防', '军工', '军工装备', '导弹'], industry: '军工' },
// ==================== 医药健康 ====================
// 医药 - 创新药 (L3_DRUG)
{ title: '创新药获批上市,医药板块迎来业绩兑现', keywords: ['创新药', '医药', '生物'], industry: '医药' },
{ title: 'CXO订单持续增长医药研发外包景气度高', keywords: ['CXO', '医药', '生物'], industry: '医药' },
{ title: '生物医药融资回暖,创新药研发管线丰富', keywords: ['医药', '生物', '创新药'], industry: '医药' },
// 医药 - 医疗器械 (L3_DEVICE)
{ title: '医疗器械集采扩面,国产替代加速', keywords: ['医疗器械', '医疗', '器械'], industry: '医药' },
{ title: '高端医疗设备国产化突破,医疗器械出口增长', keywords: ['医疗器械', '医疗', '器械'], industry: '医药' },
// ==================== 金融 ====================
// 金融 - 银行 (L3_BANK)
{ title: '银行净息差企稳,金融板块估值修复', keywords: ['银行', '金融'], industry: '金融' },
{ title: '银行业绩超预期,金融股迎来价值重估', keywords: ['银行', '金融'], industry: '金融' },
// 金融 - 券商 (L3_BROKER)
{ title: '券商业绩大幅增长,证券板块活跃', keywords: ['券商', '证券', '金融'], industry: '金融' },
{ title: 'A股成交量放大证券行业业绩弹性显现', keywords: ['证券', '券商', '金融'], industry: '金融' },
];
/** /**
* 生成动态新闻事件(实时要闻·动态追踪专用) * 生成动态新闻事件(实时要闻·动态追踪专用)
* @param {Object} timeRange - 时间范围 { startTime, endTime } * @param {Object} timeRange - 时间范围 { startTime, endTime }
@@ -1166,7 +1298,10 @@ export function generateDynamicNewsEvents(timeRange = null, count = 30) {
const timeSpan = endTime.getTime() - startTime.getTime(); const timeSpan = endTime.getTime() - startTime.getTime();
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
const industry = industries[i % industries.length]; // 使用主线事件模板生成事件,确保能匹配主线关键词
const templateIndex = i % mainlineEventTemplates.length;
const template = mainlineEventTemplates[templateIndex];
const industry = template.industry;
const imp = importanceLevels[i % importanceLevels.length]; const imp = importanceLevels[i % importanceLevels.length];
const eventType = eventTypes[i % eventTypes.length]; const eventType = eventTypes[i % eventTypes.length];
@@ -1217,11 +1352,33 @@ export function generateDynamicNewsEvents(timeRange = null, count = 30) {
} }
} }
// 使用模板标题,并生成包含模板关键词的 keywords 数组
const eventTitle = template.title;
const eventDescription = generateEventDescription(industry, imp, i);
// 生成关键词对象数组,包含模板中的关键词
const templateKeywords = template.keywords.map((kw, idx) => ({
concept: kw,
stock_count: 10 + Math.floor(Math.random() * 20),
score: parseFloat((0.7 + Math.random() * 0.25).toFixed(2)),
description: `${kw}相关概念,市场关注度较高`,
price_info: {
avg_change_pct: parseFloat((Math.random() * 15 - 5).toFixed(2))
},
match_type: ['hybrid_knn', 'keyword', 'semantic'][idx % 3]
}));
// 合并模板关键词和行业关键词
const mergedKeywords = [
...templateKeywords,
...generateKeywords(industry, i).slice(0, 2)
];
events.push({ events.push({
id: `dynamic_${i + 1}`, id: `dynamic_${i + 1}`,
title: generateEventTitle(industry, i), title: eventTitle,
description: generateEventDescription(industry, imp, i), description: eventDescription,
content: generateEventDescription(industry, imp, i), content: eventDescription,
event_type: eventType, event_type: eventType,
importance: imp, importance: imp,
status: 'published', status: 'published',
@@ -1234,7 +1391,9 @@ export function generateDynamicNewsEvents(timeRange = null, count = 30) {
related_avg_chg: parseFloat(relatedAvgChg), related_avg_chg: parseFloat(relatedAvgChg),
related_max_chg: parseFloat(relatedMaxChg), related_max_chg: parseFloat(relatedMaxChg),
related_week_chg: parseFloat(relatedWeekChg), related_week_chg: parseFloat(relatedWeekChg),
keywords: generateKeywords(industry, i), expectation_surprise_score: Math.floor(Math.random() * 60) + 30, // 30-90 超预期得分
keywords: mergedKeywords,
related_concepts: mergedKeywords, // 添加 related_concepts 字段,兼容主线匹配逻辑
is_ai_generated: i % 3 === 0, // 33% 的事件是AI生成 is_ai_generated: i % 3 === 0, // 33% 的事件是AI生成
industry: industry, industry: industry,
related_stocks: relatedStocks, related_stocks: relatedStocks,

View File

@@ -257,6 +257,159 @@ export const eventHandlers = [
} }
}), }),
// ==================== 主线模式相关(必须在 :eventId 之前,否则会被通配符匹配)====================
// 获取按主线lv1/lv2/lv3概念分组的事件列表
http.get('/api/events/mainline', async ({ request }) => {
await delay(500);
const url = new URL(request.url);
const recentDays = parseInt(url.searchParams.get('recent_days') || '7', 10);
const importance = url.searchParams.get('importance') || 'all';
const limitPerMainline = parseInt(url.searchParams.get('limit') || '20', 10);
const groupBy = url.searchParams.get('group_by') || 'lv2';
try {
// 生成 mock 事件数据 - 第一个参数是 timeRangenull 表示默认24小时第二个参数是 count
const allEvents = generateDynamicNewsEvents(null, 100);
const mainlineDefinitions = [
{ lv3_id: 'L3_AI_CHIP', lv3_name: 'AI芯片与算力', lv2_id: 'L2_AI_INFRA', lv2_name: 'AI基础设施 (算力/CPO/PCB)', lv1_id: 'L1_TMT', lv1_name: 'TMT (科技/媒体/通信)', keywords: ['算力', 'AI芯片', 'GPU', '英伟达', '华为昇腾', '寒武纪'] },
{ lv3_id: 'L3_AI_SERVER', lv3_name: '服务器与数据中心', lv2_id: 'L2_AI_INFRA', lv2_name: 'AI基础设施 (算力/CPO/PCB)', lv1_id: 'L1_TMT', lv1_name: 'TMT (科技/媒体/通信)', keywords: ['服务器', '数据中心', '智算中心', '液冷'] },
{ lv3_id: 'L3_OPTICAL', lv3_name: '光通信与CPO', lv2_id: 'L2_AI_INFRA', lv2_name: 'AI基础设施 (算力/CPO/PCB)', lv1_id: 'L1_TMT', lv1_name: 'TMT (科技/媒体/通信)', keywords: ['CPO', '光通信', '光模块', '光芯片'] },
{ lv3_id: 'L3_PCB', lv3_name: 'PCB与封装', lv2_id: 'L2_AI_INFRA', lv2_name: 'AI基础设施 (算力/CPO/PCB)', lv1_id: 'L1_TMT', lv1_name: 'TMT (科技/媒体/通信)', keywords: ['PCB', '封装', 'AI PCB'] },
{ lv3_id: 'L3_AI_APP', lv3_name: 'AI应用与大模型', lv2_id: 'L2_AI_INFRA', lv2_name: 'AI基础设施 (算力/CPO/PCB)', lv1_id: 'L1_TMT', lv1_name: 'TMT (科技/媒体/通信)', keywords: ['大模型', '智能体', 'AI', '人工智能', 'DeepSeek', 'KIMI', 'ChatGPT'] },
{ lv3_id: 'L3_CHIP_DESIGN', lv3_name: '芯片设计', lv2_id: 'L2_SEMICONDUCTOR', lv2_name: '半导体 (设计/制造/封测)', lv1_id: 'L1_TMT', lv1_name: 'TMT (科技/媒体/通信)', keywords: ['芯片设计', '半导体', 'IC设计'] },
{ lv3_id: 'L3_CHIP_MFG', lv3_name: '芯片制造', lv2_id: 'L2_SEMICONDUCTOR', lv2_name: '半导体 (设计/制造/封测)', lv1_id: 'L1_TMT', lv1_name: 'TMT (科技/媒体/通信)', keywords: ['晶圆', '光刻', '芯片制造', '中芯国际'] },
{ lv3_id: 'L3_HUMANOID', lv3_name: '人形机器人', lv2_id: 'L2_ROBOT', lv2_name: '机器人 (人形机器人/工业机器人)', lv1_id: 'L1_TMT', lv1_name: 'TMT (科技/媒体/通信)', keywords: ['人形机器人', '具身智能', '特斯拉机器人'] },
{ lv3_id: 'L3_INDUSTRIAL_ROBOT', lv3_name: '工业机器人', lv2_id: 'L2_ROBOT', lv2_name: '机器人 (人形机器人/工业机器人)', lv1_id: 'L1_TMT', lv1_name: 'TMT (科技/媒体/通信)', keywords: ['工业机器人', '自动化', '机器人'] },
{ lv3_id: 'L3_MOBILE', lv3_name: '智能手机', lv2_id: 'L2_CONSUMER_ELEC', lv2_name: '消费电子 (手机/XR/可穿戴)', lv1_id: 'L1_TMT', lv1_name: 'TMT (科技/媒体/通信)', keywords: ['手机', '华为', '苹果', '小米', '折叠屏'] },
{ lv3_id: 'L3_XR', lv3_name: 'XR与可穿戴', lv2_id: 'L2_CONSUMER_ELEC', lv2_name: '消费电子 (手机/XR/可穿戴)', lv1_id: 'L1_TMT', lv1_name: 'TMT (科技/媒体/通信)', keywords: ['XR', 'VR', 'AR', '可穿戴', 'MR', 'Vision Pro'] },
{ lv3_id: 'L3_5G', lv3_name: '5G/6G通信', lv2_id: 'L2_TELECOM', lv2_name: '通信、互联网与软件', lv1_id: 'L1_TMT', lv1_name: 'TMT (科技/媒体/通信)', keywords: ['5G', '6G', '通信', '基站'] },
{ lv3_id: 'L3_CLOUD', lv3_name: '云计算与软件', lv2_id: 'L2_TELECOM', lv2_name: '通信、互联网与软件', lv1_id: 'L1_TMT', lv1_name: 'TMT (科技/媒体/通信)', keywords: ['云计算', '软件', 'SaaS', '互联网', '数字化'] },
{ lv3_id: 'L3_PV', lv3_name: '光伏', lv2_id: 'L2_NEW_ENERGY', lv2_name: '新能源 (光伏/储能/电池)', lv1_id: 'L1_NEW_ENERGY_ENV', lv1_name: '新能源与智能汽车', keywords: ['光伏', '太阳能', '硅片', '组件'] },
{ lv3_id: 'L3_STORAGE', lv3_name: '储能与电池', lv2_id: 'L2_NEW_ENERGY', lv2_name: '新能源 (光伏/储能/电池)', lv1_id: 'L1_NEW_ENERGY_ENV', lv1_name: '新能源与智能汽车', keywords: ['储能', '电池', '锂电', '固态电池', '新能源'] },
{ lv3_id: 'L3_EV_OEM', lv3_name: '新能源整车', lv2_id: 'L2_EV', lv2_name: '智能网联汽车', lv1_id: 'L1_NEW_ENERGY_ENV', lv1_name: '新能源与智能汽车', keywords: ['新能源汽车', '电动车', '比亚迪', '特斯拉', '整车'] },
{ lv3_id: 'L3_AUTO_DRIVE', lv3_name: '智能驾驶', lv2_id: 'L2_EV', lv2_name: '智能网联汽车', lv1_id: 'L1_NEW_ENERGY_ENV', lv1_name: '新能源与智能汽车', keywords: ['智能驾驶', '自动驾驶', '智能网联', '车路协同'] },
{ lv3_id: 'L3_DRONE', lv3_name: '无人机', lv2_id: 'L2_LOW_ALTITUDE', lv2_name: '低空经济 (无人机/eVTOL)', lv1_id: 'L1_ADVANCED_MFG', lv1_name: '先进制造', keywords: ['无人机', '低空', '空域'] },
{ lv3_id: 'L3_EVTOL', lv3_name: 'eVTOL', lv2_id: 'L2_LOW_ALTITUDE', lv2_name: '低空经济 (无人机/eVTOL)', lv1_id: 'L1_ADVANCED_MFG', lv1_name: '先进制造', keywords: ['eVTOL', '飞行汽车', '空中出租车'] },
{ lv3_id: 'L3_AEROSPACE', lv3_name: '航空航天', lv2_id: 'L2_MILITARY', lv2_name: '军工 (航空航天/国防)', lv1_id: 'L1_ADVANCED_MFG', lv1_name: '先进制造', keywords: ['航空', '航天', '卫星', '火箭', '军工'] },
{ lv3_id: 'L3_DEFENSE', lv3_name: '国防军工', lv2_id: 'L2_MILITARY', lv2_name: '军工 (航空航天/国防)', lv1_id: 'L1_ADVANCED_MFG', lv1_name: '先进制造', keywords: ['国防', '导弹', '军工装备'] },
{ lv3_id: 'L3_DRUG', lv3_name: '创新药', lv2_id: 'L2_PHARMA', lv2_name: '医药医疗 (创新药/器械)', lv1_id: 'L1_PHARMA', lv1_name: '医药健康', keywords: ['创新药', '医药', '生物', 'CXO'] },
{ lv3_id: 'L3_DEVICE', lv3_name: '医疗器械', lv2_id: 'L2_PHARMA', lv2_name: '医药医疗 (创新药/器械)', lv1_id: 'L1_PHARMA', lv1_name: '医药健康', keywords: ['医疗器械', '医疗', '器械'] },
{ lv3_id: 'L3_BANK', lv3_name: '银行', lv2_id: 'L2_FINANCE', lv2_name: '金融 (银行/券商/保险)', lv1_id: 'L1_FINANCE', lv1_name: '金融', keywords: ['银行', '金融'] },
{ lv3_id: 'L3_BROKER', lv3_name: '券商', lv2_id: 'L2_FINANCE', lv2_name: '金融 (银行/券商/保险)', lv1_id: 'L1_FINANCE', lv1_name: '金融', keywords: ['券商', '证券'] },
];
const hierarchyOptions = {
lv1: [...new Map(mainlineDefinitions.map(m => [m.lv1_id, { id: m.lv1_id, name: m.lv1_name }])).values()],
lv2: [...new Map(mainlineDefinitions.map(m => [m.lv2_id, { id: m.lv2_id, name: m.lv2_name, lv1_id: m.lv1_id, lv1_name: m.lv1_name }])).values()],
lv3: mainlineDefinitions.map(m => ({ id: m.lv3_id, name: m.lv3_name, lv2_id: m.lv2_id, lv2_name: m.lv2_name, lv1_id: m.lv1_id, lv1_name: m.lv1_name })),
};
const mainlineGroups = {};
const isSpecificId = groupBy.startsWith('L1_') || groupBy.startsWith('L2_') || groupBy.startsWith('L3_');
allEvents.forEach(event => {
const keywords = event.keywords || event.related_concepts || [];
const conceptNames = keywords.map(k => {
if (typeof k === 'string') return k;
if (typeof k === 'object' && k !== null) return k.concept || k.name || '';
return '';
}).filter(Boolean).join(' ');
const titleAndDesc = `${event.title || ''} ${event.description || ''}`;
const textToMatch = `${conceptNames} ${titleAndDesc}`.toLowerCase();
mainlineDefinitions.forEach(mainline => {
const matched = mainline.keywords.some(kw => textToMatch.includes(kw.toLowerCase()));
if (matched) {
let groupKey, groupData;
if (isSpecificId) {
if (groupBy.startsWith('L1_') && mainline.lv1_id === groupBy) {
groupKey = mainline.lv2_id;
groupData = { group_id: mainline.lv2_id, group_name: mainline.lv2_name, parent_name: mainline.lv1_name, events: [] };
} else if (groupBy.startsWith('L2_') && mainline.lv2_id === groupBy) {
groupKey = mainline.lv3_id;
groupData = { group_id: mainline.lv3_id, group_name: mainline.lv3_name, parent_name: mainline.lv2_name, grandparent_name: mainline.lv1_name, events: [] };
} else if (groupBy.startsWith('L3_') && mainline.lv3_id === groupBy) {
groupKey = mainline.lv3_id;
groupData = { group_id: mainline.lv3_id, group_name: mainline.lv3_name, parent_name: mainline.lv2_name, grandparent_name: mainline.lv1_name, events: [] };
} else {
return;
}
} else if (groupBy === 'lv1') {
groupKey = mainline.lv1_id;
groupData = { group_id: mainline.lv1_id, group_name: mainline.lv1_name, events: [] };
} else if (groupBy === 'lv3') {
groupKey = mainline.lv3_id;
groupData = { group_id: mainline.lv3_id, group_name: mainline.lv3_name, parent_name: mainline.lv2_name, grandparent_name: mainline.lv1_name, events: [] };
} else {
groupKey = mainline.lv2_id;
groupData = { group_id: mainline.lv2_id, group_name: mainline.lv2_name, parent_name: mainline.lv1_name, events: [] };
}
if (!mainlineGroups[groupKey]) {
mainlineGroups[groupKey] = groupData;
}
if (!mainlineGroups[groupKey].events.find(e => e.id === event.id)) {
mainlineGroups[groupKey].events.push(event);
}
}
});
});
const generatePriceData = () => parseFloat((Math.random() * 13 - 5).toFixed(2));
const priceDataMap = { lv1: {}, lv2: {}, lv3: {} };
mainlineDefinitions.forEach(def => {
if (!priceDataMap.lv1[def.lv1_name]) priceDataMap.lv1[def.lv1_name] = generatePriceData();
if (!priceDataMap.lv2[def.lv2_name]) priceDataMap.lv2[def.lv2_name] = generatePriceData();
if (!priceDataMap.lv3[def.lv3_name]) priceDataMap.lv3[def.lv3_name] = generatePriceData();
});
const mainlines = Object.values(mainlineGroups)
.map(group => {
let avgChangePct = null;
if (groupBy === 'lv1' || groupBy.startsWith('L1_')) {
avgChangePct = groupBy.startsWith('L1_') ? priceDataMap.lv2[group.group_name] : priceDataMap.lv1[group.group_name];
} else if (groupBy === 'lv3' || groupBy.startsWith('L2_')) {
avgChangePct = priceDataMap.lv3[group.group_name];
} else {
avgChangePct = priceDataMap.lv2[group.group_name];
}
return {
...group,
events: group.events.slice(0, limitPerMainline),
event_count: Math.min(group.events.length, limitPerMainline),
avg_change_pct: avgChangePct ?? null,
price_date: new Date().toISOString().split('T')[0]
};
})
.filter(group => group.event_count > 0)
.sort((a, b) => b.event_count - a.event_count);
const groupedEventIds = new Set();
mainlines.forEach(m => m.events.forEach(e => groupedEventIds.add(e.id)));
const ungroupedCount = allEvents.filter(e => !groupedEventIds.has(e.id)).length;
return HttpResponse.json({
success: true,
data: {
mainlines,
total_events: allEvents.length,
mainline_count: mainlines.length,
ungrouped_count: ungroupedCount,
group_by: groupBy,
hierarchy_options: hierarchyOptions,
}
});
} catch (error) {
console.error('[Mock Event] 主线数据获取失败:', error);
return HttpResponse.json({ success: false, error: error.message || '获取主线数据失败' }, { status: 500 });
}
}),
// ==================== 事件详情相关 ==================== // ==================== 事件详情相关 ====================
// 获取事件详情 // 获取事件详情
@@ -1585,234 +1738,4 @@ export const eventHandlers = [
); );
} }
}), }),
// ==================== 主线模式相关 ====================
// 获取按主线lv1/lv2/lv3概念分组的事件列表
http.get('/api/events/mainline', async ({ request }) => {
await delay(500);
const url = new URL(request.url);
const recentDays = parseInt(url.searchParams.get('recent_days') || '7', 10);
const importance = url.searchParams.get('importance') || 'all';
const limitPerMainline = parseInt(url.searchParams.get('limit') || '20', 10);
// 分组方式: 'lv1' | 'lv2' | 'lv3' | 具体的概念ID如 'L1_TMT', 'L2_AI_INFRA', 'L3_AI_CHIP'
const groupBy = url.searchParams.get('group_by') || 'lv2';
console.log('[Mock Event] 获取主线数据:', { recentDays, importance, limitPerMainline, groupBy });
try {
// 生成 mock 事件数据
const allEvents = generateDynamicNewsEvents(100);
// 定义完整的 lv1 -> lv2 -> lv3 层级结构
const mainlineDefinitions = [
{ lv3_id: 'L3_AI_CHIP', lv3_name: 'AI芯片与算力', lv2_id: 'L2_AI_INFRA', lv2_name: 'AI基础设施 (算力/CPO/PCB)', lv1_id: 'L1_TMT', lv1_name: 'TMT (科技/媒体/通信)', keywords: ['算力', 'AI芯片', 'GPU', '英伟达', '华为昇腾', '寒武纪'] },
{ lv3_id: 'L3_AI_SERVER', lv3_name: '服务器与数据中心', lv2_id: 'L2_AI_INFRA', lv2_name: 'AI基础设施 (算力/CPO/PCB)', lv1_id: 'L1_TMT', lv1_name: 'TMT (科技/媒体/通信)', keywords: ['服务器', '数据中心', '智算中心', '液冷'] },
{ lv3_id: 'L3_OPTICAL', lv3_name: '光通信与CPO', lv2_id: 'L2_AI_INFRA', lv2_name: 'AI基础设施 (算力/CPO/PCB)', lv1_id: 'L1_TMT', lv1_name: 'TMT (科技/媒体/通信)', keywords: ['CPO', '光通信', '光模块', '光芯片'] },
{ lv3_id: 'L3_PCB', lv3_name: 'PCB与封装', lv2_id: 'L2_AI_INFRA', lv2_name: 'AI基础设施 (算力/CPO/PCB)', lv1_id: 'L1_TMT', lv1_name: 'TMT (科技/媒体/通信)', keywords: ['PCB', '封装', 'AI PCB'] },
{ lv3_id: 'L3_AI_APP', lv3_name: 'AI应用与大模型', lv2_id: 'L2_AI_INFRA', lv2_name: 'AI基础设施 (算力/CPO/PCB)', lv1_id: 'L1_TMT', lv1_name: 'TMT (科技/媒体/通信)', keywords: ['大模型', '智能体', 'AI', '人工智能', 'DeepSeek', 'KIMI', 'ChatGPT'] },
{ lv3_id: 'L3_CHIP_DESIGN', lv3_name: '芯片设计', lv2_id: 'L2_SEMICONDUCTOR', lv2_name: '半导体 (设计/制造/封测)', lv1_id: 'L1_TMT', lv1_name: 'TMT (科技/媒体/通信)', keywords: ['芯片设计', '半导体', 'IC设计'] },
{ lv3_id: 'L3_CHIP_MFG', lv3_name: '芯片制造', lv2_id: 'L2_SEMICONDUCTOR', lv2_name: '半导体 (设计/制造/封测)', lv1_id: 'L1_TMT', lv1_name: 'TMT (科技/媒体/通信)', keywords: ['晶圆', '光刻', '芯片制造', '中芯国际'] },
{ lv3_id: 'L3_HUMANOID', lv3_name: '人形机器人', lv2_id: 'L2_ROBOT', lv2_name: '机器人 (人形机器人/工业机器人)', lv1_id: 'L1_TMT', lv1_name: 'TMT (科技/媒体/通信)', keywords: ['人形机器人', '具身智能', '特斯拉机器人'] },
{ lv3_id: 'L3_INDUSTRIAL_ROBOT', lv3_name: '工业机器人', lv2_id: 'L2_ROBOT', lv2_name: '机器人 (人形机器人/工业机器人)', lv1_id: 'L1_TMT', lv1_name: 'TMT (科技/媒体/通信)', keywords: ['工业机器人', '自动化', '机器人'] },
{ lv3_id: 'L3_MOBILE', lv3_name: '智能手机', lv2_id: 'L2_CONSUMER_ELEC', lv2_name: '消费电子 (手机/XR/可穿戴)', lv1_id: 'L1_TMT', lv1_name: 'TMT (科技/媒体/通信)', keywords: ['手机', '华为', '苹果', '小米', '折叠屏'] },
{ lv3_id: 'L3_XR', lv3_name: 'XR与可穿戴', lv2_id: 'L2_CONSUMER_ELEC', lv2_name: '消费电子 (手机/XR/可穿戴)', lv1_id: 'L1_TMT', lv1_name: 'TMT (科技/媒体/通信)', keywords: ['XR', 'VR', 'AR', '可穿戴', 'MR', 'Vision Pro'] },
{ lv3_id: 'L3_5G', lv3_name: '5G/6G通信', lv2_id: 'L2_TELECOM', lv2_name: '通信、互联网与软件', lv1_id: 'L1_TMT', lv1_name: 'TMT (科技/媒体/通信)', keywords: ['5G', '6G', '通信', '基站'] },
{ lv3_id: 'L3_CLOUD', lv3_name: '云计算与软件', lv2_id: 'L2_TELECOM', lv2_name: '通信、互联网与软件', lv1_id: 'L1_TMT', lv1_name: 'TMT (科技/媒体/通信)', keywords: ['云计算', '软件', 'SaaS', '互联网', '数字化'] },
{ lv3_id: 'L3_PV', lv3_name: '光伏', lv2_id: 'L2_NEW_ENERGY', lv2_name: '新能源 (光伏/储能/电池)', lv1_id: 'L1_NEW_ENERGY_ENV', lv1_name: '新能源与智能汽车', keywords: ['光伏', '太阳能', '硅片', '组件'] },
{ lv3_id: 'L3_STORAGE', lv3_name: '储能与电池', lv2_id: 'L2_NEW_ENERGY', lv2_name: '新能源 (光伏/储能/电池)', lv1_id: 'L1_NEW_ENERGY_ENV', lv1_name: '新能源与智能汽车', keywords: ['储能', '电池', '锂电', '固态电池', '新能源'] },
{ lv3_id: 'L3_EV_OEM', lv3_name: '新能源整车', lv2_id: 'L2_EV', lv2_name: '智能网联汽车', lv1_id: 'L1_NEW_ENERGY_ENV', lv1_name: '新能源与智能汽车', keywords: ['新能源汽车', '电动车', '比亚迪', '特斯拉', '整车'] },
{ lv3_id: 'L3_AUTO_DRIVE', lv3_name: '智能驾驶', lv2_id: 'L2_EV', lv2_name: '智能网联汽车', lv1_id: 'L1_NEW_ENERGY_ENV', lv1_name: '新能源与智能汽车', keywords: ['智能驾驶', '自动驾驶', '智能网联', '车路协同'] },
{ lv3_id: 'L3_DRONE', lv3_name: '无人机', lv2_id: 'L2_LOW_ALTITUDE', lv2_name: '低空经济 (无人机/eVTOL)', lv1_id: 'L1_ADVANCED_MFG', lv1_name: '先进制造', keywords: ['无人机', '低空', '空域'] },
{ lv3_id: 'L3_EVTOL', lv3_name: 'eVTOL', lv2_id: 'L2_LOW_ALTITUDE', lv2_name: '低空经济 (无人机/eVTOL)', lv1_id: 'L1_ADVANCED_MFG', lv1_name: '先进制造', keywords: ['eVTOL', '飞行汽车', '空中出租车'] },
{ lv3_id: 'L3_AEROSPACE', lv3_name: '航空航天', lv2_id: 'L2_MILITARY', lv2_name: '军工 (航空航天/国防)', lv1_id: 'L1_ADVANCED_MFG', lv1_name: '先进制造', keywords: ['航空', '航天', '卫星', '火箭', '军工'] },
{ lv3_id: 'L3_DEFENSE', lv3_name: '国防军工', lv2_id: 'L2_MILITARY', lv2_name: '军工 (航空航天/国防)', lv1_id: 'L1_ADVANCED_MFG', lv1_name: '先进制造', keywords: ['国防', '导弹', '军工装备'] },
{ lv3_id: 'L3_DRUG', lv3_name: '创新药', lv2_id: 'L2_PHARMA', lv2_name: '医药医疗 (创新药/器械)', lv1_id: 'L1_PHARMA', lv1_name: '医药健康', keywords: ['创新药', '医药', '生物', 'CXO'] },
{ lv3_id: 'L3_DEVICE', lv3_name: '医疗器械', lv2_id: 'L2_PHARMA', lv2_name: '医药医疗 (创新药/器械)', lv1_id: 'L1_PHARMA', lv1_name: '医药健康', keywords: ['医疗器械', '医疗', '器械'] },
{ lv3_id: 'L3_BANK', lv3_name: '银行', lv2_id: 'L2_FINANCE', lv2_name: '金融 (银行/券商/保险)', lv1_id: 'L1_FINANCE', lv1_name: '金融', keywords: ['银行', '金融'] },
{ lv3_id: 'L3_BROKER', lv3_name: '券商', lv2_id: 'L2_FINANCE', lv2_name: '金融 (银行/券商/保险)', lv1_id: 'L1_FINANCE', lv1_name: '金融', keywords: ['券商', '证券'] },
];
// 生成层级选项供前端下拉框使用
const hierarchyOptions = {
lv1: [...new Map(mainlineDefinitions.map(m => [m.lv1_id, { id: m.lv1_id, name: m.lv1_name }])).values()],
lv2: [...new Map(mainlineDefinitions.map(m => [m.lv2_id, { id: m.lv2_id, name: m.lv2_name, lv1_id: m.lv1_id, lv1_name: m.lv1_name }])).values()],
lv3: mainlineDefinitions.map(m => ({ id: m.lv3_id, name: m.lv3_name, lv2_id: m.lv2_id, lv2_name: m.lv2_name, lv1_id: m.lv1_id, lv1_name: m.lv1_name })),
};
// 按主线分组事件
const mainlineGroups = {};
// 判断分组方式
const isSpecificId = groupBy.startsWith('L1_') || groupBy.startsWith('L2_') || groupBy.startsWith('L3_');
allEvents.forEach(event => {
const keywords = event.keywords || event.related_concepts || [];
const conceptNames = keywords.map(k => k.concept || k.name || k).join(' ');
const titleAndDesc = `${event.title || ''} ${event.description || ''}`;
const textToMatch = `${conceptNames} ${titleAndDesc}`.toLowerCase();
mainlineDefinitions.forEach(mainline => {
const matched = mainline.keywords.some(kw => textToMatch.includes(kw.toLowerCase()));
if (matched) {
let groupKey, groupData;
if (isSpecificId) {
// 筛选特定概念ID
if (groupBy.startsWith('L1_') && mainline.lv1_id === groupBy) {
groupKey = mainline.lv2_id;
groupData = {
group_id: mainline.lv2_id,
group_name: mainline.lv2_name,
parent_name: mainline.lv1_name,
events: []
};
} else if (groupBy.startsWith('L2_') && mainline.lv2_id === groupBy) {
groupKey = mainline.lv3_id;
groupData = {
group_id: mainline.lv3_id,
group_name: mainline.lv3_name,
parent_name: mainline.lv2_name,
grandparent_name: mainline.lv1_name,
events: []
};
} else if (groupBy.startsWith('L3_') && mainline.lv3_id === groupBy) {
groupKey = mainline.lv3_id;
groupData = {
group_id: mainline.lv3_id,
group_name: mainline.lv3_name,
parent_name: mainline.lv2_name,
grandparent_name: mainline.lv1_name,
events: []
};
} else {
return;
}
} else if (groupBy === 'lv1') {
groupKey = mainline.lv1_id;
groupData = {
group_id: mainline.lv1_id,
group_name: mainline.lv1_name,
events: []
};
} else if (groupBy === 'lv3') {
groupKey = mainline.lv3_id;
groupData = {
group_id: mainline.lv3_id,
group_name: mainline.lv3_name,
parent_name: mainline.lv2_name,
grandparent_name: mainline.lv1_name,
events: []
};
} else {
// 默认 lv2
groupKey = mainline.lv2_id;
groupData = {
group_id: mainline.lv2_id,
group_name: mainline.lv2_name,
parent_name: mainline.lv1_name,
events: []
};
}
if (!mainlineGroups[groupKey]) {
mainlineGroups[groupKey] = groupData;
}
if (!mainlineGroups[groupKey].events.find(e => e.id === event.id)) {
mainlineGroups[groupKey].events.push(event);
}
}
});
});
// 生成各层级的模拟涨跌幅数据
const generatePriceData = () => {
// 生成 -5% 到 +8% 之间的随机涨跌幅
return parseFloat((Math.random() * 13 - 5).toFixed(2));
};
// 为各层级生成涨跌幅 Map
const priceDataMap = {
lv1: {},
lv2: {},
lv3: {}
};
// 为每个层级生成涨跌幅
mainlineDefinitions.forEach(def => {
if (!priceDataMap.lv1[def.lv1_name]) {
priceDataMap.lv1[def.lv1_name] = generatePriceData();
}
if (!priceDataMap.lv2[def.lv2_name]) {
priceDataMap.lv2[def.lv2_name] = generatePriceData();
}
if (!priceDataMap.lv3[def.lv3_name]) {
priceDataMap.lv3[def.lv3_name] = generatePriceData();
}
});
const mainlines = Object.values(mainlineGroups)
.map(group => {
// 根据分组层级获取对应的涨跌幅
let avgChangePct = null;
if (groupBy === 'lv1' || groupBy.startsWith('L1_')) {
// lv1 分组,使用 lv1 涨跌幅(如果是 L1_ 开头,则显示的是 lv2 分组,使用 lv2 涨跌幅)
if (groupBy.startsWith('L1_')) {
avgChangePct = priceDataMap.lv2[group.group_name] ?? null;
} else {
avgChangePct = priceDataMap.lv1[group.group_name] ?? null;
}
} else if (groupBy === 'lv3' || groupBy.startsWith('L2_')) {
// lv3 分组,使用 lv3 涨跌幅
avgChangePct = priceDataMap.lv3[group.group_name] ?? null;
} else {
// lv2 分组(默认),使用 lv2 涨跌幅
avgChangePct = priceDataMap.lv2[group.group_name] ?? null;
}
return {
...group,
events: group.events.slice(0, limitPerMainline),
event_count: Math.min(group.events.length, limitPerMainline),
avg_change_pct: avgChangePct,
price_date: new Date().toISOString().split('T')[0]
};
})
.filter(group => group.event_count > 0)
.sort((a, b) => b.event_count - a.event_count);
const groupedEventIds = new Set();
mainlines.forEach(m => m.events.forEach(e => groupedEventIds.add(e.id)));
const ungroupedCount = allEvents.filter(e => !groupedEventIds.has(e.id)).length;
console.log('[Mock Event] 主线数据生成完成:', {
mainlineCount: mainlines.length,
totalEvents: allEvents.length,
ungroupedCount,
groupBy
});
return HttpResponse.json({
success: true,
data: {
mainlines,
total_events: allEvents.length,
mainline_count: mainlines.length,
ungrouped_count: ungroupedCount,
group_by: groupBy,
hierarchy_options: hierarchyOptions,
}
});
} catch (error) {
console.error('[Mock Event] 主线数据获取失败:', error);
return HttpResponse.json(
{
success: false,
error: error.message || '获取主线数据失败'
},
{ status: 500 }
);
}
}),
]; ];

View File

@@ -630,7 +630,6 @@ const MainlineTimelineViewComponent = forwardRef(
params.append("group_by", groupBy); params.append("group_by", groupBy);
const url = `${apiBase}/api/events/mainline?${params.toString()}`; const url = `${apiBase}/api/events/mainline?${params.toString()}`;
console.log("[MainlineTimelineView] 🔄 请求主线数据:", url);
const response = await fetch(url); const response = await fetch(url);
if (!response.ok) { if (!response.ok) {
@@ -638,25 +637,22 @@ const MainlineTimelineViewComponent = forwardRef(
} }
const result = await response.json(); const result = await response.json();
console.log("[MainlineTimelineView] 📦 响应数据:", {
success: result.success, // 兼容两种响应格式:{ success, data: {...} } 或 { success, mainlines, ... }
mainlineCount: result.data?.mainlines?.length, const responseData = result.data || result;
totalEvents: result.data?.total_events,
groupBy: result.data?.group_by,
});
if (result.success) { if (result.success) {
// 保存原始数据,排序在渲染时根据 sortBy 状态进行 // 保存原始数据,排序在渲染时根据 sortBy 状态进行
setMainlineData(result.data); setMainlineData(responseData);
// 保存层级选项供下拉框使用 // 保存层级选项供下拉框使用
if (result.data.hierarchy_options) { if (responseData.hierarchy_options) {
setHierarchyOptions(result.data.hierarchy_options); setHierarchyOptions(responseData.hierarchy_options);
} }
// 初始化展开状态(默认全部展开) // 初始化展开状态(默认全部展开)
const initialExpanded = {}; const initialExpanded = {};
(result.data.mainlines || []).forEach((mainline) => { (responseData.mainlines || []).forEach((mainline) => {
const groupId = mainline.group_id || mainline.lv2_id || mainline.lv1_id; const groupId = mainline.group_id || mainline.lv2_id || mainline.lv1_id;
initialExpanded[groupId] = true; initialExpanded[groupId] = true;
}); });