feat: 重构代码架构 + 新增报表/跟单/输赢统计功能

- 拆分 HomeController → TransactionController, ReportWebController, FollowPlanController
- 新增 Service 层: TransactionService, ReportService, FollowPlanService
- pk10.php JS 抽离为 4 个独立文件 (sound/race/bet/poll)
- 前台新增报表查询页面 (/report) + 跟单计划页面 (/follow-plan)
- 后台新增跟单计划管理 + 用户输赢明细统计
- 封盘状态显示倒计时 (x:xx)
- 音效仅在开奖弹窗打开时播放
- 路由按模块分组整理
- autoload 支持 App\Services 命名空间
This commit is contained in:
li
2026-03-27 18:41:06 +08:00
parent 48cfe4be04
commit 4a94fe36cf
87 changed files with 8681 additions and 646 deletions
+115 -77
View File
@@ -12,94 +12,132 @@ class ReportController extends AdminBaseController {
$this->checkLogin(); $this->checkAdmin();
$dateFrom = $_GET['from'] ?? date('Y-m-d', strtotime('-7 days'));
$dateTo = $_GET['to'] ?? date('Y-m-d');
$range = [$dateFrom . ' 00:00:00', $dateTo . ' 23:59:59'];
// 总投注(排除虚拟)
$totalBet = $this->db->sum('bets', 'amount', [
'is_virtual' => 0, 'created_at[<>]' => $range
]) ?: 0;
$reportService = new \App\Services\ReportService($this->db);
// 总派奖
$totalWin = $this->db->sum('bets', 'win_amount', [
'is_virtual' => 0, 'status' => 'win', 'created_at[<>]' => $range
]) ?: 0;
// 总体统计
$overview = $reportService->getOverviewStats($dateFrom, $dateTo);
extract($overview); // totalBet, totalWin, profit, profitRate, totalDeposit, totalWithdraw, totalCommission, totalRebate, totalUsers, newUsers, activeUsers
// 平台利润
$profit = $totalBet - $totalWin;
// 日明细统计
$dailyStats = $reportService->getDailyStats($dateFrom, $dateTo);
// 总充值
$totalDeposit = $this->db->sum('transactions', 'amount', [
'is_virtual' => 0, 'type[~]' => '%deposit%', 'amount[>]' => 0,
'created_at[<>]' => $range
]) ?: 0;
// 总提现
$totalWithdraw = abs($this->db->sum('transactions', 'amount', [
'is_virtual' => 0, 'type[~]' => '%withdraw%', 'amount[<]' => 0,
'created_at[<>]' => $range
]) ?: 0);
// 代理佣金
$totalCommission = $this->db->sum('agent_commissions', 'commission', [
'created_at[<>]' => $range
]) ?: 0;
// 反水
$totalRebate = $this->db->sum('agent_commissions', 'commission', [
'type' => 'rebate', 'created_at[<>]' => $range
]) ?: 0;
// 用户统计
$totalUsers = $this->db->count('users', ['is_virtual' => 0]);
$newUsers = $this->db->count('users', [
'is_virtual' => 0, 'created_at[<>]' => $range
]);
// 每日明细
$dailyStats = [];
$start = new \DateTime($dateFrom);
$end = new \DateTime($dateTo);
$end->modify('+1 day');
$interval = new \DateInterval('P1D');
$period = new \DatePeriod($start, $interval, $end);
foreach ($period as $day) {
$d = $day->format('Y-m-d');
$dr = [$d . ' 00:00:00', $d . ' 23:59:59'];
$dailyStats[] = [
'date' => $d,
'bets' => $this->db->sum('bets', 'amount', ['is_virtual'=>0,'created_at[<>]'=>$dr]) ?: 0,
'wins' => $this->db->sum('bets', 'win_amount', ['is_virtual'=>0,'status'=>'win','created_at[<>]'=>$dr]) ?: 0,
'deposits' => $this->db->sum('transactions', 'amount', ['is_virtual'=>0,'type[~]'=>'%deposit%','amount[>]'=>0,'created_at[<>]'=>$dr]) ?: 0,
'withdraws' => abs($this->db->sum('transactions', 'amount', ['is_virtual'=>0,'type[~]'=>'%withdraw%','amount[<]'=>0,'created_at[<>]'=>$dr]) ?: 0),
];
}
// 用户排行(投注前20
$topUsers = $reportService->getTopUsers($dateFrom, $dateTo, 20);
// 代理报表
$agentStats = [];
$agents = $this->db->select('agents', '*', ['status' => 1]);
foreach ($agents as $ag) {
$playerIds = $this->db->select('users', 'id', ['agent_id' => $ag['id'], 'is_virtual' => 0]);
$agBet = 0; $agWin = 0;
if (!empty($playerIds)) {
$agBet = $this->db->sum('bets', 'amount', ['user_id' => $playerIds, 'is_virtual'=>0, 'created_at[<>]'=>$range]) ?: 0;
$agWin = $this->db->sum('bets', 'win_amount', ['user_id' => $playerIds, 'is_virtual'=>0, 'status'=>'win', 'created_at[<>]'=>$range]) ?: 0;
}
$agComm = $this->db->sum('agent_commissions', 'commission', ['agent_id'=>$ag['id'],'created_at[<>]'=>$range]) ?: 0;
$agentStats[] = [
'agent' => $ag,
'user' => $this->db->get('users', ['username'], ['id' => $ag['user_id']]),
'players' => count($playerIds),
'bets' => $agBet, 'wins' => $agWin, 'commission' => $agComm,
'profit' => $agBet - $agWin - $agComm,
];
}
$agentStats = $reportService->getAgentStats($dateFrom, $dateTo);
// 用户输赢明细
$winLossData = $reportService->getUserWinLoss($dateFrom, $dateTo);
$userSummary = $winLossData['summary'];
$this->render('Admin/reports.php', compact(
'dateFrom','dateTo','totalBet','totalWin','profit',
'dateFrom','dateTo','totalBet','totalWin','profit','profitRate',
'totalDeposit','totalWithdraw','totalCommission','totalRebate',
'totalUsers','newUsers','dailyStats','agentStats'
'totalUsers','newUsers','activeUsers','dailyStats','topUsers','agentStats',
'userSummary'
));
}
/**
* CSV 导出
*/
public function export() {
$this->checkLogin(); $this->checkAdmin();
$dateFrom = $_GET['from'] ?? date('Y-m-d', strtotime('-7 days'));
$dateTo = $_GET['to'] ?? date('Y-m-d');
$reportService = new \App\Services\ReportService($this->db);
// 通过 Service 获取数据
$dailyStats = $reportService->getDailyStats($dateFrom, $dateTo);
$topUsers = $reportService->getTopUsers($dateFrom, $dateTo, 0); // 不限条数
$winLossData = $reportService->getUserWinLoss($dateFrom, $dateTo);
$userSummary = $winLossData['summary'];
$userWinLossRaw = $winLossData['raw'];
// 输出 CSV
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename="report_' . $dateFrom . '_' . $dateTo . '.csv"');
// BOM 头(Excel 兼容中文)
echo "\xEF\xBB\xBF";
$out = fopen('php://output', 'w');
// 日明细
fputcsv($out, ['=== 日明细报表 ===']);
fputcsv($out, ['日期', '投注额', '派彩额', '利润', '投注笔数', '中奖笔数', '胜率']);
foreach ($dailyStats as $day) {
$totalBet = floatval($day['total_bet'] ?? 0);
$totalWin = floatval($day['total_win'] ?? 0);
$betCount = intval($day['bet_count'] ?? 0);
$winCount = intval($day['win_count'] ?? 0);
$winRate = $betCount > 0 ? round($winCount / $betCount * 100, 1) . '%' : '0%';
fputcsv($out, [
$day['date'],
number_format($totalBet, 2, '.', ''),
number_format($totalWin, 2, '.', ''),
number_format($totalBet - $totalWin, 2, '.', ''),
$betCount,
$winCount,
$winRate
]);
}
fputcsv($out, []);
fputcsv($out, ['=== 用户投注排行 ===']);
fputcsv($out, ['排名', '用户名', '投注额', '派彩额', '平台盈亏', '投注笔数']);
$rank = 1;
foreach ($topUsers as $user) {
fputcsv($out, [
$rank++,
$user['username'],
number_format(floatval($user['total_bet']), 2, '.', ''),
number_format(floatval($user['total_win']), 2, '.', ''),
number_format(floatval($user['profit']), 2, '.', ''),
intval($user['bet_count'])
]);
}
// 用户输赢明细
fputcsv($out, []);
fputcsv($out, ['=== 用户输赢明细 ===']);
fputcsv($out, ['用户名', '当前余额', '总投注', '总派彩', '平台盈亏(投注-派彩)', '投注笔数', '中奖笔数', '胜率']);
foreach ($userSummary as $ud) {
$tb = $ud['total_bet'];
$tw = $ud['total_win'];
$bc = $ud['bet_count'];
$wc = $ud['win_count'];
$wr = $bc > 0 ? round($wc / $bc * 100, 1) . '%' : '0%';
fputcsv($out, [
$ud['username'],
number_format($ud['balance'], 2, '.', ''),
number_format($tb, 2, '.', ''),
number_format($tw, 2, '.', ''),
number_format($tb - $tw, 2, '.', ''),
$bc, $wc, $wr
]);
}
// 用户日输赢明细
fputcsv($out, []);
fputcsv($out, ['=== 用户日输赢明细 ===']);
fputcsv($out, ['用户名', '日期', '投注额', '派彩额', '平台盈亏', '笔数']);
foreach ($userWinLossRaw as $dd) {
$tb = floatval($dd['total_bet']);
$tw = floatval($dd['total_win']);
fputcsv($out, [
$dd['username'], $dd['date'],
number_format($tb, 2, '.', ''),
number_format($tw, 2, '.', ''),
number_format($tb - $tw, 2, '.', ''),
intval($dd['bet_count'])
]);
}
fclose($out);
exit;
}
private function json($data) { header('Content-Type: application/json'); echo json_encode($data); }
}