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

- 拆分 HomeController → TransactionController, ReportWebController, FollowPlanController
- 新增 Service 层: TransactionService, ReportService, FollowPlanService
- pk10.php JS 抽离为 4 个独立文件 (sound/race/bet/poll)
- 前台新增报表查询页面 (/report) + 跟单计划页面 (/follow-plan)
- 后台新增跟单计划管理 + 用户输赢明细统计
- 封盘状态显示倒计时 (x:xx)
- 音效仅在开奖弹窗打开时播放
- 路由按模块分组整理
- autoload 支持 App\Services 命名空间
This commit is contained in:
li
2026-03-27 18:41:06 +08:00
parent 48cfe4be04
commit 4a94fe36cf
87 changed files with 8681 additions and 646 deletions
+84 -3
View File
@@ -49,15 +49,15 @@
<div class="wallet-header">
<div class="wallet-title"><?=$t('balance')?></div>
<div class="wallet-actions">
<button class="action-btn" onclick="alert('<?=$t('deposit')?>')">
<button class="action-btn" onclick="doDeposit()">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#1E90FF" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><polyline points="19 12 12 19 5 12"/></svg>
<span><?=$t('deposit')?></span>
</button>
<button class="action-btn" onclick="alert('<?=$t('withdraw')?>')">
<button class="action-btn" onclick="doWithdraw()">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#1E90FF" stroke-width="2"><line x1="12" y1="19" x2="12" y2="5"/><polyline points="5 12 12 5 19 12"/></svg>
<span><?=$t('withdraw')?></span>
</button>
<button class="action-btn" onclick="alert('<?=$t('transfer')?>')">
<button class="action-btn" onclick="doTransfer()">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#1E90FF" stroke-width="2"><polyline points="17 1 21 5 17 9"/><path d="M3 11V9a4 4 0 0 1 4-4h14"/><polyline points="7 23 3 19 7 15"/><path d="M21 13v2a4 4 0 0 1-4 4H3"/></svg>
<span><?=$t('transfer')?></span>
</button>
@@ -81,6 +81,21 @@
</div>
</div>
<!-- 报表与跟单 -->
<div class="section">
<div class="section-title"><?=$t('bet_history')?></div>
<a href="/report" class="menu-item" style="text-decoration:none">
<div class="menu-icon"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#1E90FF" stroke-width="2"><path d="M21 12V7H5a2 2 0 0 1 0-4h14v4"/><path d="M3 5v14a2 2 0 0 0 2 2h16v-5"/><path d="M18 12a2 2 0 0 0 0 4h4v-4z"/></svg></div>
<div class="menu-text"><?=$t('report_query')?></div>
<div class="menu-arrow"></div>
</a>
<a href="/follow-plan" class="menu-item" style="text-decoration:none">
<div class="menu-icon"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#1E90FF" stroke-width="2"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><rect x="8" y="2" width="8" height="4" rx="1" ry="1"/><path d="M9 14l2 2 4-4"/></svg></div>
<div class="menu-text"><?=$t('follow_plan')?></div>
<div class="menu-arrow"></div>
</a>
</div>
<!-- 投注记录 -->
<div class="section">
<div class="section-title"><?=$t('bet_history')?></div>
@@ -111,6 +126,25 @@
<?php endforeach; endif; ?>
</div>
<!-- 充提记录 -->
<div class="section">
<div class="section-title"><?=$t('fund_request_history')?></div>
<?php if(empty($fundRequests)): ?>
<div style="text-align:center;color:#999;padding:40px 20px;font-size:14px"><?=$t('no_data')?></div>
<?php else: foreach($fundRequests as $fr): $isDeposit=$fr['type']==='deposit'; ?>
<div class="menu-item" style="cursor:default">
<div style="flex:1">
<div style="font-size:14px;color:#333"><?=$isDeposit?$t('deposit'):$t('withdraw')?></div>
<div style="font-size:11px;color:#999;margin-top:2px"><?=$fr['created_at']?></div>
</div>
<div style="text-align:right">
<div style="font-size:15px;font-weight:600;color:<?=$isDeposit?'#4CAF50':'#FF4444'?>"><?=$isDeposit?'+':'-'?><?=number_format($fr['amount'],2)?></div>
<span style="font-size:12px;color:<?=$fr['status']==='approved'?'#4CAF50':($fr['status']==='rejected'?'#FF4444':'#FF9800')?>"><?=$t('fund_status_'.$fr['status'])?></span>
</div>
</div>
<?php endforeach; endif; ?>
</div>
<!-- 交易记录 -->
<div class="section">
<div class="section-title"><?=$t('transaction_history')?></div>
@@ -143,5 +177,52 @@
<script>
async function bindUsdt(){const addr=document.getElementById('usdtAddr').value.trim();const r=await fetch('/api/bind-usdt',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({usdt_address:addr})});const d=await r.json();alert(d.message);if(d.success)location.reload();}
function doDeposit(){
var url='<?=\App\Core\SettingsHelper::get("customer_service_url")?>';
if(!url){alert('<?=$t("contact_admin")?>');return;}
var amount=prompt('<?=$t("deposit_amount")?>');
if(!amount||isNaN(amount)||parseFloat(amount)<=0)return;
fetch('/api/fund-request',{
method:'POST',
headers:{'Content-Type':'application/json'},
body:JSON.stringify({type:'deposit',amount:parseFloat(amount)})
}).then(function(r){return r.json();}).then(function(d){
if(d.success){
alert('<?=$t("deposit_submitted")?>');
window.open(url,'_blank');
location.reload();
} else {
alert(d.message||'<?=$t("error")?>');
}
});
}
function doWithdraw(){
var amount=prompt('<?=$t("withdraw_amount")?>');
if(!amount||isNaN(amount)||parseFloat(amount)<=0)return;
fetch('/api/fund-request',{
method:'POST',
headers:{'Content-Type':'application/json'},
body:JSON.stringify({type:'withdraw',amount:parseFloat(amount)})
}).then(function(r){return r.json();}).then(function(d){
if(d.success){alert('<?=$t("withdraw_submitted")?>');location.reload();}
else{alert(d.message||'<?=$t("error")?>');}
});
}
function doTransfer(){
var to=prompt('<?=$t("transfer_to")?>');
if(!to||!to.trim())return;
var amount=prompt('<?=$t("transfer_amount")?>');
if(!amount||isNaN(amount)||parseFloat(amount)<=0)return;
if(!confirm('<?=$t("transfer_confirm")?> '+to+' , <?=$t("amount")?>: '+amount))return;
fetch('/api/transfer',{
method:'POST',headers:{'Content-Type':'application/json'},
body:JSON.stringify({to_username:to.trim(),amount:parseFloat(amount)})
}).then(function(r){return r.json();}).then(function(d){
if(d.success){alert(d.message||'<?=$t("transfer_success")?>');location.reload();}
else{alert(d.message||'<?=$t("error")?>');}
});
}
</script>
</body></html>