Files
chatroom/resources/views/chat/partials/baccarat-panel.blade.php
lkddi 602dcd7cf1 feat: 神秘箱子系统完整实现 + 婚姻状态弹窗 + 工具栏优化
## 新功能
- 神秘箱子系统(MysteryBox)完整实现:
  - 新增 MysteryBox / MysteryBoxClaim 模型及迁移文件
  - DropMysteryBoxJob / ExpireMysteryBoxJob 队列作业
  - MysteryBoxController(/mystery-box/status + /mystery-box/claim)
  - 支持三种类型:普通箱(500~2000金)/ 稀有箱(5000~20000金)/ 黑化箱(陷阱扣200~1000金)
  - 调度器自动投放 + 管理员手动投放
  - CurrencySource 新增 MYSTERY_BOX / MYSTERY_BOX_TRAP 枚举

- 婚姻状态弹窗(工具栏「婚姻」按钮):
  - 工具栏「呼叫」改为「婚姻」,点击打开婚姻状态弹窗
  - 动态渲染三种状态:单身 / 求婚中 / 已婚
  - 被求婚方可直接「答应 / 婉拒」;已婚可申请离婚(含二次确认)

## 优化修复
- frame.blade.php:Alpine.js CDN 补加 defer,修复所有组件初始化报错
- scripts.blade.php:神秘箱子暗号主动拦截(不依赖轮询),领取成功后弹 chatDialog 展示结果,更新金币余额
- MysteryBoxController:claim() 时 change() 补传 room_id 记录来源房间
- 后台游戏管理页(game-configs):投放箱子按钮颜色修复;弹窗替换为 window.adminDialog
- admin/layouts:新增全局 adminDialog 弹窗组件(替代原生 alert/confirm)
- baccarat-panel:FAB 拖动重构为 Alpine.js baccaratFab() 组件,与 slotFab 一致
- GAMES_TODO.md:神秘箱子移入已完成区,补全修复记录
2026-03-03 19:29:43 +08:00

