- 拆分 HomeController → TransactionController, ReportWebController, FollowPlanController - 新增 Service 层: TransactionService, ReportService, FollowPlanService - pk10.php JS 抽离为 4 个独立文件 (sound/race/bet/poll) - 前台新增报表查询页面 (/report) + 跟单计划页面 (/follow-plan) - 后台新增跟单计划管理 + 用户输赢明细统计 - 封盘状态显示倒计时 (x:xx) - 音效仅在开奖弹窗打开时播放 - 路由按模块分组整理 - autoload 支持 App\Services 命名空间
307 lines
16 KiB
PHP
307 lines
16 KiB
PHP
<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>
|