feat(mock): 添加预测市场和论坛帖子 Mock 数据
- 新增 prediction.js Mock 数据(预测话题列表) - 新增 prediction handlers(/api/prediction/topics 等) - 新增 forum.js Mock 数据(帖子、评论) - 新增 forum handlers(Elasticsearch 风格 API) - 注册到 handlers/index.js 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
342
src/mocks/data/prediction.js
Normal file
342
src/mocks/data/prediction.js
Normal file
@@ -0,0 +1,342 @@
|
|||||||
|
/**
|
||||||
|
* 预测市场 Mock 数据
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 模拟用户
|
||||||
|
export const mockUsers = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
nickname: "价值投资者",
|
||||||
|
username: "value_investor",
|
||||||
|
avatar_url: "https://api.dicebear.com/7.x/avataaars/svg?seed=1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
nickname: "趋势猎手",
|
||||||
|
username: "trend_hunter",
|
||||||
|
avatar_url: "https://api.dicebear.com/7.x/avataaars/svg?seed=2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
nickname: "量化先锋",
|
||||||
|
username: "quant_pioneer",
|
||||||
|
avatar_url: "https://api.dicebear.com/7.x/avataaars/svg?seed=3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
nickname: "股市老兵",
|
||||||
|
username: "stock_veteran",
|
||||||
|
avatar_url: "https://api.dicebear.com/7.x/avataaars/svg?seed=4",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
nickname: "新手小白",
|
||||||
|
username: "newbie",
|
||||||
|
avatar_url: "https://api.dicebear.com/7.x/avataaars/svg?seed=5",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// 预测话题列表
|
||||||
|
export const mockTopics = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: "2024年A股能否突破3500点?",
|
||||||
|
description:
|
||||||
|
"预测2024年内上证指数是否能够突破3500点大关。以2024年12月31日收盘价为准。",
|
||||||
|
category: "stock",
|
||||||
|
tags: ["A股", "大盘", "指数"],
|
||||||
|
author_id: 1,
|
||||||
|
author_name: "价值投资者",
|
||||||
|
author_avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=1",
|
||||||
|
created_at: "2024-01-15T10:00:00Z",
|
||||||
|
deadline: "2024-12-31T15:00:00Z",
|
||||||
|
status: "active",
|
||||||
|
total_pool: 15000,
|
||||||
|
yes_total_shares: 120,
|
||||||
|
no_total_shares: 80,
|
||||||
|
yes_price: 600,
|
||||||
|
no_price: 400,
|
||||||
|
yes_lord_id: 2,
|
||||||
|
no_lord_id: 3,
|
||||||
|
yes_lord_name: "趋势猎手",
|
||||||
|
no_lord_name: "量化先锋",
|
||||||
|
participants_count: 25,
|
||||||
|
comments_count: 18,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
title: "英伟达股价年底能否突破800美元?",
|
||||||
|
description: "预测英伟达(NVDA)股价在2024年底前是否能突破800美元。",
|
||||||
|
category: "stock",
|
||||||
|
tags: ["美股", "AI", "英伟达"],
|
||||||
|
author_id: 2,
|
||||||
|
author_name: "趋势猎手",
|
||||||
|
author_avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=2",
|
||||||
|
created_at: "2024-02-01T14:30:00Z",
|
||||||
|
deadline: "2024-12-31T23:59:00Z",
|
||||||
|
status: "active",
|
||||||
|
total_pool: 28000,
|
||||||
|
yes_total_shares: 200,
|
||||||
|
no_total_shares: 100,
|
||||||
|
yes_price: 667,
|
||||||
|
no_price: 333,
|
||||||
|
yes_lord_id: 1,
|
||||||
|
no_lord_id: 4,
|
||||||
|
yes_lord_name: "价值投资者",
|
||||||
|
no_lord_name: "股市老兵",
|
||||||
|
participants_count: 42,
|
||||||
|
comments_count: 35,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
title: "比特币2024年能否创历史新高?",
|
||||||
|
description: "预测比特币在2024年是否能够突破历史最高价69000美元。",
|
||||||
|
category: "crypto",
|
||||||
|
tags: ["比特币", "加密货币", "BTC"],
|
||||||
|
author_id: 3,
|
||||||
|
author_name: "量化先锋",
|
||||||
|
author_avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=3",
|
||||||
|
created_at: "2024-01-20T09:00:00Z",
|
||||||
|
deadline: "2024-12-31T23:59:00Z",
|
||||||
|
status: "active",
|
||||||
|
total_pool: 50000,
|
||||||
|
yes_total_shares: 300,
|
||||||
|
no_total_shares: 200,
|
||||||
|
yes_price: 600,
|
||||||
|
no_price: 400,
|
||||||
|
yes_lord_id: 2,
|
||||||
|
no_lord_id: 1,
|
||||||
|
yes_lord_name: "趋势猎手",
|
||||||
|
no_lord_name: "价值投资者",
|
||||||
|
participants_count: 68,
|
||||||
|
comments_count: 52,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
title: "茅台股价能否重返2000元?",
|
||||||
|
description: "预测贵州茅台股价在2024年内是否能够重返2000元以上。",
|
||||||
|
category: "stock",
|
||||||
|
tags: ["白酒", "茅台", "A股"],
|
||||||
|
author_id: 4,
|
||||||
|
author_name: "股市老兵",
|
||||||
|
author_avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=4",
|
||||||
|
created_at: "2024-02-10T11:00:00Z",
|
||||||
|
deadline: "2024-12-31T15:00:00Z",
|
||||||
|
status: "active",
|
||||||
|
total_pool: 12000,
|
||||||
|
yes_total_shares: 60,
|
||||||
|
no_total_shares: 140,
|
||||||
|
yes_price: 300,
|
||||||
|
no_price: 700,
|
||||||
|
yes_lord_id: 5,
|
||||||
|
no_lord_id: 3,
|
||||||
|
yes_lord_name: "新手小白",
|
||||||
|
no_lord_name: "量化先锋",
|
||||||
|
participants_count: 30,
|
||||||
|
comments_count: 22,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
title: "美联储2024年会降息几次?(3次以上)",
|
||||||
|
description: "预测美联储在2024年是否会降息3次或以上。",
|
||||||
|
category: "general",
|
||||||
|
tags: ["美联储", "降息", "宏观"],
|
||||||
|
author_id: 1,
|
||||||
|
author_name: "价值投资者",
|
||||||
|
author_avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=1",
|
||||||
|
created_at: "2024-01-25T16:00:00Z",
|
||||||
|
deadline: "2024-12-31T23:59:00Z",
|
||||||
|
status: "active",
|
||||||
|
total_pool: 20000,
|
||||||
|
yes_total_shares: 150,
|
||||||
|
no_total_shares: 150,
|
||||||
|
yes_price: 500,
|
||||||
|
no_price: 500,
|
||||||
|
yes_lord_id: 4,
|
||||||
|
no_lord_id: 2,
|
||||||
|
yes_lord_name: "股市老兵",
|
||||||
|
no_lord_name: "趋势猎手",
|
||||||
|
participants_count: 55,
|
||||||
|
comments_count: 40,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// 用户账户数据
|
||||||
|
export const mockUserAccount = {
|
||||||
|
user_id: 1,
|
||||||
|
balance: 8500,
|
||||||
|
frozen: 1500,
|
||||||
|
total: 10000,
|
||||||
|
total_earned: 12000,
|
||||||
|
total_spent: 3500,
|
||||||
|
total_profit: 2000,
|
||||||
|
last_daily_bonus: null, // 可领取
|
||||||
|
stats: {
|
||||||
|
total_topics: 3,
|
||||||
|
win_count: 5,
|
||||||
|
loss_count: 2,
|
||||||
|
win_rate: 0.714,
|
||||||
|
best_profit: 1200,
|
||||||
|
total_trades: 15,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// 用户持仓
|
||||||
|
export const mockPositions = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
topic_id: 1,
|
||||||
|
topic_title: "2024年A股能否突破3500点?",
|
||||||
|
direction: "yes",
|
||||||
|
shares: 50,
|
||||||
|
avg_cost: 550,
|
||||||
|
current_price: 600,
|
||||||
|
current_value: 30000,
|
||||||
|
unrealized_pnl: 2500,
|
||||||
|
acquired_at: "2024-01-20T10:30:00Z",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
topic_id: 2,
|
||||||
|
topic_title: "英伟达股价年底能否突破800美元?",
|
||||||
|
direction: "yes",
|
||||||
|
shares: 30,
|
||||||
|
avg_cost: 600,
|
||||||
|
current_price: 667,
|
||||||
|
current_value: 20010,
|
||||||
|
unrealized_pnl: 2010,
|
||||||
|
acquired_at: "2024-02-05T14:00:00Z",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
topic_id: 4,
|
||||||
|
topic_title: "茅台股价能否重返2000元?",
|
||||||
|
direction: "no",
|
||||||
|
shares: 20,
|
||||||
|
avg_cost: 650,
|
||||||
|
current_price: 700,
|
||||||
|
current_value: 14000,
|
||||||
|
unrealized_pnl: 1000,
|
||||||
|
acquired_at: "2024-02-12T09:30:00Z",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// 评论数据
|
||||||
|
export const mockComments = {
|
||||||
|
1: [
|
||||||
|
// topic_id: 1 的评论
|
||||||
|
{
|
||||||
|
id: 101,
|
||||||
|
topic_id: 1,
|
||||||
|
user: mockUsers[1],
|
||||||
|
content:
|
||||||
|
"看好A股,政策面利好不断,预计下半年会有一波行情。技术面已经筑底完成,可以积极布局。",
|
||||||
|
parent_id: null,
|
||||||
|
likes_count: 42,
|
||||||
|
is_liked: false,
|
||||||
|
is_lord: true,
|
||||||
|
total_investment: 2500,
|
||||||
|
investment_shares: 25,
|
||||||
|
verification_status: null,
|
||||||
|
created_at: "2024-01-16T10:30:00Z",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 102,
|
||||||
|
topic_id: 1,
|
||||||
|
user: mockUsers[2],
|
||||||
|
content:
|
||||||
|
"持谨慎态度,虽然政策有支持,但经济基本面还需要时间恢复。建议观望为主。",
|
||||||
|
parent_id: null,
|
||||||
|
likes_count: 28,
|
||||||
|
is_liked: true,
|
||||||
|
is_lord: true,
|
||||||
|
total_investment: 1800,
|
||||||
|
investment_shares: 18,
|
||||||
|
verification_status: null,
|
||||||
|
created_at: "2024-01-17T14:20:00Z",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 103,
|
||||||
|
topic_id: 1,
|
||||||
|
user: mockUsers[3],
|
||||||
|
content: "从量化指标来看,当前市场估值处于历史低位,长期来看有投资价值。",
|
||||||
|
parent_id: null,
|
||||||
|
likes_count: 35,
|
||||||
|
is_liked: false,
|
||||||
|
is_lord: false,
|
||||||
|
total_investment: 500,
|
||||||
|
investment_shares: 5,
|
||||||
|
verification_status: null,
|
||||||
|
created_at: "2024-01-18T09:15:00Z",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
2: [
|
||||||
|
// topic_id: 2 的评论
|
||||||
|
{
|
||||||
|
id: 201,
|
||||||
|
topic_id: 2,
|
||||||
|
user: mockUsers[0],
|
||||||
|
content:
|
||||||
|
"AI浪潮势不可挡,英伟达作为算力龙头,业绩增长确定性强。800美元只是时间问题。",
|
||||||
|
parent_id: null,
|
||||||
|
likes_count: 56,
|
||||||
|
is_liked: true,
|
||||||
|
is_lord: true,
|
||||||
|
total_investment: 3500,
|
||||||
|
investment_shares: 35,
|
||||||
|
verification_status: null,
|
||||||
|
created_at: "2024-02-02T11:00:00Z",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 202,
|
||||||
|
topic_id: 2,
|
||||||
|
user: mockUsers[3],
|
||||||
|
content: "估值已经很高了,需要警惕回调风险。但长期仍然看好AI赛道。",
|
||||||
|
parent_id: null,
|
||||||
|
likes_count: 38,
|
||||||
|
is_liked: false,
|
||||||
|
is_lord: true,
|
||||||
|
total_investment: 2000,
|
||||||
|
investment_shares: 20,
|
||||||
|
verification_status: null,
|
||||||
|
created_at: "2024-02-03T16:30:00Z",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// 交易记录
|
||||||
|
export const mockTrades = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
topic_id: 1,
|
||||||
|
user_id: 1,
|
||||||
|
direction: "yes",
|
||||||
|
type: "buy",
|
||||||
|
shares: 50,
|
||||||
|
price: 550,
|
||||||
|
total_cost: 27500,
|
||||||
|
tax: 550,
|
||||||
|
created_at: "2024-01-20T10:30:00Z",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
topic_id: 2,
|
||||||
|
user_id: 1,
|
||||||
|
direction: "yes",
|
||||||
|
type: "buy",
|
||||||
|
shares: 30,
|
||||||
|
price: 600,
|
||||||
|
total_cost: 18000,
|
||||||
|
tax: 360,
|
||||||
|
created_at: "2024-02-05T14:00:00Z",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mockUsers,
|
||||||
|
mockTopics,
|
||||||
|
mockUserAccount,
|
||||||
|
mockPositions,
|
||||||
|
mockComments,
|
||||||
|
mockTrades,
|
||||||
|
};
|
||||||
@@ -17,6 +17,8 @@ import { posthogHandlers } from './posthog';
|
|||||||
import { externalHandlers } from './external';
|
import { externalHandlers } from './external';
|
||||||
import { agentHandlers } from './agent';
|
import { agentHandlers } from './agent';
|
||||||
import { bytedeskHandlers } from './bytedesk';
|
import { bytedeskHandlers } from './bytedesk';
|
||||||
|
import { predictionHandlers } from './prediction';
|
||||||
|
import { forumHandlers } from './forum';
|
||||||
|
|
||||||
// 可以在这里添加更多的 handlers
|
// 可以在这里添加更多的 handlers
|
||||||
// import { userHandlers } from './user';
|
// import { userHandlers } from './user';
|
||||||
@@ -38,5 +40,7 @@ export const handlers = [
|
|||||||
...externalHandlers,
|
...externalHandlers,
|
||||||
...agentHandlers,
|
...agentHandlers,
|
||||||
...bytedeskHandlers, // ⚡ Bytedesk 客服 Widget passthrough
|
...bytedeskHandlers, // ⚡ Bytedesk 客服 Widget passthrough
|
||||||
|
...predictionHandlers, // 预测市场
|
||||||
|
...forumHandlers, // 价值论坛帖子 (ES)
|
||||||
// ...userHandlers,
|
// ...userHandlers,
|
||||||
];
|
];
|
||||||
|
|||||||
619
src/mocks/handlers/prediction.js
Normal file
619
src/mocks/handlers/prediction.js
Normal file
@@ -0,0 +1,619 @@
|
|||||||
|
/**
|
||||||
|
* 预测市场 Mock Handlers
|
||||||
|
*/
|
||||||
|
import { http, HttpResponse, delay } from "msw";
|
||||||
|
import {
|
||||||
|
mockTopics,
|
||||||
|
mockUserAccount,
|
||||||
|
mockPositions,
|
||||||
|
mockComments,
|
||||||
|
mockUsers,
|
||||||
|
} from "../data/prediction";
|
||||||
|
|
||||||
|
// 内存状态(用于模拟状态变化)
|
||||||
|
let userAccount = { ...mockUserAccount };
|
||||||
|
let topics = [...mockTopics];
|
||||||
|
let positions = [...mockPositions];
|
||||||
|
let comments = JSON.parse(JSON.stringify(mockComments));
|
||||||
|
|
||||||
|
// 重置状态
|
||||||
|
const resetState = () => {
|
||||||
|
userAccount = { ...mockUserAccount };
|
||||||
|
topics = [...mockTopics];
|
||||||
|
positions = [...mockPositions];
|
||||||
|
comments = JSON.parse(JSON.stringify(mockComments));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const predictionHandlers = [
|
||||||
|
// ==================== 积分系统 ====================
|
||||||
|
|
||||||
|
// 获取用户积分账户
|
||||||
|
http.get(`/api/prediction/credit/account`, async () => {
|
||||||
|
await delay(300);
|
||||||
|
return HttpResponse.json({
|
||||||
|
success: true,
|
||||||
|
code: 200,
|
||||||
|
message: "success",
|
||||||
|
data: userAccount,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
// 领取每日奖励
|
||||||
|
http.post(`/api/prediction/credit/daily-bonus`, async () => {
|
||||||
|
await delay(500);
|
||||||
|
|
||||||
|
// 检查是否已领取
|
||||||
|
const today = new Date().toDateString();
|
||||||
|
const lastBonus = userAccount.last_daily_bonus
|
||||||
|
? new Date(userAccount.last_daily_bonus).toDateString()
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (lastBonus === today) {
|
||||||
|
return HttpResponse.json(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
code: 400,
|
||||||
|
message: "今日已领取过奖励",
|
||||||
|
data: null,
|
||||||
|
},
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发放奖励
|
||||||
|
const bonusAmount = 100;
|
||||||
|
userAccount.balance += bonusAmount;
|
||||||
|
userAccount.total += bonusAmount;
|
||||||
|
userAccount.total_earned += bonusAmount;
|
||||||
|
userAccount.last_daily_bonus = new Date().toISOString();
|
||||||
|
|
||||||
|
return HttpResponse.json({
|
||||||
|
success: true,
|
||||||
|
code: 200,
|
||||||
|
message: "领取成功",
|
||||||
|
data: {
|
||||||
|
bonus_amount: bonusAmount,
|
||||||
|
new_balance: userAccount.balance,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
// ==================== 预测话题 ====================
|
||||||
|
|
||||||
|
// 获取话题列表
|
||||||
|
http.get(`/api/prediction/topics`, async ({ request }) => {
|
||||||
|
await delay(400);
|
||||||
|
|
||||||
|
const url = new URL(request.url);
|
||||||
|
const status = url.searchParams.get("status");
|
||||||
|
const category = url.searchParams.get("category");
|
||||||
|
const sortBy = url.searchParams.get("sort_by") || "created_at";
|
||||||
|
const page = parseInt(url.searchParams.get("page") || "1", 10);
|
||||||
|
const perPage = parseInt(url.searchParams.get("per_page") || "10", 10);
|
||||||
|
|
||||||
|
let filteredTopics = [...topics];
|
||||||
|
|
||||||
|
// 过滤状态
|
||||||
|
if (status && status !== "all") {
|
||||||
|
filteredTopics = filteredTopics.filter((t) => t.status === status);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 过滤分类
|
||||||
|
if (category && category !== "all") {
|
||||||
|
filteredTopics = filteredTopics.filter((t) => t.category === category);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 排序
|
||||||
|
filteredTopics.sort((a, b) => {
|
||||||
|
if (sortBy === "total_pool") return b.total_pool - a.total_pool;
|
||||||
|
if (sortBy === "participants_count")
|
||||||
|
return b.participants_count - a.participants_count;
|
||||||
|
return new Date(b.created_at) - new Date(a.created_at);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 分页
|
||||||
|
const total = filteredTopics.length;
|
||||||
|
const start = (page - 1) * perPage;
|
||||||
|
const paginatedTopics = filteredTopics.slice(start, start + perPage);
|
||||||
|
|
||||||
|
return HttpResponse.json({
|
||||||
|
success: true,
|
||||||
|
code: 200,
|
||||||
|
message: "success",
|
||||||
|
data: {
|
||||||
|
topics: paginatedTopics,
|
||||||
|
pagination: {
|
||||||
|
page,
|
||||||
|
per_page: perPage,
|
||||||
|
total,
|
||||||
|
total_pages: Math.ceil(total / perPage),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
// 获取话题详情
|
||||||
|
http.get(`/api/prediction/topics/:topicId`, async ({ params }) => {
|
||||||
|
await delay(300);
|
||||||
|
|
||||||
|
const topicId = parseInt(params.topicId, 10);
|
||||||
|
const topic = topics.find((t) => t.id === topicId);
|
||||||
|
|
||||||
|
if (!topic) {
|
||||||
|
return HttpResponse.json(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
code: 404,
|
||||||
|
message: "话题不存在",
|
||||||
|
data: null,
|
||||||
|
},
|
||||||
|
{ status: 404 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建详细的席位信息
|
||||||
|
const topicDetail = {
|
||||||
|
...topic,
|
||||||
|
yes_seats: [
|
||||||
|
{
|
||||||
|
user_id: topic.yes_lord_id,
|
||||||
|
user_name: topic.yes_lord_name,
|
||||||
|
user_avatar: mockUsers.find((u) => u.id === topic.yes_lord_id)
|
||||||
|
?.avatar_url,
|
||||||
|
shares: Math.floor(topic.yes_total_shares * 0.4),
|
||||||
|
is_lord: true,
|
||||||
|
},
|
||||||
|
...Array(Math.min(4, Math.floor(topic.participants_count / 4)))
|
||||||
|
.fill(null)
|
||||||
|
.map((_, i) => ({
|
||||||
|
user_id: mockUsers[(i + 2) % mockUsers.length].id,
|
||||||
|
user_name: mockUsers[(i + 2) % mockUsers.length].nickname,
|
||||||
|
user_avatar: mockUsers[(i + 2) % mockUsers.length].avatar_url,
|
||||||
|
shares: Math.floor((topic.yes_total_shares * 0.15) / (i + 1)),
|
||||||
|
is_lord: false,
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
no_seats: [
|
||||||
|
{
|
||||||
|
user_id: topic.no_lord_id,
|
||||||
|
user_name: topic.no_lord_name,
|
||||||
|
user_avatar: mockUsers.find((u) => u.id === topic.no_lord_id)
|
||||||
|
?.avatar_url,
|
||||||
|
shares: Math.floor(topic.no_total_shares * 0.4),
|
||||||
|
is_lord: true,
|
||||||
|
},
|
||||||
|
...Array(Math.min(4, Math.floor(topic.participants_count / 5)))
|
||||||
|
.fill(null)
|
||||||
|
.map((_, i) => ({
|
||||||
|
user_id: mockUsers[(i + 3) % mockUsers.length].id,
|
||||||
|
user_name: mockUsers[(i + 3) % mockUsers.length].nickname,
|
||||||
|
user_avatar: mockUsers[(i + 3) % mockUsers.length].avatar_url,
|
||||||
|
shares: Math.floor((topic.no_total_shares * 0.15) / (i + 1)),
|
||||||
|
is_lord: false,
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
return HttpResponse.json({
|
||||||
|
success: true,
|
||||||
|
code: 200,
|
||||||
|
message: "success",
|
||||||
|
data: topicDetail,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
// 创建话题
|
||||||
|
http.post(`/api/prediction/topics`, async ({ request }) => {
|
||||||
|
await delay(600);
|
||||||
|
|
||||||
|
const body = await request.json();
|
||||||
|
const { title, description, category, deadline } = body;
|
||||||
|
|
||||||
|
// 扣除创建费用
|
||||||
|
const createCost = 100;
|
||||||
|
if (userAccount.balance < createCost) {
|
||||||
|
return HttpResponse.json(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
code: 400,
|
||||||
|
message: "积分不足,创建话题需要100积分",
|
||||||
|
data: null,
|
||||||
|
},
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
userAccount.balance -= createCost;
|
||||||
|
userAccount.total_spent += createCost;
|
||||||
|
|
||||||
|
const newTopic = {
|
||||||
|
id: topics.length + 1,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
category: category || "general",
|
||||||
|
tags: [],
|
||||||
|
author_id: 1,
|
||||||
|
author_name: "当前用户",
|
||||||
|
author_avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=current",
|
||||||
|
created_at: new Date().toISOString(),
|
||||||
|
deadline:
|
||||||
|
deadline ||
|
||||||
|
new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
|
||||||
|
status: "active",
|
||||||
|
total_pool: createCost,
|
||||||
|
yes_total_shares: 0,
|
||||||
|
no_total_shares: 0,
|
||||||
|
yes_price: 500,
|
||||||
|
no_price: 500,
|
||||||
|
yes_lord_id: null,
|
||||||
|
no_lord_id: null,
|
||||||
|
yes_lord_name: null,
|
||||||
|
no_lord_name: null,
|
||||||
|
participants_count: 0,
|
||||||
|
comments_count: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
topics.unshift(newTopic);
|
||||||
|
|
||||||
|
return HttpResponse.json({
|
||||||
|
success: true,
|
||||||
|
code: 200,
|
||||||
|
message: "创建成功",
|
||||||
|
data: newTopic,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
// 结算话题
|
||||||
|
http.post(
|
||||||
|
`/api/prediction/topics/:topicId/settle`,
|
||||||
|
async ({ params, request }) => {
|
||||||
|
await delay(500);
|
||||||
|
|
||||||
|
const topicId = parseInt(params.topicId, 10);
|
||||||
|
const body = await request.json();
|
||||||
|
const { result } = body;
|
||||||
|
|
||||||
|
const topicIndex = topics.findIndex((t) => t.id === topicId);
|
||||||
|
if (topicIndex === -1) {
|
||||||
|
return HttpResponse.json(
|
||||||
|
{ success: false, code: 404, message: "话题不存在", data: null },
|
||||||
|
{ status: 404 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
topics[topicIndex] = {
|
||||||
|
...topics[topicIndex],
|
||||||
|
status: "settled",
|
||||||
|
settlement_result: result,
|
||||||
|
settled_at: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
return HttpResponse.json({
|
||||||
|
success: true,
|
||||||
|
code: 200,
|
||||||
|
message: "结算成功",
|
||||||
|
data: topics[topicIndex],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
),
|
||||||
|
|
||||||
|
// ==================== 交易 ====================
|
||||||
|
|
||||||
|
// 买入份额
|
||||||
|
http.post(`/api/prediction/trade/buy`, async ({ request }) => {
|
||||||
|
await delay(500);
|
||||||
|
|
||||||
|
const body = await request.json();
|
||||||
|
const { topic_id, direction, shares } = body;
|
||||||
|
|
||||||
|
const topic = topics.find((t) => t.id === topic_id);
|
||||||
|
if (!topic) {
|
||||||
|
return HttpResponse.json(
|
||||||
|
{ success: false, code: 404, message: "话题不存在", data: null },
|
||||||
|
{ status: 404 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算成本
|
||||||
|
const currentPrice = direction === "yes" ? topic.yes_price : topic.no_price;
|
||||||
|
const totalCost = Math.floor(currentPrice * shares);
|
||||||
|
const tax = Math.floor(totalCost * 0.02);
|
||||||
|
const finalCost = totalCost + tax;
|
||||||
|
|
||||||
|
if (userAccount.balance < finalCost) {
|
||||||
|
return HttpResponse.json(
|
||||||
|
{ success: false, code: 400, message: "积分不足", data: null },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 扣除积分
|
||||||
|
userAccount.balance -= finalCost;
|
||||||
|
userAccount.frozen += totalCost;
|
||||||
|
userAccount.total_spent += finalCost;
|
||||||
|
|
||||||
|
// 更新话题数据
|
||||||
|
if (direction === "yes") {
|
||||||
|
topic.yes_total_shares += shares;
|
||||||
|
} else {
|
||||||
|
topic.no_total_shares += shares;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新计算价格
|
||||||
|
const totalShares = topic.yes_total_shares + topic.no_total_shares;
|
||||||
|
topic.yes_price = Math.round((topic.yes_total_shares / totalShares) * 1000);
|
||||||
|
topic.no_price = Math.round((topic.no_total_shares / totalShares) * 1000);
|
||||||
|
topic.total_pool += tax;
|
||||||
|
topic.participants_count += 1;
|
||||||
|
|
||||||
|
// 添加持仓
|
||||||
|
const existingPosition = positions.find(
|
||||||
|
(p) => p.topic_id === topic_id && p.direction === direction
|
||||||
|
);
|
||||||
|
if (existingPosition) {
|
||||||
|
existingPosition.shares += shares;
|
||||||
|
existingPosition.current_value =
|
||||||
|
existingPosition.shares *
|
||||||
|
(direction === "yes" ? topic.yes_price : topic.no_price);
|
||||||
|
} else {
|
||||||
|
positions.push({
|
||||||
|
id: positions.length + 1,
|
||||||
|
topic_id,
|
||||||
|
topic_title: topic.title,
|
||||||
|
direction,
|
||||||
|
shares,
|
||||||
|
avg_cost: currentPrice,
|
||||||
|
current_price: direction === "yes" ? topic.yes_price : topic.no_price,
|
||||||
|
current_value:
|
||||||
|
shares * (direction === "yes" ? topic.yes_price : topic.no_price),
|
||||||
|
unrealized_pnl: 0,
|
||||||
|
acquired_at: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return HttpResponse.json({
|
||||||
|
success: true,
|
||||||
|
code: 200,
|
||||||
|
message: "买入成功",
|
||||||
|
data: {
|
||||||
|
trade_id: Date.now(),
|
||||||
|
topic,
|
||||||
|
shares,
|
||||||
|
price: currentPrice,
|
||||||
|
total_cost: totalCost,
|
||||||
|
tax,
|
||||||
|
new_balance: userAccount.balance,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
// 获取用户持仓
|
||||||
|
http.get(`/api/prediction/positions`, async () => {
|
||||||
|
await delay(300);
|
||||||
|
return HttpResponse.json({
|
||||||
|
success: true,
|
||||||
|
code: 200,
|
||||||
|
message: "success",
|
||||||
|
data: positions,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
// ==================== 评论 ====================
|
||||||
|
|
||||||
|
// 获取评论列表
|
||||||
|
http.get(`/api/prediction/topics/:topicId/comments`, async ({ params }) => {
|
||||||
|
await delay(300);
|
||||||
|
|
||||||
|
const topicId = parseInt(params.topicId, 10);
|
||||||
|
const topicComments = comments[topicId] || [];
|
||||||
|
|
||||||
|
return HttpResponse.json({
|
||||||
|
success: true,
|
||||||
|
code: 200,
|
||||||
|
message: "success",
|
||||||
|
data: {
|
||||||
|
comments: topicComments,
|
||||||
|
total: topicComments.length,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
// 发表评论
|
||||||
|
http.post(
|
||||||
|
`/api/prediction/topics/:topicId/comments`,
|
||||||
|
async ({ params, request }) => {
|
||||||
|
await delay(400);
|
||||||
|
|
||||||
|
const topicId = parseInt(params.topicId, 10);
|
||||||
|
const body = await request.json();
|
||||||
|
const { content, parent_id } = body;
|
||||||
|
|
||||||
|
const newComment = {
|
||||||
|
id: Date.now(),
|
||||||
|
topic_id: topicId,
|
||||||
|
user: {
|
||||||
|
id: 1,
|
||||||
|
nickname: "当前用户",
|
||||||
|
username: "current_user",
|
||||||
|
avatar_url: "https://api.dicebear.com/7.x/avataaars/svg?seed=current",
|
||||||
|
},
|
||||||
|
content,
|
||||||
|
parent_id: parent_id || null,
|
||||||
|
likes_count: 0,
|
||||||
|
is_liked: false,
|
||||||
|
is_lord: false,
|
||||||
|
total_investment: 0,
|
||||||
|
investment_shares: 0,
|
||||||
|
verification_status: null,
|
||||||
|
created_at: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!comments[topicId]) {
|
||||||
|
comments[topicId] = [];
|
||||||
|
}
|
||||||
|
comments[topicId].unshift(newComment);
|
||||||
|
|
||||||
|
// 更新话题评论数
|
||||||
|
const topic = topics.find((t) => t.id === topicId);
|
||||||
|
if (topic) {
|
||||||
|
topic.comments_count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return HttpResponse.json({
|
||||||
|
success: true,
|
||||||
|
code: 200,
|
||||||
|
message: "评论成功",
|
||||||
|
data: newComment,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
),
|
||||||
|
|
||||||
|
// 点赞评论
|
||||||
|
http.post(`/api/prediction/comments/:commentId/like`, async ({ params }) => {
|
||||||
|
await delay(200);
|
||||||
|
|
||||||
|
const commentId = parseInt(params.commentId, 10);
|
||||||
|
|
||||||
|
// 在所有话题的评论中查找
|
||||||
|
for (const topicId of Object.keys(comments)) {
|
||||||
|
const comment = comments[topicId].find((c) => c.id === commentId);
|
||||||
|
if (comment) {
|
||||||
|
comment.is_liked = !comment.is_liked;
|
||||||
|
comment.likes_count += comment.is_liked ? 1 : -1;
|
||||||
|
|
||||||
|
return HttpResponse.json({
|
||||||
|
success: true,
|
||||||
|
code: 200,
|
||||||
|
message: comment.is_liked ? "点赞成功" : "取消点赞",
|
||||||
|
data: {
|
||||||
|
is_liked: comment.is_liked,
|
||||||
|
likes_count: comment.likes_count,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return HttpResponse.json(
|
||||||
|
{ success: false, code: 404, message: "评论不存在", data: null },
|
||||||
|
{ status: 404 }
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
|
||||||
|
// ==================== 观点IPO ====================
|
||||||
|
|
||||||
|
// 投资评论
|
||||||
|
http.post(
|
||||||
|
`/api/prediction/comments/:commentId/invest`,
|
||||||
|
async ({ params, request }) => {
|
||||||
|
await delay(400);
|
||||||
|
|
||||||
|
const commentId = parseInt(params.commentId, 10);
|
||||||
|
const body = await request.json();
|
||||||
|
const { shares } = body;
|
||||||
|
|
||||||
|
const investCost = shares * 100; // 每份100积分
|
||||||
|
|
||||||
|
if (userAccount.balance < investCost) {
|
||||||
|
return HttpResponse.json(
|
||||||
|
{ success: false, code: 400, message: "积分不足", data: null },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 扣除积分
|
||||||
|
userAccount.balance -= investCost;
|
||||||
|
userAccount.total_spent += investCost;
|
||||||
|
|
||||||
|
// 更新评论投资数据
|
||||||
|
for (const topicId of Object.keys(comments)) {
|
||||||
|
const comment = comments[topicId].find((c) => c.id === commentId);
|
||||||
|
if (comment) {
|
||||||
|
comment.total_investment += investCost;
|
||||||
|
comment.investment_shares += shares;
|
||||||
|
|
||||||
|
return HttpResponse.json({
|
||||||
|
success: true,
|
||||||
|
code: 200,
|
||||||
|
message: "投资成功",
|
||||||
|
data: {
|
||||||
|
comment,
|
||||||
|
invested_shares: shares,
|
||||||
|
invested_amount: investCost,
|
||||||
|
new_balance: userAccount.balance,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return HttpResponse.json(
|
||||||
|
{ success: false, code: 404, message: "评论不存在", data: null },
|
||||||
|
{ status: 404 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
|
||||||
|
// 获取评论投资列表
|
||||||
|
http.get(
|
||||||
|
`/api/prediction/comments/:commentId/investments`,
|
||||||
|
async ({ params }) => {
|
||||||
|
await delay(300);
|
||||||
|
|
||||||
|
// 返回模拟的投资列表
|
||||||
|
return HttpResponse.json({
|
||||||
|
success: true,
|
||||||
|
code: 200,
|
||||||
|
message: "success",
|
||||||
|
data: {
|
||||||
|
investments: [
|
||||||
|
{
|
||||||
|
user: mockUsers[0],
|
||||||
|
shares: 10,
|
||||||
|
amount: 1000,
|
||||||
|
invested_at: "2024-02-01T10:00:00Z",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
user: mockUsers[1],
|
||||||
|
shares: 5,
|
||||||
|
amount: 500,
|
||||||
|
invested_at: "2024-02-02T14:30:00Z",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
total: 2,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
),
|
||||||
|
|
||||||
|
// 验证评论
|
||||||
|
http.post(
|
||||||
|
`/api/prediction/comments/:commentId/verify`,
|
||||||
|
async ({ params, request }) => {
|
||||||
|
await delay(400);
|
||||||
|
|
||||||
|
const commentId = parseInt(params.commentId, 10);
|
||||||
|
const body = await request.json();
|
||||||
|
const { result } = body;
|
||||||
|
|
||||||
|
for (const topicId of Object.keys(comments)) {
|
||||||
|
const comment = comments[topicId].find((c) => c.id === commentId);
|
||||||
|
if (comment) {
|
||||||
|
comment.verification_status = result;
|
||||||
|
|
||||||
|
return HttpResponse.json({
|
||||||
|
success: true,
|
||||||
|
code: 200,
|
||||||
|
message: "验证成功",
|
||||||
|
data: comment,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return HttpResponse.json(
|
||||||
|
{ success: false, code: 404, message: "评论不存在", data: null },
|
||||||
|
{ status: 404 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
export default predictionHandlers;
|
||||||
Reference in New Issue
Block a user