diff --git a/src/mocks/handlers/account.js b/src/mocks/handlers/account.js index d7bc881f..15b1aa86 100644 --- a/src/mocks/handlers/account.js +++ b/src/mocks/handlers/account.js @@ -768,6 +768,97 @@ export const accountHandlers = [ }); }), + // ==================== 账号绑定管理 ==================== + + // 手机号发送验证码 + http.post('/api/account/phone/send-code', async ({ request }) => { + await delay(NETWORK_DELAY); + const body = await request.json(); + console.log('[Mock] 发送手机验证码:', body.phone); + return HttpResponse.json({ + success: true, + message: '验证码已发送' + }); + }), + + // 手机号绑定 + http.post('/api/account/phone/bind', async ({ request }) => { + await delay(NETWORK_DELAY); + const body = await request.json(); + console.log('[Mock] 绑定手机号:', body.phone); + return HttpResponse.json({ + success: true, + message: '手机号绑定成功', + data: { phone: body.phone, phone_confirmed: true } + }); + }), + + // 手机号解绑 + http.post('/api/account/phone/unbind', async () => { + await delay(NETWORK_DELAY); + console.log('[Mock] 解绑手机号'); + return HttpResponse.json({ + success: true, + message: '手机号解绑成功' + }); + }), + + // 邮箱发送验证码 + http.post('/api/account/email/send-bind-code', async ({ request }) => { + await delay(NETWORK_DELAY); + const body = await request.json(); + console.log('[Mock] 发送邮箱验证码:', body.email); + return HttpResponse.json({ + success: true, + message: '验证码已发送' + }); + }), + + // 邮箱绑定 + http.post('/api/account/email/bind', async ({ request }) => { + await delay(NETWORK_DELAY); + const body = await request.json(); + console.log('[Mock] 绑定邮箱:', body.email); + return HttpResponse.json({ + success: true, + message: '邮箱绑定成功', + user: { email: body.email, email_confirmed: true } + }); + }), + + // 微信获取二维码 + http.get('/api/account/wechat/qrcode', async () => { + await delay(NETWORK_DELAY); + console.log('[Mock] 获取微信绑定二维码'); + return HttpResponse.json({ + success: true, + auth_url: 'https://open.weixin.qq.com/connect/qrconnect?mock=true', + session_id: 'mock_session_' + Date.now() + }); + }), + + // 微信绑定检查 + http.post('/api/account/wechat/check', async ({ request }) => { + await delay(NETWORK_DELAY); + const body = await request.json(); + console.log('[Mock] 检查微信绑定状态:', body.session_id); + // 模拟绑定成功 + return HttpResponse.json({ + success: true, + status: 'bind_ready' + }); + }), + + // 微信解绑 + http.post('/api/account/wechat/unbind', async () => { + await delay(NETWORK_DELAY); + console.log('[Mock] 解绑微信'); + return HttpResponse.json({ + success: true, + message: '微信解绑成功' + }); + }), + // 21. 获取订阅套餐列表 http.get('/api/subscription/plans', async () => { await delay(NETWORK_DELAY); diff --git a/src/mocks/handlers/event.js b/src/mocks/handlers/event.js index 5b788842..84fbefeb 100644 --- a/src/mocks/handlers/event.js +++ b/src/mocks/handlers/event.js @@ -1,14 +1,25 @@ // src/mocks/handlers/event.js // 事件相关的 Mock API Handlers -import { http, HttpResponse } from 'msw'; -import { getEventRelatedStocks, generateMockEvents, generateHotEvents, generatePopularKeywords, generateDynamicNewsEvents } from '../data/events'; -import { getMockFutureEvents, getMockEventCountsForMonth, toggleEventFollowStatus, isEventFollowed } from '../data/account'; -import { generatePopularConcepts } from './concept'; -import { getCurrentUser } from '../data/users'; +import { http, HttpResponse } from "msw"; +import { + getEventRelatedStocks, + generateMockEvents, + generateHotEvents, + generatePopularKeywords, + generateDynamicNewsEvents, +} from "../data/events"; +import { + getMockFutureEvents, + getMockEventCountsForMonth, + toggleEventFollowStatus, + isEventFollowed, +} from "../data/account"; +import { generatePopularConcepts } from "./concept"; +import { getCurrentUser } from "../data/users"; // 模拟网络延迟 -const delay = (ms = 300) => new Promise(resolve => setTimeout(resolve, ms)); +const delay = (ms = 300) => new Promise((resolve) => setTimeout(resolve, ms)); // ==================== 评论内存存储 ==================== // 用于在 Mock 环境下持久化评论数据(按 eventId 分组) @@ -20,67 +31,67 @@ const commentsStore = new Map(); * @returns {Array} 初始的 15 条 mock 评论 */ const initializeMockComments = (eventId) => { - const comments = []; - const users = [ - { username: '张三', avatar: null }, - { username: '李四', avatar: null }, - { username: '王五', avatar: null }, - { username: '赵六', avatar: null }, - { username: '投资达人', avatar: null }, - { username: '价值投资者', avatar: null }, - { username: '技术分析师', avatar: null }, - { username: '基本面研究员', avatar: null }, - { username: '量化交易员', avatar: null }, - { username: '市场观察者', avatar: null }, - { username: '行业分析师', avatar: null }, - { username: '财经评论员', avatar: null }, - { username: '趋势跟踪者', avatar: null }, - { username: '价值发现者', avatar: null }, - { username: '理性投资人', avatar: null }, - ]; + const comments = []; + const users = [ + { username: "张三", avatar: null }, + { username: "李四", avatar: null }, + { username: "王五", avatar: null }, + { username: "赵六", avatar: null }, + { username: "投资达人", avatar: null }, + { username: "价值投资者", avatar: null }, + { username: "技术分析师", avatar: null }, + { username: "基本面研究员", avatar: null }, + { username: "量化交易员", avatar: null }, + { username: "市场观察者", avatar: null }, + { username: "行业分析师", avatar: null }, + { username: "财经评论员", avatar: null }, + { username: "趋势跟踪者", avatar: null }, + { username: "价值发现者", avatar: null }, + { username: "理性投资人", avatar: null }, + ]; - const commentTemplates = [ - '这个事件对相关板块影响很大,值得关注后续发展', - '相关概念股已经开始异动了,市场反应很快', - '感谢分享,这个事件我之前没注意到', - '从基本面来看,这个事件会带来实质性利好', - '需要观察后续政策落地情况,现在下结论还太早', - '相关产业链的龙头企业值得重点关注', - '这类事件一般都是短期刺激,长期影响有限', - '建议大家理性对待,不要盲目追高', - '这个消息已经在预期之中,股价可能提前反应了', - '关键要看后续的执行力度和落地速度', - '建议关注产业链上下游的投资机会', - '短期可能会有波动,但长期逻辑依然成立', - '市场情绪很高涨,需要警惕追高风险', - '从历史数据来看,类似事件后续表现都不错', - '这是一个结构性机会,需要精选个股', - ]; + const commentTemplates = [ + "这个事件对相关板块影响很大,值得关注后续发展", + "相关概念股已经开始异动了,市场反应很快", + "感谢分享,这个事件我之前没注意到", + "从基本面来看,这个事件会带来实质性利好", + "需要观察后续政策落地情况,现在下结论还太早", + "相关产业链的龙头企业值得重点关注", + "这类事件一般都是短期刺激,长期影响有限", + "建议大家理性对待,不要盲目追高", + "这个消息已经在预期之中,股价可能提前反应了", + "关键要看后续的执行力度和落地速度", + "建议关注产业链上下游的投资机会", + "短期可能会有波动,但长期逻辑依然成立", + "市场情绪很高涨,需要警惕追高风险", + "从历史数据来看,类似事件后续表现都不错", + "这是一个结构性机会,需要精选个股", + ]; - for (let i = 0; i < 15; i++) { - const hoursAgo = Math.floor(Math.random() * 48) + 1; // 1-48 小时前 - const createdAt = new Date(Date.now() - hoursAgo * 60 * 60 * 1000); - const user = users[i % users.length]; + for (let i = 0; i < 15; i++) { + const hoursAgo = Math.floor(Math.random() * 48) + 1; // 1-48 小时前 + const createdAt = new Date(Date.now() - hoursAgo * 60 * 60 * 1000); + const user = users[i % users.length]; - comments.push({ - id: `comment_${eventId}_${i + 1}`, - content: commentTemplates[i % commentTemplates.length], - content_type: 'text', - author: { - id: `user_${i + 1}`, - username: user.username, - avatar: user.avatar, - }, - created_at: createdAt.toISOString(), - likes_count: Math.floor(Math.random() * 20), - is_liked: false, - }); - } + comments.push({ + id: `comment_${eventId}_${i + 1}`, + content: commentTemplates[i % commentTemplates.length], + content_type: "text", + author: { + id: `user_${i + 1}`, + username: user.username, + avatar: user.avatar, + }, + created_at: createdAt.toISOString(), + likes_count: Math.floor(Math.random() * 20), + is_liked: false, + }); + } - // 按时间升序排序(最旧的在前) - comments.sort((a, b) => new Date(a.created_at) - new Date(b.created_at)); + // 按时间升序排序(最旧的在前) + comments.sort((a, b) => new Date(a.created_at) - new Date(b.created_at)); - return comments; + return comments; }; /** @@ -89,1983 +100,2491 @@ const initializeMockComments = (eventId) => { * @returns {Array} 评论列表 */ const getOrInitComments = (eventId) => { - if (!commentsStore.has(eventId)) { - commentsStore.set(eventId, initializeMockComments(eventId)); - } - return commentsStore.get(eventId); + if (!commentsStore.has(eventId)) { + commentsStore.set(eventId, initializeMockComments(eventId)); + } + return commentsStore.get(eventId); }; export const eventHandlers = [ - // ==================== 事件列表相关 ==================== + // ==================== 事件列表相关 ==================== - // 获取事件列表 - http.get('/api/events', async ({ request }) => { - await delay(500); + // 获取事件列表 + http.get("/api/events", async ({ request }) => { + await delay(500); - const url = new URL(request.url); - const params = { - page: parseInt(url.searchParams.get('page') || '1'), - per_page: parseInt(url.searchParams.get('per_page') || '10'), - sort: url.searchParams.get('sort') || 'new', - importance: url.searchParams.get('importance') || 'all', - date_range: url.searchParams.get('date_range') || '', - q: url.searchParams.get('q') || '', - industry_code: url.searchParams.get('industry_code') || '', - industry_classification: url.searchParams.get('industry_classification') || '', - stock_code: url.searchParams.get('stock_code') || '', + const url = new URL(request.url); + const params = { + page: parseInt(url.searchParams.get("page") || "1"), + per_page: parseInt(url.searchParams.get("per_page") || "10"), + sort: url.searchParams.get("sort") || "new", + importance: url.searchParams.get("importance") || "all", + date_range: url.searchParams.get("date_range") || "", + q: url.searchParams.get("q") || "", + industry_code: url.searchParams.get("industry_code") || "", + industry_classification: + url.searchParams.get("industry_classification") || "", + stock_code: url.searchParams.get("stock_code") || "", + }; + + console.log("[Mock] 获取事件列表:", params); + + try { + const result = generateMockEvents(params); + + // 返回格式兼容 useEventData 期望的结构 + // useEventData 期望: { success, data: { events: [], pagination: {} } } + return HttpResponse.json({ + success: true, + data: { + events: result.events, // 事件数组 + pagination: result.pagination, // 分页信息 + }, + message: "获取成功", + }); + } catch (error) { + console.error("[Mock] 获取事件列表失败:", error); + console.error("[Mock] Error details:", { + message: error.message, + stack: error.stack, + params: params, + }); + return HttpResponse.json( + { + success: false, + error: "获取事件列表失败", + data: [], + pagination: { + page: 1, + per_page: 10, + total: 0, + pages: 0, + has_prev: false, + has_next: false, + }, + }, + { status: 500 } + ); + } + }), + + // 获取热点事件 + http.get("/api/events/hot", async ({ request }) => { + await delay(300); + + const url = new URL(request.url); + const limit = parseInt(url.searchParams.get("limit") || "5"); + + console.log("[Mock] 获取热点事件, limit:", limit); + + try { + const hotEvents = generateHotEvents(limit); + + return HttpResponse.json({ + success: true, + data: hotEvents, + message: "获取成功", + }); + } catch (error) { + console.error("[Mock] 获取热点事件失败:", error); + return HttpResponse.json( + { + success: false, + error: "获取热点事件失败", + data: [], + }, + { status: 500 } + ); + } + }), + + // 获取热门关键词 + http.get("/api/events/keywords/popular", async ({ request }) => { + await delay(300); + + const url = new URL(request.url); + const limit = parseInt(url.searchParams.get("limit") || "20"); + + console.log("[Mock] 获取热门关键词, limit:", limit); + + try { + const keywords = generatePopularKeywords(limit); + + return HttpResponse.json({ + success: true, + data: keywords, + message: "获取成功", + }); + } catch (error) { + console.error("[Mock] 获取热门关键词失败:", error); + return HttpResponse.json( + { + success: false, + error: "获取热门关键词失败", + data: [], + }, + { status: 500 } + ); + } + }), + + // 获取动态新闻(实时要闻·动态追踪专用) + http.get("/api/events/dynamic-news", async ({ request }) => { + await delay(400); + + const url = new URL(request.url); + const count = parseInt(url.searchParams.get("count") || "30"); + const startTime = url.searchParams.get("start_time"); + const endTime = url.searchParams.get("end_time"); + + console.log( + "[Mock] 获取动态新闻, count:", + count, + "startTime:", + startTime, + "endTime:", + endTime + ); + + try { + let timeRange = null; + if (startTime && endTime) { + timeRange = { + startTime: new Date(startTime), + endTime: new Date(endTime), }; + } - console.log('[Mock] 获取事件列表:', params); + const events = generateDynamicNewsEvents(timeRange, count); - try { - const result = generateMockEvents(params); + return HttpResponse.json({ + success: true, + data: events, + total: events.length, + message: "获取成功", + }); + } catch (error) { + console.error("[Mock] 获取动态新闻失败:", error); + return HttpResponse.json( + { + success: false, + error: "获取动态新闻失败", + data: [], + }, + { status: 500 } + ); + } + }), - // 返回格式兼容 useEventData 期望的结构 - // useEventData 期望: { success, data: { events: [], pagination: {} } } - return HttpResponse.json({ - success: true, - data: { - events: result.events, // 事件数组 - pagination: result.pagination // 分页信息 - }, - message: '获取成功' - }); - } catch (error) { - console.error('[Mock] 获取事件列表失败:', error); - console.error('[Mock] Error details:', { - message: error.message, - stack: error.stack, - params: params - }); - return HttpResponse.json( - { - success: false, - error: '获取事件列表失败', - data: [], - pagination: { - page: 1, - per_page: 10, - total: 0, - pages: 0, - has_prev: false, - has_next: false - } - }, - { status: 500 } - ); - } - }), + // ==================== 主线模式相关(必须在 :eventId 之前,否则会被通配符匹配)==================== - // 获取热点事件 - http.get('/api/events/hot', async ({ request }) => { - await delay(300); + // 获取按主线(lv1/lv2/lv3概念)分组的事件列表 + http.get("/api/events/mainline", async ({ request }) => { + await delay(500); - const url = new URL(request.url); - const limit = parseInt(url.searchParams.get('limit') || '5'); + 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"; - console.log('[Mock] 获取热点事件, limit:', limit); + try { + // 生成 mock 事件数据 - 第一个参数是 timeRange(null 表示默认24小时),第二个参数是 count + const allEvents = generateDynamicNewsEvents(null, 100); - try { - const hotEvents = generateHotEvents(limit); + 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: ["券商", "证券"], + }, + ]; - return HttpResponse.json({ - success: true, - data: hotEvents, - message: '获取成功' - }); - } catch (error) { - console.error('[Mock] 获取热点事件失败:', error); - return HttpResponse.json( - { - success: false, - error: '获取热点事件失败', - data: [] - }, - { status: 500 } - ); - } - }), + 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, + })), + }; - // 获取热门关键词 - http.get('/api/events/keywords/popular', async ({ request }) => { - await delay(300); + const mainlineGroups = {}; + const isSpecificId = + groupBy.startsWith("L1_") || + groupBy.startsWith("L2_") || + groupBy.startsWith("L3_"); - const url = new URL(request.url); - const limit = parseInt(url.searchParams.get('limit') || '20'); + 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(); - console.log('[Mock] 获取热门关键词, limit:', limit); + mainlineDefinitions.forEach((mainline) => { + const matched = mainline.keywords.some((kw) => + textToMatch.includes(kw.toLowerCase()) + ); + if (matched) { + let groupKey, groupData; - try { - const keywords = generatePopularKeywords(limit); - - return HttpResponse.json({ - success: true, - data: keywords, - message: '获取成功' - }); - } catch (error) { - console.error('[Mock] 获取热门关键词失败:', error); - return HttpResponse.json( - { - success: false, - error: '获取热门关键词失败', - data: [] - }, - { status: 500 } - ); - } - }), - - // 获取动态新闻(实时要闻·动态追踪专用) - http.get('/api/events/dynamic-news', async ({ request }) => { - await delay(400); - - const url = new URL(request.url); - const count = parseInt(url.searchParams.get('count') || '30'); - const startTime = url.searchParams.get('start_time'); - const endTime = url.searchParams.get('end_time'); - - console.log('[Mock] 获取动态新闻, count:', count, 'startTime:', startTime, 'endTime:', endTime); - - try { - let timeRange = null; - if (startTime && endTime) { - timeRange = { - startTime: new Date(startTime), - endTime: new Date(endTime) + 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: [], }; - } - - const events = generateDynamicNewsEvents(timeRange, count); - - return HttpResponse.json({ - success: true, - data: events, - total: events.length, - message: '获取成功' - }); - } catch (error) { - console.error('[Mock] 获取动态新闻失败:', error); - return HttpResponse.json( - { - success: false, - error: '获取动态新闻失败', - data: [] - }, - { status: 500 } - ); - } - }), - - // ==================== 主线模式相关(必须在 :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 }); - } - }), - - // ==================== 事件详情相关 ==================== - - // 获取事件详情 - http.get('/api/events/:eventId', async ({ params }) => { - await delay(200); - - const { eventId } = params; - const numericEventId = parseInt(eventId, 10); - - console.log('[Mock] 获取事件详情, eventId:', numericEventId); - - try { - // 检查是否已关注 - const isFollowing = isEventFollowed(numericEventId); - - // 返回模拟的事件详情数据 - return HttpResponse.json({ - success: true, - data: { - id: numericEventId, - title: `测试事件 ${eventId} - 重大政策发布`, - description: '这是一个模拟的事件描述,用于开发测试。该事件涉及重要政策变化,可能对相关板块产生显著影响。建议关注后续发展动态。', - importance: ['S', 'A', 'B', 'C'][Math.floor(Math.random() * 4)], - created_at: new Date().toISOString(), - trading_date: new Date().toISOString().split('T')[0], - event_type: ['政策', '财报', '行业', '宏观'][Math.floor(Math.random() * 4)], - related_avg_chg: parseFloat((Math.random() * 10 - 5).toFixed(2)), - follower_count: Math.floor(Math.random() * 500) + 50, - view_count: Math.floor(Math.random() * 5000) + 100, - is_following: isFollowing, // 使用内存状态 - post_count: Math.floor(Math.random() * 50), - expectation_surprise_score: parseFloat((Math.random() * 100).toFixed(1)), - }, - message: '获取成功' - }); - } catch (error) { - console.error('[Mock] 获取事件详情失败:', error); - return HttpResponse.json( - { - success: false, - error: '获取事件详情失败', - data: null - }, - { status: 500 } - ); - } - }), - - // 获取事件超预期得分 - http.get('/api/events/:eventId/expectation-score', async ({ params }) => { - await delay(200); - - const { eventId } = params; - - console.log('[Mock] 获取事件超预期得分, eventId:', eventId); - - try { - // 生成模拟的超预期得分数据 - const score = parseFloat((Math.random() * 100).toFixed(1)); - const avgChange = parseFloat((Math.random() * 10 - 2).toFixed(2)); - const maxChange = parseFloat((Math.random() * 15).toFixed(2)); - - return HttpResponse.json({ - success: true, - data: { - event_id: parseInt(eventId), - expectation_score: score, - avg_change: avgChange, - max_change: maxChange, - stock_count: Math.floor(Math.random() * 20) + 5, - updated_at: new Date().toISOString(), - }, - message: '获取成功' - }); - } catch (error) { - console.error('[Mock] 获取事件超预期得分失败:', error); - return HttpResponse.json( - { - success: false, - error: '获取事件超预期得分失败', - data: null - }, - { status: 500 } - ); - } - }), - - // 获取事件相关股票 - http.get('/api/events/:eventId/stocks', async ({ params }) => { - await delay(300); - - const { eventId } = params; - - console.log('[Mock] 获取事件相关股票, eventId:', eventId); - - try { - const relatedStocks = getEventRelatedStocks(eventId); - - return HttpResponse.json({ - success: true, - data: relatedStocks, - message: '获取成功' - }); - } catch (error) { - console.error('[Mock] 获取事件相关股票失败:', error); - return HttpResponse.json( - { - success: false, - error: '获取事件相关股票失败', - data: [] - }, - { status: 500 } - ); - } - }), - - // 获取事件相关概念 - http.get('/api/events/:eventId/concepts', async ({ params }) => { - await delay(300); - - const { eventId } = params; - - console.log('[Mock] 获取事件相关概念, eventId:', eventId); - - try { - // 返回热门概念列表(模拟真实场景下根据事件标题搜索的结果) - const concepts = generatePopularConcepts(5); - - return HttpResponse.json({ - success: true, - data: concepts, - message: '获取成功' - }); - } catch (error) { - console.error('[Mock] 获取事件相关概念失败:', error); - return HttpResponse.json( - { - success: false, - error: '获取事件相关概念失败', - data: [] - }, - { status: 500 } - ); - } - }), - - // 切换事件关注状态(使用内存状态管理) - http.post('/api/events/:eventId/follow', async ({ params, request }) => { - await delay(200); - - const { eventId } = params; - const numericEventId = parseInt(eventId, 10); - - console.log('[Mock] 切换事件关注状态, eventId:', numericEventId); - - try { - // 尝试从请求体获取事件数据(用于新关注时保存完整信息) - let eventData = null; - try { - const body = await request.json(); - if (body && body.title) { - eventData = body; - } - } catch { - // 没有请求体或解析失败,忽略 - } - - // 使用内存状态管理切换关注 - const { isFollowing, followerCount } = toggleEventFollowStatus(numericEventId, eventData); - - return HttpResponse.json({ - success: true, - data: { - is_following: isFollowing, - follower_count: followerCount - }, - message: isFollowing ? '关注成功' : '取消关注成功' - }); - } catch (error) { - console.error('[Mock] 切换事件关注状态失败:', error); - return HttpResponse.json( - { - success: false, - error: '切换关注状态失败', - data: null - }, - { status: 500 } - ); - } - }), - - // 事件情绪投票(看多/看空) - http.post('/api/events/:eventId/sentiment-vote', async ({ params, request }) => { - await delay(200); - - const { eventId } = params; - const numericEventId = parseInt(eventId, 10); - - console.log('[Mock] 事件情绪投票, eventId:', numericEventId); - - try { - const body = await request.json(); - const voteType = body.vote_type; // 'bullish', 'bearish', 或 null - - // 使用内存状态管理投票 - // 简单模拟:根据 eventId 生成基础数据 - const baseBullish = (numericEventId * 7) % 50 + 10; - const baseBearish = (numericEventId * 3) % 30 + 5; - - // 根据投票类型调整计数 - let bullishCount = baseBullish; - let bearishCount = baseBearish; - - if (voteType === 'bullish') { - bullishCount += 1; - } else if (voteType === 'bearish') { - bearishCount += 1; - } - - return HttpResponse.json({ - success: true, - data: { - user_vote: voteType || null, - bullish_count: bullishCount, - bearish_count: bearishCount, - }, - message: voteType ? '投票成功' : '取消投票成功' - }); - } catch (error) { - console.error('[Mock] 事件情绪投票失败:', error); - return HttpResponse.json( - { - success: false, - error: '投票失败', - data: null - }, - { status: 500 } - ); - } - }), - - // 获取事件传导链分析数据 - http.get('/api/events/:eventId/transmission', async ({ params }) => { - await delay(500); - - const { eventId } = params; - - console.log('[Mock] 获取事件传导链分析, eventId:', eventId); - - // Mock数据:事件传导链 - const mockTransmissionData = { - success: true, - data: { - nodes: [ - { - id: '1', - name: '主要事件', - category: '事件', - value: 50, - extra: { - node_type: 'event', - description: '这是主要事件节点', - importance_score: 50, - is_main_event: true - } - }, - { - id: '2', - name: '半导体行业', - category: '行业', - value: 40, - extra: { - node_type: 'industry', - description: '受影响的半导体行业', - importance_score: 40, - is_main_event: false - } - }, - { - id: '3', - name: '芯片制造', - category: '行业', - value: 35, - extra: { - node_type: 'industry', - description: '芯片制造产业链', - importance_score: 35, - is_main_event: false - } - }, - { - id: '4', - name: 'A公司', - category: '公司', - value: 30, - extra: { - node_type: 'company', - description: '龙头企业A', - importance_score: 30, - stock_code: '600000', - is_main_event: false - } - }, - { - id: '5', - name: 'B公司', - category: '公司', - value: 25, - extra: { - node_type: 'company', - description: '龙头企业B', - importance_score: 25, - stock_code: '600001', - is_main_event: false - } - }, - { - id: '6', - name: '相关政策', - category: '政策', - value: 30, - extra: { - node_type: 'policy', - description: '国家产业政策支持', - importance_score: 30, - is_main_event: false - } - } - ], - edges: [ - { - source: '1', - target: '2', - value: 0.8, - extra: { - transmission_strength: 0.8, - transmission_type: '直接影响', - description: '主事件对半导体行业的直接影响' - } - }, - { - source: '2', - target: '3', - value: 0.7, - extra: { - transmission_strength: 0.7, - transmission_type: '产业链传导', - description: '半导体到芯片制造的传导' - } - }, - { - source: '3', - target: '4', - value: 0.6, - extra: { - transmission_strength: 0.6, - transmission_type: '企业影响', - description: '对龙头企业A的影响' - } - }, - { - source: '3', - target: '5', - value: 0.5, - extra: { - transmission_strength: 0.5, - transmission_type: '企业影响', - description: '对龙头企业B的影响' - } - }, - { - source: '6', - target: '1', - value: 0.7, - extra: { - transmission_strength: 0.7, - transmission_type: '政策驱动', - description: '政策对主事件的推动作用' - } - }, - { - source: '6', - target: '2', - value: 0.6, - extra: { - transmission_strength: 0.6, - transmission_type: '政策支持', - description: '政策对行业的支持' - } - } - ], - categories: ['事件', '行业', '公司', '政策', '技术', '市场', '其他'] - }, - message: '获取成功' - }; - - return HttpResponse.json(mockTransmissionData); - }), - - // 获取桑基图数据 - http.get('/api/events/:eventId/sankey-data', async ({ params }) => { - await delay(300); - const { eventId } = params; - console.log('[Mock] 获取桑基图数据, eventId:', eventId); - - const mockSankeyData = { - success: true, - data: { - nodes: [ - { - name: '相关政策', - type: 'policy', - level: 0, - color: '#10ac84' - }, - { - name: '主要事件', - type: 'event', - level: 0, - color: '#ff4757' - }, - { - name: '半导体行业', - type: 'industry', - level: 1, - color: '#00d2d3' - }, - { - name: '芯片制造', - type: 'industry', - level: 2, - color: '#00d2d3' - }, - { - name: 'A公司', - type: 'company', - level: 3, - color: '#54a0ff' - }, - { - name: 'B公司', - type: 'company', - level: 3, - color: '#54a0ff' - } - ], - links: [ - { source: 0, target: 1, value: 7 }, // 相关政策 -> 主要事件 - { source: 0, target: 2, value: 6 }, // 相关政策 -> 半导体行业 - { source: 1, target: 2, value: 8 }, // 主要事件 -> 半导体行业 - { source: 2, target: 3, value: 7 }, // 半导体行业 -> 芯片制造 - { source: 3, target: 4, value: 6 }, // 芯片制造 -> A公司 - { source: 3, target: 5, value: 5 } // 芯片制造 -> B公司 - ] - }, - message: '获取成功' - }; - - return HttpResponse.json(mockSankeyData); - }), - - // 获取传导链节点详情 - http.get('/api/events/:eventId/chain-node/:nodeId', async ({ params }) => { - await delay(300); - - const { eventId, nodeId } = params; - - console.log('[Mock] 获取节点详情, eventId:', eventId, 'nodeId:', nodeId); - - // 根据节点ID返回不同的详细信息 - const nodeDetailsMap = { - '1': { - success: true, - data: { - node: { - id: '1', - name: '主要事件', - type: 'event', - description: '这是影响整个产业链的重大事件,涉及政策调整和技术突破,对下游产业产生深远影响。', - importance_score: 50, - total_connections: 2, - incoming_connections: 1, - outgoing_connections: 1 - }, - parents: [ - { - id: '6', - name: '相关政策', - transmission_mechanism: { - data: [ - { - author: "国务院", - sentences: "为加快实施创新驱动发展战略,推动产业转型升级,国家将对重点领域给予财政补贴支持,单个项目最高补贴金额可达5000万元,同时享受研发费用加计扣除175%的税收优惠政策", - query_part: "国家财政补贴最高5000万元,研发费用加计扣除175%", - match_score: "好", - declare_date: "2024-01-15T00:00:00", - report_title: "关于促进产业高质量发展的若干政策措施" - }, - { - author: "工信部", - sentences: "根据《重点产业扶持目录》,对符合条件的企业和项目,将优先纳入政府采购名单,并提供专项资金支持,确保政策红利直接惠及实体经济", - query_part: "政府采购优先支持,专项资金直达企业", - match_score: "好", - declare_date: "2024-01-20T00:00:00", - report_title: "工业和信息化部关于落实产业扶持政策的通知" - } - ] - }, - direction: 'positive', - strength: 70, - is_circular: false - } - ], - children: [ - { - id: '2', - name: '半导体行业(正向影响)', - transmission_mechanism: { - data: [ - { - author: "李明", - organization: "中国电子信息产业发展研究院", - sentences: "在技术突破和应用场景快速扩张的双重驱动下,国内半导体市场呈现爆发式增长态势。据统计,2024年上半年半导体市场规模达到1.2万亿元,同比增长32%,其中新能源汽车和AI算力芯片需求贡献了超过60%的增量", - query_part: "技术突破和需求激增推动半导体市场增长32%", - match_score: "好", - declare_date: "2024-07-10T00:00:00", - report_title: "2024年上半年中国半导体产业发展报告" - } - ] - }, - direction: 'positive', - strength: 80, - is_circular: false - }, - { - id: '7', - name: '传统制造业(负向影响)', - transmission_mechanism: { - data: [ - { - author: "张华", - organization: "经济观察报", - sentences: "随着半导体等高科技产业获得大量政策和资金支持,传统制造业面临融资难、用工成本上升等多重压力。部分劳动密集型企业利润率下降15%,行业整体投资意愿降低", - query_part: "资源向高科技倾斜导致传统制造业承压", - match_score: "好", - declare_date: "2024-06-15T00:00:00", - report_title: "传统制造业转型升级调研报告" - } - ] - }, - direction: 'negative', - strength: 60, - is_circular: false - }, - { - id: '8', - name: '能源行业(中性影响)', - transmission_mechanism: { - data: [ - { - author: "王刚", - organization: "能源研究所", - sentences: "半导体产业扩张带来电力需求增长约8%,但同时推动节能技术应用,整体能源消费结构趋于优化。新建芯片工厂虽增加用电负荷,但智能电网技术应用使能源利用效率提升12%", - query_part: "半导体产业对能源行业影响相对中性", - match_score: "中", - declare_date: "2024-07-01T00:00:00", - report_title: "高科技产业能源消费分析" - } - ] - }, - direction: 'neutral', - strength: 40, - is_circular: false - }, - { - id: '9', - name: '教育培训行业(未明确方向)', - transmission_mechanism: { - data: [ - { - author: "赵敏", - organization: "教育部职业教育司", - sentences: "半导体产业快速发展催生大量专业人才需求,各类培训机构、职业院校纷纷开设相关课程。预计未来三年将新增半导体专业学员超过50万人,带动职业教育市场规模扩大", - query_part: "半导体产业推动职业教育发展", - match_score: "好", - declare_date: "2024-06-20T00:00:00", - report_title: "半导体人才培养白皮书" - } - ] - }, - strength: 50, - is_circular: false - } - ] - } - }, - '2': { - success: true, - data: { - node: { - id: '2', - name: '半导体行业', - type: 'industry', - description: '半导体行业是现代科技产业的基础,受到主事件和政策的双重推动,迎来新一轮发展机遇。', - importance_score: 40, - total_connections: 3, - incoming_connections: 2, - outgoing_connections: 1 - }, - parents: [ - { - id: '1', - name: '主要事件', - transmission_mechanism: { - data: [ - { - author: "刘洋", - organization: "中国半导体行业协会", - sentences: "受益于新能源汽车、5G通信等新兴应用领域的爆发式增长,国内半导体市场需求持续旺盛,2024年Q1市场规模同比增长28%,创历史新高", - query_part: "新兴应用推动半导体需求增长28%", - match_score: "好", - declare_date: "2024-04-05T00:00:00", - report_title: "2024年Q1中国半导体行业景气度报告" - }, - { - author: "刘洋", - organization: "中国半导体行业协会", - sentences: "受益于新能源汽车、5G通信等新兴应用领域的爆发式增长,国内半导体市场需求持续旺盛,2024年Q1市场规模同比增长28%,创历史新高", - query_part: "新兴应用推动半导体需求增长28%", - match_score: "好", - declare_date: "2024-04-05T00:00:00", - report_title: "2024年Q1中国半导体行业景气度报告" - } - ] - }, - direction: 'positive', - strength: 80, - is_circular: false - }, - { - id: '6', - name: '相关政策', - transmission_mechanism: { - data: [ - { - author: "国家发改委", - sentences: "《国家集成电路产业发展推进纲要》明确提出,到2025年半导体产业自给率要达到70%以上,国家将设立专项基金规模超过3000亿元,重点支持半导体设备、材料、设计等关键环节", - query_part: "半导体自给率目标70%,专项基金3000亿", - match_score: "好", - declare_date: "2024-02-01T00:00:00", - report_title: "国家集成电路产业发展推进纲要(2024-2030)" - } - ] - }, - direction: 'positive', - strength: 60, - is_circular: false - } - ], - children: [ - { - id: '3', - name: '芯片制造', - transmission_mechanism: { - data: [ - { - author: "张明", - organization: "中信证券", - sentences: "在半导体行业景气度持续提升的背景下,下游芯片制造企业订单饱满,产能利用率达到历史新高,预计2024年产能扩张将达到30%以上,技术工艺也将从28nm向14nm升级", - query_part: "半导体行业繁荣带动芯片制造产能扩张30%", - match_score: "好", - declare_date: "2024-03-15T00:00:00", - report_title: "半导体行业深度报告:产业链景气度传导分析" - }, - { - author: "李华", - organization: "海通证券", - sentences: "芯片制造环节作为半导体产业链核心,受益于上游材料供应稳定和下游应用需求旺盛,技术迭代速度明显加快,先进制程占比持续提升", - query_part: "技术迭代加快,先进制程占比提升", - match_score: "好", - declare_date: "2024-02-28T00:00:00", - report_title: "芯片制造行业跟踪报告" - } - ] - }, - direction: 'positive', - strength: 70, - is_circular: false - } - ] - } - }, - '3': { - success: true, - data: { - node: { - id: '3', - name: '芯片制造', - type: 'industry', - description: '芯片制造作为半导体产业链的核心环节,在上游需求推动下,产能利用率提升,技术迭代加快。', - importance_score: 35, - total_connections: 3, - incoming_connections: 1, - outgoing_connections: 2 - }, - parents: [ - { - id: '2', - name: '半导体行业', - transmission_mechanism: { - data: [ - { - author: "张明", - sentences: "在半导体行业景气度持续提升的背景下,下游芯片制造企业订单饱满,产能利用率达到历史新高,预计2024年产能扩张将达到30%以上,技术工艺也将从28nm向14nm升级", - query_part: "半导体行业繁荣带动芯片制造产能扩张30%", - match_score: "好", - declare_date: "2024-03-15T00:00:00", - report_title: "半导体行业深度报告:产业链景气度传导分析" - }, - { - author: "李华", - sentences: "芯片制造环节作为半导体产业链核心,受益于上游材料供应稳定和下游应用需求旺盛,技术迭代速度明显加快,先进制程占比持续提升", - query_part: "技术迭代加快,先进制程占比提升", - match_score: "好", - declare_date: "2024-02-28T00:00:00", - report_title: "芯片制造行业跟踪报告" - } - ] - }, - direction: 'positive', - strength: 70, - is_circular: false - } - ], - children: [ - { - id: '4', - name: 'A公司', - transmission_mechanism: { - data: [ - { - author: "王芳", - organization: "国泰君安", - sentences: "A公司作为国内芯片制造龙头企业,在手订单已排至2024年Q4,预计全年营收增长45%,净利润增长60%以上。公司28nm及以下先进制程产能占比已达到40%,技术实力行业领先", - query_part: "A公司在手订单充足,预计营收增长45%", - match_score: "好", - declare_date: "2024-04-10T00:00:00", - report_title: "A公司深度研究:受益芯片制造景气周期" - } - ] - }, - direction: 'positive', - strength: 60, - is_circular: false - }, - { - id: '5', - name: 'B公司', - transmission_mechanism: { - data: [ - { - author: "赵强", - organization: "华泰证券", - sentences: "随着芯片制造产能的大规模扩张,上游设备和材料供应商迎来历史性机遇。B公司作为核心配套企业,订单量同比增长55%,产品供不应求,预计2024年营收将突破百亿大关。公司在封装测试领域的市场份额已提升至国内第二位", - query_part: "B公司订单增长55%,营收将破百亿", - match_score: "好", - declare_date: "2024-05-08T00:00:00", - report_title: "B公司跟踪报告:芯片产业链配套龙头崛起" - }, - { - author: "陈彤", - organization: "国信证券", - sentences: "B公司深度受益于芯片制造产业链的景气度传导。公司凭借先进的封装技术和完善的产能布局,成功绑定多家头部芯片制造企业,形成稳定的供应关系。随着下游客户产能持续扩张,公司业绩增长确定性强", - query_part: "B公司受益产业链景气度,业绩增长确定性强", - match_score: "好", - declare_date: "2024-06-01T00:00:00", - report_title: "半导体封装测试行业专题:产业链景气度传导分析" - } - ] - }, - direction: 'positive', - strength: 50, - is_circular: false - } - ] - } - }, - '4': { - success: true, - data: { - node: { - id: '4', - name: 'A公司', - type: 'company', - description: 'A公司是行业龙头企业,拥有先进的芯片制造技术和完整的产业链布局,在本轮产业升级中占据有利位置。', - importance_score: 30, - stock_code: '600000', - total_connections: 1, - incoming_connections: 1, - outgoing_connections: 0 - }, - parents: [ - { - id: '3', - name: '芯片制造', - transmission_mechanism: { - data: [ - { - author: "王芳", - sentences: "A公司作为国内芯片制造龙头企业,在手订单已排至2024年Q4,预计全年营收增长45%,净利润增长60%以上。公司28nm及以下先进制程产能占比已达到40%,技术实力行业领先", - query_part: "A公司在手订单充足,预计营收增长45%", - match_score: "好", - declare_date: "2024-04-10T00:00:00", - report_title: "A公司深度研究:受益芯片制造景气周期" - } - ] - }, - direction: 'positive', - strength: 60, - is_circular: false - } - ], - children: [] - } - }, - '5': { - success: true, - data: { - node: { - id: '5', - name: 'B公司', - type: 'company', - description: 'B公司专注于芯片封装测试领域,随着上游制造产能释放,公司订单饱满,业绩稳步增长。', - importance_score: 25, - stock_code: '600001', - total_connections: 1, - incoming_connections: 1, - outgoing_connections: 0 - }, - parents: [ - { - id: '3', - name: '芯片制造', - transmission_mechanism: { - data: [ - { - author: "赵强", - organization: "华泰证券", - sentences: "随着芯片制造产能的大规模扩张,上游设备和材料供应商迎来历史性机遇。B公司作为核心配套企业,订单量同比增长55%,产品供不应求,预计2024年营收将突破百亿大关", - query_part: "B公司订单增长55%,营收将破百亿", - match_score: "好", - declare_date: "2024-05-08T00:00:00", - report_title: "B公司跟踪报告:芯片产业链配套龙头崛起" - } - ] - }, - direction: 'positive', - strength: 50, - is_circular: false - } - ], - children: [] - } - }, - '6': { - success: true, - data: { - node: { - id: '6', - name: '相关政策', - type: 'policy', - description: '国家出台了一系列产业扶持政策,包括财政补贴、税收减免和研发支持,旨在推动产业自主创新和进口替代。', - importance_score: 30, - total_connections: 2, - incoming_connections: 0, - outgoing_connections: 2 - }, - parents: [], - children: [ - { - id: '1', - name: '主要事件', - transmission_mechanism: { - data: [ - { - author: "国务院", - sentences: "为加快实施创新驱动发展战略,推动产业转型升级,国家将对重点领域给予财政补贴支持,单个项目最高补贴金额可达5000万元,同时享受研发费用加计扣除175%的税收优惠政策", - query_part: "国家财政补贴最高5000万元,研发费用加计扣除175%", - match_score: "好", - declare_date: "2024-01-15T00:00:00", - report_title: "关于促进产业高质量发展的若干政策措施" - }, - { - author: "工信部", - sentences: "将重点支持关键核心技术攻关和产业化应用,建立产业发展专项基金,规模达到1000亿元,引导社会资本共同参与产业发展", - query_part: "设立1000亿元产业发展专项基金", - match_score: "好", - declare_date: "2024-02-01T00:00:00", - report_title: "产业发展专项基金管理办法" - } - ] - }, - direction: 'positive', - strength: 70, - is_circular: false - }, - { - id: '2', - name: '半导体行业', - transmission_mechanism: { - data: [ - { - author: "国家发改委", - sentences: "《国家集成电路产业发展推进纲要》明确提出,到2025年半导体产业自给率要达到70%以上,国家将设立专项基金规模超过3000亿元,重点支持半导体设备、材料、设计等关键环节。同时,通过进口替代战略,加快培育本土产业链", - query_part: "半导体自给率目标70%,专项基金3000亿", - match_score: "好", - declare_date: "2024-02-01T00:00:00", - report_title: "国家集成电路产业发展推进纲要(2024-2030)" - }, - { - author: "工信部", - sentences: "将重点支持关键核心技术攻关和产业化应用,建立产业发展专项基金,规模达到1000亿元,引导社会资本共同参与产业发展。通过税收优惠、研发补贴等政策工具,为半导体行业创造良好的发展环境", - query_part: "设立1000亿元产业发展专项基金", - match_score: "好", - declare_date: "2024-02-01T00:00:00", - report_title: "产业发展专项基金管理办法" - } - ] - }, - direction: 'positive', - strength: 60, - is_circular: false - } - ] - } - } - }; - - // 返回对应节点的详情,如果不存在则返回默认数据 - const nodeDetail = nodeDetailsMap[nodeId] || { - success: true, - data: { - node: { - id: nodeId, - name: '未知节点', - type: 'other', - description: '该节点暂无详细信息', - importance_score: 0, - total_connections: 0, - incoming_connections: 0, - outgoing_connections: 0 - }, - parents: [], - children: [] - } - }; - - return HttpResponse.json(nodeDetail); - }), - - // ==================== 投资日历相关 ==================== - - // 获取月度事件统计 - http.get('/api/v1/calendar/event-counts', async ({ request }) => { - await delay(300); - - const url = new URL(request.url); - const year = parseInt(url.searchParams.get('year')); - const month = parseInt(url.searchParams.get('month')); - - console.log('[Mock] 获取月度事件统计:', { year, month }); - - const eventCounts = getMockEventCountsForMonth(year, month); - - return HttpResponse.json({ - success: true, - data: eventCounts - }); - }), - - // 获取指定日期的事件列表 - http.get('/api/v1/calendar/events', async ({ request }) => { - await delay(300); - - const url = new URL(request.url); - const dateStr = url.searchParams.get('date'); - const type = url.searchParams.get('type') || 'all'; - - console.log('[Mock] 获取日历事件列表:', { date: dateStr, type }); - - if (!dateStr) { - return HttpResponse.json({ - success: false, - error: 'Date parameter required' - }, { status: 400 }); - } - - const events = getMockFutureEvents(dateStr, type); - - return HttpResponse.json({ - success: true, - data: events - }); - }), - - // 切换未来事件关注状态 - http.post('/api/v1/calendar/events/:eventId/follow', async ({ params }) => { - await delay(300); - - const { eventId } = params; - - console.log('[Mock] 切换事件关注状态, eventId:', eventId); - - // 简单返回成功,实际状态管理可以后续完善 - return HttpResponse.json({ - success: true, - data: { - is_following: true, - message: '关注成功' - } - }); - }), - - // ==================== 历史事件对比相关 ==================== - - // 获取历史事件列表 - http.get('/api/events/:eventId/historical', async ({ params }) => { - await delay(400); - - const { eventId } = params; - - console.log('[Mock] 获取历史事件列表, eventId:', eventId); - - // 生成历史事件数据 - const generateHistoricalEvents = (count = 5) => { - const events = []; - const eventTitles = [ - '芯片产业链政策扶持升级', - '新能源汽车销量创历史新高', - '人工智能大模型技术突破', - '半导体设备国产化加速', - '数字经济政策利好发布', - '新能源产业链整合提速', - '医药创新药获批上市', - '5G应用场景扩展', - '智能驾驶技术迭代升级', - '储能行业景气度上行' - ]; - - const importanceLevels = [1, 2, 3, 4, 5]; - - for (let i = 0; i < count; i++) { - const daysAgo = Math.floor(Math.random() * 180) + 30; // 30-210 天前 - const date = new Date(); - date.setDate(date.getDate() - daysAgo); - - const importance = importanceLevels[Math.floor(Math.random() * importanceLevels.length)]; - const title = eventTitles[i % eventTitles.length]; - - // 带引用来源的研报数据 - const researchReports = [ - { - author: '中信证券', - report_title: `${title}深度研究报告`, - declare_date: new Date(date.getTime() - Math.floor(Math.random() * 10) * 24 * 60 * 60 * 1000).toISOString() - }, - { - author: '国泰君安', - report_title: `行业专题:${title}影响分析`, - declare_date: new Date(date.getTime() - Math.floor(Math.random() * 15) * 24 * 60 * 60 * 1000).toISOString() - }, - { - author: '华泰证券', - report_title: `${title}投资机会深度解析`, - declare_date: new Date(date.getTime() - Math.floor(Math.random() * 20) * 24 * 60 * 60 * 1000).toISOString() - } - ]; - - // 生成带引用标记的content(data结构) - const contentWithCitations = { - data: [ - { - query_part: `${title}的详细描述。该事件对相关产业链产生重要影响【1】,市场关注度高,相关概念股表现活跃。`, - sentences: `根据券商研报分析,${title}将推动相关产业链快速发展【2】。预计未来${Math.floor(Math.random() * 2 + 1)}年内,相关企业营收增速有望达到${Math.floor(Math.random() * 30 + 20)}%以上【3】。该事件的影响范围广泛,涉及多个细分领域,投资机会显著。`, - match_score: importance >= 4 ? '好' : (importance >= 2 ? '中' : '一般'), - author: researchReports[0].author, - declare_date: researchReports[0].declare_date, - report_title: researchReports[0].report_title - }, - { - query_part: `市场分析师认为,该事件将带动产业链上下游企业协同发展【2】,形成良性循环。`, - sentences: `从产业趋势来看,相关板块估值仍处于合理区间,具备较高的安全边际。机构投资者持续加仓相关标的,显示出对长期发展前景的看好。`, - match_score: importance >= 3 ? '好' : '中', - author: researchReports[1].author, - declare_date: researchReports[1].declare_date, - report_title: researchReports[1].report_title - }, - { - query_part: `根据行业数据显示,受此事件影响,相关企业订单量同比增长${Math.floor(Math.random() * 40 + 30)}%【3】。`, - sentences: `行业景气度持续提升,龙头企业凭借技术优势和规模效应,市场份额有望进一步扩大。建议关注产业链核心环节的投资机会。`, - match_score: '好', - author: researchReports[2].author, - declare_date: researchReports[2].declare_date, - report_title: researchReports[2].report_title - } - ] + } 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: [], }; - - events.push({ - id: `hist_event_${i + 1}`, - title: title, - content: contentWithCitations, // 升级版本:带引用来源的data结构 - description: `${title}的详细描述。该事件对相关产业链产生重要影响,市场关注度高,相关概念股表现活跃。`, // 降级兼容 - date: date.toISOString().split('T')[0], - importance: importance, - similarity: Math.floor(Math.random() * 10) + 1, // 1-10 - impact_sectors: [ - ['半导体', '芯片设计', 'EDA'], - ['新能源汽车', '锂电池', '充电桩'], - ['人工智能', '算力', '大模型'], - ['半导体设备', '国产替代', '集成电路'], - ['数字经济', '云计算', '大数据'] - ][i % 5], - affected_stocks_count: Math.floor(Math.random() * 30) + 10, // 10-40 只股票 - avg_change_pct: parseFloat((Math.random() * 10 - 2).toFixed(2)), // -2% to +8% - created_at: date.toISOString() - }); - } - - // 按日期降序排序 - return events.sort((a, b) => new Date(b.date) - new Date(a.date)); - }; - - try { - const historicalEvents = generateHistoricalEvents(5); - - return HttpResponse.json({ - success: true, - data: historicalEvents, - total: historicalEvents.length, - message: '获取历史事件列表成功' - }); - } catch (error) { - console.error('[Mock] 获取历史事件列表失败:', error); - return HttpResponse.json( - { - success: false, - error: '获取历史事件列表失败', - data: [] - }, - { status: 500 } - ); - } - }), - - // 获取历史事件相关股票 - http.get('/api/historical-events/:eventId/stocks', async ({ params }) => { - await delay(500); - - const { eventId } = params; - - console.log('[Mock] 获取历史事件相关股票, eventId:', eventId); - - // 生成历史事件相关股票数据 - const generateHistoricalEventStocks = (count = 10) => { - const stocks = []; - const sectors = ['半导体', '新能源', '医药', '消费电子', '人工智能', '5G通信']; - const stockNames = [ - '中芯国际', '长江存储', '华为海思', '紫光国微', '兆易创新', - '宁德时代', '比亚迪', '隆基绿能', '阳光电源', '亿纬锂能', - '恒瑞医药', '迈瑞医疗', '药明康德', '泰格医药', '康龙化成', - '立讯精密', '歌尔声学', '京东方A', 'TCL科技', '海康威视', - '科大讯飞', '商汤科技', '寒武纪', '海光信息', '中兴通讯' - ]; - - for (let i = 0; i < count; i++) { - const stockCode = `${Math.random() > 0.5 ? '6' : '0'}${String(Math.floor(Math.random() * 100000)).padStart(5, '0')}`; - const changePct = (Math.random() * 15 - 3).toFixed(2); // -3% ~ +12% - const correlation = (Math.random() * 0.4 + 0.6).toFixed(2); // 0.6 ~ 1.0 - - stocks.push({ - id: `stock_${i}`, - stock_code: `${stockCode}.${Math.random() > 0.5 ? 'SH' : 'SZ'}`, - stock_name: stockNames[i % stockNames.length], - sector: sectors[Math.floor(Math.random() * sectors.length)], - correlation: parseFloat(correlation), - event_day_change_pct: parseFloat(changePct), - relation_desc: { - data: [ - { - query_part: `该公司是${sectors[Math.floor(Math.random() * sectors.length)]}行业龙头,受事件影响显著,市场关注度高,订单量同比增长${Math.floor(Math.random() * 50 + 20)}%`, - sentences: `根据行业研究报告,该公司在${sectors[Math.floor(Math.random() * sectors.length)]}领域具有核心技术优势,产能利用率达到${Math.floor(Math.random() * 20 + 80)}%,随着事件的深入发展,公司业绩有望持续受益。机构预测未来三年复合增长率将达到${Math.floor(Math.random() * 30 + 15)}%以上`, - match_score: correlation > 0.8 ? '好' : (correlation > 0.6 ? '中' : '一般'), - author: ['中信证券', '国泰君安', '华泰证券', '招商证券'][Math.floor(Math.random() * 4)], - declare_date: new Date(Date.now() - Math.floor(Math.random() * 90) * 24 * 60 * 60 * 1000).toISOString(), - report_title: `${stockNames[i % stockNames.length]}深度研究报告` - } - ] - } - }); - } - - // 按相关度降序排序 - return stocks.sort((a, b) => b.correlation - a.correlation); - }; - - try { - const stocks = generateHistoricalEventStocks(15); - - return HttpResponse.json({ - success: true, - data: stocks, - message: '获取历史事件相关股票成功' - }); - } catch (error) { - console.error('[Mock] 获取历史事件相关股票失败:', error); - return HttpResponse.json( - { - success: false, - error: '获取历史事件相关股票失败', - data: [] - }, - { status: 500 } - ); - } - }), - - // ==================== 评论相关 ==================== - - // 获取事件评论列表 - http.get('/api/events/:eventId/posts', async ({ params, request }) => { - await delay(300); - - const { eventId } = params; - const url = new URL(request.url); - const sort = url.searchParams.get('sort') || 'latest'; - const page = parseInt(url.searchParams.get('page') || '1'); - const perPage = parseInt(url.searchParams.get('per_page') || '20'); - - console.log('[Mock] 获取评论列表, eventId:', eventId, 'sort:', sort); - - try { - // 从内存存储获取评论列表 - const allComments = getOrInitComments(eventId); - - // ✅ 创建副本并排序(避免直接修改原数组) - let sortedComments = [...allComments]; - if (sort === 'hot') { - sortedComments.sort((a, b) => b.likes_count - a.likes_count); + } 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 { - // 默认按时间升序(oldest first)- 最旧评论在前,最新在后 - sortedComments.sort((a, b) => new Date(a.created_at) - new Date(b.created_at)); + groupKey = mainline.lv2_id; + groupData = { + group_id: mainline.lv2_id, + group_name: mainline.lv2_name, + parent_name: mainline.lv1_name, + events: [], + }; } - // 分页处理(使用排序后的副本) - const startIndex = (page - 1) * perPage; - const endIndex = startIndex + perPage; - const paginatedComments = sortedComments.slice(startIndex, endIndex); + if (!mainlineGroups[groupKey]) { + mainlineGroups[groupKey] = groupData; + } + if ( + !mainlineGroups[groupKey].events.find((e) => e.id === event.id) + ) { + mainlineGroups[groupKey].events.push(event); + } + } + }); + }); - return HttpResponse.json({ - success: true, - data: paginatedComments, - pagination: { - page: page, - per_page: perPage, - total: allComments.length, - pages: Math.ceil(allComments.length / perPage), - has_prev: page > 1, - has_next: endIndex < allComments.length, - }, - message: '获取评论成功', - }); - } catch (error) { - console.error('[Mock] 获取评论列表失败:', error); - return HttpResponse.json( - { - success: false, - error: '获取评论失败', - data: [], - }, - { status: 500 } - ); - } - }), + const generatePriceData = () => + parseFloat((Math.random() * 13 - 5).toFixed(2)); + const priceDataMap = { lv1: {}, lv2: {}, lv3: {} }; - // 发表评论 - http.post('/api/events/:eventId/posts', async ({ params, request }) => { - await delay(500); + 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 { eventId } = params; + 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 } + ); + } + }), + + // ==================== 事件详情相关 ==================== + + // 获取事件详情 + http.get("/api/events/:eventId", async ({ params }) => { + await delay(200); + + const { eventId } = params; + const numericEventId = parseInt(eventId, 10); + + console.log("[Mock] 获取事件详情, eventId:", numericEventId); + + try { + // 检查是否已关注 + const isFollowing = isEventFollowed(numericEventId); + + // 返回模拟的事件详情数据 + return HttpResponse.json({ + success: true, + data: { + id: numericEventId, + title: `测试事件 ${eventId} - 重大政策发布`, + description: + "这是一个模拟的事件描述,用于开发测试。该事件涉及重要政策变化,可能对相关板块产生显著影响。建议关注后续发展动态。", + importance: ["S", "A", "B", "C"][Math.floor(Math.random() * 4)], + created_at: new Date().toISOString(), + trading_date: new Date().toISOString().split("T")[0], + event_type: ["政策", "财报", "行业", "宏观"][ + Math.floor(Math.random() * 4) + ], + related_avg_chg: parseFloat((Math.random() * 10 - 5).toFixed(2)), + follower_count: Math.floor(Math.random() * 500) + 50, + view_count: Math.floor(Math.random() * 5000) + 100, + is_following: isFollowing, // 使用内存状态 + post_count: Math.floor(Math.random() * 50), + expectation_surprise_score: parseFloat( + (Math.random() * 100).toFixed(1) + ), + }, + message: "获取成功", + }); + } catch (error) { + console.error("[Mock] 获取事件详情失败:", error); + return HttpResponse.json( + { + success: false, + error: "获取事件详情失败", + data: null, + }, + { status: 500 } + ); + } + }), + + // 获取事件超预期得分 + http.get("/api/events/:eventId/expectation-score", async ({ params }) => { + await delay(200); + + const { eventId } = params; + + console.log("[Mock] 获取事件超预期得分, eventId:", eventId); + + try { + // 生成模拟的超预期得分数据 + const score = parseFloat((Math.random() * 100).toFixed(1)); + const avgChange = parseFloat((Math.random() * 10 - 2).toFixed(2)); + const maxChange = parseFloat((Math.random() * 15).toFixed(2)); + + return HttpResponse.json({ + success: true, + data: { + event_id: parseInt(eventId), + expectation_score: score, + avg_change: avgChange, + max_change: maxChange, + stock_count: Math.floor(Math.random() * 20) + 5, + updated_at: new Date().toISOString(), + }, + message: "获取成功", + }); + } catch (error) { + console.error("[Mock] 获取事件超预期得分失败:", error); + return HttpResponse.json( + { + success: false, + error: "获取事件超预期得分失败", + data: null, + }, + { status: 500 } + ); + } + }), + + // 获取事件相关股票 + http.get("/api/events/:eventId/stocks", async ({ params }) => { + await delay(300); + + const { eventId } = params; + + console.log("[Mock] 获取事件相关股票, eventId:", eventId); + + try { + const relatedStocks = getEventRelatedStocks(eventId); + + return HttpResponse.json({ + success: true, + data: relatedStocks, + message: "获取成功", + }); + } catch (error) { + console.error("[Mock] 获取事件相关股票失败:", error); + return HttpResponse.json( + { + success: false, + error: "获取事件相关股票失败", + data: [], + }, + { status: 500 } + ); + } + }), + + // 获取事件相关概念 + http.get("/api/events/:eventId/concepts", async ({ params }) => { + await delay(300); + + const { eventId } = params; + + console.log("[Mock] 获取事件相关概念, eventId:", eventId); + + try { + // 返回热门概念列表(模拟真实场景下根据事件标题搜索的结果) + const concepts = generatePopularConcepts(5); + + return HttpResponse.json({ + success: true, + data: concepts, + message: "获取成功", + }); + } catch (error) { + console.error("[Mock] 获取事件相关概念失败:", error); + return HttpResponse.json( + { + success: false, + error: "获取事件相关概念失败", + data: [], + }, + { status: 500 } + ); + } + }), + + // 切换事件关注状态(使用内存状态管理) + http.post("/api/events/:eventId/follow", async ({ params, request }) => { + await delay(200); + + const { eventId } = params; + const numericEventId = parseInt(eventId, 10); + + console.log("[Mock] 切换事件关注状态, eventId:", numericEventId); + + try { + // 尝试从请求体获取事件数据(用于新关注时保存完整信息) + let eventData = null; + try { const body = await request.json(); - - console.log('[Mock] 发表评论, eventId:', eventId, 'content:', body.content); - - try { - // 获取当前登录用户信息 - const currentUser = getCurrentUser(); - - // 模拟创建新评论 - const newComment = { - id: `comment_${eventId}_${Date.now()}`, - content: body.content, - content_type: body.content_type || 'text', - author: { - id: currentUser?.id || 'current_user', - // 与导航区保持一致:优先显示昵称 - username: currentUser?.nickname || currentUser?.username || currentUser?.email || '当前用户', - avatar: currentUser?.avatar_url || null, - }, - created_at: new Date().toISOString(), - likes_count: 0, - is_liked: false, - }; - - // 将新评论添加到内存存储(插入到列表开头) - const comments = getOrInitComments(eventId); - comments.unshift(newComment); - - console.log('[Mock] 评论已添加到内存存储, 当前评论总数:', comments.length); - - return HttpResponse.json({ - success: true, - data: newComment, - message: '评论发布成功', - }); - } catch (error) { - console.error('[Mock] 发表评论失败:', error); - return HttpResponse.json( - { - success: false, - error: '评论发布失败', - message: '系统错误,请稍后重试', - }, - { status: 500 } - ); + if (body && body.title) { + eventData = body; } - }), + } catch { + // 没有请求体或解析失败,忽略 + } - // 删除帖子/评论 - http.delete('/api/posts/:postId', async ({ params }) => { - await delay(300); - const { postId } = params; + // 使用内存状态管理切换关注 + const { isFollowing, followerCount } = toggleEventFollowStatus( + numericEventId, + eventData + ); - console.log('[Mock] 删除帖子, postId:', postId); + return HttpResponse.json({ + success: true, + data: { + is_following: isFollowing, + follower_count: followerCount, + }, + message: isFollowing ? "关注成功" : "取消关注成功", + }); + } catch (error) { + console.error("[Mock] 切换事件关注状态失败:", error); + return HttpResponse.json( + { + success: false, + error: "切换关注状态失败", + data: null, + }, + { status: 500 } + ); + } + }), - try { - // 从内存存储中删除评论 - let deleted = false; - for (const [eventId, comments] of commentsStore.entries()) { - const index = comments.findIndex(c => String(c.id) === String(postId)); - if (index !== -1) { - comments.splice(index, 1); - deleted = true; - console.log('[Mock] 评论已从事件', eventId, '中删除'); - break; - } - } + // 事件情绪投票(看多/看空) + http.post( + "/api/events/:eventId/sentiment-vote", + async ({ params, request }) => { + await delay(200); - if (!deleted) { - console.log('[Mock] 未找到评论,但仍返回成功(可能是乐观更新的评论)'); - } + const { eventId } = params; + const numericEventId = parseInt(eventId, 10); - return HttpResponse.json({ - success: true, - message: '删除成功', - }); - } catch (error) { - console.error('[Mock] 删除帖子失败:', error); - return HttpResponse.json( - { - success: false, - error: '删除失败', - message: '系统错误,请稍后重试', - }, - { status: 500 } - ); + console.log("[Mock] 事件情绪投票, eventId:", numericEventId); + + try { + const body = await request.json(); + const voteType = body.vote_type; // 'bullish', 'bearish', 或 null + + // 使用内存状态管理投票 + // 简单模拟:根据 eventId 生成基础数据 + const baseBullish = ((numericEventId * 7) % 50) + 10; + const baseBearish = ((numericEventId * 3) % 30) + 5; + + // 根据投票类型调整计数 + let bullishCount = baseBullish; + let bearishCount = baseBearish; + + if (voteType === "bullish") { + bullishCount += 1; + } else if (voteType === "bearish") { + bearishCount += 1; } - }), - // ==================== 日历综合数据(一次性获取所有数据)==================== + return HttpResponse.json({ + success: true, + data: { + user_vote: voteType || null, + bullish_count: bullishCount, + bearish_count: bearishCount, + }, + message: voteType ? "投票成功" : "取消投票成功", + }); + } catch (error) { + console.error("[Mock] 事件情绪投票失败:", error); + return HttpResponse.json( + { + success: false, + error: "投票失败", + data: null, + }, + { status: 500 } + ); + } + } + ), - // 获取日历综合数据(涨停 + 事件 + 上证涨跌幅) - http.get('/api/v1/calendar/combined-data', async ({ request }) => { - await delay(400); + // 获取事件传导链分析数据 + http.get("/api/events/:eventId/transmission", async ({ params }) => { + await delay(500); - const url = new URL(request.url); - const year = parseInt(url.searchParams.get('year')) || new Date().getFullYear(); - const month = parseInt(url.searchParams.get('month')) || new Date().getMonth() + 1; + const { eventId } = params; - console.log('[Mock] 获取日历综合数据:', { year, month }); + console.log("[Mock] 获取事件传导链分析, eventId:", eventId); - try { - const data = []; - const today = new Date(); - const daysInMonth = new Date(year, month, 0).getDate(); + // Mock数据:事件传导链 + const mockTransmissionData = { + success: true, + data: { + nodes: [ + { + id: "1", + name: "主要事件", + category: "事件", + value: 50, + extra: { + node_type: "event", + description: "这是主要事件节点", + importance_score: 50, + is_main_event: true, + }, + }, + { + id: "2", + name: "半导体行业", + category: "行业", + value: 40, + extra: { + node_type: "industry", + description: "受影响的半导体行业", + importance_score: 40, + is_main_event: false, + }, + }, + { + id: "3", + name: "芯片制造", + category: "行业", + value: 35, + extra: { + node_type: "industry", + description: "芯片制造产业链", + importance_score: 35, + is_main_event: false, + }, + }, + { + id: "4", + name: "A公司", + category: "公司", + value: 30, + extra: { + node_type: "company", + description: "龙头企业A", + importance_score: 30, + stock_code: "600000", + is_main_event: false, + }, + }, + { + id: "5", + name: "B公司", + category: "公司", + value: 25, + extra: { + node_type: "company", + description: "龙头企业B", + importance_score: 25, + stock_code: "600001", + is_main_event: false, + }, + }, + { + id: "6", + name: "相关政策", + category: "政策", + value: 30, + extra: { + node_type: "policy", + description: "国家产业政策支持", + importance_score: 30, + is_main_event: false, + }, + }, + ], + edges: [ + { + source: "1", + target: "2", + value: 0.8, + extra: { + transmission_strength: 0.8, + transmission_type: "直接影响", + description: "主事件对半导体行业的直接影响", + }, + }, + { + source: "2", + target: "3", + value: 0.7, + extra: { + transmission_strength: 0.7, + transmission_type: "产业链传导", + description: "半导体到芯片制造的传导", + }, + }, + { + source: "3", + target: "4", + value: 0.6, + extra: { + transmission_strength: 0.6, + transmission_type: "企业影响", + description: "对龙头企业A的影响", + }, + }, + { + source: "3", + target: "5", + value: 0.5, + extra: { + transmission_strength: 0.5, + transmission_type: "企业影响", + description: "对龙头企业B的影响", + }, + }, + { + source: "6", + target: "1", + value: 0.7, + extra: { + transmission_strength: 0.7, + transmission_type: "政策驱动", + description: "政策对主事件的推动作用", + }, + }, + { + source: "6", + target: "2", + value: 0.6, + extra: { + transmission_strength: 0.6, + transmission_type: "政策支持", + description: "政策对行业的支持", + }, + }, + ], + categories: ["事件", "行业", "公司", "政策", "技术", "市场", "其他"], + }, + message: "获取成功", + }; - // 热门概念列表 - const hotConcepts = [ - '人工智能', '华为鸿蒙', '机器人', '芯片', '算力', '新能源', - '固态电池', '量子计算', '低空经济', '智能驾驶', '光伏', '储能' - ]; + return HttpResponse.json(mockTransmissionData); + }), - // 预生成概念连续段(用于测试跨天连接效果) - // 每段 2-4 天使用相同概念 - let currentConcept = hotConcepts[0]; - let conceptDaysLeft = 0; - let conceptIndex = 0; + // 获取桑基图数据 + http.get("/api/events/:eventId/sankey-data", async ({ params }) => { + await delay(300); + const { eventId } = params; + console.log("[Mock] 获取桑基图数据, eventId:", eventId); - for (let day = 1; day <= daysInMonth; day++) { - const date = new Date(year, month - 1, day); - const dayOfWeek = date.getDay(); + const mockSankeyData = { + success: true, + data: { + nodes: [ + { + name: "相关政策", + type: "policy", + level: 0, + color: "#10ac84", + }, + { + name: "主要事件", + type: "event", + level: 0, + color: "#ff4757", + }, + { + name: "半导体行业", + type: "industry", + level: 1, + color: "#00d2d3", + }, + { + name: "芯片制造", + type: "industry", + level: 2, + color: "#00d2d3", + }, + { + name: "A公司", + type: "company", + level: 3, + color: "#54a0ff", + }, + { + name: "B公司", + type: "company", + level: 3, + color: "#54a0ff", + }, + ], + links: [ + { source: 0, target: 1, value: 7 }, // 相关政策 -> 主要事件 + { source: 0, target: 2, value: 6 }, // 相关政策 -> 半导体行业 + { source: 1, target: 2, value: 8 }, // 主要事件 -> 半导体行业 + { source: 2, target: 3, value: 7 }, // 半导体行业 -> 芯片制造 + { source: 3, target: 4, value: 6 }, // 芯片制造 -> A公司 + { source: 3, target: 5, value: 5 }, // 芯片制造 -> B公司 + ], + }, + message: "获取成功", + }; - // 跳过周末 - if (dayOfWeek === 0 || dayOfWeek === 6) continue; + return HttpResponse.json(mockSankeyData); + }), - const dateStr = `${year}${String(month).padStart(2, '0')}${String(day).padStart(2, '0')}`; - const isPast = date < today; - const isToday = date.toDateString() === today.toDateString(); - const isFuture = date > today; + // 获取传导链节点详情 + http.get("/api/events/:eventId/chain-node/:nodeId", async ({ params }) => { + await delay(300); - // 使用日期作为种子生成一致的随机数 - const dateSeed = year * 10000 + month * 100 + day; - const seededRandom = (seed) => { - const x = Math.sin(seed) * 10000; - return x - Math.floor(x); - }; + const { eventId, nodeId } = params; - // 概念连续段逻辑:每段 2-4 天使用相同概念 - if (conceptDaysLeft <= 0) { - conceptIndex = Math.floor(seededRandom(dateSeed + 100) * hotConcepts.length); - currentConcept = hotConcepts[conceptIndex]; - conceptDaysLeft = Math.floor(seededRandom(dateSeed + 101) * 3) + 2; // 2-4 天 - } - conceptDaysLeft--; + console.log("[Mock] 获取节点详情, eventId:", eventId, "nodeId:", nodeId); - const item = { - date: dateStr, - zt_count: 0, - top_sector: '', - event_count: 0, - index_change: null - }; + // 根据节点ID返回不同的详细信息 + const nodeDetailsMap = { + 1: { + success: true, + data: { + node: { + id: "1", + name: "主要事件", + type: "event", + description: + "这是影响整个产业链的重大事件,涉及政策调整和技术突破,对下游产业产生深远影响。", + importance_score: 50, + total_connections: 2, + incoming_connections: 1, + outgoing_connections: 1, + }, + parents: [ + { + id: "6", + name: "相关政策", + transmission_mechanism: { + data: [ + { + author: "国务院", + sentences: + "为加快实施创新驱动发展战略,推动产业转型升级,国家将对重点领域给予财政补贴支持,单个项目最高补贴金额可达5000万元,同时享受研发费用加计扣除175%的税收优惠政策", + query_part: + "国家财政补贴最高5000万元,研发费用加计扣除175%", + match_score: "好", + declare_date: "2024-01-15T00:00:00", + report_title: "关于促进产业高质量发展的若干政策措施", + }, + { + author: "工信部", + sentences: + "根据《重点产业扶持目录》,对符合条件的企业和项目,将优先纳入政府采购名单,并提供专项资金支持,确保政策红利直接惠及实体经济", + query_part: "政府采购优先支持,专项资金直达企业", + match_score: "好", + declare_date: "2024-01-20T00:00:00", + report_title: "工业和信息化部关于落实产业扶持政策的通知", + }, + ], + }, + direction: "positive", + strength: 70, + is_circular: false, + }, + ], + children: [ + { + id: "2", + name: "半导体行业(正向影响)", + transmission_mechanism: { + data: [ + { + author: "李明", + organization: "中国电子信息产业发展研究院", + sentences: + "在技术突破和应用场景快速扩张的双重驱动下,国内半导体市场呈现爆发式增长态势。据统计,2024年上半年半导体市场规模达到1.2万亿元,同比增长32%,其中新能源汽车和AI算力芯片需求贡献了超过60%的增量", + query_part: "技术突破和需求激增推动半导体市场增长32%", + match_score: "好", + declare_date: "2024-07-10T00:00:00", + report_title: "2024年上半年中国半导体产业发展报告", + }, + ], + }, + direction: "positive", + strength: 80, + is_circular: false, + }, + { + id: "7", + name: "传统制造业(负向影响)", + transmission_mechanism: { + data: [ + { + author: "张华", + organization: "经济观察报", + sentences: + "随着半导体等高科技产业获得大量政策和资金支持,传统制造业面临融资难、用工成本上升等多重压力。部分劳动密集型企业利润率下降15%,行业整体投资意愿降低", + query_part: "资源向高科技倾斜导致传统制造业承压", + match_score: "好", + declare_date: "2024-06-15T00:00:00", + report_title: "传统制造业转型升级调研报告", + }, + ], + }, + direction: "negative", + strength: 60, + is_circular: false, + }, + { + id: "8", + name: "能源行业(中性影响)", + transmission_mechanism: { + data: [ + { + author: "王刚", + organization: "能源研究所", + sentences: + "半导体产业扩张带来电力需求增长约8%,但同时推动节能技术应用,整体能源消费结构趋于优化。新建芯片工厂虽增加用电负荷,但智能电网技术应用使能源利用效率提升12%", + query_part: "半导体产业对能源行业影响相对中性", + match_score: "中", + declare_date: "2024-07-01T00:00:00", + report_title: "高科技产业能源消费分析", + }, + ], + }, + direction: "neutral", + strength: 40, + is_circular: false, + }, + { + id: "9", + name: "教育培训行业(未明确方向)", + transmission_mechanism: { + data: [ + { + author: "赵敏", + organization: "教育部职业教育司", + sentences: + "半导体产业快速发展催生大量专业人才需求,各类培训机构、职业院校纷纷开设相关课程。预计未来三年将新增半导体专业学员超过50万人,带动职业教育市场规模扩大", + query_part: "半导体产业推动职业教育发展", + match_score: "好", + declare_date: "2024-06-20T00:00:00", + report_title: "半导体人才培养白皮书", + }, + ], + }, + strength: 50, + is_circular: false, + }, + ], + }, + }, + 2: { + success: true, + data: { + node: { + id: "2", + name: "半导体行业", + type: "industry", + description: + "半导体行业是现代科技产业的基础,受到主事件和政策的双重推动,迎来新一轮发展机遇。", + importance_score: 40, + total_connections: 3, + incoming_connections: 2, + outgoing_connections: 1, + }, + parents: [ + { + id: "1", + name: "主要事件", + transmission_mechanism: { + data: [ + { + author: "刘洋", + organization: "中国半导体行业协会", + sentences: + "受益于新能源汽车、5G通信等新兴应用领域的爆发式增长,国内半导体市场需求持续旺盛,2024年Q1市场规模同比增长28%,创历史新高", + query_part: "新兴应用推动半导体需求增长28%", + match_score: "好", + declare_date: "2024-04-05T00:00:00", + report_title: "2024年Q1中国半导体行业景气度报告", + }, + { + author: "刘洋", + organization: "中国半导体行业协会", + sentences: + "受益于新能源汽车、5G通信等新兴应用领域的爆发式增长,国内半导体市场需求持续旺盛,2024年Q1市场规模同比增长28%,创历史新高", + query_part: "新兴应用推动半导体需求增长28%", + match_score: "好", + declare_date: "2024-04-05T00:00:00", + report_title: "2024年Q1中国半导体行业景气度报告", + }, + ], + }, + direction: "positive", + strength: 80, + is_circular: false, + }, + { + id: "6", + name: "相关政策", + transmission_mechanism: { + data: [ + { + author: "国家发改委", + sentences: + "《国家集成电路产业发展推进纲要》明确提出,到2025年半导体产业自给率要达到70%以上,国家将设立专项基金规模超过3000亿元,重点支持半导体设备、材料、设计等关键环节", + query_part: "半导体自给率目标70%,专项基金3000亿", + match_score: "好", + declare_date: "2024-02-01T00:00:00", + report_title: "国家集成电路产业发展推进纲要(2024-2030)", + }, + ], + }, + direction: "positive", + strength: 60, + is_circular: false, + }, + ], + children: [ + { + id: "3", + name: "芯片制造", + transmission_mechanism: { + data: [ + { + author: "张明", + organization: "中信证券", + sentences: + "在半导体行业景气度持续提升的背景下,下游芯片制造企业订单饱满,产能利用率达到历史新高,预计2024年产能扩张将达到30%以上,技术工艺也将从28nm向14nm升级", + query_part: "半导体行业繁荣带动芯片制造产能扩张30%", + match_score: "好", + declare_date: "2024-03-15T00:00:00", + report_title: "半导体行业深度报告:产业链景气度传导分析", + }, + { + author: "李华", + organization: "海通证券", + sentences: + "芯片制造环节作为半导体产业链核心,受益于上游材料供应稳定和下游应用需求旺盛,技术迭代速度明显加快,先进制程占比持续提升", + query_part: "技术迭代加快,先进制程占比提升", + match_score: "好", + declare_date: "2024-02-28T00:00:00", + report_title: "芯片制造行业跟踪报告", + }, + ], + }, + direction: "positive", + strength: 70, + is_circular: false, + }, + ], + }, + }, + 3: { + success: true, + data: { + node: { + id: "3", + name: "芯片制造", + type: "industry", + description: + "芯片制造作为半导体产业链的核心环节,在上游需求推动下,产能利用率提升,技术迭代加快。", + importance_score: 35, + total_connections: 3, + incoming_connections: 1, + outgoing_connections: 2, + }, + parents: [ + { + id: "2", + name: "半导体行业", + transmission_mechanism: { + data: [ + { + author: "张明", + sentences: + "在半导体行业景气度持续提升的背景下,下游芯片制造企业订单饱满,产能利用率达到历史新高,预计2024年产能扩张将达到30%以上,技术工艺也将从28nm向14nm升级", + query_part: "半导体行业繁荣带动芯片制造产能扩张30%", + match_score: "好", + declare_date: "2024-03-15T00:00:00", + report_title: "半导体行业深度报告:产业链景气度传导分析", + }, + { + author: "李华", + sentences: + "芯片制造环节作为半导体产业链核心,受益于上游材料供应稳定和下游应用需求旺盛,技术迭代速度明显加快,先进制程占比持续提升", + query_part: "技术迭代加快,先进制程占比提升", + match_score: "好", + declare_date: "2024-02-28T00:00:00", + report_title: "芯片制造行业跟踪报告", + }, + ], + }, + direction: "positive", + strength: 70, + is_circular: false, + }, + ], + children: [ + { + id: "4", + name: "A公司", + transmission_mechanism: { + data: [ + { + author: "王芳", + organization: "国泰君安", + sentences: + "A公司作为国内芯片制造龙头企业,在手订单已排至2024年Q4,预计全年营收增长45%,净利润增长60%以上。公司28nm及以下先进制程产能占比已达到40%,技术实力行业领先", + query_part: "A公司在手订单充足,预计营收增长45%", + match_score: "好", + declare_date: "2024-04-10T00:00:00", + report_title: "A公司深度研究:受益芯片制造景气周期", + }, + ], + }, + direction: "positive", + strength: 60, + is_circular: false, + }, + { + id: "5", + name: "B公司", + transmission_mechanism: { + data: [ + { + author: "赵强", + organization: "华泰证券", + sentences: + "随着芯片制造产能的大规模扩张,上游设备和材料供应商迎来历史性机遇。B公司作为核心配套企业,订单量同比增长55%,产品供不应求,预计2024年营收将突破百亿大关。公司在封装测试领域的市场份额已提升至国内第二位", + query_part: "B公司订单增长55%,营收将破百亿", + match_score: "好", + declare_date: "2024-05-08T00:00:00", + report_title: "B公司跟踪报告:芯片产业链配套龙头崛起", + }, + { + author: "陈彤", + organization: "国信证券", + sentences: + "B公司深度受益于芯片制造产业链的景气度传导。公司凭借先进的封装技术和完善的产能布局,成功绑定多家头部芯片制造企业,形成稳定的供应关系。随着下游客户产能持续扩张,公司业绩增长确定性强", + query_part: "B公司受益产业链景气度,业绩增长确定性强", + match_score: "好", + declare_date: "2024-06-01T00:00:00", + report_title: + "半导体封装测试行业专题:产业链景气度传导分析", + }, + ], + }, + direction: "positive", + strength: 50, + is_circular: false, + }, + ], + }, + }, + 4: { + success: true, + data: { + node: { + id: "4", + name: "A公司", + type: "company", + description: + "A公司是行业龙头企业,拥有先进的芯片制造技术和完整的产业链布局,在本轮产业升级中占据有利位置。", + importance_score: 30, + stock_code: "600000", + total_connections: 1, + incoming_connections: 1, + outgoing_connections: 0, + }, + parents: [ + { + id: "3", + name: "芯片制造", + transmission_mechanism: { + data: [ + { + author: "王芳", + sentences: + "A公司作为国内芯片制造龙头企业,在手订单已排至2024年Q4,预计全年营收增长45%,净利润增长60%以上。公司28nm及以下先进制程产能占比已达到40%,技术实力行业领先", + query_part: "A公司在手订单充足,预计营收增长45%", + match_score: "好", + declare_date: "2024-04-10T00:00:00", + report_title: "A公司深度研究:受益芯片制造景气周期", + }, + ], + }, + direction: "positive", + strength: 60, + is_circular: false, + }, + ], + children: [], + }, + }, + 5: { + success: true, + data: { + node: { + id: "5", + name: "B公司", + type: "company", + description: + "B公司专注于芯片封装测试领域,随着上游制造产能释放,公司订单饱满,业绩稳步增长。", + importance_score: 25, + stock_code: "600001", + total_connections: 1, + incoming_connections: 1, + outgoing_connections: 0, + }, + parents: [ + { + id: "3", + name: "芯片制造", + transmission_mechanism: { + data: [ + { + author: "赵强", + organization: "华泰证券", + sentences: + "随着芯片制造产能的大规模扩张,上游设备和材料供应商迎来历史性机遇。B公司作为核心配套企业,订单量同比增长55%,产品供不应求,预计2024年营收将突破百亿大关", + query_part: "B公司订单增长55%,营收将破百亿", + match_score: "好", + declare_date: "2024-05-08T00:00:00", + report_title: "B公司跟踪报告:芯片产业链配套龙头崛起", + }, + ], + }, + direction: "positive", + strength: 50, + is_circular: false, + }, + ], + children: [], + }, + }, + 6: { + success: true, + data: { + node: { + id: "6", + name: "相关政策", + type: "policy", + description: + "国家出台了一系列产业扶持政策,包括财政补贴、税收减免和研发支持,旨在推动产业自主创新和进口替代。", + importance_score: 30, + total_connections: 2, + incoming_connections: 0, + outgoing_connections: 2, + }, + parents: [], + children: [ + { + id: "1", + name: "主要事件", + transmission_mechanism: { + data: [ + { + author: "国务院", + sentences: + "为加快实施创新驱动发展战略,推动产业转型升级,国家将对重点领域给予财政补贴支持,单个项目最高补贴金额可达5000万元,同时享受研发费用加计扣除175%的税收优惠政策", + query_part: + "国家财政补贴最高5000万元,研发费用加计扣除175%", + match_score: "好", + declare_date: "2024-01-15T00:00:00", + report_title: "关于促进产业高质量发展的若干政策措施", + }, + { + author: "工信部", + sentences: + "将重点支持关键核心技术攻关和产业化应用,建立产业发展专项基金,规模达到1000亿元,引导社会资本共同参与产业发展", + query_part: "设立1000亿元产业发展专项基金", + match_score: "好", + declare_date: "2024-02-01T00:00:00", + report_title: "产业发展专项基金管理办法", + }, + ], + }, + direction: "positive", + strength: 70, + is_circular: false, + }, + { + id: "2", + name: "半导体行业", + transmission_mechanism: { + data: [ + { + author: "国家发改委", + sentences: + "《国家集成电路产业发展推进纲要》明确提出,到2025年半导体产业自给率要达到70%以上,国家将设立专项基金规模超过3000亿元,重点支持半导体设备、材料、设计等关键环节。同时,通过进口替代战略,加快培育本土产业链", + query_part: "半导体自给率目标70%,专项基金3000亿", + match_score: "好", + declare_date: "2024-02-01T00:00:00", + report_title: "国家集成电路产业发展推进纲要(2024-2030)", + }, + { + author: "工信部", + sentences: + "将重点支持关键核心技术攻关和产业化应用,建立产业发展专项基金,规模达到1000亿元,引导社会资本共同参与产业发展。通过税收优惠、研发补贴等政策工具,为半导体行业创造良好的发展环境", + query_part: "设立1000亿元产业发展专项基金", + match_score: "好", + declare_date: "2024-02-01T00:00:00", + report_title: "产业发展专项基金管理办法", + }, + ], + }, + direction: "positive", + strength: 60, + is_circular: false, + }, + ], + }, + }, + }; - // 历史数据:涨停 + 上证涨跌幅 - if (isPast || isToday) { - item.zt_count = Math.floor(seededRandom(dateSeed) * 80 + 30); // 30-110 - item.top_sector = currentConcept; // 使用连续概念 - item.index_change = parseFloat((seededRandom(dateSeed + 2) * 4 - 2).toFixed(2)); // -2% ~ +2% - } + // 返回对应节点的详情,如果不存在则返回默认数据 + const nodeDetail = nodeDetailsMap[nodeId] || { + success: true, + data: { + node: { + id: nodeId, + name: "未知节点", + type: "other", + description: "该节点暂无详细信息", + importance_score: 0, + total_connections: 0, + incoming_connections: 0, + outgoing_connections: 0, + }, + parents: [], + children: [], + }, + }; - // 未来数据:事件数量 - if (isFuture) { - const hasEvents = seededRandom(dateSeed + 3) > 0.4; // 60% 概率有事件 - if (hasEvents) { - item.event_count = Math.floor(seededRandom(dateSeed + 4) * 10 + 1); // 1-10 - } - } + return HttpResponse.json(nodeDetail); + }), - // 今天:同时有涨停和事件 - if (isToday) { - item.event_count = Math.floor(seededRandom(dateSeed + 5) * 8 + 2); // 2-10 - } + // ==================== 投资日历相关 ==================== - data.push(item); - } + // 获取月度事件统计 + http.get("/api/v1/calendar/event-counts", async ({ request }) => { + await delay(300); - return HttpResponse.json({ - success: true, - data: data, - year: year, - month: month - }); - } catch (error) { - console.error('[Mock] 获取日历综合数据失败:', error); - return HttpResponse.json( - { - success: false, - error: '获取日历综合数据失败', - data: [] - }, - { status: 500 } - ); + const url = new URL(request.url); + const year = parseInt(url.searchParams.get("year")); + const month = parseInt(url.searchParams.get("month")); + + console.log("[Mock] 获取月度事件统计:", { year, month }); + + const eventCounts = getMockEventCountsForMonth(year, month); + + return HttpResponse.json({ + success: true, + data: eventCounts, + }); + }), + + // 获取指定日期的事件列表 + http.get("/api/v1/calendar/events", async ({ request }) => { + await delay(300); + + const url = new URL(request.url); + const dateStr = url.searchParams.get("date"); + const type = url.searchParams.get("type") || "all"; + + console.log("[Mock] 获取日历事件列表:", { date: dateStr, type }); + + if (!dateStr) { + return HttpResponse.json( + { + success: false, + error: "Date parameter required", + }, + { status: 400 } + ); + } + + const events = getMockFutureEvents(dateStr, type); + + return HttpResponse.json({ + success: true, + data: events, + }); + }), + + // 切换未来事件关注状态 + http.post("/api/v1/calendar/events/:eventId/follow", async ({ params }) => { + await delay(300); + + const { eventId } = params; + + console.log("[Mock] 切换事件关注状态, eventId:", eventId); + + // 简单返回成功,实际状态管理可以后续完善 + return HttpResponse.json({ + success: true, + data: { + is_following: true, + message: "关注成功", + }, + }); + }), + + // ==================== 历史事件对比相关 ==================== + + // 获取历史事件列表 + http.get("/api/events/:eventId/historical", async ({ params }) => { + await delay(400); + + const { eventId } = params; + + console.log("[Mock] 获取历史事件列表, eventId:", eventId); + + // 生成历史事件数据 + const generateHistoricalEvents = (count = 5) => { + const events = []; + const eventTitles = [ + "芯片产业链政策扶持升级", + "新能源汽车销量创历史新高", + "人工智能大模型技术突破", + "半导体设备国产化加速", + "数字经济政策利好发布", + "新能源产业链整合提速", + "医药创新药获批上市", + "5G应用场景扩展", + "智能驾驶技术迭代升级", + "储能行业景气度上行", + ]; + + const importanceLevels = [1, 2, 3, 4, 5]; + + for (let i = 0; i < count; i++) { + const daysAgo = Math.floor(Math.random() * 180) + 30; // 30-210 天前 + const date = new Date(); + date.setDate(date.getDate() - daysAgo); + + const importance = + importanceLevels[Math.floor(Math.random() * importanceLevels.length)]; + const title = eventTitles[i % eventTitles.length]; + + // 带引用来源的研报数据 + const researchReports = [ + { + author: "中信证券", + report_title: `${title}深度研究报告`, + declare_date: new Date( + date.getTime() - + Math.floor(Math.random() * 10) * 24 * 60 * 60 * 1000 + ).toISOString(), + }, + { + author: "国泰君安", + report_title: `行业专题:${title}影响分析`, + declare_date: new Date( + date.getTime() - + Math.floor(Math.random() * 15) * 24 * 60 * 60 * 1000 + ).toISOString(), + }, + { + author: "华泰证券", + report_title: `${title}投资机会深度解析`, + declare_date: new Date( + date.getTime() - + Math.floor(Math.random() * 20) * 24 * 60 * 60 * 1000 + ).toISOString(), + }, + ]; + + // 生成带引用标记的content(data结构) + const contentWithCitations = { + data: [ + { + query_part: `${title}的详细描述。该事件对相关产业链产生重要影响【1】,市场关注度高,相关概念股表现活跃。`, + sentences: `根据券商研报分析,${title}将推动相关产业链快速发展【2】。预计未来${Math.floor( + Math.random() * 2 + 1 + )}年内,相关企业营收增速有望达到${Math.floor( + Math.random() * 30 + 20 + )}%以上【3】。该事件的影响范围广泛,涉及多个细分领域,投资机会显著。`, + match_score: + importance >= 4 ? "好" : importance >= 2 ? "中" : "一般", + author: researchReports[0].author, + declare_date: researchReports[0].declare_date, + report_title: researchReports[0].report_title, + }, + { + query_part: `市场分析师认为,该事件将带动产业链上下游企业协同发展【2】,形成良性循环。`, + sentences: `从产业趋势来看,相关板块估值仍处于合理区间,具备较高的安全边际。机构投资者持续加仓相关标的,显示出对长期发展前景的看好。`, + match_score: importance >= 3 ? "好" : "中", + author: researchReports[1].author, + declare_date: researchReports[1].declare_date, + report_title: researchReports[1].report_title, + }, + { + query_part: `根据行业数据显示,受此事件影响,相关企业订单量同比增长${Math.floor( + Math.random() * 40 + 30 + )}%【3】。`, + sentences: `行业景气度持续提升,龙头企业凭借技术优势和规模效应,市场份额有望进一步扩大。建议关注产业链核心环节的投资机会。`, + match_score: "好", + author: researchReports[2].author, + declare_date: researchReports[2].declare_date, + report_title: researchReports[2].report_title, + }, + ], + }; + + events.push({ + id: `hist_event_${i + 1}`, + title: title, + content: contentWithCitations, // 升级版本:带引用来源的data结构 + description: `${title}的详细描述。该事件对相关产业链产生重要影响,市场关注度高,相关概念股表现活跃。`, // 降级兼容 + date: date.toISOString().split("T")[0], + importance: importance, + similarity: Math.floor(Math.random() * 10) + 1, // 1-10 + impact_sectors: [ + ["半导体", "芯片设计", "EDA"], + ["新能源汽车", "锂电池", "充电桩"], + ["人工智能", "算力", "大模型"], + ["半导体设备", "国产替代", "集成电路"], + ["数字经济", "云计算", "大数据"], + ][i % 5], + affected_stocks_count: Math.floor(Math.random() * 30) + 10, // 10-40 只股票 + avg_change_pct: parseFloat((Math.random() * 10 - 2).toFixed(2)), // -2% to +8% + created_at: date.toISOString(), + }); + } + + // 按日期降序排序 + return events.sort((a, b) => new Date(b.date) - new Date(a.date)); + }; + + try { + const historicalEvents = generateHistoricalEvents(5); + + return HttpResponse.json({ + success: true, + data: historicalEvents, + total: historicalEvents.length, + message: "获取历史事件列表成功", + }); + } catch (error) { + console.error("[Mock] 获取历史事件列表失败:", error); + return HttpResponse.json( + { + success: false, + error: "获取历史事件列表失败", + data: [], + }, + { status: 500 } + ); + } + }), + + // 获取历史事件相关股票 + http.get("/api/historical-events/:eventId/stocks", async ({ params }) => { + await delay(500); + + const { eventId } = params; + + console.log("[Mock] 获取历史事件相关股票, eventId:", eventId); + + // 生成历史事件相关股票数据 + const generateHistoricalEventStocks = (count = 10) => { + const stocks = []; + const sectors = [ + "半导体", + "新能源", + "医药", + "消费电子", + "人工智能", + "5G通信", + ]; + const stockNames = [ + "中芯国际", + "长江存储", + "华为海思", + "紫光国微", + "兆易创新", + "宁德时代", + "比亚迪", + "隆基绿能", + "阳光电源", + "亿纬锂能", + "恒瑞医药", + "迈瑞医疗", + "药明康德", + "泰格医药", + "康龙化成", + "立讯精密", + "歌尔声学", + "京东方A", + "TCL科技", + "海康威视", + "科大讯飞", + "商汤科技", + "寒武纪", + "海光信息", + "中兴通讯", + ]; + + for (let i = 0; i < count; i++) { + const stockCode = `${Math.random() > 0.5 ? "6" : "0"}${String( + Math.floor(Math.random() * 100000) + ).padStart(5, "0")}`; + const changePct = (Math.random() * 15 - 3).toFixed(2); // -3% ~ +12% + const correlation = (Math.random() * 0.4 + 0.6).toFixed(2); // 0.6 ~ 1.0 + + stocks.push({ + id: `stock_${i}`, + stock_code: `${stockCode}.${Math.random() > 0.5 ? "SH" : "SZ"}`, + stock_name: stockNames[i % stockNames.length], + sector: sectors[Math.floor(Math.random() * sectors.length)], + correlation: parseFloat(correlation), + event_day_change_pct: parseFloat(changePct), + relation_desc: { + data: [ + { + query_part: `该公司是${ + sectors[Math.floor(Math.random() * sectors.length)] + }行业龙头,受事件影响显著,市场关注度高,订单量同比增长${Math.floor( + Math.random() * 50 + 20 + )}%`, + sentences: `根据行业研究报告,该公司在${ + sectors[Math.floor(Math.random() * sectors.length)] + }领域具有核心技术优势,产能利用率达到${Math.floor( + Math.random() * 20 + 80 + )}%,随着事件的深入发展,公司业绩有望持续受益。机构预测未来三年复合增长率将达到${Math.floor( + Math.random() * 30 + 15 + )}%以上`, + match_score: + correlation > 0.8 ? "好" : correlation > 0.6 ? "中" : "一般", + author: ["中信证券", "国泰君安", "华泰证券", "招商证券"][ + Math.floor(Math.random() * 4) + ], + declare_date: new Date( + Date.now() - + Math.floor(Math.random() * 90) * 24 * 60 * 60 * 1000 + ).toISOString(), + report_title: `${ + stockNames[i % stockNames.length] + }深度研究报告`, + }, + ], + }, + }); + } + + // 按相关度降序排序 + return stocks.sort((a, b) => b.correlation - a.correlation); + }; + + try { + const stocks = generateHistoricalEventStocks(15); + + return HttpResponse.json({ + success: true, + data: stocks, + message: "获取历史事件相关股票成功", + }); + } catch (error) { + console.error("[Mock] 获取历史事件相关股票失败:", error); + return HttpResponse.json( + { + success: false, + error: "获取历史事件相关股票失败", + data: [], + }, + { status: 500 } + ); + } + }), + + // ==================== 评论相关 ==================== + + // 获取事件评论列表 + http.get("/api/events/:eventId/posts", async ({ params, request }) => { + await delay(300); + + const { eventId } = params; + const url = new URL(request.url); + const sort = url.searchParams.get("sort") || "latest"; + const page = parseInt(url.searchParams.get("page") || "1"); + const perPage = parseInt(url.searchParams.get("per_page") || "20"); + + console.log("[Mock] 获取评论列表, eventId:", eventId, "sort:", sort); + + try { + // 从内存存储获取评论列表 + const allComments = getOrInitComments(eventId); + + // ✅ 创建副本并排序(避免直接修改原数组) + let sortedComments = [...allComments]; + if (sort === "hot") { + sortedComments.sort((a, b) => b.likes_count - a.likes_count); + } else { + // 默认按时间升序(oldest first)- 最旧评论在前,最新在后 + sortedComments.sort( + (a, b) => new Date(a.created_at) - new Date(b.created_at) + ); + } + + // 分页处理(使用排序后的副本) + const startIndex = (page - 1) * perPage; + const endIndex = startIndex + perPage; + const paginatedComments = sortedComments.slice(startIndex, endIndex); + + return HttpResponse.json({ + success: true, + data: paginatedComments, + pagination: { + page: page, + per_page: perPage, + total: allComments.length, + pages: Math.ceil(allComments.length / perPage), + has_prev: page > 1, + has_next: endIndex < allComments.length, + }, + message: "获取评论成功", + }); + } catch (error) { + console.error("[Mock] 获取评论列表失败:", error); + return HttpResponse.json( + { + success: false, + error: "获取评论失败", + data: [], + }, + { status: 500 } + ); + } + }), + + // 发表评论 + http.post("/api/events/:eventId/posts", async ({ params, request }) => { + await delay(500); + + const { eventId } = params; + const body = await request.json(); + + console.log("[Mock] 发表评论, eventId:", eventId, "content:", body.content); + + try { + // 获取当前登录用户信息 + const currentUser = getCurrentUser(); + + // 模拟创建新评论 + const newComment = { + id: `comment_${eventId}_${Date.now()}`, + content: body.content, + content_type: body.content_type || "text", + author: { + id: currentUser?.id || "current_user", + // 与导航区保持一致:优先显示昵称 + username: + currentUser?.nickname || + currentUser?.username || + currentUser?.email || + "当前用户", + avatar: currentUser?.avatar_url || null, + }, + created_at: new Date().toISOString(), + likes_count: 0, + is_liked: false, + }; + + // 将新评论添加到内存存储(插入到列表开头) + const comments = getOrInitComments(eventId); + comments.unshift(newComment); + + console.log( + "[Mock] 评论已添加到内存存储, 当前评论总数:", + comments.length + ); + + return HttpResponse.json({ + success: true, + data: newComment, + message: "评论发布成功", + }); + } catch (error) { + console.error("[Mock] 发表评论失败:", error); + return HttpResponse.json( + { + success: false, + error: "评论发布失败", + message: "系统错误,请稍后重试", + }, + { status: 500 } + ); + } + }), + + // 删除帖子/评论 + http.delete("/api/posts/:postId", async ({ params }) => { + await delay(300); + const { postId } = params; + + console.log("[Mock] 删除帖子, postId:", postId); + + try { + // 从内存存储中删除评论 + let deleted = false; + for (const [eventId, comments] of commentsStore.entries()) { + const index = comments.findIndex( + (c) => String(c.id) === String(postId) + ); + if (index !== -1) { + comments.splice(index, 1); + deleted = true; + console.log("[Mock] 评论已从事件", eventId, "中删除"); + break; } - }), + } - // ==================== 事件有效性统计 ==================== + if (!deleted) { + console.log("[Mock] 未找到评论,但仍返回成功(可能是乐观更新的评论)"); + } - // 获取事件有效性统计数据 - http.get('/api/v1/events/effectiveness-stats', async ({ request }) => { - await delay(300); + return HttpResponse.json({ + success: true, + message: "删除成功", + }); + } catch (error) { + console.error("[Mock] 删除帖子失败:", error); + return HttpResponse.json( + { + success: false, + error: "删除失败", + message: "系统错误,请稍后重试", + }, + { status: 500 } + ); + } + }), - const url = new URL(request.url); - const days = parseInt(url.searchParams.get('days') || '30'); + // ==================== 日历综合数据(一次性获取所有数据)==================== - console.log('[Mock] 获取事件有效性统计:', { days }); + // 获取日历综合数据(涨停 + 事件 + 上证涨跌幅) + http.get("/api/v1/calendar/combined-data", async ({ request }) => { + await delay(400); - try { - // 模拟统计数据 - const summary = { - totalEvents: Math.floor(Math.random() * 200) + 100, // 总事件数 - effectiveEvents: Math.floor(Math.random() * 150) + 50, // 有效事件数 - effectiveRate: parseFloat((Math.random() * 30 + 60).toFixed(1)), // 有效率 60%-90% - avgReturn: parseFloat((Math.random() * 5 + 2).toFixed(2)), // 平均收益 2%-7% - maxReturn: parseFloat((Math.random() * 15 + 5).toFixed(2)), // 最大收益 5%-20% - hitRate: parseFloat((Math.random() * 20 + 70).toFixed(1)), // 命中率 70%-90% - avgHoldDays: Math.floor(Math.random() * 5) + 3, // 平均持有天数 3-8 - winLossRatio: parseFloat((Math.random() * 2 + 1.5).toFixed(2)), // 盈亏比 1.5-3.5 - }; + const url = new URL(request.url); + const year = + parseInt(url.searchParams.get("year")) || new Date().getFullYear(); + const month = + parseInt(url.searchParams.get("month")) || new Date().getMonth() + 1; - // 模拟表现最佳的事件 - const topPerformers = []; - const eventTitles = [ - '重大政策利好:半导体产业扶持政策出台', - '人工智能板块迎来突破性进展', - '新能源汽车销量创历史新高', - '消费复苏数据超预期', - '央行降准释放流动性', - '科技股龙头财报超预期', - '军工板块获重大订单', - '医药创新药获批上市', - ]; + console.log("[Mock] 获取日历综合数据:", { year, month }); - for (let i = 0; i < 5; i++) { - topPerformers.push({ - id: i + 1, - title: eventTitles[i % eventTitles.length], - importance: ['S', 'A', 'B'][Math.floor(Math.random() * 3)], - created_at: new Date(Date.now() - Math.random() * days * 24 * 60 * 60 * 1000).toISOString(), - avg_chg: parseFloat((Math.random() * 8 + 2).toFixed(2)), - max_chg: parseFloat((Math.random() * 15 + 5).toFixed(2)), - stock_count: Math.floor(Math.random() * 20) + 5, - }); - } + try { + const data = []; + const today = new Date(); + const daysInMonth = new Date(year, month, 0).getDate(); - // 按 max_chg 降序排列 - topPerformers.sort((a, b) => b.max_chg - a.max_chg); + // 热门概念列表 + const hotConcepts = [ + "人工智能", + "华为鸿蒙", + "机器人", + "芯片", + "算力", + "新能源", + "固态电池", + "量子计算", + "低空经济", + "智能驾驶", + "光伏", + "储能", + ]; - return HttpResponse.json({ - code: 200, - data: { - summary, - topPerformers, - }, - message: '获取成功' - }); - } catch (error) { - console.error('[Mock] 获取事件有效性统计失败:', error); - return HttpResponse.json( - { - code: 500, - error: '获取事件有效性统计失败', - data: null - }, - { status: 500 } - ); + // 预生成概念连续段(用于测试跨天连接效果) + // 每段 2-4 天使用相同概念 + let currentConcept = hotConcepts[0]; + let conceptDaysLeft = 0; + let conceptIndex = 0; + + for (let day = 1; day <= daysInMonth; day++) { + const date = new Date(year, month - 1, day); + const dayOfWeek = date.getDay(); + + // 跳过周末 + if (dayOfWeek === 0 || dayOfWeek === 6) continue; + + const dateStr = `${year}${String(month).padStart(2, "0")}${String( + day + ).padStart(2, "0")}`; + const isPast = date < today; + const isToday = date.toDateString() === today.toDateString(); + const isFuture = date > today; + + // 使用日期作为种子生成一致的随机数 + const dateSeed = year * 10000 + month * 100 + day; + const seededRandom = (seed) => { + const x = Math.sin(seed) * 10000; + return x - Math.floor(x); + }; + + // 概念连续段逻辑:每段 2-4 天使用相同概念 + if (conceptDaysLeft <= 0) { + conceptIndex = Math.floor( + seededRandom(dateSeed + 100) * hotConcepts.length + ); + currentConcept = hotConcepts[conceptIndex]; + conceptDaysLeft = Math.floor(seededRandom(dateSeed + 101) * 3) + 2; // 2-4 天 } - }), + conceptDaysLeft--; - // ==================== 涨停题材散点图数据 ==================== + const item = { + date: dateStr, + zt_count: 0, + top_sector: "", + event_count: 0, + index_change: null, + }; - // 获取涨停题材散点图数据 - http.get('/api/v1/zt/theme-scatter', async ({ request }) => { - await delay(400); - - const url = new URL(request.url); - const days = parseInt(url.searchParams.get('days') || '5'); - const date = url.searchParams.get('date'); - - console.log('[Mock] 获取涨停题材散点图:', { days, date }); - - try { - // 生成可用日期列表(最近5个交易日) - const availableDates = []; - const today = new Date(); - let tradingDays = 0; - let checkDate = new Date(today); - - while (tradingDays < days) { - const dayOfWeek = checkDate.getDay(); - if (dayOfWeek !== 0 && dayOfWeek !== 6) { - const dateStr = checkDate.toISOString().split('T')[0]; - availableDates.push({ - date: dateStr, - formatted: `${checkDate.getMonth() + 1}月${checkDate.getDate()}日`, - }); - tradingDays++; - } - checkDate.setDate(checkDate.getDate() - 1); - } - - // 使用日期作为随机种子 - const targetDate = date || availableDates[0]?.date; - const dateSeed = targetDate ? parseInt(targetDate.replace(/-/g, '')) : Date.now(); - const seededRandom = (offset = 0) => { - const x = Math.sin(dateSeed + offset) * 10000; - return x - Math.floor(x); - }; - - // 题材数据模板 - const themeTemplates = [ - { label: '人工智能', baseX: 5, baseY: 15, status: 'rising' }, - { label: '机器人', baseX: 4, baseY: 12, status: 'rising' }, - { label: '半导体', baseX: 3, baseY: 10, status: 'clustering' }, - { label: '光模块', baseX: 6, baseY: 8, status: 'rising' }, - { label: '算力', baseX: 4, baseY: 9, status: 'clustering' }, - { label: '新能源汽车', baseX: 2, baseY: 7, status: 'lurking' }, - { label: '固态电池', baseX: 3, baseY: 6, status: 'lurking' }, - { label: '光伏', baseX: 2, baseY: 5, status: 'declining' }, - { label: '储能', baseX: 2, baseY: 4, status: 'declining' }, - { label: '医药', baseX: 1, baseY: 3, status: 'lurking' }, - ]; - - // 状态颜色映射 - const statusColors = { - rising: '#FF4D4F', - declining: '#52C41A', - lurking: '#1890FF', - clustering: '#722ED1', - }; - - // 生成题材数据 - const themes = themeTemplates.map((template, index) => { - const xVariation = seededRandom(index * 10) * 2 - 1; // -1 ~ 1 - const yVariation = seededRandom(index * 10 + 1) * 4 - 2; // -2 ~ 2 - - return { - label: template.label, - x: Math.max(1, Math.round(template.baseX + xVariation)), // 辨识度(最高板高度) - y: Math.max(1, Math.round(template.baseY + yVariation)), // 热度(涨停家数) - status: template.status, - color: statusColors[template.status], - countTrend: parseFloat((seededRandom(index * 10 + 2) * 40 - 20).toFixed(1)), // -20% ~ 20% - boardTrend: parseFloat((seededRandom(index * 10 + 3) * 2 - 1).toFixed(1)), // -1 ~ 1 - history: [], // 历史数据(简化版不填充) - }; - }); - - return HttpResponse.json({ - success: true, - data: { - themes, - availableDates, - currentDate: targetDate, - }, - message: '获取成功' - }); - } catch (error) { - console.error('[Mock] 获取涨停题材散点图失败:', error); - return HttpResponse.json( - { - success: false, - error: '获取涨停题材散点图失败', - data: null - }, - { status: 500 } - ); + // 历史数据:涨停 + 上证涨跌幅 + if (isPast || isToday) { + item.zt_count = Math.floor(seededRandom(dateSeed) * 80 + 30); // 30-110 + item.top_sector = currentConcept; // 使用连续概念 + item.index_change = parseFloat( + (seededRandom(dateSeed + 2) * 4 - 2).toFixed(2) + ); // -2% ~ +2% } - }), + + // 未来数据:事件数量 + if (isFuture) { + const hasEvents = seededRandom(dateSeed + 3) > 0.4; // 60% 概率有事件 + if (hasEvents) { + item.event_count = Math.floor(seededRandom(dateSeed + 4) * 10 + 1); // 1-10 + } + } + + // 今天:同时有涨停和事件 + if (isToday) { + item.event_count = Math.floor(seededRandom(dateSeed + 5) * 8 + 2); // 2-10 + } + + data.push(item); + } + + return HttpResponse.json({ + success: true, + data: data, + year: year, + month: month, + }); + } catch (error) { + console.error("[Mock] 获取日历综合数据失败:", error); + return HttpResponse.json( + { + success: false, + error: "获取日历综合数据失败", + data: [], + }, + { status: 500 } + ); + } + }), + + // ==================== 事件有效性统计 ==================== + + // 获取事件有效性统计数据 + http.get("/api/v1/events/effectiveness-stats", async ({ request }) => { + await delay(300); + + const url = new URL(request.url); + const days = parseInt(url.searchParams.get("days") || "30"); + + console.log("[Mock] 获取事件有效性统计:", { days }); + + try { + // 模拟市场涨跌分布数据 + const totalCount = 5200; + const risingCount = Math.floor(Math.random() * 1500) + 1800; // 1800-3300 + const fallingCount = Math.floor(Math.random() * 1500) + 1200; // 1200-2700 + const flatCount = totalCount - risingCount - fallingCount; + const marketStats = { + totalCount, + risingCount, + fallingCount, + flatCount: Math.max(0, flatCount), + risingRate: parseFloat(((risingCount / totalCount) * 100).toFixed(1)), + }; + + // 模拟统计数据 + const totalEvents = Math.floor(Math.random() * 50) + 30; + const totalStocks = Math.floor(Math.random() * 300) + 200; + const summary = { + totalEvents, + totalStocks, + avgChg: parseFloat((Math.random() * 6 - 2).toFixed(2)), // -2% ~ 4% + maxChg: parseFloat((Math.random() * 10 + 2).toFixed(2)), // 2% ~ 12% + positiveRate: parseFloat((Math.random() * 30 + 50).toFixed(1)), // 50% ~ 80% + }; + + // 模拟表现最佳的事件 TOP10 + const topPerformers = []; + const eventTitles = [ + "重大政策利好:半导体产业扶持政策出台", + "人工智能板块迎来突破性进展", + "新能源汽车销量创历史新高", + "消费复苏数据超预期", + "央行降准释放流动性", + "科技股龙头财报超预期", + "军工板块获重大订单", + "医药创新药获批上市", + "光伏产业链订单大增", + "锂电池技术突破", + "芯片国产化加速", + "机器人概念持续活跃", + ]; + + for (let i = 0; i < 12; i++) { + topPerformers.push({ + id: i + 1, + title: eventTitles[i % eventTitles.length], + importance: ["S", "A", "B"][Math.floor(Math.random() * 3)], + created_at: new Date( + Date.now() - Math.random() * days * 24 * 60 * 60 * 1000 + ).toISOString(), + avgChg: parseFloat((Math.random() * 8 + 1).toFixed(2)), + maxChg: parseFloat((Math.random() * 15 + 5).toFixed(2)), + stockCount: Math.floor(Math.random() * 20) + 5, + }); + } + + // 按 avgChg 降序排列 + topPerformers.sort((a, b) => b.avgChg - a.avgChg); + + // 模拟股票 TOP10 + const topStocks = []; + const stockNames = [ + { code: "300750.SZ", name: "宁德时代" }, + { code: "002594.SZ", name: "比亚迪" }, + { code: "601012.SH", name: "隆基绿能" }, + { code: "300274.SZ", name: "阳光电源" }, + { code: "002475.SZ", name: "立讯精密" }, + { code: "300059.SZ", name: "东方财富" }, + { code: "600519.SH", name: "贵州茅台" }, + { code: "000858.SZ", name: "五粮液" }, + { code: "601318.SH", name: "中国平安" }, + { code: "600036.SH", name: "招商银行" }, + { code: "002371.SZ", name: "北方华创" }, + { code: "688981.SH", name: "中芯国际" }, + ]; + + for (let i = 0; i < 12; i++) { + const stock = stockNames[i]; + topStocks.push({ + stockCode: stock.code, + stockName: stock.name, + maxChg: parseFloat((Math.random() * 12 + 3).toFixed(2)), + eventCount: Math.floor(Math.random() * 5) + 1, + }); + } + + // 按 maxChg 降序排列 + topStocks.sort((a, b) => b.maxChg - a.maxChg); + + return HttpResponse.json({ + code: 200, + data: { + summary, + marketStats, + topPerformers, + topStocks, + }, + message: "获取成功", + }); + } catch (error) { + console.error("[Mock] 获取事件有效性统计失败:", error); + return HttpResponse.json( + { + code: 500, + error: "获取事件有效性统计失败", + data: null, + }, + { status: 500 } + ); + } + }), + + // ==================== 涨停题材散点图数据 ==================== + + // 获取涨停题材散点图数据 + http.get("/api/v1/zt/theme-scatter", async ({ request }) => { + await delay(400); + + const url = new URL(request.url); + const days = parseInt(url.searchParams.get("days") || "5"); + const date = url.searchParams.get("date"); + + console.log("[Mock] 获取涨停题材散点图:", { days, date }); + + try { + // 生成可用日期列表(最近5个交易日) + const availableDates = []; + const today = new Date(); + let tradingDays = 0; + let checkDate = new Date(today); + + while (tradingDays < days) { + const dayOfWeek = checkDate.getDay(); + if (dayOfWeek !== 0 && dayOfWeek !== 6) { + const dateStr = checkDate.toISOString().split("T")[0]; + availableDates.push({ + date: dateStr, + formatted: `${checkDate.getMonth() + 1}月${checkDate.getDate()}日`, + }); + tradingDays++; + } + checkDate.setDate(checkDate.getDate() - 1); + } + + // 使用日期作为随机种子 + const targetDate = date || availableDates[0]?.date; + const dateSeed = targetDate + ? parseInt(targetDate.replace(/-/g, "")) + : Date.now(); + const seededRandom = (offset = 0) => { + const x = Math.sin(dateSeed + offset) * 10000; + return x - Math.floor(x); + }; + + // 题材数据模板 + const themeTemplates = [ + { label: "人工智能", baseX: 5, baseY: 15, status: "rising" }, + { label: "机器人", baseX: 4, baseY: 12, status: "rising" }, + { label: "半导体", baseX: 3, baseY: 10, status: "clustering" }, + { label: "光模块", baseX: 6, baseY: 8, status: "rising" }, + { label: "算力", baseX: 4, baseY: 9, status: "clustering" }, + { label: "新能源汽车", baseX: 2, baseY: 7, status: "lurking" }, + { label: "固态电池", baseX: 3, baseY: 6, status: "lurking" }, + { label: "光伏", baseX: 2, baseY: 5, status: "declining" }, + { label: "储能", baseX: 2, baseY: 4, status: "declining" }, + { label: "医药", baseX: 1, baseY: 3, status: "lurking" }, + ]; + + // 状态颜色映射 + const statusColors = { + rising: "#FF4D4F", + declining: "#52C41A", + lurking: "#1890FF", + clustering: "#722ED1", + }; + + // 生成题材数据 + const themes = themeTemplates.map((template, index) => { + const xVariation = seededRandom(index * 10) * 2 - 1; // -1 ~ 1 + const yVariation = seededRandom(index * 10 + 1) * 4 - 2; // -2 ~ 2 + + return { + label: template.label, + x: Math.max(1, Math.round(template.baseX + xVariation)), // 辨识度(最高板高度) + y: Math.max(1, Math.round(template.baseY + yVariation)), // 热度(涨停家数) + status: template.status, + color: statusColors[template.status], + countTrend: parseFloat( + (seededRandom(index * 10 + 2) * 40 - 20).toFixed(1) + ), // -20% ~ 20% + boardTrend: parseFloat( + (seededRandom(index * 10 + 3) * 2 - 1).toFixed(1) + ), // -1 ~ 1 + history: [], // 历史数据(简化版不填充) + }; + }); + + // 计算统计数据 + const totalLimitUp = themes.reduce((sum, t) => sum + t.y, 0); + const totalEvents = themes.length; + const indexChange = parseFloat((seededRandom(999) * 4 - 2).toFixed(2)); // 上证涨跌 -2% ~ 2% + + return HttpResponse.json({ + success: true, + data: { + themes, + availableDates, + currentDate: targetDate, + totalLimitUp, + totalEvents, + indexChange, + }, + message: "获取成功", + }); + } catch (error) { + console.error("[Mock] 获取涨停题材散点图失败:", error); + return HttpResponse.json( + { + success: false, + error: "获取涨停题材散点图失败", + data: null, + }, + { status: 500 } + ); + } + }), ];