700 lines
18 KiB
JavaScript
700 lines
18 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/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
|
|
}
|
|
});
|
|
}),
|
|
];
|