670 lines
32 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{{--
文件功能:百家乐前台弹窗组件
聊天室内百家乐游戏面板:
- 监听 WebSocket baccarat.opened 事件触发弹窗
- 倒计时下注(大//豹子)
- 监听 baccarat.settled 展示骰子动画 + 结果 + 个人赔付
- 展示近10局历史趋势
--}}
{{-- ─── 百家乐主面板 ─── --}}
<div id="baccarat-panel" x-data="baccaratPanel()" x-show="show" x-cloak>
<div x-transition:enter="transition ease-out duration-300" x-transition:enter-start="opacity-0 scale-95"
x-transition:enter-end="opacity-100 scale-100" x-transition:leave="transition ease-in duration-200"
x-transition:leave-start="opacity-100 scale-100" x-transition:leave-end="opacity-0 scale-95"
style="position:fixed; inset:0; background:rgba(0,0,0,.7); z-index:9940;
display:flex; align-items:center; justify-content:center;">
<div
style="width:480px; max-width:96vw; border-radius:24px; overflow:hidden;
box-shadow:0 24px 80px rgba(139,92,246,.5); font-family:system-ui,sans-serif;">
{{-- ─── 顶部标题 ─── --}}
<div
style="background:linear-gradient(135deg,#4c1d95,#6d28d9,#7c3aed); padding:18px 22px 14px; position:relative;">
<div style="display:flex; align-items:center; justify-content:space-between;">
<div>
<div style="color:#fff; font-weight:900; font-size:18px; letter-spacing:1px;">🎲 百家乐</div>
<div style="color:rgba(255,255,255,.6); font-size:12px; margin-top:2px;">
<span x-text="'#' + roundId"></span>
</div>
</div>
{{-- 倒计时 --}}
<div x-show="phase === 'betting'" style="text-align:center;">
<div style="color:#fbbf24; font-size:32px; font-weight:900; line-height:1;" x-text="countdown">
</div>
<div style="color:rgba(255,255,255,.5); font-size:11px;">秒后截止</div>
</div>
{{-- 骰子结果 --}}
<div x-show="phase === 'settled'" style="display:none; text-align:center;">
<div style="font-size:28px;" x-text="diceEmoji"></div>
<div style="color:#fbbf24; font-size:12px; font-weight:bold; margin-top:2px;"
x-text="resultLabel"></div>
</div>
</div>
{{-- 进度条 --}}
<div x-show="phase === 'betting'"
style="margin-top:10px; height:4px; background:rgba(255,255,255,.15); border-radius:2px; overflow:hidden;">
<div style="height:100%; background:#fbbf24; border-radius:2px; transition:width 1s linear;"
:style="'width:' + Math.max(0, (countdown / totalSeconds * 100)) + '%'"></div>
</div>
</div>
{{-- ─── 历史趋势 ─── --}}
<div
style="background:#1e1b4b; padding:8px 16px; display:flex; gap:6px; align-items:center; flex-wrap:wrap;">
<span style="color:rgba(255,255,255,.4); font-size:11px; margin-right:2px;">近期</span>
<template x-for="h in history" :key="h.id">
<span
style="width:22px; height:22px; border-radius:50%; font-size:11px; font-weight:bold;
display:flex; align-items:center; justify-content:center;"
:style="h.result === 'big' ? 'background:#1d4ed8; color:#fff' :
h.result === 'small' ? 'background:#b45309; color:#fff' :
h.result === 'triple' ? 'background:#7c3aed; color:#fff' :
'background:#374151; color:#9ca3af'"
:title="'#' + h.id + ' ' + (h.result === 'big' ? '大' : h.result === 'small' ? '小' : h
.result === 'triple' ? '豹' : '☠')"
x-text="h.result === 'big' ? '大' : h.result === 'small' ? '小' : h.result === 'triple' ? '豹' : '☠'">
</span>
</template>
<span x-show="history.length === 0" style="color:rgba(255,255,255,.3); font-size:11px;">暂无记录</span>
</div>
{{-- ─── 主体内容 ─── --}}
<div style="background:linear-gradient(180deg,#1e1b4b,#1a1035); padding:18px 20px;">
{{-- 押注阶段 --}}
<div x-show="phase === 'betting'">
{{-- 当前下注池统计 --}}
<div style="display:grid; grid-template-columns:1fr 1fr 1fr; gap:8px; margin-bottom:14px;">
<div style="background:rgba(29,78,216,.3); border-radius:10px; padding:8px; text-align:center;">
<div style="color:#60a5fa; font-size:11px;">押大</div>
<div style="color:#fff; font-weight:bold; font-size:13px;"
x-text="Number(totalBetBig).toLocaleString()"></div>
</div>
<div style="background:rgba(180,83,9,.3); border-radius:10px; padding:8px; text-align:center;">
<div style="color:#fbbf24; font-size:11px;">押小</div>
<div style="color:#fff; font-weight:bold; font-size:13px;"
x-text="Number(totalBetSmall).toLocaleString()"></div>
</div>
<div
style="background:rgba(124,58,237,.3); border-radius:10px; padding:8px; text-align:center;">
<div style="color:#c4b5fd; font-size:11px;">押豹子</div>
<div style="color:#fff; font-weight:bold; font-size:13px;"
x-text="Number(totalBetTriple).toLocaleString()"></div>
</div>
</div>
{{-- 已下注状态 / 下注表单 --}}
<div x-show="myBet">
<div
style="background:rgba(34,197,94,.15); border:1px solid rgba(34,197,94,.3); border-radius:12px;
padding:12px 16px; text-align:center; margin-bottom:12px;">
<div style="color:#4ade80; font-weight:bold; font-size:14px;">
已押注「<span x-text="betTypeLabel(myBetType)"></span>
<span x-text="Number(myBetAmount).toLocaleString()"></span> 金币
</div>
<div style="color:rgba(255,255,255,.4); font-size:11px; margin-top:4px;">等待开奖中…</div>
</div>
</div>
<div x-show="!myBet">
{{-- 押注选项 --}}
<div style="display:grid; grid-template-columns:1fr 1fr 1fr; gap:8px; margin-bottom:12px;">
{{-- --}}
<button x-on:click="selectedType='big'"
style="border:none; border-radius:12px; padding:12px 0; cursor:pointer; transition:all .15s; font-weight:bold;"
:style="selectedType === 'big' ?
'background:#1d4ed8; color:#fff; transform:scale(1.05); box-shadow:0 4px 20px rgba(29,78,216,.5)' :
'background:rgba(29,78,216,.2); color:#93c5fd;'">
<div style="font-size:20px;">🔵</div>
<div style="font-size:13px; margin-top:2px;"></div>
<div style="font-size:10px; opacity:.7;">11~17 1:1</div>
</button>
{{-- --}}
<button x-on:click="selectedType='small'"
style="border:none; border-radius:12px; padding:12px 0; cursor:pointer; transition:all .15s; font-weight:bold;"
:style="selectedType === 'small' ?
'background:#b45309; color:#fff; transform:scale(1.05); box-shadow:0 4px 20px rgba(180,83,9,.5)' :
'background:rgba(180,83,9,.2); color:#fcd34d;'">
<div style="font-size:20px;">🟡</div>
<div style="font-size:13px; margin-top:2px;"></div>
<div style="font-size:10px; opacity:.7;">4~10 1:1</div>
</button>
{{-- 豹子 --}}
<button x-on:click="selectedType='triple'"
style="border:none; border-radius:12px; padding:12px 0; cursor:pointer; transition:all .15s; font-weight:bold;"
:style="selectedType === 'triple' ?
'background:#7c3aed; color:#fff; transform:scale(1.05); box-shadow:0 4px 20px rgba(124,58,237,.5)' :
'background:rgba(124,58,237,.2); color:#c4b5fd;'">
<div style="font-size:20px;">💥</div>
<div style="font-size:13px; margin-top:2px;">豹子</div>
<div style="font-size:10px; opacity:.7;">三同 1:24</div>
</button>
</div>
{{-- 快捷金额 + 自定义 --}}
<div style="margin-bottom:10px;">
<div style="display:flex; gap:6px; margin-bottom:8px; flex-wrap:wrap;">
<template x-for="preset in [100, 500, 1000, 5000, 10000]" :key="preset">
<button x-on:click="betAmount = preset"
style="flex:1; min-width:50px; border:none; border-radius:8px; padding:6px 4px;
font-size:12px; font-weight:bold; cursor:pointer; transition:all .1s;"
:style="betAmount === preset ?
'background:#fbbf24; color:#1a1035;' :
'background:rgba(255,255,255,.1); color:rgba(255,255,255,.7);'"
x-text="preset >= 1000 ? (preset/1000)+'k' : preset">
</button>
</template>
</div>
<input type="number" x-model.number="betAmount" min="100" placeholder="自定义金额"
style="width:100%; background:rgba(255,255,255,.1); border:1px solid rgba(255,255,255,.15);
border-radius:8px; padding:8px 12px; color:#fff; font-size:13px; box-sizing:border-box;"
x-on:focus="$event.target.select()">
</div>
{{-- 下注按钮 --}}
<button x-on:click="submitBet()" :disabled="!selectedType || betAmount < 100 || submitting"
style="width:100%; border:none; border-radius:12px; padding:14px;
font-size:15px; font-weight:bold; cursor:pointer; transition:all .2s;
letter-spacing:1px;"
:style="(!selectedType || betAmount < 100 || submitting) ? {
background: '#f1f5f9',
color: '#94a3b8',
cursor: 'not-allowed',
boxShadow: 'none'
} : {
background: 'linear-gradient(135deg,#6d28d9,#7c3aed,#8b5cf6)',
color: '#fff',
cursor: 'pointer',
boxShadow: '0 4px 16px rgba(124,58,237,0.5)'
}">
<span
x-text="submitting ? '提交中…' : (!selectedType ? '请先选择大/小/豹子' : '🎲 押注「' + betTypeLabel(selectedType) + '」 ' + Number(betAmount).toLocaleString() + ' 金币')"></span>
</button>
</div>
{{-- 规则提示 --}}
<div style="margin-top:10px; color:rgba(255,255,255,.3); font-size:10px; text-align:center;">
☠️ 3点或18点为庄家收割全灭无退款。豹子优先于大小判断。
</div>
</div>
{{-- 等待开奖阶段 --}}
<div x-show="phase === 'waiting'" style="display:none; text-align:center; padding:16px 0;">
<div style="font-size:40px; animation:spin 1s linear infinite; display:inline-block;">🎲</div>
<div style="color:rgba(255,255,255,.6); margin-top:8px;">正在摇骰子…</div>
</div>
{{-- 结算阶段 --}}
<div x-show="phase === 'settled'" style="display:none;">
{{-- 骰子展示(数字方块,跨平台兼容) --}}
<div style="display:flex; justify-content:center; gap:10px; margin-bottom:14px;">
<template x-for="(d, i) in settledDice" :key="i">
<div style="width:54px; height:54px; border-radius:12px; font-weight:900;
display:flex; align-items:center; justify-content:center;
font-size:26px; box-shadow:0 6px 20px rgba(0,0,0,.5);
animation:dice-pop .4s ease-out both; color:#1e1b4b;
background:linear-gradient(145deg,#fff,#e0e7ff);"
:style="'animation-delay:' + (i * 0.18) + 's'" x-text="d">
</div>
</template>
</div>
{{-- 结果标签 --}}
<div style="text-align:center; margin-bottom:14px;">
<div style="font-size:26px; font-weight:900; letter-spacing:2px;"
:style="settledResult === 'big' ? 'color:#60a5fa' :
settledResult === 'small' ? 'color:#fbbf24' :
settledResult === 'triple' ? 'color:#c4b5fd' :
settledResult === 'kill' ? 'color:#f87171' : 'color:#fbbf24'"
x-text="resultLabel"></div>
<div style="color:rgba(255,255,255,.35); font-size:12px; margin-top:4px;"
x-text="'骰子总点数:' + settledTotal + ' 点'"></div>
</div>
{{-- 个人结果卡片 --}}
<div x-show="myBet">
{{-- 中奖 --}}
<div x-show="myWon"
style="border-radius:14px; overflow:hidden; margin-bottom:4px;
background:linear-gradient(135deg,rgba(16,185,129,.25),rgba(5,150,105,.15));
border:1px solid rgba(52,211,153,.4);">
<div style="padding:14px 16px; text-align:center;">
<div style="font-size:32px; margin-bottom:4px;">🎉</div>
<div style="color:#34d399; font-size:18px; font-weight:900;">恭喜中奖!</div>
<div style="color:#6ee7b7; font-size:24px; font-weight:bold; margin:6px 0;"
x-text="'+' + Number(myPayout).toLocaleString() + ' 🪙'"></div>
<div style="color:rgba(255,255,255,.4); font-size:11px;"
x-text="'押「' + betTypeLabel(myBetType) + '」' + Number(myBetAmount).toLocaleString() + ' 金币 → 赢得 ' + Number(myPayout).toLocaleString() + ' 金币'">
</div>
</div>
</div>
{{-- 未中奖 --}}
<div x-show="!myWon"
style="border-radius:14px; overflow:hidden; margin-bottom:4px;
background:linear-gradient(135deg,rgba(239,68,68,.15),rgba(185,28,28,.1));
border:1px solid rgba(248,113,113,.25);">
<div style="padding:14px 16px; text-align:center;">
<div style="font-size:28px; margin-bottom:4px;">😔</div>
<div style="color:#f87171; font-size:16px; font-weight:bold; margin-bottom:6px;">本局未中奖
</div>
<div
style="display:inline-flex; align-items:center; gap:8px;
background:rgba(0,0,0,.2); border-radius:20px; padding:5px 14px;">
<span style="color:rgba(255,255,255,.5); font-size:12px;">你押了</span>
<span style="font-weight:bold; font-size:13px; color:#fbbf24;"
x-text="betTypeLabel(myBetType)"></span>
<span style="color:rgba(255,255,255,.3); font-size:11px;">·</span>
<span style="color:rgba(255,255,255,.5); font-size:12px;">开了</span>
<span style="font-weight:bold; font-size:13px;"
:style="settledResult === 'big' ? 'color:#60a5fa' :
settledResult === 'small' ? 'color:#fbbf24' :
settledResult === 'triple' ? 'color:#c4b5fd' : 'color:#f87171'"
x-text="resultLabel"></span>
</div>
<div style="color:rgba(255,255,255,.3); font-size:11px; margin-top:8px;"
x-text="'损失 ' + Number(myBetAmount).toLocaleString() + ' 金币'"></div>
</div>
</div>
</div>
{{-- 未下注但看结果 --}}
<div x-show="!myBet"
style="text-align:center; color:rgba(255,255,255,.3); font-size:12px; padding:8px 0;">
本局未参与下注
</div>
</div>
</div>
{{-- ─── 底部关闭 ─── --}}
<div style="background:rgba(15,10,40,.95); padding:10px 20px; display:flex; justify-content:center;">
<button x-on:click="close()"
style="padding:7px 28px; background:rgba(255,255,255,.08); border:none; border-radius:20px;
font-size:12px; color:rgba(255,255,255,.5); cursor:pointer; transition:all .15s;"
onmouseover="this.style.background='rgba(255,255,255,.15)'"
onmouseout="this.style.background='rgba(255,255,255,.08)'">
关闭
</button>
</div>
</div>
</div>
</div>
{{-- ─── 骨骰悬浮入口(游戏开启时常驻,支持拖拽) ─── --}}
<div id="baccarat-fab" x-data="baccaratFab()" x-show="visible" x-cloak
:style="'position:fixed; right:' + posX + 'px; bottom:' + posY + 'px; z-index:9900; touch-action:none; user-select:none;'"
@pointerdown.prevent="startDrag($event)" @pointermove.window="onDrag($event)" @pointerup.window="endDrag($event)"
@pointercancel.window="endDrag($event)">
<button
style="width:52px; height:52px; border-radius:50%; border:none;
background:linear-gradient(135deg,#7c3aed,#4f46e5);
box-shadow:0 4px 20px rgba(124,58,237,.5);
font-size:22px; display:flex; align-items:center; justify-content:center;
animation:pulse-fab 2s infinite; user-select:none;"
:style="dragging ? 'cursor:grabbing;' : 'cursor:grab;'" title="百家乐下注中(可拖动)">🎲</button>
</div>
<script>
/**
* 百家乐骨骰悬浮按钮 Alpine 组件(拖动 + localStorage 位置持久化)
*/
function baccaratFab() {
const STORAGE_KEY = 'baccarat_fab_pos';
const saved = JSON.parse(localStorage.getItem(STORAGE_KEY) || 'null');
return {
visible: false,
posX: saved?.x ?? 18,
posY: saved?.y ?? 90,
dragging: false,
_startX: 0,
_startY: 0,
_origX: 0,
_origY: 0,
_moved: false,
startDrag(e) {
this.dragging = true;
this._moved = false;
this._startX = e.clientX;
this._startY = e.clientY;
this._origX = this.posX;
this._origY = this.posY;
e.currentTarget.setPointerCapture?.(e.pointerId);
},
onDrag(e) {
if (!this.dragging) return;
const dx = e.clientX - this._startX;
const dy = e.clientY - this._startY;
if (Math.abs(dx) > 3 || Math.abs(dy) > 3) this._moved = true;
this.posX = Math.max(4, Math.min(window.innerWidth - 60, this._origX - dx));
this.posY = Math.max(4, Math.min(window.innerHeight - 60, this._origY + dy));
},
endDrag(e) {
if (!this.dragging) return;
this.dragging = false;
localStorage.setItem(STORAGE_KEY, JSON.stringify({
x: this.posX,
y: this.posY
}));
if (!this._moved) this.openPanel();
},
openPanel() {
const panel = document.getElementById('baccarat-panel');
if (!panel) return;
const p = Alpine.$data(panel);
p.show = true;
if (p.phase === 'betting' && p.countdown > 0 && !p.countdownTimer) {
p.startCountdown();
}
},
};
}
</script>
<style>
@keyframes spin {
to {
transform: rotate(360deg);
}
}
@keyframes dice-pop {
0% {
transform: scale(0) rotate(-20deg);
opacity: 0;
}
70% {
transform: scale(1.15) rotate(5deg);
}
100% {
transform: scale(1) rotate(0deg);
opacity: 1;
}
}
@keyframes pulse-fab {
0%,
100% {
box-shadow: 0 4px 20px rgba(124, 58, 237, .5);
}
50% {
box-shadow: 0 4px 30px rgba(124, 58, 237, .9);
}
}
</style>
<script>
/**
* 百家乐游戏面板 Alpine 组件
*/
function baccaratPanel() {
return {
show: false,
phase: 'betting', // betting | waiting | settled
roundId: null,
totalSeconds: 60,
countdown: 60,
countdownTimer: null,
// 下注池统计
totalBetBig: 0,
totalBetSmall: 0,
totalBetTriple: 0,
// 本人下注
myBet: false,
myBetType: '',
myBetAmount: 0,
// 下注表单
selectedType: '',
betAmount: 100,
submitting: false,
// 结算结果
settledDice: [],
settledTotal: 0,
settledResult: '',
resultLabel: '',
diceEmoji: '',
myWon: false,
myPayout: 0,
// 历史记录
history: [],
/**
* 开局:填充局次数据并开始倒计时
*/
openRound(data) {
this.phase = 'betting';
this.roundId = data.round_id;
this.countdown = data.bet_seconds || 60;
this.totalSeconds = this.countdown;
this.myBet = false;
this.myBetType = '';
this.myBetAmount = 0;
this.settledDice = [];
this.selectedType = '';
this.betAmount = 100;
this.show = true;
this.loadCurrentRound();
this.startCountdown();
this.updateFab(true);
},
/**
* 从接口获取当前局的状态(我的下注、投注池)
*/
async loadCurrentRound() {
try {
const res = await fetch('/baccarat/current');
const data = await res.json();
if (data.round) {
this.totalBetBig = data.round.total_bet_big;
this.totalBetSmall = data.round.total_bet_small;
this.totalBetTriple = data.round.total_bet_triple;
if (data.round.my_bet) {
this.myBet = true;
this.myBetType = data.round.my_bet.bet_type;
this.myBetAmount = data.round.my_bet.amount;
}
}
} catch {}
},
/**
* 启动倒计时
*/
startCountdown() {
clearInterval(this.countdownTimer);
this.countdownTimer = setInterval(() => {
this.countdown--;
if (this.countdown <= 0) {
clearInterval(this.countdownTimer);
this.phase = 'waiting';
}
}, 1000);
},
/**
* 提交下注
*/
async submitBet() {
if (!this.selectedType || this.betAmount < 100 || this.submitting) return;
this.submitting = true;
try {
const res = await fetch('/baccarat/bet', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name=csrf-token]')?.content,
},
body: JSON.stringify({
round_id: this.roundId,
bet_type: this.selectedType,
amount: this.betAmount,
}),
});
const data = await res.json();
if (data.ok) {
this.myBet = true;
this.myBetType = data.bet_type;
this.myBetAmount = data.amount;
window.chatDialog?.alert(data.message, '下注成功', '#7c3aed');
} else {
window.chatDialog?.alert(data.message || '下注失败', '提示', '#ef4444');
}
} catch {
window.chatDialog?.alert('网络异常,请稍后重试。', '错误', '#ef4444');
}
this.submitting = false;
},
/**
* 显示开奖结果动画
*/
showResult(data) {
clearInterval(this.countdownTimer);
this.settledDice = data.dice;
this.settledTotal = data.total_points;
this.settledResult = data.result;
this.resultLabel = data.result_label;
this.phase = 'settled';
this.show = true;
// 判断本人是否中奖(从后端拿到的 result 与我的下注 type 比较)
if (this.myBet && this.myBetType === data.result && data.result !== 'kill') {
this.myWon = true;
// 简单计算前端显示赔付(实际赔付以后端为准)
const payoutRate = data.result === 'triple' ? 24 : 1;
this.myPayout = this.myBetAmount * (payoutRate + 1);
} else {
this.myWon = false;
this.myPayout = 0;
}
this.updateFab(false);
this.loadHistory();
},
/**
* 加载历史趋势
*/
async loadHistory() {
try {
const res = await fetch('/baccarat/history');
const data = await res.json();
this.history = (data.history || []).reverse();
} catch {}
},
/**
* 更新悬浮按钮显示状态
*/
updateFab(visible) {
const fab = document.getElementById('baccarat-fab');
if (fab) Alpine.$data(fab).visible = visible;
},
/**
* 关闭面板
*/
close() {
this.show = false;
if (this.phase === 'betting') {
this.updateFab(true); // 还在下注阶段时保留悬浮按钮
}
},
/**
* 押注类型中文标签
*/
betTypeLabel(type) {
return {
big: '大',
small: '小',
triple: '豹子'
} [type] || '';
},
};
}
// ─── WebSocket 监听 ──────────────────────────────────────────────
/** 收到开局事件:弹出押注面板 */
window.addEventListener('chat:baccarat.opened', (e) => {
const panel = document.getElementById('baccarat-panel');
if (panel) Alpine.$data(panel).openRound(e.detail);
});
/** 收到结算事件:展示骰子动画和结果 */
window.addEventListener('chat:baccarat.settled', (e) => {
const panel = document.getElementById('baccarat-panel');
if (panel) Alpine.$data(panel).showResult(e.detail);
});
/** 页面加载时:检查是否有进行中的局,有则自动恢复面板 */
document.addEventListener('DOMContentLoaded', async () => {
try {
// 先加载历史趋势
const histRes = await fetch('/baccarat/history');
const histData = await histRes.json();
const panel = document.getElementById('baccarat-panel');
if (panel) {
Alpine.$data(panel).history = (histData.history || []).reverse();
}
// 再检查是否有正在进行的局
const curRes = await fetch('/baccarat/current');
const curData = await curRes.json();
if (curData.round && panel) {
const round = curData.round;
const seconds = round.seconds_left || 0;
const panelData = Alpine.$data(panel);
if (seconds > 0) {
// 有进行中的局且还在押注时间内 → 恢复押注面板
panelData.phase = 'betting';
panelData.roundId = round.id;
panelData.totalSeconds = 60; // 服务端配置的窗口
panelData.countdown = seconds;
panelData.totalBetBig = round.total_bet_big;
panelData.totalBetSmall = round.total_bet_small;
panelData.totalBetTriple = round.total_bet_triple;
if (round.my_bet) {
panelData.myBet = true;
panelData.myBetType = round.my_bet.bet_type;
panelData.myBetAmount = round.my_bet.amount;
}
// 只显示悬浮按钮,不自动弹出全屏(避免打扰刚进入的用户)
panelData.updateFab(true);
}
}
} catch (e) {
console.warn('[百家乐] 初始化失败', e);
}
});
</script>