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