- 拆分 HomeController → TransactionController, ReportWebController, FollowPlanController - 新增 Service 层: TransactionService, ReportService, FollowPlanService - pk10.php JS 抽离为 4 个独立文件 (sound/race/bet/poll) - 前台新增报表查询页面 (/report) + 跟单计划页面 (/follow-plan) - 后台新增跟单计划管理 + 用户输赢明细统计 - 封盘状态显示倒计时 (x:xx) - 音效仅在开奖弹窗打开时播放 - 路由按模块分组整理 - autoload 支持 App\Services 命名空间
128 lines
7.2 KiB
JavaScript
128 lines
7.2 KiB
JavaScript
/**
|
|
* PK10 Polling & Countdown
|
|
* Depends on: pk10-sound.js, pk10-race.js, pk10-bet.js
|
|
* Globals expected: GAME_ID, periodData, historyCache, I18N, localCountdown,
|
|
* localLockCountdown, lastPollStatus, lastPeriodId, drawnResultShown,
|
|
* cachedRaceResult, raceTriggered, pendingRaceHeader, raceAnimState,
|
|
* PK10_CONFIG (for status labels)
|
|
*/
|
|
|
|
// ===== Countdown interval =====
|
|
setInterval(function(){if(localCountdown>0)localCountdown--;if(localLockCountdown>0)localLockCountdown--;var m=Math.floor(localCountdown/60),s=localCountdown%60;document.getElementById('countdown').textContent=localCountdown>0?m+':'+(s<10?'0':'')+s:'--';
|
|
var badge=document.getElementById('statusBadge');
|
|
if(lastPollStatus==='pending'&&localLockCountdown>0){var lm=Math.floor(localLockCountdown/60),ls=localLockCountdown%60;badge.textContent=lm+':'+(ls<10?'0':'')+ls;badge.style.cssText='color:var(--success)';}
|
|
else if(lastPollStatus==='pending'&&localLockCountdown<=0){badge.textContent=PK10_CONFIG.lang.sealed;badge.style.cssText='color:var(--danger)';}
|
|
if(raceAnimState==='waiting')updateRaceOverlayTimer(localCountdown);
|
|
if(localCountdown>0&&localCountdown<=10&&raceAnimState==='waiting')SoundFX.tick();
|
|
},1000);
|
|
|
|
// ===== Trigger race sequence =====
|
|
function triggerRaceSequence(){
|
|
if(raceTriggered||raceAnimState!=='waiting')return;
|
|
raceTriggered=true;
|
|
var remaining=localCountdown*1000;
|
|
var readyDur=3000;
|
|
var raceDur=Math.max(5500,remaining-readyDur-500);
|
|
raceDur=Math.min(raceDur,25000);
|
|
var waitBeforeRace=Math.max(0,remaining-readyDur-raceDur);
|
|
startReadyAnimation();
|
|
setTimeout(function(){
|
|
if(cachedRaceResult&&raceAnimState==='ready'){
|
|
var raceRes=cachedRaceResult;cachedRaceResult=null;
|
|
pendingRaceHeader=raceRes._raw||null;
|
|
animateRace(raceRes.arr,raceDur);
|
|
}
|
|
},readyDur+waitBeforeRace);
|
|
}
|
|
|
|
// ===== Poll period =====
|
|
function pollPeriod(){return fetch('/api/period/current?game_id='+GAME_ID).then(function(r){return r.json();}).then(function(d){if(!d.success)return;periodData=d.data;
|
|
var serverRemaining=periodData.remaining_seconds||0;var periodChanged=(periodData.id&&periodData.id!==lastPeriodId);var statusChanged=(periodData.status!==lastPollStatus);var driftTooMuch=Math.abs(localCountdown-serverRemaining)>3;
|
|
if(periodChanged||statusChanged||driftTooMuch||localCountdown<=0)localCountdown=serverRemaining;
|
|
var serverLockCountdown=periodData.lock_countdown||0;if(periodChanged||statusChanged||Math.abs(localLockCountdown-serverLockCountdown)>3)localLockCountdown=serverLockCountdown;
|
|
if(d.balance!==null&&d.balance!==undefined)document.getElementById('balanceDisplay').textContent=parseFloat(d.balance).toFixed(2);
|
|
// Status badge
|
|
var badge=document.getElementById('statusBadge');var statusMap={pending:[PK10_CONFIG.lang.betting,'seal-status','color:var(--success)'],locked:[PK10_CONFIG.lang.sealed,'seal-status','color:var(--danger)'],drawn:[I18N.drawing,'seal-status','color:var(--warn)'],settled:[I18N.settled_label,'seal-status','color:var(--warn)']};var s=statusMap[periodData.status]||statusMap.pending;badge.textContent=s[0];badge.className=s[1];badge.style.cssText=s[2];
|
|
switchVideoOrAnim(periodData.status,statusChanged);
|
|
// Period number
|
|
var fullPn=periodData.period_number||'---';var shortPn=fullPn.length>8?fullPn.slice(0,-4)+'-'+fullPn.slice(-4):fullPn;var pnEl=document.getElementById('periodNum');pnEl.textContent=shortPn;pnEl.title=fullPn;
|
|
// Extract result array
|
|
function extractResultArr(res){if(!res)return null;var arr=[res.rank_1,res.rank_2,res.rank_3,res.rank_4,res.rank_5,res.rank_6,res.rank_7,res.rank_8,res.rank_9,res.rank_10];return arr.every(function(v){return v;})?arr:null;}
|
|
|
|
// Scene header sync
|
|
if(raceAnimState!=='racing'&&raceAnimState!=='finished'&&raceAnimState!=='ready'){
|
|
if(d.last_result) updateSceneHeader(d.last_result, d.last_result.period_number||fullPn);
|
|
}
|
|
|
|
// === Pending → idle ===
|
|
if(periodData.status==='pending'){
|
|
cachedRaceResult=null;raceTriggered=false;
|
|
if(d.last_result){
|
|
var lp=d.last_result.period_number||'';
|
|
document.getElementById('lastPeriodNum').textContent=lp.length>4?'#'+lp.slice(-4):lp;
|
|
renderResult(d.last_result,'lastResult');
|
|
}
|
|
if(raceAnimState==='finished'||raceAnimState==='racing'||raceAnimState==='ready'){
|
|
if(!window._podiumTimer){
|
|
var protectMs=8000;
|
|
if(raceAnimState==='ready')protectMs=16000;
|
|
else if(raceAnimState==='racing')protectMs=12000;
|
|
window._podiumTimer=setTimeout(function(){
|
|
resetRace();
|
|
startIdleAnimation();
|
|
window._podiumTimer=null;
|
|
},protectMs);
|
|
}
|
|
} else if(raceAnimState!=='waiting'){
|
|
resetRace();
|
|
startIdleAnimation();
|
|
}
|
|
}
|
|
|
|
// === Locked → cache result + trigger sprint ===
|
|
if(periodData.status==='locked'){
|
|
var raceRes=extractResultArr(d.race_result);
|
|
if(raceRes&&!cachedRaceResult){
|
|
cachedRaceResult={arr:raceRes,_raw:d.race_result};
|
|
}
|
|
if(cachedRaceResult)triggerRaceSequence();
|
|
}
|
|
|
|
// === Drawn/Settled → show result + podium ===
|
|
if(d.last_result&&(periodData.status==='drawn'||periodData.status==='settled')){
|
|
if(statusChanged||periodChanged){
|
|
var lp=d.last_result.period_number||'';
|
|
document.getElementById('lastPeriodNum').textContent=lp.length>4?'#'+lp.slice(-4):lp;
|
|
var ra=extractResultArr(d.last_result);
|
|
if(ra&&raceAnimState!=='finished'&&raceAnimState!=='racing'&&raceAnimState!=='ready'){
|
|
cachedRaceResult=null;
|
|
pendingRaceHeader=d.last_result;
|
|
animateRace(ra);
|
|
} else if(raceAnimState!=='racing'&&raceAnimState!=='ready'){
|
|
updateSceneHeader(d.last_result, lp);
|
|
}
|
|
setTimeout(function(){renderResult(d.last_result,'lastResult');},5500);
|
|
}
|
|
}
|
|
|
|
// === First load ===
|
|
if(d.last_result&&!lastAnimatedResult&&raceAnimState==='idle'){
|
|
var ra=extractResultArr(d.last_result);
|
|
if(ra){
|
|
var lp=d.last_result.period_number||'';
|
|
document.getElementById('lastPeriodNum').textContent=lp.length>4?'#'+lp.slice(-4):lp;
|
|
renderResult(d.last_result,'lastResult');
|
|
startIdleAnimation();
|
|
}
|
|
}
|
|
renderMyBets(d.my_bets||[]);renderLastMyBets(d.last_my_bets||[]);
|
|
// History
|
|
if(d.history&&periodChanged){historyCache=d.history;var hl=document.getElementById('historyList');hl.innerHTML='';d.history.forEach(function(h){var row=document.createElement('div');row.className='card';row.style.cssText='padding:6px 8px;margin-bottom:4px';var pn=h.period_number||'';var sp=pn.length>4?'#'+pn.slice(-4):pn;var balls='';['rank_1','rank_2','rank_3','rank_4','rank_5','rank_6','rank_7','rank_8','rank_9','rank_10'].forEach(function(k){if(h[k])balls+='<div class="car c'+h[k]+'" style="width:18px;height:18px;font-size:8px;flex-shrink:0">'+h[k]+'</div>';});row.innerHTML='<div style="color:var(--text3);font-size:10px;margin-bottom:2px">'+sp+'</div><div style="display:flex;gap:2px;flex-wrap:wrap">'+balls+'</div>';hl.appendChild(row);});if(!document.getElementById('panelDragon').classList.contains('hidden'))renderDragonStreak();}
|
|
lastPollStatus=periodData.status;lastPeriodId=periodData.id||0;
|
|
}).catch(function(e){console.error('pollPeriod error:',e);});}
|
|
|
|
// ===== Start polling =====
|
|
pollPeriod();
|
|
(function dynamicPoll(){var delay=(lastPollStatus==='locked'||lastPollStatus==='drawn'||lastPollStatus==='settled')?1500:3000;setTimeout(function(){pollPeriod().then(dynamicPoll).catch(dynamicPoll);},delay);})();
|
|
|