Files
pk10/App/Views/Web/pk10_stats.php
T
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

166 lines
10 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('statistics')?></title>
<link rel="stylesheet" href="/Static/css/app.css">
<style>
.stats-header{background:#fff;padding:12px 15px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid #F0F0F0;position:sticky;top:0;z-index:10}
.stats-header .back{background:none;border:none;font-size:20px;color:#333;cursor:pointer;padding:0 8px 0 0}
.stats-tabs{display:flex;background:#fff;border-bottom:1px solid #F0F0F0}
.stats-tab{flex:1;text-align:center;padding:14px 0;font-size:15px;color:#666;cursor:pointer;position:relative;background:none;border:none}
.stats-tab.active{color:#333;font-weight:600}
.stats-tab.active::after{content:'';position:absolute;bottom:0;left:30%;right:30%;height:2px;background:var(--primary)}
.filter-card{background:#fff;margin:10px;border-radius:12px;padding:15px;box-shadow:0 1px 6px rgba(0,0,0,.06)}
.filter-title{text-align:center;color:var(--primary);font-size:14px;font-weight:600;margin-bottom:10px}
.tag-row{display:flex;flex-wrap:wrap;gap:8px;justify-content:center}
.tag{padding:6px 14px;border:1px solid #ddd;border-radius:20px;font-size:13px;color:#666;cursor:pointer;background:#fff;transition:all .2s}
.tag.active{border-color:var(--primary);color:var(--primary);background:#F0F8FF}
.ball-grid{display:flex;justify-content:center;gap:6px;flex-wrap:wrap;margin:15px 0}
.ball-col{display:flex;flex-direction:column;align-items:center;gap:4px}
.ball-col .count{font-size:11px;color:#999}
.bead-grid{display:grid;grid-template-columns:repeat(10,1fr);gap:2px;margin:10px;font-size:12px;text-align:center}
.bead-grid .cell{padding:6px 2px;color:#666;border-bottom:1px dotted #eee}
/* PLACEHOLDER_STREAK */
.streak-table{width:100%;border-collapse:collapse;margin:10px}
.streak-table th{background:var(--primary);color:#fff;padding:10px;font-size:14px}
.streak-table td{padding:10px 12px;border-bottom:1px solid #F0F0F0;font-size:14px;text-align:center}
.streak-table tr:nth-child(even){background:#FAFAFA}
.streak-badge{display:inline-block;padding:3px 10px;border-radius:4px;color:#fff;font-size:12px;font-weight:600}
</style>
</head>
<body class="pb-nav" style="background:#F5F5F5">
<div class="stats-header">
<button class="back" onclick="history.back()"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#333" stroke-width="2"><polyline points="15 18 9 12 15 6"/></svg></button>
<span style="font-weight:700;font-size:16px"><?=$t('statistics')?><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="vertical-align:middle;margin-left:4px"><line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/></svg></span>
<span style="width:28px"></span>
</div>
<div class="stats-tabs">
<button class="stats-tab active" onclick="showStatsTab('bead',this)"><?=$t('road_bead')?></button>
<button class="stats-tab" onclick="showStatsTab('streak',this)"><?=$t('two_side_streak')?></button>
</div>
<!-- 路珠 Tab -->
<div id="tabBead">
<?php
$rankLabels=[];
for($i=1;$i<=10;$i++) $rankLabels[$i]=$i<=2?($i==1?$t('champion'):$t('runner_up')):$t('rank_n',['n'=>$i]);
?>
<div class="filter-card">
<div class="filter-title"><?=$t('filter_rank')?></div>
<div class="tag-row" id="rankTags">
<?php for($i=1;$i<=10;$i++): ?>
<span class="tag <?=$i==1?'active':''?>" onclick="filterRank(<?=$i?>,this)"><?=$rankLabels[$i]?></span>
<?php endfor; ?>
</div>
</div>
<div class="filter-card">
<div class="ball-grid" id="ballStats"></div>
</div>
<div class="filter-card">
<div class="filter-title"><?=$t('filter_bead')?></div>
<div class="tag-row" id="beadTags">
<span class="tag active" onclick="filterBead('rank',this)"><?=$t('champion')?></span>
<span class="tag" onclick="filterBead('bs',this)"><?=$t('big')?>/<?=$t('small')?></span>
<span class="tag" onclick="filterBead('oe',this)"><?=$t('odd')?>/<?=$t('even')?></span>
<span class="tag" onclick="filterBead('sum',this)"><?=$t('sum')?></span>
<span class="tag" onclick="filterBead('sum_bs',this)"><?=$t('sum_bs_big')?>/<?=$t('sum_bs_small')?></span>
<span class="tag" onclick="filterBead('sum_oe',this)"><?=$t('sum_bs_odd')?>/<?=$t('sum_bs_even')?></span>
</div>
<div class="bead-grid" id="beadGrid"></div>
</div>
</div>
<!-- 两面长龙 Tab -->
<div id="tabStreak" class="hidden">
<div style="margin:10px;background:#fff;border-radius:12px;overflow:hidden;box-shadow:0 1px 6px rgba(0,0,0,.06)">
<table class="streak-table">
<thead><tr><th><?=$t('rank_number')?></th><th><?=$t('periods_count')?></th></tr></thead>
<tbody id="streakBody"></tbody>
</table>
</div>
</div>
<?php $navActive='game'; include __DIR__.'/_nav.php'; ?>
<script>
const DATA=<?=json_encode(array_values($results??[]))?>;
const COLORS=['','#f1c40f','#3498db','#555555','#e67e22','#2ecc71','#2c3e99','#999999','#e74c3c','#cc2222','#27ae60'];
const rankLabels=<?=json_encode(array_values($rankLabels))?>;
const I18N={big:'<?=$t('big')?>',small:'<?=$t('small')?>',odd:'<?=$t('odd')?>',even:'<?=$t('even')?>',dragon:'<?=$t('dragon')?>',tiger:'<?=$t('tiger')?>',sum_big:'<?=$t('sum_bs_big')?>',sum_small:'<?=$t('sum_bs_small')?>',sum_odd:'<?=$t('sum_bs_odd')?>',sum_even:'<?=$t('sum_bs_even')?>'};
let curRank=1,curBead='rank';
function showStatsTab(id,btn){
document.getElementById('tabBead').classList.toggle('hidden',id!=='bead');
document.getElementById('tabStreak').classList.toggle('hidden',id!=='streak');
document.querySelectorAll('.stats-tab').forEach(t=>t.classList.remove('active'));
btn.classList.add('active');
if(id==='streak')renderStreak();
}
function filterRank(r,el){curRank=r;document.querySelectorAll('#rankTags .tag').forEach(t=>t.classList.remove('active'));el.classList.add('active');renderBalls();renderBead();}
function filterBead(type,el){curBead=type;document.querySelectorAll('#beadTags .tag').forEach(t=>t.classList.remove('active'));el.classList.add('active');renderBead();}
function renderBalls(){
const counts={};for(let i=1;i<=10;i++)counts[i]=0;
DATA.forEach(r=>{const v=+r['rank_'+curRank];if(v)counts[v]++;});
const el=document.getElementById('ballStats');
let h='';for(let i=1;i<=10;i++){h+='<div class="ball-col"><div class="car c'+i+'" style="width:32px;height:32px;font-size:13px">'+i+'</div><div class="count">'+counts[i]+'</div></div>';}
el.innerHTML=h;
}
function renderBead(){
const grid=document.getElementById('beadGrid');
const rows=DATA.slice(0,20);// last 20 periods, 10 cols each
let h='';
rows.forEach(r=>{
for(let c=1;c<=10;c++){
let val='';
if(curBead==='rank'){val=r['rank_'+c]||'';h+='<div class="cell" style="color:'+COLORS[+val||0]+'">'+val+'</div>';}
else if(curBead==='bs'){const v=+r['rank_'+c];val=v>5?I18N.big:I18N.small;h+='<div class="cell">'+val+'</div>';}
else if(curBead==='oe'){const v=+r['rank_'+c];val=v%2?I18N.odd:I18N.even;h+='<div class="cell">'+val+'</div>';}
else if(curBead==='sum'){const s=(+r.rank_1)+(+r.rank_2);h+='<div class="cell" style="grid-column:span 10;font-weight:600">'+s+'</div>';break;}
else if(curBead==='sum_bs'){const s=(+r.rank_1)+(+r.rank_2);val=s>=12?I18N.sum_big:I18N.sum_small;h+='<div class="cell" style="grid-column:span 10">'+val+'</div>';break;}
else if(curBead==='sum_oe'){const s=(+r.rank_1)+(+r.rank_2);val=s%2?I18N.sum_odd:I18N.sum_even;h+='<div class="cell" style="grid-column:span 10">'+val+'</div>';break;}
}
});
grid.innerHTML=h;
}
function renderStreak(){
// 计算两面长龙:每个名次的大小/单双/龙虎连续出现次数
const streaks=[];
for(let r=1;r<=10;r++){
if(DATA.length<2)continue;
const v0=+DATA[0]['rank_'+r];
// 大小
const bs0=v0>5?'big':'small';let bsCnt=1;
for(let i=1;i<DATA.length;i++){const v=+DATA[i]['rank_'+r];if((v>5?'big':'small')===bs0)bsCnt++;else break;}
if(bsCnt>=2)streaks.push({rank:rankLabels[r-1],type:bs0==='big'?I18N.big:I18N.small,color:bs0==='big'?'#e74c3c':'#3498db',count:bsCnt});
// 单双
const oe0=v0%2?'odd':'even';let oeCnt=1;
for(let i=1;i<DATA.length;i++){const v=+DATA[i]['rank_'+r];if((v%2?'odd':'even')===oe0)oeCnt++;else break;}
if(oeCnt>=2)streaks.push({rank:rankLabels[r-1],type:oe0==='odd'?I18N.odd:I18N.even,color:oe0==='odd'?'#f39c12':'#9b59b6',count:oeCnt});
// 龙虎 (rank 1-5)
if(r<=5){const a=+DATA[0]['rank_'+r],b=+DATA[0]['rank_'+(11-r)];const dt0=a>b?'dragon':'tiger';let dtCnt=1;
for(let i=1;i<DATA.length;i++){const a2=+DATA[i]['rank_'+r],b2=+DATA[i]['rank_'+(11-r)];if((a2>b2?'dragon':'tiger')===dt0)dtCnt++;else break;}
if(dtCnt>=2)streaks.push({rank:rankLabels[r-1],type:dt0==='dragon'?I18N.dragon:I18N.tiger,color:dt0==='dragon'?'#e74c3c':'#3498db',count:dtCnt});}
}
// 冠亚和大小单双
if(DATA.length>=2){
const s0=(+DATA[0].rank_1)+(+DATA[0].rank_2);
const sbs0=s0>=12?'big':'small';let sbsCnt=1;
for(let i=1;i<DATA.length;i++){const s=(+DATA[i].rank_1)+(+DATA[i].rank_2);if((s>=12?'big':'small')===sbs0)sbsCnt++;else break;}
if(sbsCnt>=2)streaks.push({rank:I18N.sum_big.replace(I18N.big,'').trim()||'<?=$t('sum')?>',type:sbs0==='big'?I18N.sum_big:I18N.sum_small,color:sbs0==='big'?'#e74c3c':'#3498db',count:sbsCnt});
const soe0=s0%2?'odd':'even';let soeCnt=1;
for(let i=1;i<DATA.length;i++){const s=(+DATA[i].rank_1)+(+DATA[i].rank_2);if((s%2?'odd':'even')===soe0)soeCnt++;else break;}
if(soeCnt>=2)streaks.push({rank:'<?=$t('sum_bs_tab')?>',type:soe0==='odd'?I18N.sum_odd:I18N.sum_even,color:soe0==='odd'?'#f39c12':'#9b59b6',count:soeCnt});
}
streaks.sort((a,b)=>b.count-a.count);
const body=document.getElementById('streakBody');
body.innerHTML=streaks.map(s=>'<tr><td style="text-align:left;color:var(--primary)">'+s.rank+' <span class="streak-badge" style="background:'+s.color+'">'+s.type+'</span></td><td style="font-weight:600">'+s.count+'</td></tr>').join('');
}
renderBalls();renderBead();
</script>
</body></html>