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:
@@ -0,0 +1,444 @@
|
||||
<h1 class="text-2xl font-bold text-gray-800 mb-6 flex items-center">
|
||||
<i class="fas fa-robot text-primary mr-3"></i>
|
||||
Bot 管理
|
||||
</h1>
|
||||
|
||||
<?php
|
||||
$botOptions = [];
|
||||
foreach (($bots ?? []) as $botItem) {
|
||||
$botOptions[] = [
|
||||
'id' => (int)($botItem['id'] ?? 0),
|
||||
'name' => (string)($botItem['name'] ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
$gameOptions = [];
|
||||
foreach (($games ?? []) as $gameItem) {
|
||||
$gameOptions[] = [
|
||||
'id' => (int)($gameItem['id'] ?? 0),
|
||||
'name' => (string)($gameItem['name'] ?? ''),
|
||||
'type' => (string)($gameItem['type'] ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
$ruleOptions = [];
|
||||
foreach (($rules ?? []) as $ruleItem) {
|
||||
$ruleOptions[] = [
|
||||
'id' => (int)($ruleItem['id'] ?? 0),
|
||||
'name' => (string)($ruleItem['name'] ?? ''),
|
||||
'rule_code' => (string)($ruleItem['rule_code'] ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
$groupOptions = [];
|
||||
foreach (($groups ?? []) as $groupItem) {
|
||||
$countdown = '';
|
||||
if (!empty($groupItem['countdown_config'])) {
|
||||
$decodedCountdown = json_decode((string)$groupItem['countdown_config'], true);
|
||||
if (is_array($decodedCountdown)) {
|
||||
$countdown = implode(',', array_map('intval', $decodedCountdown));
|
||||
}
|
||||
}
|
||||
|
||||
$groupOptions[] = [
|
||||
'id' => (int)($groupItem['id'] ?? 0),
|
||||
'bot_id' => (int)($groupItem['bot_id'] ?? 0),
|
||||
'game_id' => (int)($groupItem['game_id'] ?? 0),
|
||||
'bet_format_rule_id' => (int)($groupItem['bet_format_rule_id'] ?? 0),
|
||||
'tg_group_id' => (string)($groupItem['tg_group_id'] ?? ''),
|
||||
'group_name' => (string)($groupItem['group_name'] ?? ''),
|
||||
'group_type' => (string)($groupItem['group_type'] ?? 'group'),
|
||||
'countdown_config' => $countdown,
|
||||
];
|
||||
}
|
||||
|
||||
$userOptions = [];
|
||||
foreach (($users ?? []) as $userItem) {
|
||||
$userOptions[] = [
|
||||
'id' => (int)($userItem['id'] ?? 0),
|
||||
'username' => (string)($userItem['username'] ?? ''),
|
||||
'balance' => number_format((float)($userItem['balance'] ?? 0), 2, '.', ''),
|
||||
];
|
||||
}
|
||||
?>
|
||||
|
||||
<div id="botAdminAlert" class="hidden mb-6 rounded-lg border px-4 py-3 text-sm"></div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-5 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">Bot 实例数</p><h3 class="text-2xl font-bold mt-1"><?= (int)($stats['bot_count'] ?? 0) ?></h3></div><div class="w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center"><i class="fas fa-robot 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-2xl font-bold mt-1"><?= (int)($stats['group_count'] ?? 0) ?></h3></div><div class="w-10 h-10 rounded-full bg-success/10 flex items-center justify-center"><i class="fas fa-users 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-2xl font-bold mt-1"><?= (int)($stats['wallet_count'] ?? 0) ?></h3></div><div class="w-10 h-10 rounded-full bg-warning/10 flex items-center justify-center"><i class="fas fa-wallet 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-2xl font-bold mt-1"><?= (int)($stats['enabled_group_count'] ?? 0) ?></h3></div><div class="w-10 h-10 rounded-full bg-danger/10 flex items-center justify-center"><i class="fas fa-signal 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-2xl font-bold mt-1"><?= (int)($stats['member_count'] ?? 0) ?></h3></div><div class="w-10 h-10 rounded-full bg-indigo-100 flex items-center justify-center"><i class="fas fa-user-tag text-indigo-600"></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-2xl font-bold mt-1"><?= (int)($stats['shill_count'] ?? 0) ?></h3></div><div class="w-10 h-10 rounded-full bg-rose-100 flex items-center justify-center"><i class="fas fa-user-secret text-rose-600"></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-2xl font-bold mt-1"><?= (int)($stats['push_count'] ?? 0) ?></h3></div><div class="w-10 h-10 rounded-full bg-cyan-100 flex items-center justify-center"><i class="fas fa-paper-plane text-cyan-600"></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-2xl font-bold mt-1"><?= (int)($stats['push_success_count'] ?? 0) ?></h3></div><div class="w-10 h-10 rounded-full bg-emerald-100 flex items-center justify-center"><i class="fas fa-circle-check text-emerald-600"></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-2xl font-bold mt-1"><?= (int)($stats['countdown_group_count'] ?? 0) ?></h3></div><div class="w-10 h-10 rounded-full bg-amber-100 flex items-center justify-center"><i class="fas fa-hourglass-half text-amber-600"></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-2xl font-bold mt-1"><?= (int)($stats['animation_group_count'] ?? 0) ?></h3></div><div class="w-10 h-10 rounded-full bg-fuchsia-100 flex items-center justify-center"><i class="fas fa-image text-fuchsia-600"></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">API 请求数</p><h3 class="text-2xl font-bold mt-1"><?= (int)($stats['api_request_count'] ?? 0) ?></h3></div><div class="w-10 h-10 rounded-full bg-slate-100 flex items-center justify-center"><i class="fas fa-arrow-right-arrow-left text-slate-600"></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-2xl font-bold mt-1"><?= (int)($stats['api_signature_ok_count'] ?? 0) ?></h3></div><div class="w-10 h-10 rounded-full bg-lime-100 flex items-center justify-center"><i class="fas fa-shield-halved text-lime-600"></i></div></div></div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 xl:grid-cols-3 gap-6 mb-6">
|
||||
<div class="xl:col-span-2 bg-white rounded-xl shadow-md p-6">
|
||||
<div class="flex items-start justify-between gap-4 mb-6">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-gray-800">Bot 实例总览</h2>
|
||||
<p class="text-sm text-gray-500 mt-1">现在已支持新增、编辑、启停 Bot 实例。</p>
|
||||
</div>
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs bg-primary/10 text-primary"><i class="fas fa-hammer mr-1"></i> Phase 6 进阶</span>
|
||||
</div>
|
||||
<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 tracking-wider">实例</th><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">运行模式</th><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">群数</th><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">状态</th><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">更新时间</th><th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">操作</th></tr></thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
<?php if (!empty($bots)): foreach ($bots as $bot): ?>
|
||||
<?php $enabled = (int)($bot['status'] ?? 0) === 1; ?>
|
||||
<tr class="hover:bg-gray-50 transition-colors">
|
||||
<td class="px-4 py-4 align-top"><div class="space-y-1"><div class="text-sm font-semibold text-gray-900"><?= htmlspecialchars((string)($bot['name'] ?? '未命名 Bot')) ?></div><div class="text-xs text-gray-500">Key:<?= htmlspecialchars((string)($bot['bot_key'] ?? '-')) ?></div><div class="text-xs text-gray-400">@<?= htmlspecialchars((string)($bot['bot_username'] ?? 'unknown')) ?></div></div></td>
|
||||
<td class="px-4 py-4 align-top text-sm text-gray-600"><?= htmlspecialchars((string)($bot['run_mode'] ?? 'polling')) ?></td>
|
||||
<td class="px-4 py-4 align-top"><div class="text-sm text-gray-700">总群数:<?= (int)($bot['group_count'] ?? 0) ?></div><div class="text-xs text-gray-500 mt-1">启用下注:<?= (int)($bot['active_group_count'] ?? 0) ?></div></td>
|
||||
<td class="px-4 py-4 align-top"><span class="inline-flex items-center px-2 py-1 rounded-full text-xs <?= $enabled ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-700' ?>"><span class="w-2 h-2 rounded-full mr-1 <?= $enabled ? 'bg-green-500' : 'bg-gray-400' ?>"></span><?= $enabled ? '启用' : '停用' ?></span></td>
|
||||
<td class="px-4 py-4 align-top text-sm text-gray-500"><?= htmlspecialchars((string)($bot['updated_at'] ?? ($bot['created_at'] ?? '-'))) ?></td>
|
||||
<td class="px-4 py-4 align-top text-right"><div class="flex items-center justify-end gap-2"><button type="button" class="edit-bot-btn inline-flex items-center px-3 py-1.5 rounded-lg border border-primary/20 text-primary text-xs hover:bg-primary/5" data-id="<?= (int)$bot['id'] ?>" data-name="<?= htmlspecialchars((string)($bot['name'] ?? ''), ENT_QUOTES) ?>" data-token="<?= htmlspecialchars((string)($bot['bot_token'] ?? ''), ENT_QUOTES) ?>" data-username="<?= htmlspecialchars((string)($bot['bot_username'] ?? ''), ENT_QUOTES) ?>" data-run-mode="<?= htmlspecialchars((string)($bot['run_mode'] ?? 'polling'), ENT_QUOTES) ?>" data-webhook-url="<?= htmlspecialchars((string)($bot['webhook_url'] ?? ''), ENT_QUOTES) ?>" data-remark="<?= htmlspecialchars((string)($bot['remark'] ?? ''), ENT_QUOTES) ?>"><i class="fas fa-pen mr-1"></i>编辑</button><button type="button" class="toggle-btn inline-flex items-center px-3 py-1.5 rounded-lg text-xs <?= $enabled ? 'bg-red-50 text-red-600 border border-red-200' : 'bg-green-50 text-green-600 border border-green-200' ?>" data-url="/admin/bots/toggle" data-id="<?= (int)$bot['id'] ?>" data-status="<?= $enabled ? 0 : 1 ?>"><?= $enabled ? '停用' : '启用' ?></button></div></td>
|
||||
</tr>
|
||||
<?php endforeach; else: ?><tr><td colspan="6" class="px-4 py-10 text-center text-sm text-gray-500">尚未配置任何 Bot 实例。</td></tr><?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-xl shadow-md p-6 space-y-6">
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-4"><h2 class="text-xl font-semibold text-gray-800">新增 Bot 实例</h2><button type="button" id="cancelBotEditBtn" class="hidden text-xs text-gray-500 hover:text-gray-700">取消编辑</button></div>
|
||||
<form id="botForm" class="space-y-3">
|
||||
<input type="hidden" name="id">
|
||||
<input type="text" name="name" placeholder="Bot 名称" required class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
|
||||
<input type="text" name="bot_token" placeholder="Telegram Bot Token" required class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
|
||||
<input type="text" name="bot_username" placeholder="Bot Username,可选" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
|
||||
<select name="run_mode" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm bg-white"><option value="polling">polling</option><option value="webhook">webhook</option></select>
|
||||
<input type="text" name="webhook_url" placeholder="Webhook URL,可选" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
|
||||
<textarea name="remark" rows="2" placeholder="备注,可选" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm"></textarea>
|
||||
<button type="submit" class="w-full bg-primary hover:bg-primary/90 text-white px-4 py-2.5 rounded-lg text-sm" data-create-text="创建 Bot 实例" data-update-text="保存 Bot 修改">创建 Bot 实例</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="border-t pt-6">
|
||||
<div class="flex items-center justify-between mb-4"><h2 class="text-xl font-semibold text-gray-800">绑定群配置</h2><button type="button" id="cancelGroupEditBtn" class="hidden text-xs text-gray-500 hover:text-gray-700">取消编辑</button></div>
|
||||
<form id="groupForm" class="space-y-3">
|
||||
<input type="hidden" name="id">
|
||||
<select name="bot_id" required class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm bg-white"><option value="">选择 Bot</option><?php foreach (($bots ?? []) as $bot): ?><option value="<?= (int)$bot['id'] ?>"><?= htmlspecialchars((string)$bot['name']) ?></option><?php endforeach; ?></select>
|
||||
<select name="game_id" required class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm bg-white"><option value="">选择游戏</option><?php foreach (($games ?? []) as $game): ?><option value="<?= (int)$game['id'] ?>"><?= htmlspecialchars((string)$game['name']) ?> (<?= htmlspecialchars((string)$game['type']) ?>)</option><?php endforeach; ?></select>
|
||||
<input type="text" name="tg_group_id" placeholder="Telegram 群 ID,例如 -100123456" required class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
|
||||
<input type="text" name="group_name" placeholder="群名称" required class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
|
||||
<select name="group_type" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm bg-white"><option value="group">group</option><option value="supergroup">supergroup</option><option value="channel">channel</option></select>
|
||||
<select name="bet_format_rule_id" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm bg-white"><option value="">下注规则(可选)</option><?php foreach (($rules ?? []) as $rule): ?><option value="<?= (int)$rule['id'] ?>"><?= htmlspecialchars((string)$rule['name']) ?> / <?= htmlspecialchars((string)$rule['rule_code']) ?></option><?php endforeach; ?></select>
|
||||
<input type="text" name="countdown_config" placeholder="倒计时配置,如 60,30,10" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
|
||||
<div class="grid grid-cols-2 gap-3 text-sm text-gray-700"><label class="flex items-center gap-2"><input type="checkbox" name="remind_bet_success" checked>下注成功提醒</label><label class="flex items-center gap-2"><input type="checkbox" name="remind_draw_result" checked>开奖提醒</label><label class="flex items-center gap-2"><input type="checkbox" name="remind_close_countdown" checked>封盘倒计时</label><label class="flex items-center gap-2"><input type="checkbox" name="animation_enabled">开奖动画</label><label class="flex items-center gap-2"><input type="checkbox" name="bet_enabled" checked>允许下注同步</label></div>
|
||||
<button type="submit" class="w-full bg-success hover:bg-success/90 text-white px-4 py-2.5 rounded-lg text-sm" data-create-text="创建群配置" data-update-text="保存群配置修改">创建群配置</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="border-t pt-6">
|
||||
<div class="flex items-center justify-between mb-4"><h2 class="text-xl font-semibold text-gray-800">绑定群总账号</h2><button type="button" id="cancelWalletEditBtn" class="hidden text-xs text-gray-500 hover:text-gray-700">取消编辑</button></div>
|
||||
<form id="walletForm" class="space-y-3">
|
||||
<input type="hidden" name="id">
|
||||
<select name="group_id" required class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm bg-white"><option value="">选择群配置</option><?php foreach (($groups ?? []) as $group): ?><option value="<?= (int)$group['id'] ?>"><?= htmlspecialchars((string)$group['group_name']) ?> / <?= htmlspecialchars((string)$group['tg_group_id']) ?></option><?php endforeach; ?></select>
|
||||
<select name="platform_user_id" required class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm bg-white"><option value="">选择网站账号</option><?php foreach (($users ?? []) as $user): ?><option value="<?= (int)$user['id'] ?>"><?= htmlspecialchars((string)$user['username']) ?> / 余额 <?= number_format((float)($user['balance'] ?? 0), 2) ?></option><?php endforeach; ?></select>
|
||||
<select name="wallet_mode" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm bg-white"><option value="master_pool">master_pool</option><option value="per_member">per_member</option></select>
|
||||
<button type="submit" class="w-full bg-warning hover:bg-warning/90 text-white px-4 py-2.5 rounded-lg text-sm" data-create-text="绑定群总账号" data-update-text="保存总账号修改">绑定群总账号</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 xl:grid-cols-2 gap-6 mb-6">
|
||||
<div class="bg-white rounded-xl shadow-md p-6">
|
||||
<h2 class="text-xl font-semibold text-gray-800 mb-4">群配置总览</h2>
|
||||
<div class="overflow-x-auto"><table class="w-full text-sm"><thead class="bg-gray-50"><tr><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">群</th><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Bot / 游戏</th><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">开关</th><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">状态</th><th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">操作</th></tr></thead><tbody class="divide-y divide-gray-200"><?php if (!empty($groups)): foreach ($groups as $group): ?><?php $active = (int)($group['status'] ?? 0) === 1; $countdownText = ''; if (!empty($group['countdown_config'])) { $decoded = json_decode((string)$group['countdown_config'], true); if (is_array($decoded) && !empty($decoded)) { $countdownText = implode(',', array_map('intval', $decoded)); } } ?><tr class="hover:bg-gray-50"><td class="px-4 py-4 align-top"><div class="font-medium text-gray-800"><?= htmlspecialchars((string)$group['group_name']) ?></div><div class="text-xs text-gray-500 mt-1"><?= htmlspecialchars((string)$group['tg_group_id']) ?> / <?= htmlspecialchars((string)$group['group_type']) ?></div><?php if ($countdownText !== ''): ?><div class="text-xs text-gray-400 mt-1">倒计时:<?= htmlspecialchars($countdownText) ?></div><?php endif; ?></td><td class="px-4 py-4 align-top text-sm text-gray-600"><div><?= htmlspecialchars((string)($group['bot_name'] ?? '-')) ?></div><div class="text-xs text-gray-500 mt-1"><?= htmlspecialchars((string)($group['game_name'] ?? '-')) ?></div></td><td class="px-4 py-4 align-top text-xs text-gray-600"><div>下注:<?= !empty($group['bet_enabled']) ? '开' : '关' ?></div><div>提醒:<?= !empty($group['remind_bet_success']) || !empty($group['remind_draw_result']) || !empty($group['remind_close_countdown']) ? '开' : '关' ?></div><div>动画:<?= !empty($group['animation_enabled']) ? '开' : '关' ?></div></td><td class="px-4 py-4 align-top"><span class="inline-flex items-center px-2 py-1 rounded-full text-xs <?= $active ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-700' ?>"><?= $active ? '启用' : '停用' ?></span></td><td class="px-4 py-4 align-top text-right"><div class="flex items-center justify-end gap-2"><button type="button" class="edit-group-btn inline-flex items-center px-3 py-1.5 rounded-lg border border-primary/20 text-primary text-xs hover:bg-primary/5" data-id="<?= (int)$group['id'] ?>" data-bot-id="<?= (int)($group['bot_id'] ?? 0) ?>" data-game-id="<?= (int)($group['game_id'] ?? 0) ?>" data-rule-id="<?= (int)($group['bet_format_rule_id'] ?? 0) ?>" data-tg-group-id="<?= htmlspecialchars((string)($group['tg_group_id'] ?? ''), ENT_QUOTES) ?>" data-group-name="<?= htmlspecialchars((string)($group['group_name'] ?? ''), ENT_QUOTES) ?>" data-group-type="<?= htmlspecialchars((string)($group['group_type'] ?? 'group'), ENT_QUOTES) ?>" data-countdown-config="<?= htmlspecialchars($countdownText, ENT_QUOTES) ?>" data-remind-bet-success="<?= !empty($group['remind_bet_success']) ? 1 : 0 ?>" data-remind-draw-result="<?= !empty($group['remind_draw_result']) ? 1 : 0 ?>" data-remind-close-countdown="<?= !empty($group['remind_close_countdown']) ? 1 : 0 ?>" data-animation-enabled="<?= !empty($group['animation_enabled']) ? 1 : 0 ?>" data-bet-enabled="<?= !empty($group['bet_enabled']) ? 1 : 0 ?>"><i class="fas fa-pen mr-1"></i>编辑</button><button type="button" class="toggle-btn inline-flex items-center px-3 py-1.5 rounded-lg text-xs <?= $active ? 'bg-red-50 text-red-600 border border-red-200' : 'bg-green-50 text-green-600 border border-green-200' ?>" data-url="/admin/bots/groups/toggle" data-id="<?= (int)$group['id'] ?>" data-status="<?= $active ? 0 : 1 ?>"><?= $active ? '停用' : '启用' ?></button></div></td></tr><?php endforeach; else: ?><tr><td colspan="5" class="px-4 py-10 text-center text-sm text-gray-500">暂无群配置。</td></tr><?php endif; ?></tbody></table></div>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl shadow-md p-6">
|
||||
<h2 class="text-xl font-semibold text-gray-800 mb-4">群总账号绑定</h2>
|
||||
<div class="overflow-x-auto"><table class="w-full text-sm"><thead class="bg-gray-50"><tr><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">群</th><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">网站账号</th><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">模式</th><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">状态</th><th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">操作</th></tr></thead><tbody class="divide-y divide-gray-200"><?php if (!empty($wallets)): foreach ($wallets as $wallet): ?><?php $active = (int)($wallet['status'] ?? 0) === 1; ?><tr class="hover:bg-gray-50"><td class="px-4 py-4 align-top"><div class="font-medium text-gray-800"><?= htmlspecialchars((string)($wallet['group_name'] ?? '-')) ?></div><div class="text-xs text-gray-500 mt-1"><?= htmlspecialchars((string)($wallet['tg_group_id'] ?? '-')) ?></div></td><td class="px-4 py-4 align-top text-sm text-gray-600"><div><?= htmlspecialchars((string)($wallet['platform_username'] ?? '-')) ?></div><div class="text-xs text-gray-500 mt-1">UID <?= (int)($wallet['platform_user_id'] ?? 0) ?> / 余额 <?= number_format((float)($wallet['platform_balance'] ?? 0), 2) ?></div></td><td class="px-4 py-4 align-top text-sm text-gray-600"><?= htmlspecialchars((string)($wallet['wallet_mode'] ?? 'master_pool')) ?></td><td class="px-4 py-4 align-top"><span class="inline-flex items-center px-2 py-1 rounded-full text-xs <?= $active ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-700' ?>"><?= $active ? '启用' : '停用' ?></span></td><td class="px-4 py-4 align-top text-right"><div class="flex items-center justify-end gap-2"><button type="button" class="edit-wallet-btn inline-flex items-center px-3 py-1.5 rounded-lg border border-primary/20 text-primary text-xs hover:bg-primary/5" data-id="<?= (int)$wallet['id'] ?>" data-group-id="<?= (int)($wallet['group_id'] ?? 0) ?>" data-platform-user-id="<?= (int)($wallet['platform_user_id'] ?? 0) ?>" data-wallet-mode="<?= htmlspecialchars((string)($wallet['wallet_mode'] ?? 'master_pool'), ENT_QUOTES) ?>"><i class="fas fa-pen mr-1"></i>编辑</button><button type="button" class="toggle-btn inline-flex items-center px-3 py-1.5 rounded-lg text-xs <?= $active ? 'bg-red-50 text-red-600 border border-red-200' : 'bg-green-50 text-green-600 border border-green-200' ?>" data-url="/admin/bots/wallets/toggle" data-id="<?= (int)$wallet['id'] ?>" data-status="<?= $active ? 0 : 1 ?>"><?= $active ? '停用' : '启用' ?></button></div></td></tr><?php endforeach; else: ?><tr><td colspan="5" class="px-4 py-10 text-center text-sm text-gray-500">暂无群总账号绑定。</td></tr><?php endif; ?></tbody></table></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 xl:grid-cols-2 gap-6 mb-6">
|
||||
<div class="bg-white rounded-xl shadow-md p-6 space-y-6">
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-4"><h2 class="text-xl font-semibold text-gray-800">群成员映射</h2><button type="button" id="cancelMemberEditBtn" class="hidden text-xs text-gray-500 hover:text-gray-700">取消编辑</button></div>
|
||||
<form id="memberForm" class="space-y-3">
|
||||
<input type="hidden" name="id">
|
||||
<select name="group_id" required class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm bg-white"><option value="">选择群配置</option><?php foreach (($groups ?? []) as $group): ?><option value="<?= (int)$group['id'] ?>"><?= htmlspecialchars((string)$group['group_name']) ?> / <?= htmlspecialchars((string)$group['tg_group_id']) ?></option><?php endforeach; ?></select>
|
||||
<input type="text" name="tg_user_id" placeholder="TG 用户 ID" required class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
|
||||
<input type="text" name="tg_username" placeholder="TG Username,可选" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
|
||||
<input type="text" name="tg_nickname" placeholder="TG 昵称,可选" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
|
||||
<select name="platform_user_id" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm bg-white"><option value="">绑定网站账号(可选)</option><?php foreach (($users ?? []) as $user): ?><option value="<?= (int)$user['id'] ?>"><?= htmlspecialchars((string)$user['username']) ?> / 余额 <?= number_format((float)($user['balance'] ?? 0), 2) ?></option><?php endforeach; ?></select>
|
||||
<select name="role" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm bg-white"><option value="member">member</option><option value="admin">admin</option><option value="shill">shill</option></select>
|
||||
<input type="text" name="shill_note" placeholder="若标记为托,可填写备注" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
|
||||
<label class="flex items-center gap-2 text-sm text-gray-700"><input type="checkbox" name="bet_enabled" checked>允许该成员下注同步</label>
|
||||
<button type="submit" class="w-full bg-indigo-600 hover:bg-indigo-500 text-white px-4 py-2.5 rounded-lg text-sm" data-create-text="创建成员映射" data-update-text="保存成员修改">创建成员映射</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="border-t pt-6">
|
||||
<h2 class="text-xl font-semibold text-gray-800 mb-4">成员映射列表</h2>
|
||||
<div class="overflow-x-auto"><table class="w-full text-sm"><thead class="bg-gray-50"><tr><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">成员</th><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">群 / 网站账号</th><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">角色</th><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">下注</th><th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">操作</th></tr></thead><tbody class="divide-y divide-gray-200"><?php if (!empty($members)): foreach ($members as $member): ?><?php $betEnabled = (int)($member['bet_enabled'] ?? 0) === 1; ?><tr class="hover:bg-gray-50"><td class="px-4 py-4 align-top"><div class="font-medium text-gray-800"><?= htmlspecialchars((string)($member['tg_nickname'] ?: $member['tg_username'] ?: $member['tg_user_id'])) ?></div><div class="text-xs text-gray-500 mt-1">UID <?= htmlspecialchars((string)$member['tg_user_id']) ?> / @<?= htmlspecialchars((string)($member['tg_username'] ?? '-')) ?></div></td><td class="px-4 py-4 align-top text-sm text-gray-600"><div><?= htmlspecialchars((string)($member['group_name'] ?? '-')) ?></div><div class="text-xs text-gray-500 mt-1">网站:<?= htmlspecialchars((string)($member['platform_username'] ?? '未绑定')) ?></div></td><td class="px-4 py-4 align-top"><span class="inline-flex items-center px-2 py-1 rounded-full text-xs <?= ($member['role'] ?? 'member') === 'shill' ? 'bg-rose-100 text-rose-700' : 'bg-slate-100 text-slate-700' ?>"><?= htmlspecialchars((string)($member['role'] ?? 'member')) ?></span></td><td class="px-4 py-4 align-top"><span class="inline-flex items-center px-2 py-1 rounded-full text-xs <?= $betEnabled ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-700' ?>"><?= $betEnabled ? '允许' : '关闭' ?></span></td><td class="px-4 py-4 align-top text-right"><div class="flex items-center justify-end gap-2"><button type="button" class="edit-member-btn inline-flex items-center px-3 py-1.5 rounded-lg border border-primary/20 text-primary text-xs hover:bg-primary/5" data-id="<?= (int)$member['id'] ?>" data-group-id="<?= (int)($member['group_id'] ?? 0) ?>" data-tg-user-id="<?= htmlspecialchars((string)($member['tg_user_id'] ?? ''), ENT_QUOTES) ?>" data-tg-username="<?= htmlspecialchars((string)($member['tg_username'] ?? ''), ENT_QUOTES) ?>" data-tg-nickname="<?= htmlspecialchars((string)($member['tg_nickname'] ?? ''), ENT_QUOTES) ?>" data-platform-user-id="<?= (int)($member['platform_user_id'] ?? 0) ?>" data-role="<?= htmlspecialchars((string)($member['role'] ?? 'member'), ENT_QUOTES) ?>" data-bet-enabled="<?= $betEnabled ? 1 : 0 ?>"><i class="fas fa-pen mr-1"></i>编辑</button><button type="button" class="member-bet-toggle-btn inline-flex items-center px-3 py-1.5 rounded-lg text-xs <?= $betEnabled ? 'bg-red-50 text-red-600 border border-red-200' : 'bg-green-50 text-green-600 border border-green-200' ?>" data-id="<?= (int)$member['id'] ?>" data-bet-enabled="<?= $betEnabled ? 0 : 1 ?>"><?= $betEnabled ? '停用下注' : '启用下注' ?></button></div></td></tr><?php endforeach; else: ?><tr><td colspan="5" class="px-4 py-10 text-center text-sm text-gray-500">暂无成员映射。</td></tr><?php endif; ?></tbody></table></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl shadow-md p-6 space-y-6">
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-4"><h2 class="text-xl font-semibold text-gray-800">托号管理</h2><button type="button" id="cancelShillEditBtn" class="hidden text-xs text-gray-500 hover:text-gray-700">取消编辑</button></div>
|
||||
<form id="shillForm" class="space-y-3">
|
||||
<input type="hidden" name="id">
|
||||
<select name="group_id" required class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm bg-white"><option value="">选择群配置</option><?php foreach (($groups ?? []) as $group): ?><option value="<?= (int)$group['id'] ?>"><?= htmlspecialchars((string)$group['group_name']) ?> / <?= htmlspecialchars((string)$group['tg_group_id']) ?></option><?php endforeach; ?></select>
|
||||
<input type="text" name="tg_user_id" placeholder="TG 用户 ID" required class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
|
||||
<input type="text" name="note" placeholder="备注,可选" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
|
||||
<button type="submit" class="w-full bg-rose-600 hover:bg-rose-500 text-white px-4 py-2.5 rounded-lg text-sm" data-create-text="创建托号" data-update-text="保存托号修改">创建托号</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="border-t pt-6">
|
||||
<h2 class="text-xl font-semibold text-gray-800 mb-4">托号列表</h2>
|
||||
<div class="overflow-x-auto"><table class="w-full text-sm"><thead class="bg-gray-50"><tr><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">托号</th><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">群</th><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">备注</th><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">状态</th><th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">操作</th></tr></thead><tbody class="divide-y divide-gray-200"><?php if (!empty($shills)): foreach ($shills as $shill): ?><?php $enabled = (int)($shill['enabled'] ?? 0) === 1; ?><tr class="hover:bg-gray-50"><td class="px-4 py-4 align-top"><div class="font-medium text-gray-800"><?= htmlspecialchars((string)($shill['tg_nickname'] ?: $shill['tg_username'] ?: $shill['tg_user_id'])) ?></div><div class="text-xs text-gray-500 mt-1">UID <?= htmlspecialchars((string)$shill['tg_user_id']) ?></div></td><td class="px-4 py-4 align-top text-sm text-gray-600"><?= htmlspecialchars((string)($shill['group_name'] ?? '-')) ?></td><td class="px-4 py-4 align-top text-sm text-gray-600"><?= htmlspecialchars((string)($shill['note'] ?? '-')) ?></td><td class="px-4 py-4 align-top"><span class="inline-flex items-center px-2 py-1 rounded-full text-xs <?= $enabled ? 'bg-rose-100 text-rose-700' : 'bg-gray-100 text-gray-700' ?>"><?= $enabled ? '排除统计中' : '已停用' ?></span></td><td class="px-4 py-4 align-top text-right"><div class="flex items-center justify-end gap-2"><button type="button" class="edit-shill-btn inline-flex items-center px-3 py-1.5 rounded-lg border border-primary/20 text-primary text-xs hover:bg-primary/5" data-id="<?= (int)$shill['id'] ?>" data-group-id="<?= (int)($shill['group_id'] ?? 0) ?>" data-tg-user-id="<?= htmlspecialchars((string)($shill['tg_user_id'] ?? ''), ENT_QUOTES) ?>" data-note="<?= htmlspecialchars((string)($shill['note'] ?? ''), ENT_QUOTES) ?>"><i class="fas fa-pen mr-1"></i>编辑</button><button type="button" class="shill-toggle-btn inline-flex items-center px-3 py-1.5 rounded-lg text-xs <?= $enabled ? 'bg-red-50 text-red-600 border border-red-200' : 'bg-green-50 text-green-600 border border-green-200' ?>" data-id="<?= (int)$shill['id'] ?>" data-enabled="<?= $enabled ? 0 : 1 ?>"><?= $enabled ? '取消排除' : '重新排除' ?></button></div></td></tr><?php endforeach; else: ?><tr><td colspan="5" class="px-4 py-10 text-center text-sm text-gray-500">暂无托号配置。</td></tr><?php endif; ?></tbody></table></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-xl shadow-md p-6 mb-6">
|
||||
<div class="flex items-start justify-between gap-4 mb-4">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-gray-800">消息提醒 / 推送运营面</h2>
|
||||
<p class="text-sm text-gray-500 mt-1">先把自动消息、开奖提醒、封盘倒计时、上下分提醒的运营可见性拉出来,便于对照 TG 成品补齐行为。</p>
|
||||
</div>
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs bg-cyan-100 text-cyan-700"><i class="fas fa-wave-square mr-1"></i> Push Ops</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<h3 class="text-base font-semibold text-gray-800 mb-3">推送类型汇总</h3>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">类型</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">总数</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">成功</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">失败</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">待发/跳过</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
<?php foreach (($pushSummary ?? []) as $pushType => $summary): ?>
|
||||
<?php if ((int)($summary['total'] ?? 0) === 0) continue; ?>
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-4 py-3 font-medium text-gray-800"><?= htmlspecialchars((string)$pushType) ?></td>
|
||||
<td class="px-4 py-3 text-gray-600"><?= (int)($summary['total'] ?? 0) ?></td>
|
||||
<td class="px-4 py-3 text-emerald-600"><?= (int)($summary['success'] ?? 0) ?></td>
|
||||
<td class="px-4 py-3 text-red-600"><?= (int)($summary['failed'] ?? 0) ?></td>
|
||||
<td class="px-4 py-3 text-amber-600"><?= (int)($summary['pending'] ?? 0) + (int)($summary['skipped'] ?? 0) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty(array_filter($pushSummary ?? [], static function ($item) { return (int)($item['total'] ?? 0) > 0; }))): ?>
|
||||
<tr><td colspan="5" class="px-4 py-8 text-center text-sm text-gray-500">暂无推送汇总数据,待 TG runtime 写入。</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-base font-semibold text-gray-800 mb-3">群提醒能力快照</h3>
|
||||
<div class="space-y-3">
|
||||
<?php if (!empty($groups)): foreach (array_slice($groups, 0, 8) as $group): ?>
|
||||
<div class="border border-gray-200 rounded-lg p-4">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<div class="text-sm font-semibold text-gray-800"><?= htmlspecialchars((string)($group['group_name'] ?? '-')) ?></div>
|
||||
<div class="text-xs text-gray-500 mt-1"><?= htmlspecialchars((string)($group['tg_group_id'] ?? '-')) ?></div>
|
||||
</div>
|
||||
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs <?= !empty($group['status']) ? 'bg-green-100 text-green-700' : 'bg-gray-100 text-gray-600' ?>"><?= !empty($group['status']) ? '启用中' : '停用中' ?></span>
|
||||
</div>
|
||||
<div class="mt-3 flex flex-wrap gap-2 text-xs">
|
||||
<span class="px-2 py-1 rounded-full <?= !empty($group['remind_bet_success']) ? 'bg-emerald-100 text-emerald-700' : 'bg-gray-100 text-gray-500' ?>">下注成功</span>
|
||||
<span class="px-2 py-1 rounded-full <?= !empty($group['remind_draw_result']) ? 'bg-sky-100 text-sky-700' : 'bg-gray-100 text-gray-500' ?>">开奖提醒</span>
|
||||
<span class="px-2 py-1 rounded-full <?= !empty($group['remind_close_countdown']) ? 'bg-amber-100 text-amber-700' : 'bg-gray-100 text-gray-500' ?>">封盘倒计时</span>
|
||||
<span class="px-2 py-1 rounded-full <?= !empty($group['animation_enabled']) ? 'bg-fuchsia-100 text-fuchsia-700' : 'bg-gray-100 text-gray-500' ?>">开奖动画</span>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; else: ?>
|
||||
<div class="text-sm text-gray-500">暂无群配置,无法展示提醒快照。</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-xl shadow-md p-6 mb-6">
|
||||
<div class="flex items-start justify-between gap-4 mb-4"><div><h2 class="text-xl font-semibold text-gray-800">最近推送日志</h2><p class="text-sm text-gray-500 mt-1">覆盖 countdown / bet_success / draw / credit / debit / system,先做观测面,再回填自动触发链。</p></div></div>
|
||||
<div class="overflow-x-auto"><table class="w-full text-sm"><thead class="bg-gray-50"><tr><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">群</th><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">类型</th><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">期号 / 消息</th><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">载荷摘要</th><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">状态</th><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">时间</th></tr></thead><tbody class="divide-y divide-gray-200"><?php if (!empty($pushLogs)): foreach ($pushLogs as $pushLog): ?><?php $status = (string)($pushLog['status'] ?? 'pending'); $payload = is_array($pushLog['payload'] ?? null) ? $pushLog['payload'] : []; $summaryParts = []; if (isset($payload['amount'])) { $summaryParts[] = '金额 ' . number_format((float)$payload['amount'], 2); } if (!empty($payload['balance_after'])) { $summaryParts[] = '余额 ' . number_format((float)$payload['balance_after'], 2); } if (!empty($payload['operator'])) { $summaryParts[] = '操作人 ' . (string)$payload['operator']; } if (!empty($payload['username'])) { $summaryParts[] = '用户 ' . (string)$payload['username']; } if (!empty($payload['text'])) { $summaryParts[] = mb_substr((string)$payload['text'], 0, 36); } $summaryText = !empty($summaryParts) ? implode(' / ', $summaryParts) : json_encode($payload, JSON_UNESCAPED_UNICODE); if (!$summaryText) { $summaryText = '-'; } ?><tr class="hover:bg-gray-50"><td class="px-4 py-4 align-top"><div class="font-medium text-gray-800"><?= htmlspecialchars((string)($pushLog['group_name'] ?? '未知群组')) ?></div><div class="text-xs text-gray-500 mt-1"><?= htmlspecialchars((string)($pushLog['tg_group_id'] ?? '-')) ?></div></td><td class="px-4 py-4 align-top"><span class="inline-flex items-center px-2 py-1 rounded-full text-xs bg-cyan-100 text-cyan-700"><?= htmlspecialchars((string)($pushLog['push_type'] ?? 'system')) ?></span></td><td class="px-4 py-4 align-top text-sm text-gray-600"><div>期号:<?= htmlspecialchars((string)($pushLog['period_number'] ?? '-')) ?></div><div class="text-xs text-gray-500 mt-1">TG Msg ID:<?= htmlspecialchars((string)($pushLog['tg_message_id'] ?? '-')) ?></div></td><td class="px-4 py-4 align-top text-sm text-gray-600 max-w-xs break-words"><?= htmlspecialchars((string)$summaryText) ?><?php if (!empty($pushLog['error_message'])): ?><div class="text-xs text-red-500 mt-1">错误:<?= htmlspecialchars((string)$pushLog['error_message']) ?></div><?php endif; ?></td><td class="px-4 py-4 align-top"><span class="inline-flex items-center px-2 py-1 rounded-full text-xs <?= $status === 'success' ? 'bg-green-100 text-green-800' : ($status === 'failed' ? 'bg-red-100 text-red-700' : ($status === 'skipped' ? 'bg-gray-100 text-gray-700' : 'bg-amber-100 text-amber-700')) ?>"><?= htmlspecialchars($status) ?></span></td><td class="px-4 py-4 align-top text-sm text-gray-500"><?= htmlspecialchars((string)($pushLog['created_at'] ?? '-')) ?></td></tr><?php endforeach; else: ?><tr><td colspan="6" class="px-4 py-10 text-center text-sm text-gray-500">暂无推送日志。</td></tr><?php endif; ?></tbody></table></div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-xl shadow-md p-6 mb-6">
|
||||
<div class="flex items-start justify-between gap-4 mb-4"><div><h2 class="text-xl font-semibold text-gray-800">Bot API 请求审计</h2><p class="text-sm text-gray-500 mt-1">对照成品 TG 机器人的发送队列与多账号路由,这里补入网站侧入站请求审计链,便于排查签名、限流、重复请求。</p></div></div>
|
||||
<div class="overflow-x-auto"><table class="w-full text-sm"><thead class="bg-gray-50"><tr><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Bot / 群</th><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">请求</th><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">幂等 / IP</th><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">签名 / 响应</th><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">时间</th></tr></thead><tbody class="divide-y divide-gray-200"><?php if (!empty($apiRequestLogs)): foreach ($apiRequestLogs as $apiLog): ?><?php $signatureOk = (int)($apiLog['signature_ok'] ?? 0) === 1; ?><tr class="hover:bg-gray-50"><td class="px-4 py-4 align-top"><div class="font-medium text-gray-800"><?= htmlspecialchars((string)($apiLog['bot_name'] ?? '未知 Bot')) ?></div><div class="text-xs text-gray-500 mt-1"><?= htmlspecialchars((string)($apiLog['group_name'] ?? '-')) ?> / <?= htmlspecialchars((string)($apiLog['tg_group_id'] ?? '-')) ?></div></td><td class="px-4 py-4 align-top text-sm text-gray-600"><div><?= htmlspecialchars((string)($apiLog['http_method'] ?? 'POST')) ?></div><div class="text-xs text-gray-500 mt-1 break-all"><?= htmlspecialchars((string)($apiLog['request_uri'] ?? '-')) ?></div></td><td class="px-4 py-4 align-top text-sm text-gray-600"><div class="break-all"><?= htmlspecialchars((string)($apiLog['idempotency_key'] ?? '-')) ?></div><div class="text-xs text-gray-500 mt-1">IP: <?= htmlspecialchars((string)($apiLog['client_ip'] ?? '-')) ?></div></td><td class="px-4 py-4 align-top"><div><span class="inline-flex items-center px-2 py-1 rounded-full text-xs <?= $signatureOk ? 'bg-lime-100 text-lime-700' : 'bg-red-100 text-red-700' ?>"><?= $signatureOk ? 'signature ok' : 'signature fail' ?></span></div><div class="text-xs text-gray-500 mt-2">HTTP <?= (int)($apiLog['response_code'] ?? 0) ?></div></td><td class="px-4 py-4 align-top text-sm text-gray-500"><?= htmlspecialchars((string)($apiLog['created_at'] ?? '-')) ?></td></tr><?php endforeach; else: ?><tr><td colspan="5" class="px-4 py-10 text-center text-sm text-gray-500">暂无 API 请求日志。</td></tr><?php endif; ?></tbody></table></div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-xl shadow-md p-6">
|
||||
<div class="flex items-center justify-between mb-4"><div><h2 class="text-xl font-semibold text-gray-800">最近 Bot 下注同步</h2><p class="text-sm text-gray-500 mt-1">用于快速核对群消息→平台订单的同步状态。</p></div></div>
|
||||
<div class="overflow-x-auto"><table class="w-full text-sm"><thead class="bg-gray-50"><tr><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">群</th><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">用户</th><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">期号</th><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">金额 / 注数</th><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">状态</th><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">时间</th></tr></thead><tbody class="divide-y divide-gray-200"><?php if (!empty($recentOrders)): foreach ($recentOrders as $order): ?><?php $ok = ($order['sync_status'] ?? '') === 'success'; $isShill = (int)($order['is_shill'] ?? 0) === 1; ?><tr class="hover:bg-gray-50 transition-colors"><td class="px-4 py-4 align-top"><div class="text-sm font-medium text-gray-800"><?= htmlspecialchars((string)($order['group_name'] ?? '未知群组')) ?></div><div class="text-xs text-gray-500 mt-1"><?= htmlspecialchars((string)($order['tg_group_id'] ?? '-')) ?></div></td><td class="px-4 py-4 align-top text-sm text-gray-600"><?= htmlspecialchars((string)($order['tg_username'] ?? '-')) ?><?php if ($isShill): ?><span class="ml-2 inline-flex items-center px-2 py-0.5 rounded-full text-xs bg-rose-100 text-rose-700">托</span><?php endif; ?></td><td class="px-4 py-4 align-top text-sm text-gray-600"><?= htmlspecialchars((string)($order['period_number'] ?? '-')) ?></td><td class="px-4 py-4 align-top text-sm text-gray-600"><?= number_format((float)($order['bet_amount_total'] ?? 0), 2) ?> / <?= (int)($order['accepted_bet_count'] ?? 0) ?> 注</td><td class="px-4 py-4 align-top"><span class="inline-flex items-center px-2 py-1 rounded-full text-xs <?= $ok ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-700' ?>"><span class="w-2 h-2 rounded-full mr-1 <?= $ok ? 'bg-green-500' : 'bg-red-500' ?>"></span><?= htmlspecialchars((string)($order['sync_status'] ?? 'unknown')) ?></span><?php if (!$ok && !empty($order['sync_error'])): ?><div class="text-xs text-red-500 mt-1 max-w-xs break-words"><?= htmlspecialchars((string)$order['sync_error']) ?></div><?php endif; ?></td><td class="px-4 py-4 align-top text-sm text-gray-500"><?= htmlspecialchars((string)($order['created_at'] ?? '-')) ?></td></tr><?php endforeach; else: ?><tr><td colspan="6" class="px-4 py-10 text-center text-sm text-gray-500">暂无 Bot 同步记录。</td></tr><?php endif; ?></tbody></table></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
const alertBox = document.getElementById('botAdminAlert');
|
||||
|
||||
function showAlert(success, message) {
|
||||
alertBox.className = 'mb-6 rounded-lg border px-4 py-3 text-sm ' + (success ? 'border-green-200 bg-green-50 text-green-700' : 'border-red-200 bg-red-50 text-red-700');
|
||||
alertBox.textContent = message;
|
||||
alertBox.classList.remove('hidden');
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
|
||||
function setSubmitButtonState(form, isEdit) {
|
||||
const submitButton = form.querySelector('button[type="submit"]');
|
||||
if (!submitButton) return;
|
||||
submitButton.textContent = isEdit ? submitButton.dataset.updateText : submitButton.dataset.createText;
|
||||
}
|
||||
|
||||
function setCheckboxValue(form, name, checked) {
|
||||
const input = form.querySelector('[name="' + name + '"]');
|
||||
if (input) input.checked = !!checked;
|
||||
}
|
||||
|
||||
async function requestJson(url, payload) {
|
||||
const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), credentials: 'same-origin' });
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async function submitManagedForm(formId, createUrl, updateUrl, resetHandler) {
|
||||
const form = document.getElementById(formId);
|
||||
if (!form) return;
|
||||
form.addEventListener('submit', async function (event) {
|
||||
event.preventDefault();
|
||||
const formData = new FormData(form);
|
||||
const payload = {};
|
||||
for (const [key, value] of formData.entries()) payload[key] = value;
|
||||
form.querySelectorAll('input[type="checkbox"]').forEach(function (checkbox) { payload[checkbox.name] = checkbox.checked ? 1 : 0; });
|
||||
const isEdit = !!(payload.id && String(payload.id).trim() !== '');
|
||||
try {
|
||||
const result = await requestJson(isEdit ? updateUrl : createUrl, payload);
|
||||
showAlert(!!result.success, result.message || '请求完成');
|
||||
if (result.success) {
|
||||
if (typeof resetHandler === 'function') resetHandler();
|
||||
setTimeout(function () { window.location.reload(); }, 600);
|
||||
}
|
||||
} catch (error) {
|
||||
showAlert(false, '请求失败,请稍后重试');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function bindToggleButtons(selector, url, key) {
|
||||
document.querySelectorAll(selector).forEach(function (button) {
|
||||
button.addEventListener('click', async function () {
|
||||
const payload = { id: Number(button.dataset.id || 0) };
|
||||
payload[key] = Number(button.dataset[key] || 0);
|
||||
try {
|
||||
const result = await requestJson(url, payload);
|
||||
showAlert(!!result.success, result.message || '状态已更新');
|
||||
if (result.success) setTimeout(function () { window.location.reload(); }, 600);
|
||||
} catch (error) {
|
||||
showAlert(false, '状态更新失败,请稍后重试');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function bindStatusButtons() {
|
||||
document.querySelectorAll('.toggle-btn').forEach(function (button) {
|
||||
button.addEventListener('click', async function () {
|
||||
try {
|
||||
const result = await requestJson(button.dataset.url, {
|
||||
id: Number(button.dataset.id || 0),
|
||||
status: Number(button.dataset.status || 0)
|
||||
});
|
||||
showAlert(!!result.success, result.message || '状态已更新');
|
||||
if (result.success) setTimeout(function () { window.location.reload(); }, 600);
|
||||
} catch (error) {
|
||||
showAlert(false, '状态更新失败,请稍后重试');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function bindEditForm(formId, cancelId, buttonSelector, fieldMap, defaults) {
|
||||
const form = document.getElementById(formId);
|
||||
const cancelButton = document.getElementById(cancelId);
|
||||
if (!form || !cancelButton) return;
|
||||
|
||||
function resetForm() {
|
||||
form.reset();
|
||||
const idInput = form.querySelector('[name="id"]');
|
||||
if (idInput) idInput.value = '';
|
||||
Object.keys(defaults || {}).forEach(function (key) {
|
||||
const field = form.querySelector('[name="' + key + '"]');
|
||||
if (!field) return;
|
||||
if (field.type === 'checkbox') {
|
||||
field.checked = !!defaults[key];
|
||||
} else {
|
||||
field.value = defaults[key];
|
||||
}
|
||||
});
|
||||
setSubmitButtonState(form, false);
|
||||
cancelButton.classList.add('hidden');
|
||||
}
|
||||
|
||||
document.querySelectorAll(buttonSelector).forEach(function (button) {
|
||||
button.addEventListener('click', function () {
|
||||
Object.keys(fieldMap).forEach(function (fieldName) {
|
||||
const datasetKey = fieldMap[fieldName];
|
||||
const field = form.querySelector('[name="' + fieldName + '"]');
|
||||
if (!field) return;
|
||||
if (field.type === 'checkbox') {
|
||||
field.checked = Number(button.dataset[datasetKey] || 0) === 1;
|
||||
} else {
|
||||
field.value = button.dataset[datasetKey] || '';
|
||||
}
|
||||
});
|
||||
setSubmitButtonState(form, true);
|
||||
cancelButton.classList.remove('hidden');
|
||||
form.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
});
|
||||
});
|
||||
|
||||
cancelButton.addEventListener('click', resetForm);
|
||||
resetForm();
|
||||
return resetForm;
|
||||
}
|
||||
|
||||
const resetBotForm = bindEditForm('botForm', 'cancelBotEditBtn', '.edit-bot-btn', {
|
||||
id: 'id', name: 'name', bot_token: 'token', bot_username: 'username', run_mode: 'runMode', webhook_url: 'webhookUrl', remark: 'remark'
|
||||
}, { run_mode: 'polling' });
|
||||
|
||||
const resetGroupForm = bindEditForm('groupForm', 'cancelGroupEditBtn', '.edit-group-btn', {
|
||||
id: 'id', bot_id: 'botId', game_id: 'gameId', bet_format_rule_id: 'ruleId', tg_group_id: 'tgGroupId', group_name: 'groupName', group_type: 'groupType', countdown_config: 'countdownConfig', remind_bet_success: 'remindBetSuccess', remind_draw_result: 'remindDrawResult', remind_close_countdown: 'remindCloseCountdown', animation_enabled: 'animationEnabled', bet_enabled: 'betEnabled'
|
||||
}, { remind_bet_success: true, remind_draw_result: true, remind_close_countdown: true, animation_enabled: false, bet_enabled: true, group_type: 'group' });
|
||||
|
||||
const resetWalletForm = bindEditForm('walletForm', 'cancelWalletEditBtn', '.edit-wallet-btn', {
|
||||
id: 'id', group_id: 'groupId', platform_user_id: 'platformUserId', wallet_mode: 'walletMode'
|
||||
}, { wallet_mode: 'master_pool' });
|
||||
|
||||
const resetMemberForm = bindEditForm('memberForm', 'cancelMemberEditBtn', '.edit-member-btn', {
|
||||
id: 'id', group_id: 'groupId', tg_user_id: 'tgUserId', tg_username: 'tgUsername', tg_nickname: 'tgNickname', platform_user_id: 'platformUserId', role: 'role', bet_enabled: 'betEnabled'
|
||||
}, { role: 'member', bet_enabled: true });
|
||||
|
||||
const resetShillForm = bindEditForm('shillForm', 'cancelShillEditBtn', '.edit-shill-btn', {
|
||||
id: 'id', group_id: 'groupId', tg_user_id: 'tgUserId', note: 'note'
|
||||
}, {});
|
||||
|
||||
submitManagedForm('botForm', '/admin/bots/save', '/admin/bots/update', resetBotForm);
|
||||
submitManagedForm('groupForm', '/admin/bots/groups/save', '/admin/bots/groups/update', resetGroupForm);
|
||||
submitManagedForm('walletForm', '/admin/bots/wallets/save', '/admin/bots/wallets/update', resetWalletForm);
|
||||
submitManagedForm('memberForm', '/admin/bots/members/save', '/admin/bots/members/update', resetMemberForm);
|
||||
submitManagedForm('shillForm', '/admin/bots/shills/save', '/admin/bots/shills/update', resetShillForm);
|
||||
|
||||
bindStatusButtons();
|
||||
bindToggleButtons('.member-bet-toggle-btn', '/admin/bots/members/toggle-bet', 'betEnabled');
|
||||
bindToggleButtons('.shill-toggle-btn', '/admin/bots/shills/toggle', 'enabled');
|
||||
})();
|
||||
</script>
|
||||
@@ -0,0 +1,152 @@
|
||||
<h1 class="text-2xl font-bold text-gray-800 mb-6 flex items-center">
|
||||
<i class="fas fa-clipboard-list text-primary mr-3"></i>
|
||||
跟单计划管理
|
||||
</h1>
|
||||
|
||||
<!-- 新建按钮 -->
|
||||
<div class="mb-4">
|
||||
<button onclick="showAddForm()" class="px-5 py-2 bg-primary text-white rounded-lg text-sm hover:opacity-90 transition">
|
||||
<i class="fas fa-plus mr-1"></i> 新建计划
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 计划列表 -->
|
||||
<div class="bg-white rounded-xl shadow-md p-6 mb-6">
|
||||
<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">ID</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-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-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>
|
||||
<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($plans)): ?>
|
||||
<?php foreach ($plans as $p): ?>
|
||||
<tr class="hover:bg-gray-50 transition-colors">
|
||||
<td class="px-4 py-3 text-sm text-gray-500"><?= $p['id'] ?></td>
|
||||
<td class="px-4 py-3 text-sm font-medium text-gray-900"><?= htmlspecialchars($p['name']) ?></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 bg-primary/10 text-primary">
|
||||
<?= ['bs'=>'大小','oe'=>'单双','dt'=>'龙虎','sum_bs'=>'冠亚大小'][$p['plan_type']] ?? $p['plan_type'] ?>
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-sm text-center"><?= $p['target_rank'] ?></td>
|
||||
<td class="px-4 py-3 text-sm text-right"><?= number_format($p['bet_amount'], 2) ?></td>
|
||||
<td class="px-4 py-3 text-sm text-center"><?= $p['total_records'] ?></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 <?= $p['win_rate'] >= 50 ? 'bg-success/10 text-success' : 'bg-danger/10 text-danger' ?>">
|
||||
<?= $p['win_rate'] ?>%
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-sm text-center font-semibold"><?= $p['follower_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 cursor-pointer <?= $p['status'] ? 'bg-success/10 text-success' : 'bg-gray-200 text-gray-500' ?>"
|
||||
onclick="togglePlan(<?= $p['id'] ?>)">
|
||||
<?= $p['status'] ? '启用' : '禁用' ?>
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-sm text-center">
|
||||
<button onclick='editPlan(<?= json_encode($p) ?>)' class="text-primary hover:underline text-xs mr-2">编辑</button>
|
||||
<button onclick="deletePlan(<?= $p['id'] ?>)" class="text-danger hover:underline text-xs">删除</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<tr>
|
||||
<td colspan="10" class="px-6 py-12 text-center">
|
||||
<div class="flex flex-col items-center">
|
||||
<i class="fas fa-clipboard-list 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>
|
||||
var planTypes = {bs:'大小', oe:'单双', dt:'龙虎', sum_bs:'冠亚大小'};
|
||||
|
||||
function showAddForm(data) {
|
||||
var isEdit = data && data.id;
|
||||
var html = '<div style="padding:20px">' +
|
||||
'<div style="margin-bottom:12px"><label style="display:block;margin-bottom:4px;font-size:13px;color:#666">计划名称</label>' +
|
||||
'<input type="text" id="fp_name" value="' + (data ? data.name : '') + '" class="layui-input" style="width:100%"></div>' +
|
||||
'<div style="display:flex;gap:12px;margin-bottom:12px">' +
|
||||
'<div style="flex:1"><label style="display:block;margin-bottom:4px;font-size:13px;color:#666">类型</label>' +
|
||||
'<select id="fp_type" class="layui-input">' +
|
||||
'<option value="bs"' + (data && data.plan_type === 'bs' ? ' selected' : '') + '>大小</option>' +
|
||||
'<option value="oe"' + (data && data.plan_type === 'oe' ? ' selected' : '') + '>单双</option>' +
|
||||
'<option value="dt"' + (data && data.plan_type === 'dt' ? ' selected' : '') + '>龙虎</option>' +
|
||||
'<option value="sum_bs"' + (data && data.plan_type === 'sum_bs' ? ' selected' : '') + '>冠亚大小</option>' +
|
||||
'</select></div>' +
|
||||
'<div style="flex:1"><label style="display:block;margin-bottom:4px;font-size:13px;color:#666">目标名次</label>' +
|
||||
'<input type="number" id="fp_rank" min="1" max="10" value="' + (data ? data.target_rank : 1) + '" class="layui-input"></div>' +
|
||||
'</div>' +
|
||||
'<div style="margin-bottom:12px"><label style="display:block;margin-bottom:4px;font-size:13px;color:#666">建议投注金额</label>' +
|
||||
'<input type="number" id="fp_amount" value="' + (data ? data.bet_amount : 100) + '" class="layui-input"></div>' +
|
||||
'</div>';
|
||||
|
||||
layui.use('layer', function() {
|
||||
layui.layer.open({
|
||||
type: 1, title: isEdit ? '编辑计划' : '新建计划',
|
||||
area: ['420px', '360px'], content: html,
|
||||
btn: ['保存', '取消'],
|
||||
yes: function(index) {
|
||||
var postData = {
|
||||
name: document.getElementById('fp_name').value,
|
||||
plan_type: document.getElementById('fp_type').value,
|
||||
target_rank: document.getElementById('fp_rank').value,
|
||||
bet_amount: document.getElementById('fp_amount').value,
|
||||
game_id: 1, status: 1
|
||||
};
|
||||
if (isEdit) postData.id = data.id;
|
||||
fetch('/admin/follow-plans/save', {
|
||||
method: 'POST', headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(postData)
|
||||
}).then(function(r) { return r.json(); }).then(function(d) {
|
||||
if (d.success) { layui.layer.close(index); location.reload(); }
|
||||
else { layui.layer.msg(d.message || '保存失败'); }
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function editPlan(plan) { showAddForm(plan); }
|
||||
|
||||
function togglePlan(id) {
|
||||
fetch('/admin/follow-plans/toggle', {
|
||||
method: 'POST', headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({id: id})
|
||||
}).then(function(r) { return r.json(); }).then(function(d) {
|
||||
if (d.success) location.reload();
|
||||
});
|
||||
}
|
||||
|
||||
function deletePlan(id) {
|
||||
layui.use('layer', function() {
|
||||
layui.layer.confirm('确定删除此计划?所有相关记录将被清除。', function(index) {
|
||||
fetch('/admin/follow-plans/delete', {
|
||||
method: 'POST', headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({id: id})
|
||||
}).then(function(r) { return r.json(); }).then(function(d) {
|
||||
if (d.success) { layui.layer.close(index); location.reload(); }
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,306 @@
|
||||
<h1 class="text-2xl font-bold text-gray-800 mb-6 flex items-center">
|
||||
<i class="fas fa-file-invoice-dollar text-primary mr-3"></i>
|
||||
充提审核
|
||||
</h1>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 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" id="statPending">
|
||||
<?= (int)($pendingCount ?? 0) ?>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="w-10 h-10 rounded-full bg-warning/10 flex items-center justify-center">
|
||||
<i class="fas fa-clock 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">
|
||||
<?= (int)($todayDeposit ?? 0) ?>
|
||||
</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">
|
||||
<?= (int)($todayWithdraw ?? 0) ?>
|
||||
</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>
|
||||
|
||||
<!-- 筛选栏 -->
|
||||
<div class="bg-white rounded-xl shadow-md p-6 mb-6">
|
||||
<div class="flex flex-col md:flex-row md:items-center gap-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="text-sm text-gray-600 whitespace-nowrap">类型:</label>
|
||||
<select id="filterType" class="px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-primary/50 focus:border-primary">
|
||||
<option value="">全部</option>
|
||||
<option value="deposit">充值</option>
|
||||
<option value="withdraw">提现</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="text-sm text-gray-600 whitespace-nowrap">状态:</label>
|
||||
<select id="filterStatus" class="px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-primary/50 focus:border-primary">
|
||||
<option value="">全部</option>
|
||||
<option value="pending">待处理</option>
|
||||
<option value="approved">已通过</option>
|
||||
<option value="rejected">已拒绝</option>
|
||||
</select>
|
||||
</div>
|
||||
<button id="btnFilter" class="px-4 py-2 bg-primary text-white rounded-lg text-sm hover:bg-primary/90 transition-colors">
|
||||
<i class="fas fa-search mr-1"></i>筛选
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 申请列表 -->
|
||||
<div class="bg-white rounded-xl shadow-md p-6 mb-8">
|
||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4 mb-6">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-gray-800">充提申请列表</h2>
|
||||
<p class="text-sm text-gray-500 mt-1">审核用户的充值和提现申请</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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 tracking-wider">ID</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">用户</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">钱包地址</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">类型</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">金额</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">状态</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider hidden md:table-cell">用户备注</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider hidden md:table-cell">管理员备注</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider hidden lg:table-cell">创建时间</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200" id="requestList">
|
||||
<?php if (!empty($requests) && is_array($requests)): ?>
|
||||
<?php foreach ($requests as $req): ?>
|
||||
<tr class="hover:bg-gray-50 transition-colors fund-row"
|
||||
data-type="<?= htmlspecialchars((string)($req['type'] ?? '')) ?>"
|
||||
data-status="<?= htmlspecialchars((string)($req['status'] ?? '')) ?>">
|
||||
<td class="px-4 py-4">
|
||||
<span class="text-sm font-medium text-gray-900">#<?= htmlspecialchars((string)($req['id'] ?? '')) ?></span>
|
||||
</td>
|
||||
<td class="px-4 py-4">
|
||||
<div class="text-sm text-gray-900"><?= htmlspecialchars((string)($req['username'] ?? '未知')) ?></div>
|
||||
<div class="text-xs text-gray-500">ID: <?= htmlspecialchars((string)($req['user_id'] ?? '')) ?></div>
|
||||
</td>
|
||||
<td class="px-4 py-4">
|
||||
<?php $addr = $req['usdt_address'] ?? ''; ?>
|
||||
<?php if ($addr): ?>
|
||||
<div class="text-xs text-gray-700 font-mono max-w-[160px] truncate cursor-pointer" title="<?= htmlspecialchars($addr) ?>" onclick="navigator.clipboard.writeText('<?= htmlspecialchars($addr) ?>');layui.layer&&layui.layer.msg('已复制')">
|
||||
<?= htmlspecialchars($addr) ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<span class="text-xs text-gray-400">未绑定</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="px-4 py-4">
|
||||
<?php if (($req['type'] ?? '') === 'deposit'): ?>
|
||||
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs bg-success/10 text-success">充值</span>
|
||||
<?php else: ?>
|
||||
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs bg-danger/10 text-danger">提现</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="px-4 py-4">
|
||||
<span class="text-sm font-semibold text-gray-900">
|
||||
<?= number_format(floatval($req['amount'] ?? 0), 2) ?>
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-4 py-4">
|
||||
<?php
|
||||
$status = $req['status'] ?? '';
|
||||
$statusMap = [
|
||||
'pending' => ['text' => '待处理', 'class' => 'bg-warning/10 text-warning'],
|
||||
'approved' => ['text' => '已通过', 'class' => 'bg-success/10 text-success'],
|
||||
'rejected' => ['text' => '已拒绝', 'class' => 'bg-danger/10 text-danger'],
|
||||
];
|
||||
$statusInfo = $statusMap[$status] ?? ['text' => $status, 'class' => 'bg-gray-100 text-gray-700'];
|
||||
?>
|
||||
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs <?= $statusInfo['class'] ?>">
|
||||
<?= $statusInfo['text'] ?>
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-4 py-4 hidden md:table-cell">
|
||||
<span class="text-sm text-gray-600 max-w-[120px] truncate block">
|
||||
<?= htmlspecialchars((string)($req['remark'] ?? '-')) ?>
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-4 py-4 hidden md:table-cell">
|
||||
<span class="text-sm text-gray-600 max-w-[120px] truncate block">
|
||||
<?= htmlspecialchars((string)($req['admin_remark'] ?? '-')) ?>
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-4 py-4 hidden lg:table-cell">
|
||||
<?php if (!empty($req['created_at'])): ?>
|
||||
<div class="text-xs text-gray-500">
|
||||
<?= date('Y-m-d H:i:s', strtotime((string)$req['created_at'])) ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<span class="text-xs text-gray-400">-</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="px-4 py-4">
|
||||
<?php if ($status === 'pending'): ?>
|
||||
<div class="flex gap-2">
|
||||
<button onclick="approveRequest(<?= (int)$req['id'] ?>)"
|
||||
class="px-3 py-1 bg-success text-white text-xs rounded-lg hover:bg-success/90 transition-colors">
|
||||
<i class="fas fa-check mr-1"></i>通过
|
||||
</button>
|
||||
<button onclick="rejectRequest(<?= (int)$req['id'] ?>)"
|
||||
class="px-3 py-1 bg-danger text-white text-xs rounded-lg hover:bg-danger/90 transition-colors">
|
||||
<i class="fas fa-times mr-1"></i>拒绝
|
||||
</button>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<span class="text-xs text-gray-400">已处理</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<tr>
|
||||
<td colspan="10" class="px-6 py-12 text-center">
|
||||
<div class="flex flex-col items-center">
|
||||
<i class="fas fa-file-invoice 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(['layer'], function() {
|
||||
var layer = layui.layer;
|
||||
|
||||
// --- 筛选逻辑 ---
|
||||
document.getElementById('btnFilter').addEventListener('click', function() {
|
||||
var typeVal = document.getElementById('filterType').value;
|
||||
var statusVal = document.getElementById('filterStatus').value;
|
||||
var rows = document.querySelectorAll('.fund-row');
|
||||
rows.forEach(function(row) {
|
||||
var show = true;
|
||||
if (typeVal && row.dataset.type !== typeVal) show = false;
|
||||
if (statusVal && row.dataset.status !== statusVal) show = false;
|
||||
row.style.display = show ? '' : 'none';
|
||||
});
|
||||
});
|
||||
|
||||
// --- 审批通过 ---
|
||||
window.approveRequest = function(id) {
|
||||
layer.prompt({title: '审批通过 - 管理员备注(可留空)', formType: 2, value: ''}, function(remark, promptIndex) {
|
||||
layer.close(promptIndex);
|
||||
var loadIdx = layer.load(1);
|
||||
fetch('/admin/fund-requests/approve', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({id: id, admin_remark: remark})
|
||||
})
|
||||
.then(function(r){ return r.json(); })
|
||||
.then(function(d){
|
||||
layer.close(loadIdx);
|
||||
if(d.success){
|
||||
layer.msg('审批通过', {icon: 1});
|
||||
setTimeout(function(){ location.reload(); }, 800);
|
||||
} else {
|
||||
layer.msg(d.message || '操作失败', {icon: 2});
|
||||
}
|
||||
})
|
||||
.catch(function(){
|
||||
layer.close(loadIdx);
|
||||
layer.msg('网络错误', {icon: 2});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// --- 拒绝 ---
|
||||
window.rejectRequest = function(id) {
|
||||
layer.prompt({title: '拒绝原因(可留空)', formType: 2, value: ''}, function(remark, promptIndex) {
|
||||
layer.close(promptIndex);
|
||||
var loadIdx = layer.load(1);
|
||||
fetch('/admin/fund-requests/reject', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({id: id, admin_remark: remark})
|
||||
})
|
||||
.then(function(r){ return r.json(); })
|
||||
.then(function(d){
|
||||
layer.close(loadIdx);
|
||||
if(d.success){
|
||||
layer.msg('已拒绝', {icon: 1});
|
||||
setTimeout(function(){ location.reload(); }, 800);
|
||||
} else {
|
||||
layer.msg(d.message || '操作失败', {icon: 2});
|
||||
}
|
||||
})
|
||||
.catch(function(){
|
||||
layer.close(loadIdx);
|
||||
layer.msg('网络错误', {icon: 2});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// --- 语音提醒 + 标题闪烁轮询 ---
|
||||
var lastPendingCount = <?= (int)($pendingCount ?? 0) ?>;
|
||||
var originalTitle = document.title;
|
||||
setInterval(function() {
|
||||
fetch('/admin/fund-requests/pending-count')
|
||||
.then(function(r){ return r.json(); })
|
||||
.then(function(d){
|
||||
var count = d.count || 0;
|
||||
document.getElementById('statPending').textContent = count;
|
||||
if (count > lastPendingCount) {
|
||||
// 播放短促提示音
|
||||
try {
|
||||
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
||||
var osc = audioCtx.createOscillator();
|
||||
var gain = audioCtx.createGain();
|
||||
osc.connect(gain);
|
||||
gain.connect(audioCtx.destination);
|
||||
osc.frequency.value = 880;
|
||||
osc.type = 'sine';
|
||||
gain.gain.value = 0.3;
|
||||
osc.start();
|
||||
osc.stop(audioCtx.currentTime + 0.15);
|
||||
} catch(e){}
|
||||
// 标题闪烁
|
||||
document.title = '⚠️ 新充提申请(' + count + ')';
|
||||
setTimeout(function(){ document.title = originalTitle; }, 3000);
|
||||
}
|
||||
lastPendingCount = count;
|
||||
})
|
||||
.catch(function(){});
|
||||
}, 15000);
|
||||
});
|
||||
</script>
|
||||
@@ -36,6 +36,13 @@
|
||||
<a href="/admin/dashboard" class="flex items-center text-primary font-bold text-xl">
|
||||
<?php
|
||||
$adminSettings = \App\Core\SettingsHelper::getAll();
|
||||
// 查询充提待处理数量(侧边栏角标用)
|
||||
try {
|
||||
$_fundDb = new \Db\Database();
|
||||
$pendingFundCount = $_fundDb->count('fund_requests', ['status' => 'pending']);
|
||||
} catch (\Throwable $e) {
|
||||
$pendingFundCount = 0;
|
||||
}
|
||||
?>
|
||||
<img src="<?= htmlspecialchars($adminSettings['site_logo'] ?? '/Static/tm68/logo_tm68.png.png') ?>" class="w-10 mr-2">
|
||||
<span>管理后台</span>
|
||||
@@ -116,6 +123,11 @@
|
||||
<i class="fas fa-list-alt w-5 text-center"></i>
|
||||
<span>投注记录</span>
|
||||
</a>
|
||||
<a href="/admin/fund-requests" class="flex items-center gap-3 px-4 py-3 rounded-lg transition-all duration-200 hover:bg-gray-light main-menu-item relative">
|
||||
<i class="fas fa-file-invoice-dollar w-5 text-center"></i>
|
||||
<span>充提审核</span>
|
||||
<span id="fundRequestBadge" class="absolute right-2 top-1/2 -translate-y-1/2 bg-danger text-white text-xs rounded-full min-w-[20px] h-5 flex items-center justify-center px-1" style="<?= ((int)($pendingFundCount ?? 0)) > 0 ? '' : 'display:none' ?>"><?= (int)($pendingFundCount ?? 0) ?></span>
|
||||
</a>
|
||||
<a href="/admin/finance" class="flex items-center gap-3 px-4 py-3 rounded-lg transition-all duration-200 hover:bg-gray-light main-menu-item">
|
||||
<i class="fas fa-dollar-sign w-5 text-center"></i>
|
||||
<span>财务管理</span>
|
||||
@@ -136,6 +148,14 @@
|
||||
<i class="fas fa-chart-bar w-5 text-center"></i>
|
||||
<span>数据报表</span>
|
||||
</a>
|
||||
<a href="/admin/follow-plans" class="flex items-center gap-3 px-4 py-3 rounded-lg transition-all duration-200 hover:bg-gray-light main-menu-item">
|
||||
<i class="fas fa-clipboard-list w-5 text-center"></i>
|
||||
<span>跟单计划</span>
|
||||
</a>
|
||||
<a href="/admin/bots" class="flex items-center gap-3 px-4 py-3 rounded-lg transition-all duration-200 hover:bg-gray-light main-menu-item">
|
||||
<i class="fas fa-robot w-5 text-center"></i>
|
||||
<span>Bot 管理</span>
|
||||
</a>
|
||||
<a href="/admin/settings" class="flex items-center gap-3 px-4 py-3 rounded-lg transition-all duration-200 hover:bg-gray-light main-menu-item">
|
||||
<i class="fas fa-cog w-5 text-center"></i>
|
||||
<span>系统设置</span>
|
||||
@@ -265,7 +285,7 @@
|
||||
<footer class="bg-white border-t border-gray-200 py-4 md:ml-64 transition-all duration-300">
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex flex-col md:flex-row justify-between items-center">
|
||||
<p class="text-sm text-gray-500"> © <?=date('Y')?> PK10 极速赛车后台管理系统 </p>
|
||||
<p class="text-sm text-gray-500"> © <?=date('Y')?> F1赛车后台管理系统 </p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
@@ -274,6 +294,22 @@
|
||||
<script> const targetUsername = '<?= htmlspecialchars($_SESSION[' username '] ?? ' ') ?>'; </script>
|
||||
<script src="/Static/js/controller.js"> </script>
|
||||
<script src="/Static/js/admin.js"> </script>
|
||||
<script>
|
||||
// 全局轮询充提待处理角标
|
||||
(function(){
|
||||
var badge = document.getElementById('fundRequestBadge');
|
||||
if(!badge) return;
|
||||
setInterval(function(){
|
||||
fetch('/admin/fund-requests/pending-count')
|
||||
.then(function(r){return r.json();})
|
||||
.then(function(d){
|
||||
var c = d.count || 0;
|
||||
badge.textContent = c;
|
||||
badge.style.display = c > 0 ? '' : 'none';
|
||||
}).catch(function(){});
|
||||
}, 15000);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
+395
-52
@@ -1,60 +1,403 @@
|
||||
<div class="p-6">
|
||||
<h2 class="text-2xl font-bold mb-4">📊 数据报表</h2>
|
||||
<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-4 shadow mb-6 flex items-center gap-4 flex-wrap">
|
||||
<label class="text-sm text-gray-400">起始:</label><input type="date" value="<?=$dateFrom?>" id="dateFrom" class="border rounded px-3 py-2 text-sm">
|
||||
<label class="text-sm text-gray-400">截止:</label><input type="date" value="<?=$dateTo?>" id="dateTo" class="border rounded px-3 py-2 text-sm">
|
||||
<button onclick="location.href='/admin/reports?from='+document.getElementById('dateFrom').value+'&to='+document.getElementById('dateTo').value" class="px-4 py-2 bg-blue-500 text-white rounded text-sm">筛选</button>
|
||||
<!-- 日期筛选栏 -->
|
||||
<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 gap-4 mb-6">
|
||||
<div class="bg-white rounded-xl p-4 shadow"><div class="text-gray-400 text-xs">总投注</div><div class="text-xl font-bold text-blue-500"><?=number_format($totalBet,2)?></div></div>
|
||||
<div class="bg-white rounded-xl p-4 shadow"><div class="text-gray-400 text-xs">总中奖</div><div class="text-xl font-bold text-red-500"><?=number_format($totalWin,2)?></div></div>
|
||||
<div class="bg-white rounded-xl p-4 shadow"><div class="text-gray-400 text-xs">平台盈利</div><div class="text-xl font-bold <?=$profit>=0?'text-green-500':'text-red-500'?>"><?=number_format($profit,2)?></div></div>
|
||||
<div class="bg-white rounded-xl p-4 shadow"><div class="text-gray-400 text-xs">佣金</div><div class="text-xl font-bold text-orange-500"><?=number_format($totalCommission,2)?></div></div>
|
||||
<div class="bg-white rounded-xl p-4 shadow"><div class="text-gray-400 text-xs">总充值</div><div class="text-xl font-bold text-green-500"><?=number_format($totalDeposit,2)?></div></div>
|
||||
<div class="bg-white rounded-xl p-4 shadow"><div class="text-gray-400 text-xs">总提现</div><div class="text-xl font-bold text-red-500"><?=number_format($totalWithdraw,2)?></div></div>
|
||||
<div class="bg-white rounded-xl p-4 shadow"><div class="text-gray-400 text-xs">总用户</div><div class="text-xl font-bold"><?=$totalUsers?></div></div>
|
||||
<div class="bg-white rounded-xl p-4 shadow"><div class="text-gray-400 text-xs">新增用户</div><div class="text-xl font-bold text-blue-500"><?=$newUsers?></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 overflow-hidden mb-6">
|
||||
<h3 class="font-bold p-4 border-b">每日明细</h3>
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-gray-50"><tr><th class="px-4 py-2 text-left">日期</th><th class="px-4 py-2">投注</th><th class="px-4 py-2">中奖</th><th class="px-4 py-2">盈利</th><th class="px-4 py-2">充值</th><th class="px-4 py-2">提现</th></tr></thead>
|
||||
<tbody>
|
||||
<?php foreach($dailyStats??[] as $d): $dp=$d['bets']-$d['wins']; ?>
|
||||
<tr class="border-t hover:bg-gray-50">
|
||||
<td class="px-4 py-2"><?=$d['date']?></td>
|
||||
<td class="px-4 py-2 text-center"><?=number_format($d['bets'],2)?></td>
|
||||
<td class="px-4 py-2 text-center"><?=number_format($d['wins'],2)?></td>
|
||||
<td class="px-4 py-2 text-center <?=$dp>=0?'text-green-500':'text-red-500'?>"><?=number_format($dp,2)?></td>
|
||||
<td class="px-4 py-2 text-center"><?=number_format($d['deposits'],2)?></td>
|
||||
<td class="px-4 py-2 text-center"><?=number_format($d['withdraws'],2)?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody></table></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 overflow-hidden">
|
||||
<h3 class="font-bold p-4 border-b">代理报表</h3>
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-gray-50"><tr><th class="px-4 py-2 text-left">代理</th><th class="px-4 py-2">玩家数</th><th class="px-4 py-2">投注</th><th class="px-4 py-2">中奖</th><th class="px-4 py-2">佣金</th><th class="px-4 py-2">盈利</th></tr></thead>
|
||||
<tbody>
|
||||
<?php foreach($agentStats as $as): ?>
|
||||
<tr class="border-t hover:bg-gray-50">
|
||||
<td class="px-4 py-2"><?=htmlspecialchars($as['user']['username']??'')?></td>
|
||||
<td class="px-4 py-2 text-center"><?=$as['players']?></td>
|
||||
<td class="px-4 py-2 text-center"><?=number_format($as['bets'],2)?></td>
|
||||
<td class="px-4 py-2 text-center"><?=number_format($as['wins'],2)?></td>
|
||||
<td class="px-4 py-2 text-center text-orange-500"><?=number_format($as['commission'],2)?></td>
|
||||
<td class="px-4 py-2 text-center <?=$as['profit']>=0?'text-green-500':'text-red-500'?>"><?=number_format($as['profit'],2)?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody></table></div>
|
||||
<?php endif; ?>
|
||||
<?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>
|
||||
|
||||
@@ -99,6 +99,24 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 客服设置 -->
|
||||
<div class="bg-white rounded-xl shadow-md p-6 mb-6">
|
||||
<h3 class="text-lg font-semibold text-gray-800 mb-4 flex items-center">
|
||||
<i class="fas fa-headset text-primary mr-2"></i>
|
||||
客服设置
|
||||
</h3>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label for="customer_service_url" class="block text-sm font-medium text-gray-700 mb-1">客服链接</label>
|
||||
<input type="text" id="customer_service_url" name="customer_service_url"
|
||||
value="<?= htmlspecialchars($settings['customer_service_url'] ?? '') ?>"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary/50 focus:border-primary transition-colors"
|
||||
placeholder="请输入客服链接,如 https://t.me/your_support">
|
||||
<p class="text-xs text-gray-500 mt-1">用户充值时将跳转至此链接联系客服,支持 Telegram、WhatsApp 等链接</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="flex justify-end gap-3 pt-4">
|
||||
<button type="button" onclick="resetSettings()"
|
||||
|
||||
@@ -2,6 +2,19 @@
|
||||
<h2 class="text-2xl font-bold mb-2">🌊 放水控制 & 投注限额</h2>
|
||||
<p class="text-gray-500 text-sm mb-6">放水 = 控制玩家胜率。百分比越低,平台赢得越多。设为50%表示公平对赌。</p>
|
||||
|
||||
<!-- 平台盈利率目标 -->
|
||||
<div class="bg-white rounded-xl p-4 shadow mb-6">
|
||||
<h3 class="font-bold mb-2">🎯 平台盈利率目标</h3>
|
||||
<div style="display:flex;align-items:center;gap:15px;flex-wrap:wrap">
|
||||
<span>目标盈利率:</span>
|
||||
<input type="number" id="targetProfitRate" value="<?=htmlspecialchars($targetProfitRate ?? '15')?>"
|
||||
min="0" max="100" step="1" class="w-24 border rounded px-2 py-1 text-sm text-center">
|
||||
<span>%</span>
|
||||
<button onclick="saveProfitRate()" class="px-4 py-1 bg-purple-500 text-white rounded hover:bg-purple-600 text-sm">保存</button>
|
||||
<span style="color:#999;font-size:12px">说明:0=取最大利润(原逻辑),15=平台每期目标盈利15%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
// 投注类型中文映射
|
||||
$betTypeLabels = [
|
||||
@@ -87,6 +100,12 @@ function getBetLabel($type, $labels) {
|
||||
</div>
|
||||
|
||||
<script>
|
||||
async function saveProfitRate(){
|
||||
const rate=parseFloat(document.getElementById('targetProfitRate').value);
|
||||
if(isNaN(rate)||rate<0||rate>100){alert('请输入0-100之间的数值');return;}
|
||||
const r=await fetch('/admin/water/profit-rate',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({target_profit_rate:rate})});
|
||||
const d=await r.json();alert(d.status==='success'?'盈利率目标保存成功!':d.message);
|
||||
}
|
||||
async function saveWater(){
|
||||
const items=[];
|
||||
document.querySelectorAll('.water-pct').forEach(el=>{
|
||||
|
||||
Reference in New Issue
Block a user