// src/mocks/handlers/account.js import { http, HttpResponse, delay } from 'msw'; import { getCurrentUser } from '../data/users'; import { mockWatchlist, mockRealtimeQuotes, mockFollowingEvents, mockEventComments, mockInvestmentPlans, mockCalendarEvents, mockSubscriptionCurrent, getCalendarEventsByDateRange, getFollowedEvents } from '../data/account'; // 模拟网络延迟(毫秒) const NETWORK_DELAY = 300; export const accountHandlers = [ // ==================== 用户资料管理 ==================== // 1. 获取资料完整度 http.get('/api/account/profile-completeness', async () => { await delay(NETWORK_DELAY); const currentUser = getCurrentUser(); if (!currentUser) { return HttpResponse.json({ success: false, error: '用户未登录' }, { status: 401 }); } console.log('[Mock] 获取资料完整度:', currentUser); const isWechatUser = currentUser.has_wechat || !!currentUser.wechat_openid; const completeness = { hasPassword: !!currentUser.password_hash || !isWechatUser, hasPhone: !!currentUser.phone, hasEmail: !!currentUser.email && currentUser.email.includes('@') && !currentUser.email.endsWith('@valuefrontier.temp'), isWechatUser: isWechatUser }; const totalItems = 3; const completedItems = [completeness.hasPassword, completeness.hasPhone, completeness.hasEmail].filter(Boolean).length; const completenessPercentage = Math.round((completedItems / totalItems) * 100); let needsAttention = false; const missingItems = []; if (isWechatUser && completenessPercentage < 100) { needsAttention = true; if (!completeness.hasPassword) missingItems.push('登录密码'); if (!completeness.hasPhone) missingItems.push('手机号'); if (!completeness.hasEmail) missingItems.push('邮箱'); } const result = { success: true, data: { completeness, completenessPercentage, needsAttention, missingItems, isComplete: completedItems === totalItems, showReminder: needsAttention } }; console.log('[Mock] 资料完整度结果:', result.data); return HttpResponse.json(result); }), // 2. 更新用户资料 http.put('/api/account/profile', async ({ request }) => { await delay(NETWORK_DELAY); const currentUser = getCurrentUser(); if (!currentUser) { return HttpResponse.json({ success: false, error: '用户未登录' }, { status: 401 }); } const body = await request.json(); console.log('[Mock] 更新用户资料:', body); Object.assign(currentUser, body); return HttpResponse.json({ success: true, message: '资料更新成功', data: currentUser }); }), // 3. 获取用户资料 http.get('/api/account/profile', async () => { await delay(NETWORK_DELAY); const currentUser = getCurrentUser(); if (!currentUser) { return HttpResponse.json({ success: false, error: '用户未登录' }, { status: 401 }); } console.log('[Mock] 获取用户资料:', currentUser); return HttpResponse.json({ success: true, data: currentUser }); }), // ==================== 自选股管理 ==================== // 4. 获取自选股列表 http.get('/api/account/watchlist', async () => { await delay(NETWORK_DELAY); const currentUser = getCurrentUser(); if (!currentUser) { return HttpResponse.json( { success: false, error: '未登录' }, { status: 401 } ); } // console.log('[Mock] 获取自选股列表'); // 已关闭:减少日志 return HttpResponse.json({ success: true, data: mockWatchlist }); }), // 5. 获取自选股实时行情 http.get('/api/account/watchlist/realtime', async () => { await delay(200); const currentUser = getCurrentUser(); if (!currentUser) { return HttpResponse.json( { success: false, error: '未登录' }, { status: 401 } ); } console.log('[Mock] 获取自选股实时行情'); return HttpResponse.json({ success: true, data: mockRealtimeQuotes }); }), // 6. 添加自选股 http.post('/api/account/watchlist', async ({ request }) => { await delay(NETWORK_DELAY); const currentUser = getCurrentUser(); if (!currentUser) { return HttpResponse.json( { success: false, error: '未登录' }, { status: 401 } ); } const body = await request.json(); const { stock_code, stock_name } = body; console.log('[Mock] 添加自选股:', { stock_code, stock_name }); const newItem = { id: mockWatchlist.length + 1, user_id: currentUser.id, stock_code, stock_name, added_at: new Date().toISOString(), industry: '未知', current_price: null, change_percent: null }; mockWatchlist.push(newItem); // 同步添加到 mockRealtimeQuotes(导航栏自选股菜单使用此数组) mockRealtimeQuotes.push({ stock_code: stock_code, stock_name: stock_name, current_price: null, change_percent: 0, change: 0, volume: 0, turnover: 0, high: 0, low: 0, open: 0, prev_close: 0, update_time: new Date().toTimeString().slice(0, 8) }); return HttpResponse.json({ success: true, message: '添加成功', data: newItem }); }), // 7. 删除自选股 http.delete('/api/account/watchlist/:id', async ({ params }) => { await delay(NETWORK_DELAY); const currentUser = getCurrentUser(); if (!currentUser) { return HttpResponse.json( { success: false, error: '未登录' }, { status: 401 } ); } const { id } = params; console.log('[Mock] 删除自选股:', id); // 支持按 stock_code 或 id 匹配删除 const index = mockWatchlist.findIndex(item => item.stock_code === id || item.id === parseInt(id) ); if (index !== -1) { const stockCode = mockWatchlist[index].stock_code; mockWatchlist.splice(index, 1); // 同步从 mockRealtimeQuotes 移除 const quotesIndex = mockRealtimeQuotes.findIndex(item => item.stock_code === stockCode); if (quotesIndex !== -1) { mockRealtimeQuotes.splice(quotesIndex, 1); } } return HttpResponse.json({ success: true, message: '删除成功' }); }), // ==================== 事件关注管理 ==================== // 8. 获取关注的事件(使用内存状态动态返回) http.get('/api/account/events/following', async () => { await delay(NETWORK_DELAY); const currentUser = getCurrentUser(); if (!currentUser) { return HttpResponse.json( { success: false, error: '未登录' }, { status: 401 } ); } // 从内存存储获取已关注的事件列表 const followedEvents = getFollowedEvents(); console.log('[Mock] 获取关注的事件, 数量:', followedEvents.length); return HttpResponse.json({ success: true, data: followedEvents }); }), // 9. 获取事件评论 http.get('/api/account/events/comments', async () => { await delay(NETWORK_DELAY); const currentUser = getCurrentUser(); if (!currentUser) { return HttpResponse.json( { success: false, error: '未登录' }, { status: 401 } ); } console.log('[Mock] 获取事件评论'); return HttpResponse.json({ success: true, data: mockEventComments }); }), // 10. 获取事件帖子(用户发布的评论/帖子) http.get('/api/account/events/posts', async () => { await delay(NETWORK_DELAY); const currentUser = getCurrentUser(); if (!currentUser) { return HttpResponse.json( { success: false, error: '未登录' }, { status: 401 } ); } console.log('[Mock] 获取事件帖子'); return HttpResponse.json({ success: true, data: mockEventComments // 复用 mockEventComments 数据 }); }), // ==================== 投资计划与复盘 ==================== // 10. 获取投资计划列表 http.get('/api/account/investment-plans', async () => { await delay(NETWORK_DELAY); const currentUser = getCurrentUser(); if (!currentUser) { return HttpResponse.json( { success: false, error: '未登录' }, { status: 401 } ); } console.log('[Mock] 获取投资计划列表'); return HttpResponse.json({ success: true, data: mockInvestmentPlans }); }), // 11. 创建投资计划 http.post('/api/account/investment-plans', async ({ request }) => { await delay(NETWORK_DELAY); const currentUser = getCurrentUser(); if (!currentUser) { return HttpResponse.json( { success: false, error: '未登录' }, { status: 401 } ); } const body = await request.json(); console.log('[Mock] 创建投资计划:', body); const newPlan = { id: mockInvestmentPlans.length + 301, user_id: currentUser.id, ...body, created_at: new Date().toISOString(), updated_at: new Date().toISOString() }; mockInvestmentPlans.push(newPlan); return HttpResponse.json({ success: true, message: '创建成功', data: newPlan }); }), // 12. 更新投资计划 http.put('/api/account/investment-plans/:id', async ({ request, params }) => { await delay(NETWORK_DELAY); const currentUser = getCurrentUser(); if (!currentUser) { return HttpResponse.json( { success: false, error: '未登录' }, { status: 401 } ); } const { id } = params; const body = await request.json(); console.log('[Mock] 更新投资计划:', { id, body }); const index = mockInvestmentPlans.findIndex(plan => plan.id === parseInt(id)); if (index !== -1) { mockInvestmentPlans[index] = { ...mockInvestmentPlans[index], ...body, updated_at: new Date().toISOString() }; return HttpResponse.json({ success: true, message: '更新成功', data: mockInvestmentPlans[index] }); } return HttpResponse.json({ success: false, error: '计划不存在' }, { status: 404 }); }), // 13. 删除投资计划 http.delete('/api/account/investment-plans/:id', async ({ params }) => { await delay(NETWORK_DELAY); const currentUser = getCurrentUser(); if (!currentUser) { return HttpResponse.json( { success: false, error: '未登录' }, { status: 401 } ); } const { id } = params; console.log('[Mock] 删除投资计划:', id); const index = mockInvestmentPlans.findIndex(plan => plan.id === parseInt(id)); if (index !== -1) { mockInvestmentPlans.splice(index, 1); } return HttpResponse.json({ success: true, message: '删除成功' }); }), // ==================== 投资日历 ==================== // 14. 获取日历事件(可选日期范围)- 合并投资计划和日历事件 http.get('/api/account/calendar/events', async ({ request }) => { await delay(NETWORK_DELAY); // Mock 模式下允许无登录访问,使用默认用户 id: 1 const currentUser = getCurrentUser() || { id: 1 }; const url = new URL(request.url); const startDate = url.searchParams.get('start_date'); const endDate = url.searchParams.get('end_date'); console.log('[Mock] 获取日历事件:', { startDate, endDate }); // 1. 获取日历事件 let calendarEvents = mockCalendarEvents; if (startDate && endDate) { calendarEvents = getCalendarEventsByDateRange(currentUser.id, startDate, endDate); } // 2. 获取投资计划和复盘,转换为日历事件格式 // Mock 模式:不过滤 user_id,显示所有 mock 数据(方便开发测试) const investmentPlansAsEvents = mockInvestmentPlans .map(plan => ({ id: plan.id, user_id: plan.user_id, title: plan.title, date: plan.target_date || plan.date, event_date: plan.target_date || plan.date, type: plan.type, // 'plan' or 'review' category: plan.type === 'plan' ? 'investment_plan' : 'investment_review', description: plan.content || '', importance: 3, // 默认重要度 source: 'user', // 标记为用户创建 stocks: plan.stocks || [], tags: plan.tags || [], status: plan.status, created_at: plan.created_at, updated_at: plan.updated_at })); // 3. 合并两个数据源 const allEvents = [...calendarEvents, ...investmentPlansAsEvents]; // 4. 如果提供了日期范围,对合并后的数据进行过滤 let filteredEvents = allEvents; if (startDate && endDate) { const start = new Date(startDate); const end = new Date(endDate); filteredEvents = allEvents.filter(event => { const eventDate = new Date(event.date || event.event_date); return eventDate >= start && eventDate <= end; }); } console.log('[Mock] 日历事件详情:', { currentUserId: currentUser.id, calendarEvents: calendarEvents.length, investmentPlansAsEvents: investmentPlansAsEvents.length, total: filteredEvents.length, plansCount: filteredEvents.filter(e => e.type === 'plan').length, reviewsCount: filteredEvents.filter(e => e.type === 'review').length }); return HttpResponse.json({ success: true, data: filteredEvents }); }), // 15. 创建日历事件 http.post('/api/account/calendar/events', async ({ request }) => { await delay(NETWORK_DELAY); const currentUser = getCurrentUser(); if (!currentUser) { return HttpResponse.json( { success: false, error: '未登录' }, { status: 401 } ); } const body = await request.json(); console.log('[Mock] 创建日历事件:', body); const newEvent = { id: mockCalendarEvents.length + 401, user_id: currentUser.id, ...body, source: 'user', // 用户创建的事件标记为 'user' created_at: new Date().toISOString() }; mockCalendarEvents.push(newEvent); return HttpResponse.json({ success: true, message: '创建成功', data: newEvent }); }), // 16. 更新日历事件 http.put('/api/account/calendar/events/:id', async ({ request, params }) => { await delay(NETWORK_DELAY); const currentUser = getCurrentUser(); if (!currentUser) { return HttpResponse.json( { success: false, error: '未登录' }, { status: 401 } ); } const { id } = params; const body = await request.json(); console.log('[Mock] 更新日历事件:', { id, body }); const index = mockCalendarEvents.findIndex(event => event.id === parseInt(id)); if (index !== -1) { mockCalendarEvents[index] = { ...mockCalendarEvents[index], ...body }; return HttpResponse.json({ success: true, message: '更新成功', data: mockCalendarEvents[index] }); } return HttpResponse.json({ success: false, error: '事件不存在' }, { status: 404 }); }), // 17. 删除日历事件 http.delete('/api/account/calendar/events/:id', async ({ params }) => { await delay(NETWORK_DELAY); const currentUser = getCurrentUser(); if (!currentUser) { return HttpResponse.json( { success: false, error: '未登录' }, { status: 401 } ); } const { id } = params; console.log('[Mock] 删除日历事件:', id); const index = mockCalendarEvents.findIndex(event => event.id === parseInt(id)); if (index !== -1) { mockCalendarEvents.splice(index, 1); } return HttpResponse.json({ success: true, message: '删除成功' }); }), // ==================== 订阅信息 ==================== // 18. 获取订阅信息 http.get('/api/subscription/info', async () => { await delay(NETWORK_DELAY); const currentUser = getCurrentUser(); if (!currentUser) { return HttpResponse.json({ success: true, data: { type: 'free', status: 'active', is_active: true, days_left: 0, end_date: null } }); } console.log('[Mock] 获取订阅信息:', currentUser); const subscriptionInfo = { type: currentUser.subscription_type || 'free', status: currentUser.subscription_status || 'active', is_active: currentUser.is_subscription_active !== false, days_left: currentUser.subscription_days_left || 0, end_date: currentUser.subscription_end_date || null }; console.log('[Mock] 订阅信息结果:', subscriptionInfo); return HttpResponse.json({ success: true, data: subscriptionInfo }); }), // 19. 获取当前订阅详情 http.get('/api/subscription/current', async () => { await delay(NETWORK_DELAY); const currentUser = getCurrentUser(); if (!currentUser) { console.warn('[Mock API] 获取订阅详情失败: 用户未登录'); return HttpResponse.json( { success: false, error: '未登录' }, { status: 401 } ); } // 基于当前用户的订阅类型返回详情 const userSubscriptionType = (currentUser.subscription_type || 'free').toLowerCase(); const subscriptionDetails = { ...mockSubscriptionCurrent, type: userSubscriptionType, status: currentUser.subscription_status || 'active', is_active: currentUser.is_subscription_active !== false, days_left: currentUser.subscription_days_left || 0, end_date: currentUser.subscription_end_date || null }; return HttpResponse.json({ success: true, data: subscriptionDetails }); }), // 20. 获取订阅权限 http.get('/api/subscription/permissions', async () => { await delay(NETWORK_DELAY); const currentUser = getCurrentUser(); if (!currentUser) { return HttpResponse.json({ success: true, data: { permissions: { 'related_stocks': false, 'related_concepts': false, 'transmission_chain': false, 'historical_events': 'limited', 'concept_html_detail': false, 'concept_stats_panel': false, 'concept_related_stocks': false, 'concept_timeline': false, 'hot_stocks': false } } }); } const subscriptionType = (currentUser.subscription_type || 'free').toLowerCase(); let permissions = {}; if (subscriptionType === 'free') { permissions = { 'related_stocks': false, 'related_concepts': false, 'transmission_chain': false, 'historical_events': 'limited', 'concept_html_detail': false, 'concept_stats_panel': false, 'concept_related_stocks': false, 'concept_timeline': false, 'hot_stocks': false }; } else if (subscriptionType === 'pro') { permissions = { 'related_stocks': true, 'related_concepts': true, 'transmission_chain': false, 'historical_events': 'full', 'concept_html_detail': true, 'concept_stats_panel': true, 'concept_related_stocks': true, 'concept_timeline': false, 'hot_stocks': true }; } else if (subscriptionType === 'max') { permissions = { 'related_stocks': true, 'related_concepts': true, 'transmission_chain': true, 'historical_events': 'full', 'concept_html_detail': true, 'concept_stats_panel': true, 'concept_related_stocks': true, 'concept_timeline': true, 'hot_stocks': true }; } console.log('[Mock] 订阅权限:', { subscriptionType, permissions }); return HttpResponse.json({ success: true, data: { subscription_type: subscriptionType, permissions } }); }), // 21. 获取订阅套餐列表 http.get('/api/subscription/plans', async () => { await delay(NETWORK_DELAY); const plans = [ { id: 1, name: 'pro', display_name: 'Pro 专业版', description: '事件关联股票深度分析 | 历史事件智能对比复盘 | 事件概念关联与挖掘 | 概念板块个股追踪 | 概念深度研报与解读 | 个股异动实时预警', monthly_price: 299, yearly_price: 2699, pricing_options: [ { cycle_key: 'monthly', label: '月付', months: 1, price: 299, original_price: null, discount_percent: 0 }, { cycle_key: 'quarterly', label: '季付', months: 3, price: 799, original_price: 897, discount_percent: 11 }, { cycle_key: 'semiannual', label: '半年付', months: 6, price: 1499, original_price: 1794, discount_percent: 16 }, { cycle_key: 'yearly', label: '年付', months: 12, price: 2699, original_price: 3588, discount_percent: 25 } ], features: [ '新闻信息流', '历史事件对比', '事件传导链分析(AI)', '事件-相关标的分析', '相关概念展示', 'AI复盘功能', '企业概览', '个股深度分析(AI) - 50家/月', '高效数据筛选工具', '概念中心(548大概念)', '历史时间轴查询 - 100天', '涨停板块数据分析', '个股涨停分析' ], sort_order: 1 }, { id: 2, name: 'max', display_name: 'Max 旗舰版', description: '包含Pro版全部功能 | 事件传导链路智能分析 | 概念演变时间轴追溯 | 个股全方位深度研究 | 价小前投研助手无限使用 | 新功能优先体验权 | 专属客服一对一服务', monthly_price: 599, yearly_price: 5399, pricing_options: [ { cycle_key: 'monthly', label: '月付', months: 1, price: 599, original_price: null, discount_percent: 0 }, { cycle_key: 'quarterly', label: '季付', months: 3, price: 1599, original_price: 1797, discount_percent: 11 }, { cycle_key: 'semiannual', label: '半年付', months: 6, price: 2999, original_price: 3594, discount_percent: 17 }, { cycle_key: 'yearly', label: '年付', months: 12, price: 5399, original_price: 7188, discount_percent: 25 } ], features: [ '新闻信息流', '历史事件对比', '事件传导链分析(AI)', '事件-相关标的分析', '相关概念展示', '板块深度分析(AI)', 'AI复盘功能', '企业概览', '个股深度分析(AI) - 无限制', '高效数据筛选工具', '概念中心(548大概念)', '历史时间轴查询 - 无限制', '概念高频更新', '涨停板块数据分析', '个股涨停分析' ], sort_order: 2 } ]; console.log('[Mock] 获取订阅套餐列表:', plans.length, '个套餐'); return HttpResponse.json({ success: true, data: plans }); }), ];