diff --git a/src/mocks/data/events.js b/src/mocks/data/events.js index 162b9b72..94a38ccd 100644 --- a/src/mocks/data/events.js +++ b/src/mocks/data/events.js @@ -1142,6 +1142,138 @@ function generateTransmissionChain(industry, index) { return { nodes, edges }; } +// 主线事件标题模板 - 确保生成的事件能够匹配主线定义的关键词 +// 每个主线定义至少有 2-3 个事件模板,确保数据充足 +const mainlineEventTemplates = [ + // ==================== TMT (科技/媒体/通信) ==================== + + // AI基础设施 - 算力/芯片 (L3_AI_CHIP) + { title: '英伟达发布新一代GPU,AI算力大幅提升', 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 } @@ -1166,7 +1298,10 @@ export function generateDynamicNewsEvents(timeRange = null, count = 30) { const timeSpan = endTime.getTime() - startTime.getTime(); 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 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({ id: `dynamic_${i + 1}`, - title: generateEventTitle(industry, i), - description: generateEventDescription(industry, imp, i), - content: generateEventDescription(industry, imp, i), + title: eventTitle, + description: eventDescription, + content: eventDescription, event_type: eventType, importance: imp, status: 'published', @@ -1234,7 +1391,9 @@ export function generateDynamicNewsEvents(timeRange = null, count = 30) { related_avg_chg: parseFloat(relatedAvgChg), related_max_chg: parseFloat(relatedMaxChg), 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生成 industry: industry, related_stocks: relatedStocks, diff --git a/src/mocks/handlers/event.js b/src/mocks/handlers/event.js index c609c206..500da6fa 100644 --- a/src/mocks/handlers/event.js +++ b/src/mocks/handlers/event.js @@ -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 事件数据 - 第一个参数是 timeRange(null 表示默认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 } - ); - } - }), ]; diff --git a/src/views/Community/components/DynamicNews/layouts/MainlineTimelineView.js b/src/views/Community/components/DynamicNews/layouts/MainlineTimelineView.js index 49eba932..9c8c75b5 100644 --- a/src/views/Community/components/DynamicNews/layouts/MainlineTimelineView.js +++ b/src/views/Community/components/DynamicNews/layouts/MainlineTimelineView.js @@ -630,7 +630,6 @@ const MainlineTimelineViewComponent = forwardRef( params.append("group_by", groupBy); const url = `${apiBase}/api/events/mainline?${params.toString()}`; - console.log("[MainlineTimelineView] 🔄 请求主线数据:", url); const response = await fetch(url); if (!response.ok) { @@ -638,25 +637,22 @@ const MainlineTimelineViewComponent = forwardRef( } const result = await response.json(); - console.log("[MainlineTimelineView] 📦 响应数据:", { - success: result.success, - mainlineCount: result.data?.mainlines?.length, - totalEvents: result.data?.total_events, - groupBy: result.data?.group_by, - }); + + // 兼容两种响应格式:{ success, data: {...} } 或 { success, mainlines, ... } + const responseData = result.data || result; if (result.success) { // 保存原始数据,排序在渲染时根据 sortBy 状态进行 - setMainlineData(result.data); + setMainlineData(responseData); // 保存层级选项供下拉框使用 - if (result.data.hierarchy_options) { - setHierarchyOptions(result.data.hierarchy_options); + if (responseData.hierarchy_options) { + setHierarchyOptions(responseData.hierarchy_options); } // 初始化展开状态(默认全部展开) const initialExpanded = {}; - (result.data.mainlines || []).forEach((mainline) => { + (responseData.mainlines || []).forEach((mainline) => { const groupId = mainline.group_id || mainline.lv2_id || mainline.lv1_id; initialExpanded[groupId] = true; });