- 拆分 HomeController → TransactionController, ReportWebController, FollowPlanController - 新增 Service 层: TransactionService, ReportService, FollowPlanService - pk10.php JS 抽离为 4 个独立文件 (sound/race/bet/poll) - 前台新增报表查询页面 (/report) + 跟单计划页面 (/follow-plan) - 后台新增跟单计划管理 + 用户输赢明细统计 - 封盘状态显示倒计时 (x:xx) - 音效仅在开奖弹窗打开时播放 - 路由按模块分组整理 - autoload 支持 App\Services 命名空间
185 lines
14 KiB
JavaScript
185 lines
14 KiB
JavaScript
/**
|
|
* PK10 Betting Logic
|
|
* Depends on: pk10-sound.js, pk10-race.js
|
|
* Globals expected: GAME_ID, selectedChip, bets, periodData, historyCache,
|
|
* rankNames, dtPairs, I18N, curMissRank
|
|
*/
|
|
|
|
function formatBetLabel(type,value){
|
|
var m;
|
|
if(type==='rank'&&(m=value.match(/^rank(\d+)_(\d+)$/))){return(rankNames[+m[1]]||I18N.rank_prefix+m[1])+' #'+m[2];}
|
|
if(type==='bs'&&(m=value.match(/^rank(\d+)_(big|small)$/))){return(rankNames[+m[1]]||I18N.rank_prefix+m[1])+' '+(m[2]==='big'?I18N.big:I18N.small);}
|
|
if(type==='oe'&&(m=value.match(/^rank(\d+)_(odd|even)$/))){return(rankNames[+m[1]]||I18N.rank_prefix+m[1])+' '+(m[2]==='odd'?I18N.odd:I18N.even);}
|
|
if(type==='dt'&&(m=value.match(/^dt(\d+)_(dragon|tiger)$/))){var p=dtPairs[+m[1]]||[0,0];return(rankNames[p[0]]||p[0])+'vs'+(rankNames[p[1]]||p[1])+' '+(m[2]==='dragon'?I18N.dragon:I18N.tiger);}
|
|
if(type==='sum'&&(m=value.match(/^sum_(\d+)$/))){return I18N.sum+' '+m[1];}
|
|
if(type==='sum_bs'){var sm={sum_big:I18N.sum_big,sum_small:I18N.sum_small,sum_odd:I18N.sum_odd,sum_even:I18N.sum_even};return sm[value]||value;}
|
|
return type+':'+value;
|
|
}
|
|
function selectChip(a,el){selectedChip=a;document.querySelectorAll('.amount-btn').forEach(function(c){c.classList.remove('active');});if(el)el.classList.add('active');
|
|
var ci=document.getElementById('customAmount');if(ci)ci.value=a;}
|
|
function toggleCustomInput(){var row=document.getElementById('customInputRow');if(row)row.style.display=row.style.display==='none'?'flex':'none';}
|
|
function applyCustomChip(){var v=parseInt(document.getElementById('customAmount').value);if(!v||v<=0){showToast(I18N.error+': > 0','error');return;}selectedChip=v;document.querySelectorAll('.amount-btn').forEach(function(c){c.classList.remove('active');});}
|
|
function addBet(type,target,el){if(periodData&&periodData.status!=='pending'){showToast(I18N.period_closed,'error');return;}var key=type+'_'+target;if(!bets[key])bets[key]={type:type,value:target,amount:0,el:el};bets[key].amount+=selectedChip;el.classList.add('selected');var badge=el.querySelector('.bet-amount-badge');if(!badge){badge=document.createElement('span');badge.className='bet-amount-badge';el.appendChild(badge);}badge.textContent=bets[key].amount>=1000?(bets[key].amount/1000).toFixed(1)+'k':bets[key].amount;updateBetSummary();}
|
|
function clearBets(){bets={};document.querySelectorAll('.bet-btn.selected,.bet-cell.selected').forEach(function(e){e.classList.remove('selected');var b=e.querySelector('.bet-amount-badge');if(b)b.remove();});updateBetSummary();}
|
|
function updateBetSummary(){var total=0,count=0;Object.values(bets).forEach(function(b){total+=b.amount;count++;});document.getElementById('betCount').textContent=count;
|
|
var badge=document.getElementById('betFloatBadge');if(badge)badge.classList.toggle('hide',count===0);}
|
|
function toggleBetPanel(){/* placeholder */}
|
|
function submitBets(){var arr=Object.values(bets);if(!arr.length){showToast(I18N.place_bet,'error');return;}if(!periodData||!periodData.period_number){showToast(I18N.waiting,'error');return;}fetch('/api/bet',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({game_id:GAME_ID,period_number:periodData.period_number,bets:arr})}).then(function(r){return r.json();}).then(function(d){if(d.success){showToast(d.message,'ok');document.getElementById('balanceDisplay').textContent=parseFloat(d.new_balance).toFixed(2);clearBets();pollPeriod();}else{showToast(d.message,'error');}}).catch(function(e){showToast(I18N.net_err,'error');});}
|
|
function showBetTab(tab,btn){['Rank','Bs','Sum','Quick','Dragon','Miss'].forEach(function(t){var p=document.getElementById('panel'+t);if(p)p.classList.toggle('hidden',t.toLowerCase()!==tab);});document.querySelectorAll('.side-menu .mi').forEach(function(m){m.classList.remove('active');});if(btn)btn.classList.add('active');if(tab==='dragon')renderDragonStreak();if(tab==='miss')renderMissPanel();}
|
|
|
|
// ===== Quick bet =====
|
|
function quickBet(type,side){
|
|
if(periodData&&periodData.status!=='pending'){showToast(I18N.period_closed,'error');return;}
|
|
var count=0;
|
|
if(type==='bs'||type==='oe'){
|
|
for(var r=1;r<=10;r++){
|
|
var target='rank'+r+'_'+side;
|
|
var key=type+'_'+target;
|
|
if(!bets[key])bets[key]={type:type,value:target,amount:0};
|
|
bets[key].amount+=selectedChip;count++;
|
|
syncBetCellUI(key,bets[key].amount);
|
|
}
|
|
} else if(type==='dt'){
|
|
for(var r=1;r<=5;r++){
|
|
var target='dt'+r+'_'+side;
|
|
var key='dt_'+target;
|
|
if(!bets[key])bets[key]={type:'dt',value:target,amount:0};
|
|
bets[key].amount+=selectedChip;count++;
|
|
syncBetCellUI(key,bets[key].amount);
|
|
}
|
|
} else if(type==='sum_bs'){
|
|
var key='sum_bs_'+side;
|
|
if(!bets[key])bets[key]={type:'sum_bs',value:side,amount:0};
|
|
bets[key].amount+=selectedChip;count=1;
|
|
syncBetCellUI(key,bets[key].amount);
|
|
}
|
|
updateBetSummary();
|
|
showToast(count+I18N.bets_added,'ok');
|
|
}
|
|
function syncBetCellUI(key,amount){
|
|
var parts=key.split('_');var type=parts[0];
|
|
var value=key.substring(type.length+1);
|
|
if(type==='sum'){type='sum_bs';value=key.substring(7);}
|
|
var cells=document.querySelectorAll('.bet-cell,.bet-btn');
|
|
for(var i=0;i<cells.length;i++){
|
|
var onclick=cells[i].getAttribute('onclick')||'';
|
|
if(onclick.indexOf("addBet('"+type+"','"+value+"'")>-1){
|
|
cells[i].classList.add('selected');
|
|
var badge=cells[i].querySelector('.bet-amount-badge');
|
|
if(!badge){badge=document.createElement('span');badge.className='bet-amount-badge';cells[i].appendChild(badge);}
|
|
badge.textContent=amount>=1000?(amount/1000).toFixed(1)+'k':amount;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ===== Miss stats =====
|
|
function switchMissRank(idx,el){
|
|
curMissRank=idx;
|
|
document.querySelectorAll('#missTabs .miss-tab').forEach(function(t){t.classList.remove('active');});
|
|
if(el)el.classList.add('active');
|
|
renderMissPanel();
|
|
}
|
|
function renderMissPanel(){
|
|
var D=historyCache;
|
|
var grid=document.getElementById('missGrid');
|
|
var tsBox=document.getElementById('missTwoSide');
|
|
if(!grid||!tsBox)return;
|
|
if(!D||!D.length){grid.innerHTML='<div style="grid-column:span 5;text-align:center;color:#999;padding:20px">'+I18N.no_bets+'</div>';tsBox.innerHTML='';return;}
|
|
var r=curMissRank+1;
|
|
var html='';
|
|
for(var num=1;num<=10;num++){
|
|
var miss=-1;
|
|
for(var i=0;i<D.length;i++){if(+D[i]['rank_'+r]===num){miss=i;break;}}
|
|
var val=miss===-1?D.length+'+':miss;
|
|
var cls='';
|
|
if(miss===0)cls='hot';
|
|
else if(miss===-1||miss>=5)cls='cold';
|
|
html+='<div class="miss-cell"><div class="miss-num"><span class="car c'+num+'" style="width:22px;height:22px;font-size:10px;display:inline-flex">'+num+'</span></div><div class="miss-val '+cls+'">'+val+'</div></div>';
|
|
}
|
|
grid.innerHTML=html;
|
|
var v0=+D[0]['rank_'+r];
|
|
var sides=[
|
|
{name:I18N.big,check:function(v){return v>5;}},
|
|
{name:I18N.small,check:function(v){return v<=5;}},
|
|
{name:I18N.odd,check:function(v){return v%2===1;}},
|
|
{name:I18N.even,check:function(v){return v%2===0;}}
|
|
];
|
|
if(r<=5){
|
|
sides.push({name:I18N.dragon,check:function(v,row){return +row['rank_'+r]>+row['rank_'+(11-r)];}});
|
|
sides.push({name:I18N.tiger,check:function(v,row){return +row['rank_'+r]<+row['rank_'+(11-r)];}});
|
|
}
|
|
var tsHtml='';
|
|
sides.forEach(function(sd){
|
|
var miss=-1;
|
|
for(var i=0;i<D.length;i++){var v=+D[i]['rank_'+r];if(sd.check(v,D[i])){miss=i;break;}}
|
|
var val=miss===-1?D.length+'+':miss;
|
|
tsHtml+='<div class="miss-ts-item"><span class="miss-ts-name">'+sd.name+'</span><span class="miss-ts-val">'+val+'</span></div>';
|
|
});
|
|
tsBox.innerHTML=tsHtml;
|
|
}
|
|
|
|
// ===== Dragon streak stats =====
|
|
function renderDragonStreak(){
|
|
var box=document.getElementById('dragonStreakList');
|
|
if(!box)return;
|
|
var D=historyCache;
|
|
if(!D||D.length<2){box.innerHTML='<div class="dragon-empty">'+I18N.no_bets+'</div>';return;}
|
|
var streaks=[];
|
|
var COLORS={big:'#e74c3c',small:'#3498db',odd:'#f39c12',even:'#9b59b6',dragon:'#e74c3c',tiger:'#3498db'};
|
|
var LABELS={big:I18N.big,small:I18N.small,odd:I18N.odd,even:I18N.even,dragon:I18N.dragon,tiger:I18N.tiger};
|
|
for(var r=1;r<=10;r++){
|
|
var v0=+D[0]['rank_'+r];if(!v0)continue;
|
|
var bs0=v0>5?'big':'small',bsCnt=1;
|
|
for(var i=1;i<D.length;i++){var v=+D[i]['rank_'+r];if((v>5?'big':'small')===bs0)bsCnt++;else break;}
|
|
if(bsCnt>=2)streaks.push({rank:rankNames[r],key:bs0,color:COLORS[bs0],label:LABELS[bs0],count:bsCnt});
|
|
var oe0=v0%2?'odd':'even',oeCnt=1;
|
|
for(var i=1;i<D.length;i++){var v=+D[i]['rank_'+r];if((v%2?'odd':'even')===oe0)oeCnt++;else break;}
|
|
if(oeCnt>=2)streaks.push({rank:rankNames[r],key:oe0,color:COLORS[oe0],label:LABELS[oe0],count:oeCnt});
|
|
if(r<=5){
|
|
var a=+D[0]['rank_'+r],b=+D[0]['rank_'+(11-r)];
|
|
var dt0=a>b?'dragon':'tiger',dtCnt=1;
|
|
for(var i=1;i<D.length;i++){var a2=+D[i]['rank_'+r],b2=+D[i]['rank_'+(11-r)];if((a2>b2?'dragon':'tiger')===dt0)dtCnt++;else break;}
|
|
if(dtCnt>=2)streaks.push({rank:rankNames[r],key:dt0,color:COLORS[dt0],label:LABELS[dt0],count:dtCnt});
|
|
}
|
|
}
|
|
var s0=(+D[0].rank_1)+(+D[0].rank_2);
|
|
if(s0){
|
|
var sbs0=s0>=12?'big':'small',sbsCnt=1;
|
|
for(var i=1;i<D.length;i++){var s=(+D[i].rank_1)+(+D[i].rank_2);if((s>=12?'big':'small')===sbs0)sbsCnt++;else break;}
|
|
if(sbsCnt>=2)streaks.push({rank:I18N.sum,key:sbs0,color:COLORS[sbs0],label:LABELS[sbs0],count:sbsCnt});
|
|
var soe0=s0%2?'odd':'even',soeCnt=1;
|
|
for(var i=1;i<D.length;i++){var s=(+D[i].rank_1)+(+D[i].rank_2);if((s%2?'odd':'even')===soe0)soeCnt++;else break;}
|
|
if(soeCnt>=2)streaks.push({rank:I18N.sum,key:soe0,color:COLORS[soe0],label:LABELS[soe0],count:soeCnt});
|
|
}
|
|
streaks.sort(function(a,b){return b.count-a.count;});
|
|
if(!streaks.length){box.innerHTML='<div class="dragon-empty">'+I18N.no_bets+'</div>';return;}
|
|
var maxCnt=streaks[0].count;
|
|
var html='';
|
|
streaks.forEach(function(s){
|
|
var pct=Math.round(s.count/Math.max(maxCnt,1)*100);
|
|
var fire=s.count>=5?'🔥':s.count>=3?'💨':'';
|
|
html+='<div class="dragon-item">'
|
|
+'<span class="dragon-rank">'+s.rank+'</span>'
|
|
+'<span class="dragon-type" style="background:'+s.color+'">'+s.label+'</span>'
|
|
+'<div class="dragon-bar-wrap"><div class="dragon-bar" style="width:'+pct+'%;background:'+s.color+'"></div></div>'
|
|
+'<span class="dragon-count">'+s.count+'</span>'
|
|
+(fire?'<span class="dragon-fire">'+fire+'</span>':'')
|
|
+'</div>';
|
|
});
|
|
box.innerHTML=html;
|
|
}
|
|
|
|
// ===== Render result =====
|
|
function renderResult(result,container){if(!result)return;var el=document.getElementById(container);el.innerHTML='';['rank_1','rank_2','rank_3','rank_4','rank_5','rank_6','rank_7','rank_8','rank_9','rank_10'].forEach(function(k){var v=result[k];if(!v)return;var d=document.createElement('div');d.className='car c'+v;d.style.cssText='width:28px;height:28px;font-size:11px';d.textContent=v;el.appendChild(d);});
|
|
var tags=document.getElementById('lastResultTags');if(!tags)return;tags.innerHTML='';var r1=+result.rank_1,r2=+result.rank_2;if(r1&&r2){var sum=r1+r2;var items=[sum,(sum>=12?I18N.big:I18N.small),(sum%2?I18N.odd:I18N.even)];for(var i=1;i<=5;i++){var a=+result['rank_'+i],b=+result['rank_'+(11-i)];if(a&&b)items.push(a>b?I18N.dragon:I18N.tiger);}items.forEach(function(t){var s=document.createElement('span');s.textContent=t;tags.appendChild(s);});
|
|
var badge=document.getElementById('sumHotBadge');if(badge){badge.textContent=sum;badge.style.display='inline-flex';}}}
|
|
|
|
// ===== Render my bets =====
|
|
function renderMyBets(myBets){var list=document.getElementById('myBetsList');var countEl=document.getElementById('myBetsCount');if(!myBets||!myBets.length){list.innerHTML='<div style="color:var(--text3);font-size:12px;text-align:center;padding:8px">'+I18N.no_bets+'</div>';countEl.textContent='0';return;}countEl.textContent=myBets.length;var total=0,html='';myBets.forEach(function(b){var label=formatBetLabel(b.bet_type,b.bet_value);var amt=parseFloat(b.amount);total+=amt;var sl=b.status==='pending'?'<span style="color:var(--warn)">'+I18N.draw+'</span>':b.status==='win'?'<span style="color:var(--success)">+'+parseFloat(b.win_amount).toFixed(0)+'</span>':'<span style="color:var(--danger)">-'+amt.toFixed(0)+'</span>';html+='<div style="display:flex;justify-content:space-between;align-items:center;font-size:12px;padding:4px 0;border-bottom:1px solid var(--border)"><span style="color:var(--text2)">'+label+'</span><span style="color:var(--primary)">'+amt.toFixed(0)+' <span style="color:var(--text3)">x'+b.odds+'</span></span>'+sl+'</div>';});html+='<div style="display:flex;justify-content:space-between;font-size:12px;padding-top:4px;color:var(--text3)"><span>'+I18N.total+'</span><span style="color:var(--primary);font-weight:700">'+total.toFixed(0)+'</span></div>';list.innerHTML=html;}
|
|
function renderLastMyBets(lastMyBets){var panel=document.getElementById('lastResultPanel');var list=document.getElementById('lastMyBetsList');var profitEl=document.getElementById('lastProfitDisplay');if(!lastMyBets||!lastMyBets.length){panel.classList.add('hidden');return;}panel.classList.remove('hidden');var totalWin=0,totalBet=0,html='';lastMyBets.forEach(function(b){var label=formatBetLabel(b.bet_type,b.bet_value);var amt=parseFloat(b.amount);totalBet+=amt;if(b.status==='win'){var win=parseFloat(b.win_amount);totalWin+=win+amt;html+='<div style="display:flex;justify-content:space-between;font-size:12px;padding:2px 0"><span style="color:var(--text2)">'+label+'</span><span style="color:var(--success)">+'+win.toFixed(0)+'</span></div>';}else{html+='<div style="display:flex;justify-content:space-between;font-size:12px;padding:2px 0"><span style="color:var(--text2)">'+label+'</span><span style="color:var(--danger)">-'+amt.toFixed(0)+'</span></div>';}});list.innerHTML=html;var profit=totalWin-totalBet;if(profit>0){profitEl.style.color='var(--success)';profitEl.textContent='+'+profit.toFixed(0);}else if(profit<0){profitEl.style.color='var(--danger)';profitEl.textContent=profit.toFixed(0);}else{profitEl.style.color='var(--text3)';profitEl.textContent='0';}}
|
|
|
|
// ===== Toast & Lang =====
|
|
function showToast(msg,type){var t=document.createElement('div');t.style.cssText='position:fixed;top:80px;left:50%;transform:translateX(-50%);padding:10px 24px;border-radius:24px;font-size:13px;z-index:999;color:#fff;box-shadow:0 4px 16px rgba(0,0,0,.15)';t.style.background=type==='ok'?'var(--success)':'var(--danger)';t.textContent=msg;document.body.appendChild(t);setTimeout(function(){t.remove();},2500);}
|
|
function setLang(l){fetch('/api/set-lang',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({lang:l})}).then(function(){location.reload();});}
|