Files
li 4a94fe36cf feat: 重构代码架构 + 新增报表/跟单/输赢统计功能
- 拆分 HomeController → TransactionController, ReportWebController, FollowPlanController
- 新增 Service 层: TransactionService, ReportService, FollowPlanService
- pk10.php JS 抽离为 4 个独立文件 (sound/race/bet/poll)
- 前台新增报表查询页面 (/report) + 跟单计划页面 (/follow-plan)
- 后台新增跟单计划管理 + 用户输赢明细统计
- 封盘状态显示倒计时 (x:xx)
- 音效仅在开奖弹窗打开时播放
- 路由按模块分组整理
- autoload 支持 App\Services 命名空间
2026-03-27 18:41:06 +08:00

229 lines
13 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php use App\Core\I18n; I18n::init(); $t = function($k,$p=[]){return I18n::t($k,$p);}; ?>
<!DOCTYPE html><html lang="<?=I18n::getLang()?>">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title><?=$t('my_profile')?></title>
<link rel="stylesheet" href="/Static/css/app.css">
<style>
.header-bg{background:linear-gradient(135deg,#E8F4FF,#D0E8FF);padding:15px 15px 80px}
.top-toolbar{display:flex;justify-content:flex-end;gap:20px;margin-bottom:30px}
.top-toolbar .icon{cursor:pointer;opacity:.9}
.top-toolbar .icon:hover{opacity:1}
.user-info{display:flex;align-items:center;gap:15px}
.avatar-circle{width:60px;height:60px;border-radius:50%;border:3px solid #fff;box-shadow:0 2px 8px rgba(0,0,0,.1);background:#c8dff5;display:flex;align-items:center;justify-content:center}
.avatar-circle svg{width:32px;height:32px;color:#7ab0e0}
.username{flex:1;font-size:18px;font-weight:600;color:#333;display:flex;align-items:center;gap:5px}
.wallet-card{background:#fff;margin:-60px 15px 15px;padding:20px;border-radius:16px;box-shadow:0 2px 12px rgba(0,0,0,.08);position:relative;z-index:2}
.wallet-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:15px}
.wallet-title{font-size:16px;font-weight:600;color:#333}
.wallet-actions{display:flex;gap:20px}
.action-btn{display:flex;flex-direction:column;align-items:center;gap:4px;cursor:pointer;background:none;border:none;padding:0}
.action-btn span{font-size:12px;color:#1E90FF}
.balance-num{font-size:32px;font-weight:700;color:#333;margin:10px 0}
.section{background:#fff;margin:0 15px 15px;border-radius:12px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,.06)}
.section-title{font-size:16px;font-weight:600;color:#333;padding:15px 20px;border-bottom:1px solid #F5F5F5}
.menu-item{display:flex;align-items:center;padding:16px 20px;border-bottom:1px solid #F5F5F5;cursor:pointer;transition:background .2s}
.menu-item:last-child{border-bottom:none}
.menu-item:active{background:#F8F8F8}
.menu-icon{margin-right:15px;display:flex}
.menu-text{flex:1;font-size:15px;color:#333}
.menu-arrow{font-size:20px;color:#CCC}
</style>
</head>
<body style="background:#F5F5F5;padding-bottom:60px">
<!-- 顶部背景 -->
<div class="header-bg">
<div class="top-toolbar">
<a href="/logout" class="icon" title="<?=$t('logout')?>">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#666" stroke-width="2"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>
</a>
</div>
<div class="user-info">
<div class="avatar-circle"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg></div>
<div class="username"><?=htmlspecialchars($user['username']??'')?><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#999" stroke-width="3"><polyline points="9 18 15 12 9 6"/></svg></div>
</div>
</div>
<!-- 钱包卡片 -->
<div class="wallet-card">
<div class="wallet-header">
<div class="wallet-title"><?=$t('balance')?></div>
<div class="wallet-actions">
<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="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="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>
</div>
</div>
<div class="balance-num"><?=number_format($user['balance']??0,2)?></div>
</div>
<!-- USDT绑定 -->
<div class="section">
<div class="section-title"><?=$t('usdt_address')?></div>
<div style="padding:16px 20px">
<?php if(!empty($user['usdt_address'])): ?>
<div style="background:#F0FFF0;border-radius:8px;padding:10px;font-size:13px;font-family:monospace;color:#4CAF50;word-break:break-all"><?=htmlspecialchars($user['usdt_address'])?> (<?=$user['usdt_chain']?>)</div>
<?php else: ?>
<div style="display:flex;gap:8px">
<input type="text" id="usdtAddr" placeholder="T..." class="input" style="flex:1;font-size:13px">
<button onclick="bindUsdt()" class="btn btn-primary btn-sm"><?=$t('bind_address')?></button>
</div>
<?php endif; ?>
</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>
<?php
function formatBetDisplay($betType, $betValue, $t) {
if ($betType==='rank'&&preg_match('/^rank(\d+)_(\d+)$/',$betValue,$m)){$r=(int)$m[1];$rl=$r===1?$t('champion'):($r===2?$t('runner_up'):$t('rank_n',['n'=>$r]));return $rl.' #'.$m[2];}
if ($betType==='bs'&&preg_match('/^rank(\d+)_(big|small)$/',$betValue,$m)){$r=(int)$m[1];$rl=$r===1?$t('champion'):($r===2?$t('runner_up'):$t('rank_n',['n'=>$r]));return $rl.' '.($m[2]==='big'?$t('big'):$t('small'));}
if ($betType==='oe'&&preg_match('/^rank(\d+)_(odd|even)$/',$betValue,$m)){$r=(int)$m[1];$rl=$r===1?$t('champion'):($r===2?$t('runner_up'):$t('rank_n',['n'=>$r]));return $rl.' '.($m[2]==='odd'?$t('odd'):$t('even'));}
if ($betType==='dt'&&preg_match('/^dt(\d+)_(dragon|tiger)$/',$betValue,$m)){$ps=[1=>[1,10],2=>[2,9],3=>[3,8],4=>[4,7],5=>[5,6]];$p=$ps[(int)$m[1]]??[0,0];return $t('rank_n',['n'=>$p[0]]).'vs'.$t('rank_n',['n'=>$p[1]]).' '.($m[2]==='dragon'?$t('dragon'):$t('tiger'));}
if ($betType==='sum'&&preg_match('/^sum_(\d+)$/',$betValue,$m))return $t('sum').' '.$m[1];
if ($betType==='sum_bs'){$map=['sum_big'=>$t('sum_big'),'sum_small'=>$t('sum_small'),'sum_odd'=>$t('sum_odd'),'sum_even'=>$t('sum_even')];return $map[$betValue]??$betValue;}
return $betType.':'.$betValue;
}
?>
<?php if(empty($bets)): ?>
<div style="text-align:center;color:#999;padding:40px 20px;font-size:14px"><?=$t('no_data')?></div>
<?php else: foreach($bets as $b): ?>
<div class="menu-item" style="cursor:default">
<div style="flex:1;min-width:0">
<div style="font-size:14px;color:#333"><?=htmlspecialchars(formatBetDisplay($b['bet_type'],$b['bet_value'],$t))?></div>
<div style="font-size:11px;color:#999;margin-top:2px"><?=$b['period_number']?> · <?=$b['created_at']?></div>
</div>
<div style="text-align:right">
<div style="font-size:14px;color:#333"><?=number_format($b['amount'],2)?> x<?=$b['odds']?></div>
<span style="font-size:12px;color:<?=$b['status']==='win'?'#4CAF50':($b['status']==='lose'?'#FF4444':'#FF9800')?>;font-weight:600"><?=$t($b['status'])?></span>
</div>
</div>
<?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>
<?php if(empty($transactions)): ?>
<div style="text-align:center;color:#999;padding:40px 20px;font-size:14px"><?=$t('no_data')?></div>
<?php else: foreach($transactions as $tx): $isPos=(float)$tx['amount']>0; ?>
<div class="menu-item" style="cursor:default">
<div style="flex:1"><div style="font-size:14px;color:#333"><?=$tx['type']?></div><div style="font-size:11px;color:#999;margin-top:2px"><?=$tx['created_at']?></div></div>
<span style="font-size:15px;font-weight:600;color:<?=$isPos?'#4CAF50':'#FF4444'?>"><?=$isPos?'+':''?><?=number_format($tx['amount'],2)?></span>
</div>
<?php endforeach; endif; ?>
</div>
<!-- 其他 -->
<div class="section">
<div class="section-title"><?=$t('other')?></div>
<a href="/details" class="menu-item" style="text-decoration:none">
<div class="menu-icon"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#666" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg></div>
<div class="menu-text"><?=$t('bet_details')?></div>
<div class="menu-arrow"></div>
</a>
<a href="/lottery" class="menu-item" style="text-decoration:none">
<div class="menu-icon"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#666" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg></div>
<div class="menu-text"><?=$t('lottery_history')?></div>
<div class="menu-arrow"></div>
</a>
</div>
<?php $navActive='profile'; include __DIR__.'/_nav.php'; ?>
<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>