Files
pk10/App/Views/Admin/reports.php
T
li 4a94fe36cf 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 命名空间
2026-03-27 18:41:06 +08:00

404 lines
23 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<h1 class="text-2xl font-bold text-gray-800 mb-6 flex items-center">
<i class="fas fa-chart-bar text-primary mr-3"></i>
数据报表
</h1>
<!-- 日期筛选栏 -->
<div class="bg-white rounded-xl p-5 card-shadow mb-6">
<div class="flex flex-wrap items-center gap-4">
<div class="flex items-center gap-2">
<label class="text-sm text-gray-500">起始日期</label>
<input type="text" id="dateFrom" value="<?= htmlspecialchars($dateFrom) ?>" class="border border-gray-300 rounded-lg px-3 py-2 text-sm w-36" readonly placeholder="选择日期">
</div>
<div class="flex items-center gap-2">
<label class="text-sm text-gray-500">截止日期</label>
<input type="text" id="dateTo" value="<?= htmlspecialchars($dateTo) ?>" class="border border-gray-300 rounded-lg px-3 py-2 text-sm w-36" readonly placeholder="选择日期">
</div>
<button onclick="doQuery()" class="px-5 py-2 bg-primary text-white rounded-lg text-sm hover:opacity-90 transition">
<i class="fas fa-search mr-1"></i> 查询
</button>
<button onclick="doExport()" class="px-5 py-2 bg-success text-white rounded-lg text-sm hover:opacity-90 transition">
<i class="fas fa-file-csv mr-1"></i> 导出CSV
</button>
</div>
</div>
<!-- 总体统计卡片 -->
<div class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-8 gap-4 mb-6">
<div class="bg-white rounded-xl p-5 card-shadow hover-lift">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm">总投注</p>
<h3 class="text-xl font-bold mt-1 text-warning"><?= number_format((float)$totalBet, 2) ?></h3>
</div>
<div class="w-10 h-10 rounded-full bg-warning/10 flex items-center justify-center">
<i class="fas fa-coins text-warning"></i>
</div>
</div>
</div>
<div class="bg-white rounded-xl p-5 card-shadow hover-lift">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm">总派彩</p>
<h3 class="text-xl font-bold mt-1 text-primary"><?= number_format((float)$totalWin, 2) ?></h3>
</div>
<div class="w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center">
<i class="fas fa-trophy text-primary"></i>
</div>
</div>
</div>
<div class="bg-white rounded-xl p-5 card-shadow hover-lift">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm">总利润</p>
<h3 class="text-xl font-bold mt-1 <?= $profit >= 0 ? 'text-success' : 'text-danger' ?>"><?= number_format((float)$profit, 2) ?></h3>
</div>
<div class="w-10 h-10 rounded-full <?= $profit >= 0 ? 'bg-success/10' : 'bg-danger/10' ?> flex items-center justify-center">
<i class="fas fa-chart-line <?= $profit >= 0 ? 'text-success' : 'text-danger' ?>"></i>
</div>
</div>
</div>
<div class="bg-white rounded-xl p-5 card-shadow hover-lift">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm">利润率</p>
<?php
$prColor = 'text-warning';
if ($profitRate > 10) $prColor = 'text-success';
elseif ($profitRate < 0) $prColor = 'text-danger';
?>
<h3 class="text-xl font-bold mt-1 <?= $prColor ?>"><?= $profitRate ?>%</h3>
</div>
<div class="w-10 h-10 rounded-full bg-warning/10 flex items-center justify-center">
<i class="fas fa-percentage text-warning"></i>
</div>
</div>
</div>
<div class="bg-white rounded-xl p-5 card-shadow hover-lift">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm">总充值</p>
<h3 class="text-xl font-bold mt-1 text-success"><?= number_format((float)$totalDeposit, 2) ?></h3>
</div>
<div class="w-10 h-10 rounded-full bg-success/10 flex items-center justify-center">
<i class="fas fa-arrow-down text-success"></i>
</div>
</div>
</div>
<div class="bg-white rounded-xl p-5 card-shadow hover-lift">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm">总提现</p>
<h3 class="text-xl font-bold mt-1 text-danger"><?= number_format((float)$totalWithdraw, 2) ?></h3>
</div>
<div class="w-10 h-10 rounded-full bg-danger/10 flex items-center justify-center">
<i class="fas fa-arrow-up text-danger"></i>
</div>
</div>
</div>
<div class="bg-white rounded-xl p-5 card-shadow hover-lift">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm">活跃用户</p>
<h3 class="text-xl font-bold mt-1 text-primary"><?= $activeUsers ?></h3>
</div>
<div class="w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center">
<i class="fas fa-user-check text-primary"></i>
</div>
</div>
</div>
<div class="bg-white rounded-xl p-5 card-shadow hover-lift">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm">新增用户</p>
<h3 class="text-xl font-bold mt-1 text-secondary"><?= $newUsers ?></h3>
</div>
<div class="w-10 h-10 rounded-full bg-secondary/10 flex items-center justify-center">
<i class="fas fa-user-plus text-secondary"></i>
</div>
</div>
</div>
</div>
<!-- 日明细表 -->
<div class="bg-white rounded-xl shadow-md p-6 mb-6">
<h2 class="text-lg font-semibold text-gray-800 mb-4 flex items-center">
<i class="fas fa-calendar-alt text-primary mr-2"></i> 日明细统计
</h2>
<div class="overflow-x-auto">
<table class="w-full bg-white rounded-xl overflow-hidden">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">日期</th>
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">投注额</th>
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">派彩额</th>
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">利润</th>
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">充值</th>
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">提现</th>
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase">笔数</th>
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase">胜率</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
<?php if (!empty($dailyStats)): ?>
<?php foreach ($dailyStats as $d): ?>
<tr class="hover:bg-gray-50 transition-colors">
<td class="px-4 py-3 text-sm font-medium text-gray-900"><?= $d['date'] ?></td>
<td class="px-4 py-3 text-sm text-right"><?= number_format($d['total_bet'], 2) ?></td>
<td class="px-4 py-3 text-sm text-right"><?= number_format($d['total_win'], 2) ?></td>
<td class="px-4 py-3 text-sm text-right font-semibold <?= $d['profit'] >= 0 ? 'text-success' : 'text-danger' ?>">
<?= $d['profit'] >= 0 ? '+' : '' ?><?= number_format($d['profit'], 2) ?>
</td>
<td class="px-4 py-3 text-sm text-right text-success"><?= number_format($d['deposits'], 2) ?></td>
<td class="px-4 py-3 text-sm text-right text-danger"><?= number_format($d['withdraws'], 2) ?></td>
<td class="px-4 py-3 text-sm text-center"><?= $d['bet_count'] ?></td>
<td class="px-4 py-3 text-sm text-center">
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs <?= $d['win_rate'] > 50 ? 'bg-danger/10 text-danger' : 'bg-success/10 text-success' ?>">
<?= $d['win_rate'] ?>%
</span>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="8" class="px-6 py-12 text-center">
<div class="flex flex-col items-center">
<i class="fas fa-chart-bar text-gray-300 text-5xl mb-4"></i>
<h3 class="text-lg font-medium text-gray-900">暂无数据</h3>
<p class="mt-1 text-gray-500 text-sm">选定日期范围内没有投注记录</p>
</div>
</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<!-- 用户投注排行 -->
<div class="bg-white rounded-xl shadow-md p-6 mb-6">
<h2 class="text-lg font-semibold text-gray-800 mb-4 flex items-center">
<i class="fas fa-ranking-star text-warning mr-2"></i> 用户投注排行(前20
</h2>
<div class="overflow-x-auto">
<table class="w-full bg-white rounded-xl overflow-hidden">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase w-16">排名</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">用户名</th>
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">投注额</th>
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">派彩额</th>
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">平台盈亏</th>
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase">笔数</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
<?php if (!empty($topUsers)): ?>
<?php $rank = 1; foreach ($topUsers as $u): ?>
<tr class="hover:bg-gray-50 transition-colors">
<td class="px-4 py-3 text-center">
<?php if ($rank <= 3): ?>
<span class="inline-flex items-center justify-center w-7 h-7 rounded-full text-white text-xs font-bold
<?= $rank === 1 ? 'bg-yellow-500' : ($rank === 2 ? 'bg-gray-400' : 'bg-amber-600') ?>">
<?= $rank ?>
</span>
<?php else: ?>
<span class="text-sm text-gray-500"><?= $rank ?></span>
<?php endif; ?>
</td>
<td class="px-4 py-3 text-sm font-medium text-gray-900"><?= htmlspecialchars($u['username'] ?? '') ?></td>
<td class="px-4 py-3 text-sm text-right"><?= number_format(floatval($u['total_bet']), 2) ?></td>
<td class="px-4 py-3 text-sm text-right"><?= number_format(floatval($u['total_win']), 2) ?></td>
<td class="px-4 py-3 text-sm text-right font-semibold <?= floatval($u['profit']) >= 0 ? 'text-success' : 'text-danger' ?>">
<?= floatval($u['profit']) >= 0 ? '+' : '' ?><?= number_format(floatval($u['profit']), 2) ?>
</td>
<td class="px-4 py-3 text-sm text-center"><?= intval($u['bet_count']) ?></td>
</tr>
<?php $rank++; endforeach; ?>
<?php else: ?>
<tr>
<td colspan="6" class="px-6 py-12 text-center">
<div class="flex flex-col items-center">
<i class="fas fa-users text-gray-300 text-5xl mb-4"></i>
<h3 class="text-lg font-medium text-gray-900">暂无数据</h3>
<p class="mt-1 text-gray-500 text-sm">选定日期范围内没有用户投注</p>
</div>
</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<!-- 代理报表 -->
<?php if (!empty($agentStats)): ?>
<div class="bg-white rounded-xl shadow-md p-6 mb-6">
<h2 class="text-lg font-semibold text-gray-800 mb-4 flex items-center">
<i class="fas fa-user-tie text-secondary mr-2"></i> 代理报表
</h2>
<div class="overflow-x-auto">
<table class="w-full bg-white rounded-xl overflow-hidden">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">代理</th>
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase">玩家数</th>
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">投注额</th>
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">中奖额</th>
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">佣金</th>
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">盈利</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
<?php foreach ($agentStats as $as): ?>
<tr class="hover:bg-gray-50 transition-colors">
<td class="px-4 py-3 text-sm font-medium text-gray-900"><?= htmlspecialchars($as['user']['username'] ?? '') ?></td>
<td class="px-4 py-3 text-sm text-center"><?= $as['players'] ?></td>
<td class="px-4 py-3 text-sm text-right"><?= number_format($as['bets'], 2) ?></td>
<td class="px-4 py-3 text-sm text-right"><?= number_format($as['wins'], 2) ?></td>
<td class="px-4 py-3 text-sm text-right text-warning"><?= number_format($as['commission'], 2) ?></td>
<td class="px-4 py-3 text-sm text-right font-semibold <?= $as['profit'] >= 0 ? 'text-success' : 'text-danger' ?>">
<?= $as['profit'] >= 0 ? '+' : '' ?><?= number_format($as['profit'], 2) ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<?php endif; ?>
<!-- 用户输赢明细 -->
<div class="bg-white rounded-xl shadow-md p-6 mb-6">
<h2 class="text-lg font-semibold text-gray-800 mb-4 flex items-center">
<i class="fas fa-money-bill-trend-up text-danger mr-2"></i> 用户输赢明细
</h2>
<div class="overflow-x-auto">
<table class="w-full bg-white rounded-xl overflow-hidden" id="userWinLossTable">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">用户名</th>
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">当前余额</th>
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">总投注</th>
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">总派彩</th>
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">平台盈亏</th>
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase">笔数</th>
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase">胜率</th>
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase">日明细</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
<?php if (!empty($userSummary)): ?>
<?php foreach ($userSummary as $uid => $us):
$uProfit = $us['total_bet'] - $us['total_win'];
$uWinRate = $us['bet_count'] > 0 ? round($us['win_count'] / $us['bet_count'] * 100, 1) : 0;
?>
<tr class="hover:bg-gray-50 transition-colors">
<td class="px-4 py-3 text-sm font-medium text-gray-900"><?= htmlspecialchars($us['username']) ?></td>
<td class="px-4 py-3 text-sm text-right"><?= number_format($us['balance'], 2) ?></td>
<td class="px-4 py-3 text-sm text-right"><?= number_format($us['total_bet'], 2) ?></td>
<td class="px-4 py-3 text-sm text-right"><?= number_format($us['total_win'], 2) ?></td>
<td class="px-4 py-3 text-sm text-right font-semibold <?= $uProfit >= 0 ? 'text-success' : 'text-danger' ?>">
<?= $uProfit >= 0 ? '+' : '' ?><?= number_format($uProfit, 2) ?>
</td>
<td class="px-4 py-3 text-sm text-center"><?= $us['bet_count'] ?></td>
<td class="px-4 py-3 text-sm text-center">
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs <?= $uWinRate > 50 ? 'bg-danger/10 text-danger' : 'bg-success/10 text-success' ?>">
<?= $uWinRate ?>%
</span>
</td>
<td class="px-4 py-3 text-sm text-center">
<button onclick="toggleDays('days_<?= $uid ?>')" class="text-primary hover:underline text-xs">
<i class="fas fa-chevron-down"></i> 展开
</button>
</td>
</tr>
<tr id="days_<?= $uid ?>" style="display:none" class="bg-gray-50/50">
<td colspan="8" class="px-6 py-2">
<table class="w-full text-xs">
<thead><tr class="text-gray-400">
<th class="py-1 text-left">日期</th>
<th class="py-1 text-right">投注</th>
<th class="py-1 text-right">派彩</th>
<th class="py-1 text-right">盈亏</th>
<th class="py-1 text-center">笔数</th>
</tr></thead>
<tbody>
<?php foreach ($us['days'] as $day): ?>
<tr class="border-t border-gray-100">
<td class="py-1 text-gray-600"><?= $day['date'] ?></td>
<td class="py-1 text-right"><?= number_format($day['bet'], 2) ?></td>
<td class="py-1 text-right"><?= number_format($day['win'], 2) ?></td>
<td class="py-1 text-right font-semibold <?= $day['profit'] >= 0 ? 'text-success' : 'text-danger' ?>">
<?= $day['profit'] >= 0 ? '+' : '' ?><?= number_format($day['profit'], 2) ?>
</td>
<td class="py-1 text-center"><?= $day['count'] ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="8" class="px-6 py-12 text-center">
<div class="flex flex-col items-center">
<i class="fas fa-money-bill text-gray-300 text-5xl mb-4"></i>
<h3 class="text-lg font-medium text-gray-900">暂无数据</h3>
<p class="mt-1 text-gray-500 text-sm">选定日期范围内没有用户投注记录</p>
</div>
</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<script src="/Static/js/admin.js"></script>
<script>
layui.use(['laydate', 'layer'], function() {
var laydate = layui.laydate;
laydate.render({
elem: '#dateFrom',
type: 'date',
format: 'yyyy-MM-dd',
value: '<?= htmlspecialchars($dateFrom) ?>',
max: '<?= htmlspecialchars($dateTo) ?>'
});
laydate.render({
elem: '#dateTo',
type: 'date',
format: 'yyyy-MM-dd',
value: '<?= htmlspecialchars($dateTo) ?>',
max: 0
});
});
function doQuery() {
var from = document.getElementById('dateFrom').value;
var to = document.getElementById('dateTo').value;
window.location.href = '/admin/reports?from=' + from + '&to=' + to;
}
function toggleDays(id) {
var el = document.getElementById(id);
if (!el) return;
el.style.display = el.style.display === 'none' ? '' : 'none';
var btn = el.previousElementSibling.querySelector('button');
if (btn) {
var isOpen = el.style.display !== 'none';
btn.innerHTML = isOpen ? '<i class="fas fa-chevron-up"></i> 收起' : '<i class="fas fa-chevron-down"></i> 展开';
}
}
function doExport() {
var from = document.getElementById('dateFrom').value;
var to = document.getElementById('dateTo').value;
window.location.href = '/admin/reports/export?from=' + from + '&to=' + to;
}
</script>