- 拆分 HomeController → TransactionController, ReportWebController, FollowPlanController - 新增 Service 层: TransactionService, ReportService, FollowPlanService - pk10.php JS 抽离为 4 个独立文件 (sound/race/bet/poll) - 前台新增报表查询页面 (/report) + 跟单计划页面 (/follow-plan) - 后台新增跟单计划管理 + 用户输赢明细统计 - 封盘状态显示倒计时 (x:xx) - 音效仅在开奖弹窗打开时播放 - 路由按模块分组整理 - autoload 支持 App\Services 命名空间
34 lines
3.1 KiB
JavaScript
34 lines
3.1 KiB
JavaScript
/**
|
|
* PK10 Sound Engine (SoundFX)
|
|
* Depends on: (none - first to load)
|
|
* Provides: SoundFX, toggleRaceSound
|
|
*
|
|
* Sound only plays when the race modal is open (raceModalOpen === true).
|
|
* betSuccess/error are exempt — they are UI feedback, not race sounds.
|
|
*/
|
|
var SoundFX={
|
|
enabled:localStorage.getItem('raceSoundOn')!=='0',
|
|
_cache:{},
|
|
// Core play — checks both enabled AND raceModalOpen
|
|
_play:function(name,vol,startAt){if(!this.enabled||!window.raceModalOpen)return null;var a=new Audio('/Static/sounds/'+name+'.mp3');a.volume=Math.min(vol||0.6,1);if(startAt)a.currentTime=startAt;a.play().catch(function(){});return a;},
|
|
_stop:function(key){if(this._cache[key]){try{this._cache[key].pause();this._cache[key].currentTime=0;}catch(e){}delete this._cache[key];}},
|
|
engineIdle:function(){this._cache.idle=this._play('engine',0.3);},
|
|
trafficBeep:function(n){this._play('beep',n>0?0.4:0.6);},
|
|
engineStart:function(){this._stop('idle');this._cache.eng=this._play('engine',0.7);},
|
|
accelerate:function(){this._stop('eng');this._cache.race=this._play('racing',0.5);},
|
|
gearShift:function(){this._play('engine',0.25,1.5);},
|
|
whoosh:function(){this._play('whoosh',0.5);},
|
|
brakeSqueal:function(){this._stop('race');this._play('brake',0.6);},
|
|
finish:function(){this._play('finish',0.7);},
|
|
podium:function(){this._play('finish',0.5);},
|
|
// tick — race countdown sound, only when modal open
|
|
tick:function(){if(!this.enabled||!window.raceModalOpen)return;try{var ctx=new(window.AudioContext||window.webkitAudioContext)();var o=ctx.createOscillator(),g=ctx.createGain();o.type='sine';o.frequency.value=800;g.gain.setValueAtTime(0.1,ctx.currentTime);g.gain.linearRampToValueAtTime(0,ctx.currentTime+0.05);o.connect(g).connect(ctx.destination);o.start();o.stop(ctx.currentTime+0.05);}catch(e){}},
|
|
// betSuccess/error — UI feedback, always plays when enabled (no modal check)
|
|
betSuccess:function(){if(!this.enabled)return;try{var ctx=new(window.AudioContext||window.webkitAudioContext)();[880,1175].forEach(function(f,i){var o=ctx.createOscillator(),g=ctx.createGain();o.type='sine';o.frequency.value=f;g.gain.setValueAtTime(0.08,ctx.currentTime+i*0.08);g.gain.linearRampToValueAtTime(0,ctx.currentTime+i*0.08+0.12);o.connect(g).connect(ctx.destination);o.start(ctx.currentTime+i*0.08);o.stop(ctx.currentTime+i*0.08+0.12);});}catch(e){}},
|
|
error:function(){if(!this.enabled)return;try{var ctx=new(window.AudioContext||window.webkitAudioContext)();var o=ctx.createOscillator(),g=ctx.createGain();o.type='square';o.frequency.setValueAtTime(200,ctx.currentTime);o.frequency.setValueAtTime(150,ctx.currentTime+0.15);g.gain.setValueAtTime(0.06,ctx.currentTime);g.gain.linearRampToValueAtTime(0,ctx.currentTime+0.3);o.connect(g).connect(ctx.destination);o.start();o.stop(ctx.currentTime+0.3);}catch(e){}},
|
|
// stopAll — kill all cached sounds (called when modal closes)
|
|
stopAll:function(){for(var k in this._cache){this._stop(k);}},
|
|
toggle:function(){this.enabled=!this.enabled;localStorage.setItem('raceSoundOn',this.enabled?'1':'0');return this.enabled;}
|
|
};
|
|
function toggleRaceSound(){var on=SoundFX.toggle();var btn=document.querySelector('.rs-sound');if(btn)btn.textContent=on?'🔊':'🔇';if(on)SoundFX.tick();}
|