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