269 lines
9.4 KiB
TypeScript
269 lines
9.4 KiB
TypeScript
|
|
/**
|
|||
|
|
* 管理端分销数据概览API - 从真实数据库查询
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
import { NextRequest, NextResponse } from 'next/server'
|
|||
|
|
import { query } from '@/lib/db'
|
|||
|
|
import { requireAdminResponse } from '@/lib/admin-auth'
|
|||
|
|
|
|||
|
|
export async function GET(req: NextRequest) {
|
|||
|
|
// 验证管理员权限
|
|||
|
|
const authErr = requireAdminResponse(req)
|
|||
|
|
if (authErr) return authErr
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const now = new Date()
|
|||
|
|
const today = now.toISOString().split('T')[0]
|
|||
|
|
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1).toISOString()
|
|||
|
|
const sevenDaysLater = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000).toISOString()
|
|||
|
|
|
|||
|
|
// === 1. 订单数据统计 ===
|
|||
|
|
let orderStats = {
|
|||
|
|
todayOrders: 0,
|
|||
|
|
todayAmount: 0,
|
|||
|
|
monthOrders: 0,
|
|||
|
|
monthAmount: 0,
|
|||
|
|
totalOrders: 0,
|
|||
|
|
totalAmount: 0
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const orderResults = await query(`
|
|||
|
|
SELECT
|
|||
|
|
COUNT(*) as total_count,
|
|||
|
|
COALESCE(SUM(amount), 0) as total_amount,
|
|||
|
|
COALESCE(SUM(CASE WHEN DATE(created_at) = ? THEN 1 ELSE 0 END), 0) as today_count,
|
|||
|
|
COALESCE(SUM(CASE WHEN DATE(created_at) = ? THEN amount ELSE 0 END), 0) as today_amount,
|
|||
|
|
COALESCE(SUM(CASE WHEN created_at >= ? THEN 1 ELSE 0 END), 0) as month_count,
|
|||
|
|
COALESCE(SUM(CASE WHEN created_at >= ? THEN amount ELSE 0 END), 0) as month_amount
|
|||
|
|
FROM orders
|
|||
|
|
WHERE status = 'paid'
|
|||
|
|
`, [today, today, monthStart, monthStart]) as any[]
|
|||
|
|
|
|||
|
|
if (orderResults.length > 0) {
|
|||
|
|
const r = orderResults[0]
|
|||
|
|
orderStats = {
|
|||
|
|
todayOrders: parseInt(r.today_count) || 0,
|
|||
|
|
todayAmount: parseFloat(r.today_amount) || 0,
|
|||
|
|
monthOrders: parseInt(r.month_count) || 0,
|
|||
|
|
monthAmount: parseFloat(r.month_amount) || 0,
|
|||
|
|
totalOrders: parseInt(r.total_count) || 0,
|
|||
|
|
totalAmount: parseFloat(r.total_amount) || 0
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('[Admin Overview] 订单统计失败:', e)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// === 2. 绑定数据统计 ===
|
|||
|
|
let bindingStats = {
|
|||
|
|
todayBindings: 0,
|
|||
|
|
todayConversions: 0,
|
|||
|
|
monthBindings: 0,
|
|||
|
|
monthConversions: 0,
|
|||
|
|
totalBindings: 0,
|
|||
|
|
totalConversions: 0,
|
|||
|
|
activeBindings: 0,
|
|||
|
|
expiredBindings: 0,
|
|||
|
|
expiringBindings: 0
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const bindingResults = await query(`
|
|||
|
|
SELECT
|
|||
|
|
COUNT(*) as total_count,
|
|||
|
|
SUM(CASE WHEN status = 'active' AND expiry_date > NOW() THEN 1 ELSE 0 END) as active_count,
|
|||
|
|
SUM(CASE WHEN status = 'converted' THEN 1 ELSE 0 END) as converted_count,
|
|||
|
|
SUM(CASE WHEN status = 'expired' OR (status = 'active' AND expiry_date <= NOW()) THEN 1 ELSE 0 END) as expired_count,
|
|||
|
|
SUM(CASE WHEN DATE(binding_date) = ? THEN 1 ELSE 0 END) as today_count,
|
|||
|
|
SUM(CASE WHEN DATE(binding_date) = ? AND status = 'converted' THEN 1 ELSE 0 END) as today_converted,
|
|||
|
|
SUM(CASE WHEN binding_date >= ? THEN 1 ELSE 0 END) as month_count,
|
|||
|
|
SUM(CASE WHEN binding_date >= ? AND status = 'converted' THEN 1 ELSE 0 END) as month_converted,
|
|||
|
|
SUM(CASE WHEN status = 'active' AND expiry_date <= ? AND expiry_date > NOW() THEN 1 ELSE 0 END) as expiring_count
|
|||
|
|
FROM referral_bindings
|
|||
|
|
`, [today, today, monthStart, monthStart, sevenDaysLater]) as any[]
|
|||
|
|
|
|||
|
|
if (bindingResults.length > 0) {
|
|||
|
|
const r = bindingResults[0]
|
|||
|
|
bindingStats = {
|
|||
|
|
todayBindings: parseInt(r.today_count) || 0,
|
|||
|
|
todayConversions: parseInt(r.today_converted) || 0,
|
|||
|
|
monthBindings: parseInt(r.month_count) || 0,
|
|||
|
|
monthConversions: parseInt(r.month_converted) || 0,
|
|||
|
|
totalBindings: parseInt(r.total_count) || 0,
|
|||
|
|
totalConversions: parseInt(r.converted_count) || 0,
|
|||
|
|
activeBindings: parseInt(r.active_count) || 0,
|
|||
|
|
expiredBindings: parseInt(r.expired_count) || 0,
|
|||
|
|
expiringBindings: parseInt(r.expiring_count) || 0
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('[Admin Overview] 绑定统计失败:', e)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// === 3. 收益数据统计 ===
|
|||
|
|
let earningsStats = {
|
|||
|
|
totalEarnings: 0,
|
|||
|
|
todayEarnings: 0,
|
|||
|
|
monthEarnings: 0,
|
|||
|
|
pendingEarnings: 0
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 从 users 表累加所有用户的收益
|
|||
|
|
const earningsResults = await query(`
|
|||
|
|
SELECT
|
|||
|
|
COALESCE(SUM(earnings), 0) as total_earnings,
|
|||
|
|
COALESCE(SUM(pending_earnings), 0) as pending_earnings
|
|||
|
|
FROM users
|
|||
|
|
`) as any[]
|
|||
|
|
|
|||
|
|
if (earningsResults.length > 0) {
|
|||
|
|
earningsStats.totalEarnings = parseFloat(earningsResults[0].total_earnings) || 0
|
|||
|
|
earningsStats.pendingEarnings = parseFloat(earningsResults[0].pending_earnings) || 0
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 今日和本月收益:从 orders 表计算(status='paid' 的订单)
|
|||
|
|
const periodEarningsResults = await query(`
|
|||
|
|
SELECT
|
|||
|
|
COALESCE(SUM(CASE WHEN DATE(pay_time) = ? THEN amount * 0.9 ELSE 0 END), 0) as today_earnings,
|
|||
|
|
COALESCE(SUM(CASE WHEN pay_time >= ? THEN amount * 0.9 ELSE 0 END), 0) as month_earnings
|
|||
|
|
FROM orders
|
|||
|
|
WHERE status = 'paid'
|
|||
|
|
`, [today, monthStart]) as any[]
|
|||
|
|
|
|||
|
|
if (periodEarningsResults.length > 0) {
|
|||
|
|
earningsStats.todayEarnings = parseFloat(periodEarningsResults[0].today_earnings) || 0
|
|||
|
|
earningsStats.monthEarnings = parseFloat(periodEarningsResults[0].month_earnings) || 0
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('[Admin Overview] 收益统计失败:', e)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// === 4. 提现数据统计 ===
|
|||
|
|
let withdrawalStats = {
|
|||
|
|
pendingCount: 0,
|
|||
|
|
pendingAmount: 0
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const withdrawalResults = await query(`
|
|||
|
|
SELECT
|
|||
|
|
COUNT(*) as pending_count,
|
|||
|
|
COALESCE(SUM(amount), 0) as pending_amount
|
|||
|
|
FROM withdrawals
|
|||
|
|
WHERE status = 'pending'
|
|||
|
|
`) as any[]
|
|||
|
|
|
|||
|
|
if (withdrawalResults.length > 0) {
|
|||
|
|
withdrawalStats.pendingCount = parseInt(withdrawalResults[0].pending_count) || 0
|
|||
|
|
withdrawalStats.pendingAmount = parseFloat(withdrawalResults[0].pending_amount) || 0
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('[Admin Overview] 提现统计失败:', e)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// === 5. 访问数据统计 ===
|
|||
|
|
let visitStats = {
|
|||
|
|
todayVisits: 0,
|
|||
|
|
monthVisits: 0,
|
|||
|
|
totalVisits: 0
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const visitResults = await query(`
|
|||
|
|
SELECT
|
|||
|
|
COUNT(*) as total_count,
|
|||
|
|
COUNT(DISTINCT CASE WHEN DATE(created_at) = ? THEN id END) as today_count,
|
|||
|
|
COUNT(DISTINCT CASE WHEN created_at >= ? THEN id END) as month_count
|
|||
|
|
FROM referral_visits
|
|||
|
|
`, [today, monthStart]) as any[]
|
|||
|
|
|
|||
|
|
if (visitResults.length > 0) {
|
|||
|
|
visitStats.totalVisits = parseInt(visitResults[0].total_count) || 0
|
|||
|
|
visitStats.todayVisits = parseInt(visitResults[0].today_count) || 0
|
|||
|
|
visitStats.monthVisits = parseInt(visitResults[0].month_count) || 0
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('[Admin Overview] 访问统计失败:', e)
|
|||
|
|
// 访问表可能不存在,使用绑定数作为替代
|
|||
|
|
visitStats = {
|
|||
|
|
todayVisits: bindingStats.todayBindings,
|
|||
|
|
monthVisits: bindingStats.monthBindings,
|
|||
|
|
totalVisits: bindingStats.totalBindings
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// === 6. 分销商数据统计 ===
|
|||
|
|
let distributorStats = {
|
|||
|
|
totalDistributors: 0,
|
|||
|
|
activeDistributors: 0
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const distributorResults = await query(`
|
|||
|
|
SELECT
|
|||
|
|
COUNT(*) as total_count,
|
|||
|
|
SUM(CASE WHEN earnings > 0 THEN 1 ELSE 0 END) as active_count
|
|||
|
|
FROM users
|
|||
|
|
WHERE referral_code IS NOT NULL AND referral_code != ''
|
|||
|
|
`) as any[]
|
|||
|
|
|
|||
|
|
if (distributorResults.length > 0) {
|
|||
|
|
distributorStats.totalDistributors = parseInt(distributorResults[0].total_count) || 0
|
|||
|
|
distributorStats.activeDistributors = parseInt(distributorResults[0].active_count) || 0
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('[Admin Overview] 分销商统计失败:', e)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// === 7. 计算转化率 ===
|
|||
|
|
const conversionRate = visitStats.totalVisits > 0
|
|||
|
|
? ((bindingStats.totalConversions / visitStats.totalVisits) * 100).toFixed(2)
|
|||
|
|
: '0.00'
|
|||
|
|
|
|||
|
|
// 返回完整概览数据
|
|||
|
|
const overview = {
|
|||
|
|
// 今日数据
|
|||
|
|
todayClicks: visitStats.todayVisits,
|
|||
|
|
todayBindings: bindingStats.todayBindings,
|
|||
|
|
todayConversions: bindingStats.todayConversions,
|
|||
|
|
todayEarnings: earningsStats.todayEarnings,
|
|||
|
|
|
|||
|
|
// 本月数据
|
|||
|
|
monthClicks: visitStats.monthVisits,
|
|||
|
|
monthBindings: bindingStats.monthBindings,
|
|||
|
|
monthConversions: bindingStats.monthConversions,
|
|||
|
|
monthEarnings: earningsStats.monthEarnings,
|
|||
|
|
|
|||
|
|
// 总计数据
|
|||
|
|
totalClicks: visitStats.totalVisits,
|
|||
|
|
totalBindings: bindingStats.totalBindings,
|
|||
|
|
totalConversions: bindingStats.totalConversions,
|
|||
|
|
totalEarnings: earningsStats.totalEarnings,
|
|||
|
|
|
|||
|
|
// 其他统计
|
|||
|
|
expiringBindings: bindingStats.expiringBindings,
|
|||
|
|
pendingWithdrawals: withdrawalStats.pendingCount,
|
|||
|
|
pendingWithdrawAmount: withdrawalStats.pendingAmount,
|
|||
|
|
conversionRate,
|
|||
|
|
totalDistributors: distributorStats.totalDistributors,
|
|||
|
|
activeDistributors: distributorStats.activeDistributors,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log('[Admin Overview] 数据统计完成:', overview)
|
|||
|
|
|
|||
|
|
return NextResponse.json({
|
|||
|
|
success: true,
|
|||
|
|
overview
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('[Admin Overview] 统计失败:', error)
|
|||
|
|
return NextResponse.json({
|
|||
|
|
success: false,
|
|||
|
|
error: '获取分销概览失败: ' + (error as Error).message
|
|||
|
|
}, { status: 500 })
|
|||
|
|
}
|
|||
|
|
}
|