feat(baccarat): 实现百家乐实时下注人数统计功能

- 新增 BaccaratPoolUpdated 事件,用于通过 WebSocket 广播实时下注数据更新
- 增加数据库迁移以在 baccarat_rounds 表中添加对应的下注人数统计字段
- 更新 BaccaratRound 模型以及 BaccaratController,支持实时下注统计更新与 WebSocket 事件分发
- 更新前端 chat.js 以及 baccarat-panel.blade.php,利用 Alpine.js 和 Echo 接收事件并动态渲染 "大"、"小"、"豹子" 的实时下注计数
This commit is contained in:
2026-03-28 17:02:10 +08:00
parent a68e82107e
commit 8fcccf72a5
6 changed files with 174 additions and 11 deletions

View File

@@ -0,0 +1,66 @@
<?php
/**
* 文件功能:百家乐押注人数实时广播事件
*
* 当有用户成功下注时,向房间内所有用户广播最新的
* 各选项下注总人次,供前端实时更新面板。
*
* @author ChatRoom Laravel
*
* @version 1.0.0
*/
namespace App\Events;
use App\Models\BaccaratRound;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class BaccaratPoolUpdated implements ShouldBroadcastNow
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* @param BaccaratRound $round 本局信息
*/
public function __construct(
public readonly BaccaratRound $round,
) {}
/**
* 广播至房间公共频道。
*
* @return array<int, \Illuminate\Broadcasting\Channel>
*/
public function broadcastOn(): array
{
return [new PresenceChannel('room.1')];
}
/**
* 广播事件名(前端监听 .baccarat.pool_updated
*/
public function broadcastAs(): string
{
return 'baccarat.pool_updated';
}
/**
* 广播数据。
*
* @return array<string, mixed>
*/
public function broadcastWith(): array
{
return [
'round_id' => $this->round->id,
'bet_count_big' => $this->round->bet_count_big,
'bet_count_small' => $this->round->bet_count_small,
'bet_count_triple' => $this->round->bet_count_triple,
];
}
}

View File

@@ -56,6 +56,9 @@ class BaccaratController extends Controller
'total_bet_big' => $round->total_bet_big,
'total_bet_small' => $round->total_bet_small,
'total_bet_triple' => $round->total_bet_triple,
'bet_count_big' => $round->bet_count_big,
'bet_count_small' => $round->bet_count_small,
'bet_count_triple' => $round->bet_count_triple,
'my_bet' => $myBet ? [
'bet_type' => $myBet->bet_type,
'amount' => $myBet->amount,
@@ -139,9 +142,14 @@ class BaccaratController extends Controller
// 更新局次汇总统计
$field = 'total_bet_'.$data['bet_type'];
$countField = 'bet_count_'.$data['bet_type'];
$round->increment($field, $data['amount']);
$round->increment($countField);
$round->increment('bet_count');
// 广播各选项的最新押注人数
event(new \App\Events\BaccaratPoolUpdated($round));
$betLabel = match ($data['bet_type']) {
'big' => '大', 'small' => '小', default => '豹子'
};

View File

@@ -24,6 +24,7 @@ class BaccaratRound extends Model
'bet_opens_at', 'bet_closes_at', 'settled_at',
'total_bet_big', 'total_bet_small', 'total_bet_triple',
'total_payout', 'bet_count',
'bet_count_big', 'bet_count_small', 'bet_count_triple',
];
/**
@@ -44,6 +45,9 @@ class BaccaratRound extends Model
'total_bet_triple' => 'integer',
'total_payout' => 'integer',
'bet_count' => 'integer',
'bet_count_big' => 'integer',
'bet_count_small' => 'integer',
'bet_count_triple' => 'integer',
];
}

View File

@@ -0,0 +1,36 @@
<?php
/**
* 文件功能:在百家乐局次表增加下注人数分项统计
*
* 为支持前端实时显示“押大”、“押小”、“押豹子”的人数,增加记录各类型的押注人次。
*/
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* 执行迁移。
*/
public function up(): void
{
Schema::table('baccarat_rounds', function (Blueprint $table) {
$table->unsignedInteger('bet_count_big')->default(0)->after('bet_count')->comment('押大人数');
$table->unsignedInteger('bet_count_small')->default(0)->after('bet_count_big')->comment('押小人数');
$table->unsignedInteger('bet_count_triple')->default(0)->after('bet_count_small')->comment('押豹子人数');
});
}
/**
* 回滚迁移。
*/
public function down(): void
{
Schema::table('baccarat_rounds', function (Blueprint $table) {
$table->dropColumn(['bet_count_big', 'bet_count_small', 'bet_count_triple']);
});
}
};

