2026-01-23 16:31:54 +08:00
|
|
|
|
/**
|
2026-01-29 09:47:04 +08:00
|
|
|
|
* 分销数据API - 增强版
|
|
|
|
|
|
*
|
|
|
|
|
|
* 可见数据:
|
|
|
|
|
|
* - 绑定用户数(当前有效绑定)
|
|
|
|
|
|
* - 通过链接进的人数(总访问量)
|
|
|
|
|
|
* - 带来的付款人数(已转化购买)
|
|
|
|
|
|
* - 收益统计(90%归分发者)
|
2026-01-23 16:31:54 +08:00
|
|
|
|
*/
|
|
|
|
|
|
|
2026-01-25 19:37:59 +08:00
|
|
|
|
import { NextRequest, NextResponse } from 'next/server'
|
2026-01-29 09:47:04 +08:00
|
|
|
|
import { query, getConfig } from '@/lib/db'
|
|
|
|
|
|
|
|
|
|
|
|
// 分成比例(默认90%给推广者)
|
|
|
|
|
|
const DISTRIBUTOR_SHARE = 0.9
|
2026-01-23 16:31:54 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
2026-01-25 19:37:59 +08:00
|
|
|
|
* GET - 获取分销数据
|
2026-01-23 16:31:54 +08:00
|
|
|
|
*/
|
2026-01-25 19:37:59 +08:00
|
|
|
|
export async function GET(request: NextRequest) {
|
|
|
|
|
|
const { searchParams } = new URL(request.url)
|
|
|
|
|
|
const userId = searchParams.get('userId')
|
|
|
|
|
|
|
|
|
|
|
|
if (!userId) {
|
2026-01-23 16:31:54 +08:00
|
|
|
|
return NextResponse.json({
|
|
|
|
|
|
success: false,
|
2026-01-25 19:37:59 +08:00
|
|
|
|
error: '用户ID不能为空'
|
|
|
|
|
|
}, { status: 400 })
|
2026-01-23 16:31:54 +08:00
|
|
|
|
}
|
2026-01-25 19:37:59 +08:00
|
|
|
|
|
2026-01-23 16:31:54 +08:00
|
|
|
|
try {
|
2026-01-29 09:47:04 +08:00
|
|
|
|
// 获取分销配置
|
|
|
|
|
|
let distributorShare = DISTRIBUTOR_SHARE
|
|
|
|
|
|
try {
|
|
|
|
|
|
const config = await getConfig('referral_config')
|
|
|
|
|
|
if (config?.distributorShare) {
|
|
|
|
|
|
distributorShare = config.distributorShare / 100
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) { /* 使用默认配置 */ }
|
|
|
|
|
|
|
2026-01-25 19:37:59 +08:00
|
|
|
|
// 1. 获取用户基本信息
|
|
|
|
|
|
const users = await query(`
|
|
|
|
|
|
SELECT id, nickname, referral_code, earnings, pending_earnings,
|
|
|
|
|
|
withdrawn_earnings, referral_count
|
|
|
|
|
|
FROM users WHERE id = ?
|
|
|
|
|
|
`, [userId]) as any[]
|
|
|
|
|
|
|
|
|
|
|
|
if (users.length === 0) {
|
2026-01-23 16:31:54 +08:00
|
|
|
|
return NextResponse.json({
|
|
|
|
|
|
success: false,
|
2026-01-25 19:37:59 +08:00
|
|
|
|
error: '用户不存在'
|
|
|
|
|
|
}, { status: 404 })
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const user = users[0]
|
|
|
|
|
|
|
2026-01-29 09:47:04 +08:00
|
|
|
|
// 2. 获取绑定关系统计(从referral_bindings表)
|
|
|
|
|
|
let bindingStats = { total: 0, active: 0, converted: 0, expired: 0 }
|
|
|
|
|
|
try {
|
|
|
|
|
|
const bindings = await query(`
|
|
|
|
|
|
SELECT
|
|
|
|
|
|
COUNT(*) as total,
|
|
|
|
|
|
SUM(CASE WHEN status = 'active' AND expiry_date > NOW() THEN 1 ELSE 0 END) as active,
|
|
|
|
|
|
SUM(CASE WHEN status = 'converted' THEN 1 ELSE 0 END) as converted,
|
|
|
|
|
|
SUM(CASE WHEN status = 'expired' OR (status = 'active' AND expiry_date <= NOW()) THEN 1 ELSE 0 END) as expired
|
|
|
|
|
|
FROM referral_bindings
|
|
|
|
|
|
WHERE referrer_id = ?
|
|
|
|
|
|
`, [userId]) as any[]
|
|
|
|
|
|
|
|
|
|
|
|
if (bindings.length > 0) {
|
|
|
|
|
|
bindingStats = {
|
|
|
|
|
|
total: parseInt(bindings[0].total) || 0,
|
|
|
|
|
|
active: parseInt(bindings[0].active) || 0,
|
|
|
|
|
|
converted: parseInt(bindings[0].converted) || 0,
|
|
|
|
|
|
expired: parseInt(bindings[0].expired) || 0
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) { /* 忽略 */ }
|
2026-01-25 19:37:59 +08:00
|
|
|
|
|
2026-01-29 09:47:04 +08:00
|
|
|
|
// 3. 获取通过链接进入的总人数(访问日志)
|
|
|
|
|
|
let totalVisits = 0
|
|
|
|
|
|
try {
|
|
|
|
|
|
const visits = await query(`
|
|
|
|
|
|
SELECT COUNT(DISTINCT visitor_id) as count
|
|
|
|
|
|
FROM referral_visits
|
|
|
|
|
|
WHERE referrer_id = ?
|
|
|
|
|
|
`, [userId]) as any[]
|
|
|
|
|
|
totalVisits = parseInt(visits[0]?.count) || 0
|
|
|
|
|
|
} catch (e) { /* 访问记录表可能不存在 */ }
|
2026-01-25 19:37:59 +08:00
|
|
|
|
|
2026-01-29 09:47:04 +08:00
|
|
|
|
// 如果没有访问记录表,用绑定总数替代
|
|
|
|
|
|
if (totalVisits === 0) {
|
|
|
|
|
|
totalVisits = bindingStats.total
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 获取带来的付款人数和金额
|
|
|
|
|
|
let paymentStats = { paidCount: 0, totalAmount: 0 }
|
|
|
|
|
|
try {
|
|
|
|
|
|
const payments = await query(`
|
|
|
|
|
|
SELECT
|
|
|
|
|
|
COUNT(DISTINCT o.user_id) as paid_count,
|
|
|
|
|
|
COALESCE(SUM(o.amount), 0) as total_amount
|
|
|
|
|
|
FROM orders o
|
|
|
|
|
|
JOIN referral_bindings rb ON o.user_id = rb.referee_id
|
|
|
|
|
|
WHERE rb.referrer_id = ? AND o.status = 'paid'
|
|
|
|
|
|
`, [userId]) as any[]
|
2026-01-25 19:37:59 +08:00
|
|
|
|
|
2026-01-29 09:47:04 +08:00
|
|
|
|
if (payments.length > 0) {
|
|
|
|
|
|
paymentStats = {
|
|
|
|
|
|
paidCount: parseInt(payments[0].paid_count) || 0,
|
|
|
|
|
|
totalAmount: parseFloat(payments[0].total_amount) || 0
|
|
|
|
|
|
}
|
2026-01-25 19:37:59 +08:00
|
|
|
|
}
|
2026-01-29 09:47:04 +08:00
|
|
|
|
} catch (e) { /* 忽略 */ }
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 获取活跃绑定用户列表
|
|
|
|
|
|
const activeBindings = await query(`
|
|
|
|
|
|
SELECT rb.id, rb.referee_id, rb.expiry_date, rb.binding_date,
|
|
|
|
|
|
u.nickname, u.avatar, u.has_full_book,
|
|
|
|
|
|
DATEDIFF(rb.expiry_date, NOW()) as days_remaining
|
|
|
|
|
|
FROM referral_bindings rb
|
|
|
|
|
|
JOIN users u ON rb.referee_id = u.id
|
|
|
|
|
|
WHERE rb.referrer_id = ? AND rb.status = 'active' AND rb.expiry_date > NOW()
|
|
|
|
|
|
ORDER BY rb.binding_date DESC
|
|
|
|
|
|
LIMIT 50
|
|
|
|
|
|
`, [userId]) as any[]
|
2026-01-25 19:37:59 +08:00
|
|
|
|
|
2026-01-29 09:47:04 +08:00
|
|
|
|
// 6. 获取已转化用户列表
|
|
|
|
|
|
const convertedBindings = await query(`
|
|
|
|
|
|
SELECT rb.id, rb.referee_id, rb.conversion_date, rb.commission_amount,
|
|
|
|
|
|
u.nickname, u.avatar
|
|
|
|
|
|
FROM referral_bindings rb
|
|
|
|
|
|
JOIN users u ON rb.referee_id = u.id
|
|
|
|
|
|
WHERE rb.referrer_id = ? AND rb.status = 'converted'
|
|
|
|
|
|
ORDER BY rb.conversion_date DESC
|
|
|
|
|
|
LIMIT 50
|
|
|
|
|
|
`, [userId]) as any[]
|
|
|
|
|
|
|
|
|
|
|
|
// 7. 获取收益明细
|
2026-01-25 19:37:59 +08:00
|
|
|
|
let earningsDetails: any[] = []
|
|
|
|
|
|
try {
|
|
|
|
|
|
earningsDetails = await query(`
|
2026-01-29 09:47:04 +08:00
|
|
|
|
SELECT o.id, o.order_sn, o.amount, o.product_type, o.pay_time,
|
|
|
|
|
|
u.nickname as buyer_nickname,
|
|
|
|
|
|
rb.commission_amount
|
2026-01-25 19:37:59 +08:00
|
|
|
|
FROM orders o
|
|
|
|
|
|
JOIN users u ON o.user_id = u.id
|
2026-01-29 09:47:04 +08:00
|
|
|
|
JOIN referral_bindings rb ON o.user_id = rb.referee_id AND rb.referrer_id = ?
|
|
|
|
|
|
WHERE o.status = 'paid'
|
|
|
|
|
|
ORDER BY o.pay_time DESC
|
|
|
|
|
|
LIMIT 30
|
2026-01-25 19:37:59 +08:00
|
|
|
|
`, [userId]) as any[]
|
2026-01-29 09:47:04 +08:00
|
|
|
|
} catch (e) { /* 忽略 */ }
|
2026-01-25 19:37:59 +08:00
|
|
|
|
|
2026-01-29 09:47:04 +08:00
|
|
|
|
// 8. 计算预估收益
|
|
|
|
|
|
const estimatedEarnings = paymentStats.totalAmount * distributorShare
|
2026-01-23 16:31:54 +08:00
|
|
|
|
|
|
|
|
|
|
return NextResponse.json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
data: {
|
2026-01-29 09:47:04 +08:00
|
|
|
|
// === 核心可见数据 ===
|
|
|
|
|
|
// 绑定用户数(当前有效绑定)
|
|
|
|
|
|
bindingCount: bindingStats.active,
|
|
|
|
|
|
// 通过链接进的人数
|
|
|
|
|
|
visitCount: totalVisits,
|
|
|
|
|
|
// 带来的付款人数
|
|
|
|
|
|
paidCount: paymentStats.paidCount,
|
|
|
|
|
|
|
|
|
|
|
|
// === 收益数据 ===
|
|
|
|
|
|
// 已结算收益
|
2026-01-25 19:37:59 +08:00
|
|
|
|
earnings: parseFloat(user.earnings) || 0,
|
2026-01-29 09:47:04 +08:00
|
|
|
|
// 待结算收益
|
2026-01-25 19:37:59 +08:00
|
|
|
|
pendingEarnings: parseFloat(user.pending_earnings) || 0,
|
2026-01-29 09:47:04 +08:00
|
|
|
|
// 已提现金额
|
2026-01-25 19:37:59 +08:00
|
|
|
|
withdrawnEarnings: parseFloat(user.withdrawn_earnings) || 0,
|
2026-01-29 09:47:04 +08:00
|
|
|
|
// 预估总收益
|
|
|
|
|
|
estimatedEarnings: Math.round(estimatedEarnings * 100) / 100,
|
|
|
|
|
|
// 分成比例
|
|
|
|
|
|
shareRate: Math.round(distributorShare * 100),
|
2026-01-25 19:37:59 +08:00
|
|
|
|
|
2026-01-29 09:47:04 +08:00
|
|
|
|
// === 推荐码 ===
|
2026-01-25 19:37:59 +08:00
|
|
|
|
referralCode: user.referral_code,
|
2026-01-29 09:47:04 +08:00
|
|
|
|
referralCount: user.referral_count || bindingStats.total,
|
|
|
|
|
|
|
|
|
|
|
|
// === 详细统计 ===
|
|
|
|
|
|
stats: {
|
|
|
|
|
|
totalBindings: bindingStats.total,
|
|
|
|
|
|
activeBindings: bindingStats.active,
|
|
|
|
|
|
convertedBindings: bindingStats.converted,
|
|
|
|
|
|
expiredBindings: bindingStats.expired,
|
|
|
|
|
|
// 即将过期(7天内)
|
|
|
|
|
|
expiringCount: activeBindings.filter((b: any) => b.days_remaining <= 7 && b.days_remaining > 0).length,
|
|
|
|
|
|
// 总支付金额
|
|
|
|
|
|
totalPaymentAmount: paymentStats.totalAmount
|
|
|
|
|
|
},
|
2026-01-25 19:37:59 +08:00
|
|
|
|
|
2026-01-29 09:47:04 +08:00
|
|
|
|
// === 用户列表 ===
|
|
|
|
|
|
activeUsers: activeBindings.map((b: any) => ({
|
|
|
|
|
|
id: b.referee_id,
|
|
|
|
|
|
nickname: b.nickname || '用户' + b.referee_id.slice(-4),
|
|
|
|
|
|
avatar: b.avatar,
|
|
|
|
|
|
daysRemaining: Math.max(0, b.days_remaining),
|
|
|
|
|
|
hasFullBook: b.has_full_book,
|
|
|
|
|
|
bindingDate: b.binding_date
|
|
|
|
|
|
})),
|
2026-01-25 19:37:59 +08:00
|
|
|
|
|
2026-01-29 09:47:04 +08:00
|
|
|
|
convertedUsers: convertedBindings.map((b: any) => ({
|
|
|
|
|
|
id: b.referee_id,
|
|
|
|
|
|
nickname: b.nickname || '用户' + b.referee_id.slice(-4),
|
|
|
|
|
|
avatar: b.avatar,
|
|
|
|
|
|
commission: parseFloat(b.commission_amount) || 0,
|
|
|
|
|
|
conversionDate: b.conversion_date
|
|
|
|
|
|
})),
|
2026-01-25 19:37:59 +08:00
|
|
|
|
|
2026-01-29 09:47:04 +08:00
|
|
|
|
// === 收益明细 ===
|
|
|
|
|
|
earningsDetails: earningsDetails.map((e: any) => ({
|
2026-01-25 19:37:59 +08:00
|
|
|
|
id: e.id,
|
2026-01-29 09:47:04 +08:00
|
|
|
|
orderSn: e.order_sn,
|
|
|
|
|
|
amount: parseFloat(e.amount),
|
|
|
|
|
|
commission: parseFloat(e.commission_amount) || parseFloat(e.amount) * distributorShare,
|
2026-01-25 19:37:59 +08:00
|
|
|
|
productType: e.product_type,
|
|
|
|
|
|
buyerNickname: e.buyer_nickname,
|
2026-01-29 09:47:04 +08:00
|
|
|
|
payTime: e.pay_time
|
2026-01-25 19:37:59 +08:00
|
|
|
|
}))
|
2026-01-23 16:31:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
})
|
2026-01-25 19:37:59 +08:00
|
|
|
|
|
2026-01-23 16:31:54 +08:00
|
|
|
|
} catch (error) {
|
2026-01-25 19:37:59 +08:00
|
|
|
|
console.error('[ReferralData] 错误:', error)
|
2026-01-23 16:31:54 +08:00
|
|
|
|
return NextResponse.json({
|
|
|
|
|
|
success: false,
|
2026-01-25 19:37:59 +08:00
|
|
|
|
error: '获取分销数据失败: ' + (error as Error).message
|
2026-01-23 16:31:54 +08:00
|
|
|
|
}, { status: 500 })
|
|
|
|
|
|
}
|
2026-01-25 19:37:59 +08:00
|
|
|
|
}
|