feat: 完整重构小程序匹配功能 + 修复UI对齐 + 文章数据API
主要更新: 1. 按H5网页端完全重构匹配功能(match页面) - 4种匹配类型: 创业合伙/资源对接/导师顾问/团队招募 - 资源对接等类型弹出手机号/微信号输入框 - 去掉重新匹配按钮,改为返回按钮 2. 修复所有卡片对齐和宽度问题 - 目录页附录卡片居中 - 首页阅读进度卡片满宽度 - 我的页面菜单卡片对齐 - 推广中心分享卡片统一宽度 3. 修复目录页图标和文字对齐 - section-icon固定40rpx宽高 - section-title与图标垂直居中 4. 更新真实完整文章标题(62篇) - 从book目录读取真实markdown文件名 - 替换之前的简化标题 5. 新增文章数据API - /api/db/chapters - 获取完整书籍结构 - 支持按ID获取单篇文章内容
This commit is contained in:
108
app/api/distribution/auto-withdraw-config/route.ts
Normal file
108
app/api/distribution/auto-withdraw-config/route.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* 自动提现配置API
|
||||
*/
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
// 内存存储(实际应用中应该存入数据库)
|
||||
const autoWithdrawConfigs: Map<string, {
|
||||
userId: string;
|
||||
enabled: boolean;
|
||||
minAmount: number;
|
||||
method: 'wechat' | 'alipay';
|
||||
account: string;
|
||||
name: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}> = new Map();
|
||||
|
||||
// GET: 获取用户自动提现配置
|
||||
export async function GET(req: NextRequest) {
|
||||
const { searchParams } = new URL(req.url);
|
||||
const userId = searchParams.get('userId');
|
||||
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: '缺少用户ID' }, { status: 400 });
|
||||
}
|
||||
|
||||
const config = autoWithdrawConfigs.get(userId);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
config: config || null,
|
||||
});
|
||||
}
|
||||
|
||||
// POST: 保存/更新自动提现配置
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const body = await req.json();
|
||||
const { userId, enabled, minAmount, method, account, name } = body;
|
||||
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: '缺少用户ID' }, { status: 400 });
|
||||
}
|
||||
|
||||
// 验证参数
|
||||
if (enabled) {
|
||||
if (!minAmount || minAmount < 10) {
|
||||
return NextResponse.json({ error: '最低提现金额不能少于10元' }, { status: 400 });
|
||||
}
|
||||
if (!account) {
|
||||
return NextResponse.json({ error: '请填写提现账号' }, { status: 400 });
|
||||
}
|
||||
if (!name) {
|
||||
return NextResponse.json({ error: '请填写真实姓名' }, { status: 400 });
|
||||
}
|
||||
}
|
||||
|
||||
const now = new Date().toISOString();
|
||||
const existingConfig = autoWithdrawConfigs.get(userId);
|
||||
|
||||
const config = {
|
||||
userId,
|
||||
enabled: Boolean(enabled),
|
||||
minAmount: Number(minAmount) || 100,
|
||||
method: method || 'wechat',
|
||||
account: account || '',
|
||||
name: name || '',
|
||||
createdAt: existingConfig?.createdAt || now,
|
||||
updatedAt: now,
|
||||
};
|
||||
|
||||
autoWithdrawConfigs.set(userId, config);
|
||||
|
||||
console.log('[AutoWithdrawConfig] 保存配置:', {
|
||||
userId,
|
||||
enabled: config.enabled,
|
||||
minAmount: config.minAmount,
|
||||
method: config.method,
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
config,
|
||||
message: enabled ? '自动提现已启用' : '自动提现已关闭',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[AutoWithdrawConfig] 保存失败:', error);
|
||||
return NextResponse.json({ error: '保存配置失败' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE: 删除自动提现配置
|
||||
export async function DELETE(req: NextRequest) {
|
||||
const { searchParams } = new URL(req.url);
|
||||
const userId = searchParams.get('userId');
|
||||
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: '缺少用户ID' }, { status: 400 });
|
||||
}
|
||||
|
||||
autoWithdrawConfigs.delete(userId);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: '配置已删除',
|
||||
});
|
||||
}
|
||||
53
app/api/distribution/messages/route.ts
Normal file
53
app/api/distribution/messages/route.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* 分销消息API
|
||||
* 用于WebSocket轮询获取实时消息
|
||||
*/
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { getMessages, clearMessages } from '@/lib/modules/distribution/websocket';
|
||||
|
||||
// GET: 获取用户消息
|
||||
export async function GET(req: NextRequest) {
|
||||
const { searchParams } = new URL(req.url);
|
||||
const userId = searchParams.get('userId');
|
||||
const since = searchParams.get('since');
|
||||
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: '缺少用户ID' }, { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
const messages = getMessages(userId, since || undefined);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
messages,
|
||||
count: messages.length,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[MessagesAPI] 获取消息失败:', error);
|
||||
return NextResponse.json({ error: '获取消息失败' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// POST: 标记消息已读
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const body = await req.json();
|
||||
const { userId, messageIds } = body;
|
||||
|
||||
if (!userId || !messageIds || !Array.isArray(messageIds)) {
|
||||
return NextResponse.json({ error: '参数错误' }, { status: 400 });
|
||||
}
|
||||
|
||||
clearMessages(userId, messageIds);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: '消息已标记为已读',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[MessagesAPI] 标记已读失败:', error);
|
||||
return NextResponse.json({ error: '操作失败' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
885
app/api/distribution/route.ts
Normal file
885
app/api/distribution/route.ts
Normal file
@@ -0,0 +1,885 @@
|
||||
/**
|
||||
* 分销模块API
|
||||
* 功能:绑定追踪、提现管理、统计概览
|
||||
*/
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
// 模拟数据存储(实际项目应使用数据库)
|
||||
let distributionBindings: Array<{
|
||||
id: string;
|
||||
referrerId: string;
|
||||
referrerCode: string;
|
||||
visitorId: string;
|
||||
visitorPhone?: string;
|
||||
visitorNickname?: string;
|
||||
bindingTime: string;
|
||||
expireTime: string;
|
||||
status: 'active' | 'converted' | 'expired' | 'cancelled';
|
||||
convertedAt?: string;
|
||||
orderId?: string;
|
||||
orderAmount?: number;
|
||||
commission?: number;
|
||||
source: 'link' | 'miniprogram' | 'poster' | 'qrcode';
|
||||
createdAt: string;
|
||||
}> = [];
|
||||
|
||||
let clickRecords: Array<{
|
||||
id: string;
|
||||
referralCode: string;
|
||||
referrerId: string;
|
||||
visitorId: string;
|
||||
source: string;
|
||||
clickTime: string;
|
||||
}> = [];
|
||||
|
||||
let distributors: Array<{
|
||||
id: string;
|
||||
userId: string;
|
||||
nickname: string;
|
||||
phone: string;
|
||||
referralCode: string;
|
||||
totalClicks: number;
|
||||
totalBindings: number;
|
||||
activeBindings: number;
|
||||
convertedBindings: number;
|
||||
expiredBindings: number;
|
||||
totalEarnings: number;
|
||||
pendingEarnings: number;
|
||||
withdrawnEarnings: number;
|
||||
autoWithdraw: boolean;
|
||||
autoWithdrawThreshold: number;
|
||||
autoWithdrawAccount?: {
|
||||
type: 'wechat' | 'alipay';
|
||||
account: string;
|
||||
name: string;
|
||||
};
|
||||
level: 'normal' | 'silver' | 'gold' | 'diamond';
|
||||
commissionRate: number;
|
||||
status: 'active' | 'frozen' | 'disabled';
|
||||
createdAt: string;
|
||||
}> = [];
|
||||
|
||||
let withdrawRecords: Array<{
|
||||
id: string;
|
||||
distributorId: string;
|
||||
userId: string;
|
||||
userName: string;
|
||||
amount: number;
|
||||
fee: number;
|
||||
actualAmount: number;
|
||||
method: 'wechat' | 'alipay';
|
||||
account: string;
|
||||
accountName: string;
|
||||
status: 'pending' | 'processing' | 'completed' | 'failed' | 'rejected';
|
||||
isAuto: boolean;
|
||||
paymentNo?: string;
|
||||
paymentTime?: string;
|
||||
reviewNote?: string;
|
||||
createdAt: string;
|
||||
completedAt?: string;
|
||||
}> = [];
|
||||
|
||||
// 配置
|
||||
const BINDING_DAYS = 30;
|
||||
const MIN_WITHDRAW_AMOUNT = 10;
|
||||
const DEFAULT_COMMISSION_RATE = 90;
|
||||
|
||||
// 生成ID
|
||||
function generateId(prefix: string = ''): string {
|
||||
return `${prefix}${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
}
|
||||
|
||||
// GET: 获取分销数据
|
||||
export async function GET(req: NextRequest) {
|
||||
const { searchParams } = new URL(req.url);
|
||||
const type = searchParams.get('type') || 'overview';
|
||||
const userId = searchParams.get('userId');
|
||||
const page = parseInt(searchParams.get('page') || '1');
|
||||
const pageSize = parseInt(searchParams.get('pageSize') || '20');
|
||||
|
||||
try {
|
||||
switch (type) {
|
||||
case 'overview':
|
||||
return getOverview();
|
||||
|
||||
case 'bindings':
|
||||
return getBindings(userId, page, pageSize);
|
||||
|
||||
case 'my-bindings':
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: '缺少用户ID' }, { status: 400 });
|
||||
}
|
||||
return getMyBindings(userId);
|
||||
|
||||
case 'withdrawals':
|
||||
return getWithdrawals(userId, page, pageSize);
|
||||
|
||||
case 'reminders':
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: '缺少用户ID' }, { status: 400 });
|
||||
}
|
||||
return getReminders(userId);
|
||||
|
||||
case 'distributor':
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: '缺少用户ID' }, { status: 400 });
|
||||
}
|
||||
return getDistributor(userId);
|
||||
|
||||
case 'ranking':
|
||||
return getRanking();
|
||||
|
||||
default:
|
||||
return NextResponse.json({ error: '未知类型' }, { status: 400 });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('分销API错误:', error);
|
||||
return NextResponse.json({ error: '服务器错误' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// POST: 分销操作
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const body = await req.json();
|
||||
const { action } = body;
|
||||
|
||||
switch (action) {
|
||||
case 'record_click':
|
||||
return recordClick(body);
|
||||
|
||||
case 'convert':
|
||||
return convertBinding(body);
|
||||
|
||||
case 'request_withdraw':
|
||||
return requestWithdraw(body);
|
||||
|
||||
case 'set_auto_withdraw':
|
||||
return setAutoWithdraw(body);
|
||||
|
||||
case 'process_expired':
|
||||
return processExpiredBindings();
|
||||
|
||||
default:
|
||||
return NextResponse.json({ error: '未知操作' }, { status: 400 });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('分销API错误:', error);
|
||||
return NextResponse.json({ error: '服务器错误' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// PUT: 更新操作(后台管理)
|
||||
export async function PUT(req: NextRequest) {
|
||||
try {
|
||||
const body = await req.json();
|
||||
const { action } = body;
|
||||
|
||||
switch (action) {
|
||||
case 'approve_withdraw':
|
||||
return approveWithdraw(body);
|
||||
|
||||
case 'reject_withdraw':
|
||||
return rejectWithdraw(body);
|
||||
|
||||
case 'update_distributor':
|
||||
return updateDistributor(body);
|
||||
|
||||
default:
|
||||
return NextResponse.json({ error: '未知操作' }, { status: 400 });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('分销API错误:', error);
|
||||
return NextResponse.json({ error: '服务器错误' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 具体实现 ==========
|
||||
|
||||
// 获取概览数据
|
||||
function getOverview() {
|
||||
const now = new Date();
|
||||
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
const weekFromNow = new Date();
|
||||
weekFromNow.setDate(weekFromNow.getDate() + 7);
|
||||
|
||||
const overview = {
|
||||
// 今日数据
|
||||
todayClicks: clickRecords.filter(c => new Date(c.clickTime) >= today).length,
|
||||
todayBindings: distributionBindings.filter(b => new Date(b.createdAt) >= today).length,
|
||||
todayConversions: distributionBindings.filter(b =>
|
||||
b.status === 'converted' && b.convertedAt && new Date(b.convertedAt) >= today
|
||||
).length,
|
||||
todayEarnings: distributionBindings
|
||||
.filter(b => b.status === 'converted' && b.convertedAt && new Date(b.convertedAt) >= today)
|
||||
.reduce((sum, b) => sum + (b.commission || 0), 0),
|
||||
|
||||
// 本月数据
|
||||
monthClicks: clickRecords.filter(c => new Date(c.clickTime) >= monthStart).length,
|
||||
monthBindings: distributionBindings.filter(b => new Date(b.createdAt) >= monthStart).length,
|
||||
monthConversions: distributionBindings.filter(b =>
|
||||
b.status === 'converted' && b.convertedAt && new Date(b.convertedAt) >= monthStart
|
||||
).length,
|
||||
monthEarnings: distributionBindings
|
||||
.filter(b => b.status === 'converted' && b.convertedAt && new Date(b.convertedAt) >= monthStart)
|
||||
.reduce((sum, b) => sum + (b.commission || 0), 0),
|
||||
|
||||
// 总计
|
||||
totalClicks: clickRecords.length,
|
||||
totalBindings: distributionBindings.length,
|
||||
totalConversions: distributionBindings.filter(b => b.status === 'converted').length,
|
||||
totalEarnings: distributionBindings
|
||||
.filter(b => b.status === 'converted')
|
||||
.reduce((sum, b) => sum + (b.commission || 0), 0),
|
||||
|
||||
// 即将过期
|
||||
expiringBindings: distributionBindings.filter(b =>
|
||||
b.status === 'active' &&
|
||||
new Date(b.expireTime) <= weekFromNow &&
|
||||
new Date(b.expireTime) > now
|
||||
).length,
|
||||
|
||||
// 待处理提现
|
||||
pendingWithdrawals: withdrawRecords.filter(w => w.status === 'pending').length,
|
||||
pendingWithdrawAmount: withdrawRecords
|
||||
.filter(w => w.status === 'pending')
|
||||
.reduce((sum, w) => sum + w.amount, 0),
|
||||
|
||||
// 转化率
|
||||
conversionRate: clickRecords.length > 0
|
||||
? (distributionBindings.filter(b => b.status === 'converted').length / clickRecords.length * 100).toFixed(2)
|
||||
: '0.00',
|
||||
|
||||
// 分销商数量
|
||||
totalDistributors: distributors.length,
|
||||
activeDistributors: distributors.filter(d => d.status === 'active').length,
|
||||
};
|
||||
|
||||
return NextResponse.json({ success: true, overview });
|
||||
}
|
||||
|
||||
// 获取绑定列表(后台)
|
||||
function getBindings(userId: string | null, page: number, pageSize: number) {
|
||||
let filteredBindings = [...distributionBindings];
|
||||
|
||||
if (userId) {
|
||||
filteredBindings = filteredBindings.filter(b => b.referrerId === userId);
|
||||
}
|
||||
|
||||
// 按创建时间倒序
|
||||
filteredBindings.sort((a, b) =>
|
||||
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
||||
);
|
||||
|
||||
const total = filteredBindings.length;
|
||||
const start = (page - 1) * pageSize;
|
||||
const paginatedBindings = filteredBindings.slice(start, start + pageSize);
|
||||
|
||||
// 添加剩余天数
|
||||
const now = new Date();
|
||||
const bindingsWithDays = paginatedBindings.map(b => ({
|
||||
...b,
|
||||
daysRemaining: b.status === 'active'
|
||||
? Math.max(0, Math.ceil((new Date(b.expireTime).getTime() - now.getTime()) / (1000 * 60 * 60 * 24)))
|
||||
: 0,
|
||||
}));
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
bindings: bindingsWithDays,
|
||||
pagination: {
|
||||
page,
|
||||
pageSize,
|
||||
total,
|
||||
totalPages: Math.ceil(total / pageSize),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 获取我的绑定用户(分销中心)
|
||||
function getMyBindings(userId: string) {
|
||||
const myBindings = distributionBindings.filter(b => b.referrerId === userId);
|
||||
const now = new Date();
|
||||
|
||||
// 按状态分类
|
||||
const active = myBindings
|
||||
.filter(b => b.status === 'active')
|
||||
.map(b => ({
|
||||
...b,
|
||||
daysRemaining: Math.max(0, Math.ceil((new Date(b.expireTime).getTime() - now.getTime()) / (1000 * 60 * 60 * 24))),
|
||||
}))
|
||||
.sort((a, b) => a.daysRemaining - b.daysRemaining); // 即将过期的排前面
|
||||
|
||||
const converted = myBindings.filter(b => b.status === 'converted');
|
||||
const expired = myBindings.filter(b => b.status === 'expired');
|
||||
|
||||
// 统计
|
||||
const stats = {
|
||||
totalBindings: myBindings.length,
|
||||
activeCount: active.length,
|
||||
convertedCount: converted.length,
|
||||
expiredCount: expired.length,
|
||||
expiringCount: active.filter(b => b.daysRemaining <= 7).length,
|
||||
totalCommission: converted.reduce((sum, b) => sum + (b.commission || 0), 0),
|
||||
};
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
bindings: {
|
||||
active,
|
||||
converted,
|
||||
expired,
|
||||
},
|
||||
stats,
|
||||
});
|
||||
}
|
||||
|
||||
// 获取提现记录
|
||||
function getWithdrawals(userId: string | null, page: number, pageSize: number) {
|
||||
let filteredWithdrawals = [...withdrawRecords];
|
||||
|
||||
if (userId) {
|
||||
filteredWithdrawals = filteredWithdrawals.filter(w => w.userId === userId);
|
||||
}
|
||||
|
||||
// 按创建时间倒序
|
||||
filteredWithdrawals.sort((a, b) =>
|
||||
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
||||
);
|
||||
|
||||
const total = filteredWithdrawals.length;
|
||||
const start = (page - 1) * pageSize;
|
||||
const paginatedWithdrawals = filteredWithdrawals.slice(start, start + pageSize);
|
||||
|
||||
// 统计
|
||||
const stats = {
|
||||
pending: filteredWithdrawals.filter(w => w.status === 'pending').length,
|
||||
pendingAmount: filteredWithdrawals
|
||||
.filter(w => w.status === 'pending')
|
||||
.reduce((sum, w) => sum + w.amount, 0),
|
||||
completed: filteredWithdrawals.filter(w => w.status === 'completed').length,
|
||||
completedAmount: filteredWithdrawals
|
||||
.filter(w => w.status === 'completed')
|
||||
.reduce((sum, w) => sum + w.amount, 0),
|
||||
};
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
withdrawals: paginatedWithdrawals,
|
||||
stats,
|
||||
pagination: {
|
||||
page,
|
||||
pageSize,
|
||||
total,
|
||||
totalPages: Math.ceil(total / pageSize),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 获取提醒
|
||||
function getReminders(userId: string) {
|
||||
const now = new Date();
|
||||
const weekFromNow = new Date();
|
||||
weekFromNow.setDate(weekFromNow.getDate() + 7);
|
||||
|
||||
const myBindings = distributionBindings.filter(b =>
|
||||
b.referrerId === userId && b.status === 'active'
|
||||
);
|
||||
|
||||
const expiringSoon = myBindings.filter(b => {
|
||||
const expireTime = new Date(b.expireTime);
|
||||
return expireTime <= weekFromNow && expireTime > now;
|
||||
}).map(b => ({
|
||||
type: 'expiring_soon',
|
||||
binding: b,
|
||||
daysRemaining: Math.ceil((new Date(b.expireTime).getTime() - now.getTime()) / (1000 * 60 * 60 * 24)),
|
||||
message: `用户 ${b.visitorNickname || b.visitorPhone || '未知'} 的绑定将在 ${Math.ceil((new Date(b.expireTime).getTime() - now.getTime()) / (1000 * 60 * 60 * 24))} 天后过期`,
|
||||
}));
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
reminders: expiringSoon,
|
||||
count: expiringSoon.length,
|
||||
});
|
||||
}
|
||||
|
||||
// 获取分销商信息
|
||||
function getDistributor(userId: string) {
|
||||
const distributor = distributors.find(d => d.userId === userId);
|
||||
|
||||
if (!distributor) {
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
distributor: null,
|
||||
message: '用户尚未成为分销商',
|
||||
});
|
||||
}
|
||||
|
||||
return NextResponse.json({ success: true, distributor });
|
||||
}
|
||||
|
||||
// 获取排行榜
|
||||
function getRanking() {
|
||||
const ranking = [...distributors]
|
||||
.filter(d => d.status === 'active')
|
||||
.sort((a, b) => b.totalEarnings - a.totalEarnings)
|
||||
.slice(0, 10)
|
||||
.map((d, index) => ({
|
||||
rank: index + 1,
|
||||
distributorId: d.id,
|
||||
nickname: d.nickname,
|
||||
totalEarnings: d.totalEarnings,
|
||||
totalConversions: d.convertedBindings,
|
||||
level: d.level,
|
||||
}));
|
||||
|
||||
return NextResponse.json({ success: true, ranking });
|
||||
}
|
||||
|
||||
// 记录点击
|
||||
function recordClick(body: {
|
||||
referralCode: string;
|
||||
referrerId: string;
|
||||
visitorId: string;
|
||||
visitorPhone?: string;
|
||||
visitorNickname?: string;
|
||||
source: 'link' | 'miniprogram' | 'poster' | 'qrcode';
|
||||
}) {
|
||||
const now = new Date();
|
||||
|
||||
// 1. 记录点击
|
||||
const click = {
|
||||
id: generateId('click_'),
|
||||
referralCode: body.referralCode,
|
||||
referrerId: body.referrerId,
|
||||
visitorId: body.visitorId,
|
||||
source: body.source,
|
||||
clickTime: now.toISOString(),
|
||||
};
|
||||
clickRecords.push(click);
|
||||
|
||||
// 2. 检查现有绑定
|
||||
const existingBinding = distributionBindings.find(b =>
|
||||
b.visitorId === body.visitorId &&
|
||||
b.status === 'active' &&
|
||||
new Date(b.expireTime) > now
|
||||
);
|
||||
|
||||
if (existingBinding) {
|
||||
// 已有有效绑定,只记录点击
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: '点击已记录,用户已被其他分销商绑定',
|
||||
click,
|
||||
binding: null,
|
||||
});
|
||||
}
|
||||
|
||||
// 3. 创建新绑定
|
||||
const expireDate = new Date(now);
|
||||
expireDate.setDate(expireDate.getDate() + BINDING_DAYS);
|
||||
|
||||
const binding = {
|
||||
id: generateId('bind_'),
|
||||
referrerId: body.referrerId,
|
||||
referrerCode: body.referralCode,
|
||||
visitorId: body.visitorId,
|
||||
visitorPhone: body.visitorPhone,
|
||||
visitorNickname: body.visitorNickname,
|
||||
bindingTime: now.toISOString(),
|
||||
expireTime: expireDate.toISOString(),
|
||||
status: 'active' as const,
|
||||
source: body.source,
|
||||
createdAt: now.toISOString(),
|
||||
};
|
||||
distributionBindings.push(binding);
|
||||
|
||||
// 4. 更新分销商统计
|
||||
const distributorIndex = distributors.findIndex(d => d.userId === body.referrerId);
|
||||
if (distributorIndex !== -1) {
|
||||
distributors[distributorIndex].totalClicks++;
|
||||
distributors[distributorIndex].totalBindings++;
|
||||
distributors[distributorIndex].activeBindings++;
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: '点击已记录,绑定创建成功',
|
||||
click,
|
||||
binding,
|
||||
expireTime: expireDate.toISOString(),
|
||||
bindingDays: BINDING_DAYS,
|
||||
});
|
||||
}
|
||||
|
||||
// 转化绑定(用户付款)
|
||||
function convertBinding(body: {
|
||||
visitorId: string;
|
||||
orderId: string;
|
||||
orderAmount: number;
|
||||
}) {
|
||||
const now = new Date();
|
||||
|
||||
// 查找有效绑定
|
||||
const bindingIndex = distributionBindings.findIndex(b =>
|
||||
b.visitorId === body.visitorId &&
|
||||
b.status === 'active' &&
|
||||
new Date(b.expireTime) > now
|
||||
);
|
||||
|
||||
if (bindingIndex === -1) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
message: '未找到有效绑定,该订单不计入分销',
|
||||
});
|
||||
}
|
||||
|
||||
const binding = distributionBindings[bindingIndex];
|
||||
|
||||
// 查找分销商
|
||||
const distributorIndex = distributors.findIndex(d => d.userId === binding.referrerId);
|
||||
const commissionRate = distributorIndex !== -1
|
||||
? distributors[distributorIndex].commissionRate
|
||||
: DEFAULT_COMMISSION_RATE;
|
||||
|
||||
const commission = body.orderAmount * (commissionRate / 100);
|
||||
|
||||
// 更新绑定
|
||||
distributionBindings[bindingIndex] = {
|
||||
...binding,
|
||||
status: 'converted',
|
||||
convertedAt: now.toISOString(),
|
||||
orderId: body.orderId,
|
||||
orderAmount: body.orderAmount,
|
||||
commission,
|
||||
};
|
||||
|
||||
// 更新分销商
|
||||
if (distributorIndex !== -1) {
|
||||
distributors[distributorIndex].activeBindings--;
|
||||
distributors[distributorIndex].convertedBindings++;
|
||||
distributors[distributorIndex].totalEarnings += commission;
|
||||
distributors[distributorIndex].pendingEarnings += commission;
|
||||
|
||||
// 检查是否需要自动提现
|
||||
checkAutoWithdraw(distributors[distributorIndex]);
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: '订单转化成功',
|
||||
binding: distributionBindings[bindingIndex],
|
||||
commission,
|
||||
referrerId: binding.referrerId,
|
||||
});
|
||||
}
|
||||
|
||||
// 申请提现
|
||||
function requestWithdraw(body: {
|
||||
userId: string;
|
||||
amount: number;
|
||||
method: 'wechat' | 'alipay';
|
||||
account: string;
|
||||
accountName: string;
|
||||
}) {
|
||||
// 查找分销商
|
||||
const distributorIndex = distributors.findIndex(d => d.userId === body.userId);
|
||||
|
||||
if (distributorIndex === -1) {
|
||||
return NextResponse.json({ success: false, error: '分销商不存在' }, { status: 404 });
|
||||
}
|
||||
|
||||
const distributor = distributors[distributorIndex];
|
||||
|
||||
if (body.amount < MIN_WITHDRAW_AMOUNT) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: `最低提现金额为 ${MIN_WITHDRAW_AMOUNT} 元`
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
if (body.amount > distributor.pendingEarnings) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: '提现金额超过可提现余额'
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
// 创建提现记录
|
||||
const withdrawal = {
|
||||
id: generateId('withdraw_'),
|
||||
distributorId: distributor.id,
|
||||
userId: body.userId,
|
||||
userName: distributor.nickname,
|
||||
amount: body.amount,
|
||||
fee: 0,
|
||||
actualAmount: body.amount,
|
||||
method: body.method,
|
||||
account: body.account,
|
||||
accountName: body.accountName,
|
||||
status: 'pending' as const,
|
||||
isAuto: false,
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
withdrawRecords.push(withdrawal);
|
||||
|
||||
// 扣除待提现金额
|
||||
distributors[distributorIndex].pendingEarnings -= body.amount;
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: '提现申请已提交',
|
||||
withdrawal,
|
||||
});
|
||||
}
|
||||
|
||||
// 设置自动提现
|
||||
function setAutoWithdraw(body: {
|
||||
userId: string;
|
||||
enabled: boolean;
|
||||
threshold?: number;
|
||||
account?: {
|
||||
type: 'wechat' | 'alipay';
|
||||
account: string;
|
||||
name: string;
|
||||
};
|
||||
}) {
|
||||
const distributorIndex = distributors.findIndex(d => d.userId === body.userId);
|
||||
|
||||
if (distributorIndex === -1) {
|
||||
return NextResponse.json({ success: false, error: '分销商不存在' }, { status: 404 });
|
||||
}
|
||||
|
||||
distributors[distributorIndex] = {
|
||||
...distributors[distributorIndex],
|
||||
autoWithdraw: body.enabled,
|
||||
autoWithdrawThreshold: body.threshold || distributors[distributorIndex].autoWithdrawThreshold,
|
||||
autoWithdrawAccount: body.account || distributors[distributorIndex].autoWithdrawAccount,
|
||||
};
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: body.enabled ? '自动提现已开启' : '自动提现已关闭',
|
||||
distributor: distributors[distributorIndex],
|
||||
});
|
||||
}
|
||||
|
||||
// 处理过期绑定
|
||||
function processExpiredBindings() {
|
||||
const now = new Date();
|
||||
let expiredCount = 0;
|
||||
|
||||
distributionBindings.forEach((binding, index) => {
|
||||
if (binding.status === 'active' && new Date(binding.expireTime) <= now) {
|
||||
distributionBindings[index].status = 'expired';
|
||||
expiredCount++;
|
||||
|
||||
// 更新分销商统计
|
||||
const distributorIndex = distributors.findIndex(d => d.userId === binding.referrerId);
|
||||
if (distributorIndex !== -1) {
|
||||
distributors[distributorIndex].activeBindings--;
|
||||
distributors[distributorIndex].expiredBindings++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: `已处理 ${expiredCount} 个过期绑定`,
|
||||
expiredCount,
|
||||
});
|
||||
}
|
||||
|
||||
// 审核通过提现
|
||||
async function approveWithdraw(body: { withdrawalId: string; reviewedBy?: string }) {
|
||||
const withdrawalIndex = withdrawRecords.findIndex(w => w.id === body.withdrawalId);
|
||||
|
||||
if (withdrawalIndex === -1) {
|
||||
return NextResponse.json({ success: false, error: '提现记录不存在' }, { status: 404 });
|
||||
}
|
||||
|
||||
const withdrawal = withdrawRecords[withdrawalIndex];
|
||||
|
||||
if (withdrawal.status !== 'pending') {
|
||||
return NextResponse.json({ success: false, error: '该提现申请已处理' }, { status: 400 });
|
||||
}
|
||||
|
||||
// 更新状态为处理中
|
||||
withdrawRecords[withdrawalIndex].status = 'processing';
|
||||
|
||||
// 模拟打款(实际项目中调用支付接口)
|
||||
try {
|
||||
// 模拟延迟
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
// 打款成功
|
||||
withdrawRecords[withdrawalIndex] = {
|
||||
...withdrawRecords[withdrawalIndex],
|
||||
status: 'completed',
|
||||
paymentNo: `PAY${Date.now()}`,
|
||||
paymentTime: new Date().toISOString(),
|
||||
completedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
// 更新分销商已提现金额
|
||||
const distributorIndex = distributors.findIndex(d => d.userId === withdrawal.userId);
|
||||
if (distributorIndex !== -1) {
|
||||
distributors[distributorIndex].withdrawnEarnings += withdrawal.amount;
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: '打款成功',
|
||||
withdrawal: withdrawRecords[withdrawalIndex],
|
||||
});
|
||||
} catch (error) {
|
||||
// 打款失败,退还金额
|
||||
withdrawRecords[withdrawalIndex].status = 'failed';
|
||||
|
||||
const distributorIndex = distributors.findIndex(d => d.userId === withdrawal.userId);
|
||||
if (distributorIndex !== -1) {
|
||||
distributors[distributorIndex].pendingEarnings += withdrawal.amount;
|
||||
}
|
||||
|
||||
return NextResponse.json({ success: false, error: '打款失败' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// 拒绝提现
|
||||
function rejectWithdraw(body: { withdrawalId: string; reason: string; reviewedBy?: string }) {
|
||||
const withdrawalIndex = withdrawRecords.findIndex(w => w.id === body.withdrawalId);
|
||||
|
||||
if (withdrawalIndex === -1) {
|
||||
return NextResponse.json({ success: false, error: '提现记录不存在' }, { status: 404 });
|
||||
}
|
||||
|
||||
const withdrawal = withdrawRecords[withdrawalIndex];
|
||||
|
||||
if (withdrawal.status !== 'pending') {
|
||||
return NextResponse.json({ success: false, error: '该提现申请已处理' }, { status: 400 });
|
||||
}
|
||||
|
||||
// 更新状态
|
||||
withdrawRecords[withdrawalIndex] = {
|
||||
...withdrawal,
|
||||
status: 'rejected',
|
||||
reviewNote: body.reason,
|
||||
};
|
||||
|
||||
// 退还金额
|
||||
const distributorIndex = distributors.findIndex(d => d.userId === withdrawal.userId);
|
||||
if (distributorIndex !== -1) {
|
||||
distributors[distributorIndex].pendingEarnings += withdrawal.amount;
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: '提现申请已拒绝',
|
||||
withdrawal: withdrawRecords[withdrawalIndex],
|
||||
});
|
||||
}
|
||||
|
||||
// 更新分销商信息
|
||||
function updateDistributor(body: {
|
||||
userId: string;
|
||||
commissionRate?: number;
|
||||
level?: 'normal' | 'silver' | 'gold' | 'diamond';
|
||||
status?: 'active' | 'frozen' | 'disabled';
|
||||
}) {
|
||||
const distributorIndex = distributors.findIndex(d => d.userId === body.userId);
|
||||
|
||||
if (distributorIndex === -1) {
|
||||
return NextResponse.json({ success: false, error: '分销商不存在' }, { status: 404 });
|
||||
}
|
||||
|
||||
distributors[distributorIndex] = {
|
||||
...distributors[distributorIndex],
|
||||
...(body.commissionRate !== undefined && { commissionRate: body.commissionRate }),
|
||||
...(body.level && { level: body.level }),
|
||||
...(body.status && { status: body.status }),
|
||||
};
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: '分销商信息已更新',
|
||||
distributor: distributors[distributorIndex],
|
||||
});
|
||||
}
|
||||
|
||||
// 检查自动提现
|
||||
function checkAutoWithdraw(distributor: typeof distributors[0]) {
|
||||
if (!distributor.autoWithdraw || !distributor.autoWithdrawAccount) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (distributor.pendingEarnings >= distributor.autoWithdrawThreshold) {
|
||||
// 创建自动提现记录
|
||||
const withdrawal = {
|
||||
id: generateId('withdraw_'),
|
||||
distributorId: distributor.id,
|
||||
userId: distributor.userId,
|
||||
userName: distributor.nickname,
|
||||
amount: distributor.pendingEarnings,
|
||||
fee: 0,
|
||||
actualAmount: distributor.pendingEarnings,
|
||||
method: distributor.autoWithdrawAccount.type,
|
||||
account: distributor.autoWithdrawAccount.account,
|
||||
accountName: distributor.autoWithdrawAccount.name,
|
||||
status: 'processing' as const,
|
||||
isAuto: true,
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
withdrawRecords.push(withdrawal);
|
||||
|
||||
// 扣除待提现金额
|
||||
const distributorIndex = distributors.findIndex(d => d.id === distributor.id);
|
||||
if (distributorIndex !== -1) {
|
||||
distributors[distributorIndex].pendingEarnings = 0;
|
||||
}
|
||||
|
||||
// 模拟打款(实际项目中调用支付接口)
|
||||
processAutoWithdraw(withdrawal.id);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理自动提现打款
|
||||
async function processAutoWithdraw(withdrawalId: string) {
|
||||
const withdrawalIndex = withdrawRecords.findIndex(w => w.id === withdrawalId);
|
||||
if (withdrawalIndex === -1) return;
|
||||
|
||||
try {
|
||||
// 模拟延迟
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
// 打款成功
|
||||
const withdrawal = withdrawRecords[withdrawalIndex];
|
||||
withdrawRecords[withdrawalIndex] = {
|
||||
...withdrawal,
|
||||
status: 'completed',
|
||||
paymentNo: `AUTO_PAY${Date.now()}`,
|
||||
paymentTime: new Date().toISOString(),
|
||||
completedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
// 更新分销商已提现金额
|
||||
const distributorIndex = distributors.findIndex(d => d.userId === withdrawal.userId);
|
||||
if (distributorIndex !== -1) {
|
||||
distributors[distributorIndex].withdrawnEarnings += withdrawal.amount;
|
||||
}
|
||||
|
||||
console.log(`自动提现成功: ${withdrawal.userName}, 金额: ¥${withdrawal.amount}`);
|
||||
} catch (error) {
|
||||
// 打款失败
|
||||
withdrawRecords[withdrawalIndex].status = 'failed';
|
||||
|
||||
const withdrawal = withdrawRecords[withdrawalIndex];
|
||||
const distributorIndex = distributors.findIndex(d => d.userId === withdrawal.userId);
|
||||
if (distributorIndex !== -1) {
|
||||
distributors[distributorIndex].pendingEarnings += withdrawal.amount;
|
||||
}
|
||||
|
||||
console.error(`自动提现失败: ${withdrawal.userName}`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user