View File

@@ -116,6 +116,12 @@ export function initChat(roomId) {
new CustomEvent("chat:baccarat.opened", { detail: e }),
);
})
.listen(".baccarat.pool_updated", (e) => {
console.log("百家乐押注更新:", e);
window.dispatchEvent(
new CustomEvent("chat:baccarat.pool_updated", { detail: e }),
);
})
.listen(".baccarat.settled", (e) => {
console.log("百家乐结算:", e);
window.dispatchEvent(

View File

@@ -124,7 +124,10 @@
'border:2px solid #1d4ed8; background:#1d4ed8; color:#fff; transform:scale(1.05); box-shadow:0 4px 14px rgba(29,78,216,.3);' :
'border:2px solid #bfdbfe; background:#eff6ff; color:#1d4ed8;'">
<div style="font-size:22px;">🔵</div>
<div style="font-size:13px; margin-top:4px;"></div>
<div style="font-size:13px; margin-top:4px; display:flex; align-items:center; justify-content:center; gap:4px;">
<span></span>
<span x-show="betCountBig > 0" x-text="'👤 ' + betCountBig" style="font-size:11px; opacity:0.85; font-weight:normal;"></span>
</div>
<div style="font-size:10px; margin-top:2px; opacity:.75;">11~17 1:1</div>
</button>
{{-- --}}
@@ -135,7 +138,10 @@
'border:2px solid #d97706; background:#d97706; color:#fff; transform:scale(1.05); box-shadow:0 4px 14px rgba(217,119,6,.3);' :
'border:2px solid #fde68a; background:#fffbeb; color:#b45309;'">
<div style="font-size:22px;">🟡</div>
<div style="font-size:13px; margin-top:4px;"></div>
<div style="font-size:13px; margin-top:4px; display:flex; align-items:center; justify-content:center; gap:4px;">
<span></span>
<span x-show="betCountSmall > 0" x-text="'👤 ' + betCountSmall" style="font-size:11px; opacity:0.85; font-weight:normal;"></span>
</div>
<div style="font-size:10px; margin-top:2px; opacity:.75;">4~10 1:1</div>
</button>
{{-- 豹子 --}}
@@ -146,7 +152,10 @@
'border:2px solid #7c3aed; background:#7c3aed; color:#fff; transform:scale(1.05); box-shadow:0 4px 14px rgba(124,58,237,.3);' :
'border:2px solid #ddd6fe; background:#f5f3ff; color:#7c3aed;'">
<div style="font-size:22px;">💥</div>
<div style="font-size:13px; margin-top:4px;">豹子</div>
<div style="font-size:13px; margin-top:4px; display:flex; align-items:center; justify-content:center; gap:4px;">
<span>豹子</span>
<span x-show="betCountTriple > 0" x-text="'👤 ' + betCountTriple" style="font-size:11px; opacity:0.85; font-weight:normal;"></span>
</div>
<div style="font-size:10px; margin-top:2px; opacity:.75;">三同 1:24</div>
</button>
</div>
@@ -165,20 +174,21 @@
</template>
</div>
{{-- 自定义金额 --}}
<input type="number" x-model.number="betAmount" min="100" placeholder="自定义金额"
{{-- 自定义金额 (带有 id name 防止 Bitwarden 等密码管理插件检索 DOM 时因属性为 null 报错) --}}
<input type="number" id="baccarat-bet-amount" name="bet_amount" x-model.number="betAmount" min="100" placeholder="自定义金额"
autocomplete="off" data-bwignore="true" data-lpignore="true" data-1p-ignore="true"
style="width:100%; background:#f6faff; border:1.5px solid #d0e4f5;
border-radius:8px; padding:8px 12px; color:#225588; font-size:13px;
box-sizing:border-box; margin-bottom:10px;"
x-on:focus="$event.target.select()">
{{-- 下注按钮 --}}
<button x-on:click="submitBet()" :disabled="!selectedType || betAmount < 100 || submitting"
:style="(!selectedType || betAmount < 100 || submitting) ?
<button x-on:click="submitBet()" :disabled="!roundId || !selectedType || betAmount < 100 || submitting"
:style="(!roundId || !selectedType || betAmount < 100 || submitting) ?
'display:block; width:100%; border:none; border-radius:12px; padding:13px 0; font-size:14px; font-weight:bold; cursor:not-allowed; transition:all .2s; background:#e0e8f0; color:#99a8b8; box-shadow:none; font-family:inherit;' :
'display:block; width:100%; border:none; border-radius:12px; padding:13px 0; font-size:14px; font-weight:bold; cursor:pointer; transition:all .2s; background:linear-gradient(135deg,#336699,#5a8fc0); color:#fff; box-shadow:0 4px 14px rgba(51,102,153,.3); font-family:inherit;'">
<span
x-text="submitting ? '提交中…' : (!selectedType ? '请先选择大/小/豹子' : '🎲 押注「' + betTypeLabel(selectedType) + '」 ' + Number(betAmount).toLocaleString() + ' 金币')"></span>
x-text="!roundId ? '尚未开局' : (submitting ? '提交中…' : (!selectedType ? '请先选择大/小/豹子' : '🎲 押注「' + betTypeLabel(selectedType) + '」 ' + Number(betAmount).toLocaleString() + ' 金币'))"></span>
</button>
</div>
@@ -408,6 +418,11 @@
totalBetSmall: 0,
totalBetTriple: 0,
// 押注人数统计
betCountBig: 0,
betCountSmall: 0,
betCountTriple: 0,
// 本人下注
myBet: false,
myBetType: '',
@@ -448,6 +463,9 @@
this.settledDice = [];
this.selectedType = '';
this.betAmount = 100;
this.betCountBig = 0;
this.betCountSmall = 0;
this.betCountTriple = 0;
this.show = true;
this.loadCurrentRound();
@@ -466,6 +484,9 @@
this.totalBetBig = data.round.total_bet_big;
this.totalBetSmall = data.round.total_bet_small;
this.totalBetTriple = data.round.total_bet_triple;
this.betCountBig = data.round.bet_count_big;
this.betCountSmall = data.round.bet_count_small;
this.betCountTriple = data.round.bet_count_triple;
if (data.round.my_bet) {
this.myBet = true;
this.myBetType = data.round.my_bet.bet_type;
@@ -493,7 +514,7 @@
* 提交下注
*/
async submitBet() {
if (!this.selectedType || this.betAmount < 100 || this.submitting) return;
if (!this.roundId || !this.selectedType || this.betAmount < 100 || this.submitting) return;
this.submitting = true;
try {
@@ -512,11 +533,15 @@
});
const data = await res.json();
if (data.ok) {
if (res.ok && data.ok) {
this.myBet = true;
this.myBetType = data.bet_type;
this.myBetAmount = data.amount;
window.chatDialog?.alert(data.message, '下注成功', '#336699');
} else if (res.status === 422 && data.errors) {
// 取出第一条 Laravel 验证失败原因
const firstError = Object.values(data.errors)[0][0];
window.chatDialog?.alert(firstError, '下注验证失败', '#ef4444');
} else {
window.chatDialog?.alert(data.message || '下注失败', '提示', '#ef4444');
}
@@ -627,6 +652,21 @@
if (panel) Alpine.$data(panel).showResult(e.detail);
});
/** 收到下注池更新:更新押注人数 */
window.addEventListener('chat:baccarat.pool_updated', (e) => {
const panel = document.getElementById('baccarat-panel');
if (panel) {
const pd = Alpine.$data(panel);
const data = e.detail;
// 判断 round_id 是否一致
if (pd.roundId === data.round_id) {
pd.betCountBig = data.bet_count_big;
pd.betCountSmall = data.bet_count_small;
pd.betCountTriple = data.bet_count_triple;
}
}
});
/** 页面加载时:检查是否有进行中的局,有则自动恢复面板 */
document.addEventListener('DOMContentLoaded', async () => {
try {
@@ -656,6 +696,9 @@
panelData.totalBetBig = round.total_bet_big;
panelData.totalBetSmall = round.total_bet_small;
panelData.totalBetTriple = round.total_bet_triple;
panelData.betCountBig = round.bet_count_big;
panelData.betCountSmall = round.bet_count_small;
panelData.betCountTriple = round.bet_count_triple;
if (round.my_bet) {
panelData.myBet = true;