Files
vf_react/src/mocks/handlers/account.js
2025-12-03 15:56:24 +08:00

797 lines
22 KiB
JavaScript

// 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
} 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/add', 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);
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);
const index = mockWatchlist.findIndex(item => item.id === parseInt(id));
if (index !== -1) {
mockWatchlist.splice(index, 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 }
);
}
console.log('[Mock] 获取关注的事件');
return HttpResponse.json({
success: true,
data: mockFollowingEvents
});
}),
// 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);
const currentUser = getCurrentUser();
if (!currentUser) {
return HttpResponse.json(
{ success: false, error: '未登录' },
{ status: 401 }
);
}
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. 获取投资计划和复盘,转换为日历事件格式
const investmentPlansAsEvents = mockInvestmentPlans
.filter(plan => plan.user_id === currentUser.id)
.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] 合并后的日历事件数量:', {
calendarEvents: calendarEvents.length,
investmentPlansAsEvents: investmentPlansAsEvents.length,
total: filteredEvents.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
});
}),
];