迁移百家乐主面板脚本
This commit is contained in:
@@ -23,6 +23,7 @@
|
||||
* - user-target-actions.js:处理点击用户名切换私聊目标和打开名片。
|
||||
* - welcome-menu.js:处理欢迎菜单交互。
|
||||
* - admin-menu.js:处理聊天室管理菜单交互。
|
||||
* - baccarat-panel.js:提供百家乐主面板 Alpine 组件和下注流程。
|
||||
* - baccarat-fab.js:处理百家乐悬浮按钮拖动与打开面板。
|
||||
* - baccarat-events.js:处理百家乐广播事件和页面恢复当前局。
|
||||
* - baccarat-loss-cover-admin.js:处理百家乐买单活动管理弹层。
|
||||
@@ -93,6 +94,7 @@ export { bindToolbarControls, runFeatureShortcut, runToolbarAction } from "./cha
|
||||
export { bindUserTargetActions, openUserCard, switchTarget } from "./chat-room/user-target-actions.js";
|
||||
export { bindWelcomeMenuControls } from "./chat-room/welcome-menu.js";
|
||||
export { bindAdminMenuControls } from "./chat-room/admin-menu.js";
|
||||
export { baccaratPanel, bindBaccaratPanelControls } from "./chat-room/baccarat-panel.js";
|
||||
export { baccaratFab, bindBaccaratFabControls } from "./chat-room/baccarat-fab.js";
|
||||
export { bindBaccaratEvents } from "./chat-room/baccarat-events.js";
|
||||
export {
|
||||
@@ -260,6 +262,7 @@ import { bindToolbarControls, runFeatureShortcut, runToolbarAction } from "./cha
|
||||
import { bindUserTargetActions, openUserCard, switchTarget } from "./chat-room/user-target-actions.js";
|
||||
import { bindWelcomeMenuControls } from "./chat-room/welcome-menu.js";
|
||||
import { bindAdminMenuControls } from "./chat-room/admin-menu.js";
|
||||
import { baccaratPanel, bindBaccaratPanelControls } from "./chat-room/baccarat-panel.js";
|
||||
import { baccaratFab, bindBaccaratFabControls } from "./chat-room/baccarat-fab.js";
|
||||
import { bindBaccaratEvents } from "./chat-room/baccarat-events.js";
|
||||
import {
|
||||
@@ -437,6 +440,8 @@ if (typeof window !== "undefined") {
|
||||
switchTarget,
|
||||
bindWelcomeMenuControls,
|
||||
bindAdminMenuControls,
|
||||
baccaratPanel,
|
||||
bindBaccaratPanelControls,
|
||||
baccaratFab,
|
||||
bindBaccaratFabControls,
|
||||
bindBaccaratEvents,
|
||||
@@ -602,6 +607,7 @@ if (typeof window !== "undefined") {
|
||||
window.scheduleRenderMobileUserList = scheduleRenderMobileUserList;
|
||||
window.switchMobileTab = switchMobileTab;
|
||||
window.switchTarget = switchTarget;
|
||||
window.baccaratPanel = baccaratPanel;
|
||||
window.baccaratFab = baccaratFab;
|
||||
window.clearChatBotContext = clearChatBotContext;
|
||||
window.sendToChatBot = sendToChatBot;
|
||||
@@ -714,6 +720,7 @@ if (typeof window !== "undefined") {
|
||||
bindToolbarControls();
|
||||
bindUserTargetActions();
|
||||
bindAdminMenuControls();
|
||||
bindBaccaratPanelControls();
|
||||
bindBaccaratFabControls();
|
||||
bindBaccaratEvents();
|
||||
bindBaccaratLossCoverAdminControls();
|
||||
|
||||
@@ -0,0 +1,448 @@
|
||||
// 乐彩百家乐主面板 Alpine 组件,负责当前局状态、下注、开奖结果和历史趋势。
|
||||
|
||||
const DEFAULT_BACCARAT_CURRENT_URL = "/baccarat/current";
|
||||
const DEFAULT_BACCARAT_BET_URL = "/baccarat/bet";
|
||||
const DEFAULT_BACCARAT_HISTORY_URL = "/baccarat/history";
|
||||
const DEFAULT_BACCARAT_MIN_BET = 100;
|
||||
const DEFAULT_BACCARAT_MAX_BET = 50000;
|
||||
const DEFAULT_BACCARAT_TOTAL_SECONDS = 60;
|
||||
const RESULT_LABELS = {
|
||||
big: "大",
|
||||
small: "小",
|
||||
triple: "豹子",
|
||||
};
|
||||
|
||||
/**
|
||||
* 读取百家乐接口地址,优先使用 Blade 写入的命名路由。
|
||||
*
|
||||
* @returns {{current: string, bet: string, history: string}}
|
||||
*/
|
||||
function baccaratUrls() {
|
||||
const panel = document.getElementById("baccarat-panel");
|
||||
|
||||
return {
|
||||
current: panel?.dataset.baccaratCurrentUrl || DEFAULT_BACCARAT_CURRENT_URL,
|
||||
bet: panel?.dataset.baccaratBetUrl || DEFAULT_BACCARAT_BET_URL,
|
||||
history: panel?.dataset.baccaratHistoryUrl || DEFAULT_BACCARAT_HISTORY_URL,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取 CSRF Token。
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
function csrfToken() {
|
||||
return document.querySelector('meta[name="csrf-token"]')?.content ?? "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成快捷下注金额,保持最小值、最大值和中间档位可快速选择。
|
||||
*
|
||||
* @param {number} min 最小下注额
|
||||
* @param {number} max 最大下注额
|
||||
* @returns {number[]}
|
||||
*/
|
||||
function buildQuickBetAmounts(min, max) {
|
||||
const candidates = [1, 2, 5, 10, 20, 50, 100, 200, 500, 1000];
|
||||
const steps = candidates
|
||||
.map((multiplier) => min * multiplier)
|
||||
.filter((value) => value >= min && value < max);
|
||||
let sortedSteps = [...new Set([...steps, max])].sort((a, b) => a - b);
|
||||
|
||||
if (sortedSteps.length >= 5) {
|
||||
return [
|
||||
sortedSteps[0],
|
||||
sortedSteps[Math.floor((sortedSteps.length - 1) * 0.25)],
|
||||
sortedSteps[Math.floor((sortedSteps.length - 1) * 0.5)],
|
||||
sortedSteps[Math.floor((sortedSteps.length - 1) * 0.75)],
|
||||
sortedSteps[sortedSteps.length - 1],
|
||||
];
|
||||
}
|
||||
|
||||
// 下注范围很小时补齐中间档位,避免模板渲染时按钮数量跳变。
|
||||
while (sortedSteps.length < 5) {
|
||||
let maxGap = 0;
|
||||
let insertIndex = -1;
|
||||
|
||||
for (let index = 0; index < sortedSteps.length - 1; index += 1) {
|
||||
if (sortedSteps[index + 1] - sortedSteps[index] > maxGap) {
|
||||
maxGap = sortedSteps[index + 1] - sortedSteps[index];
|
||||
insertIndex = index;
|
||||
}
|
||||
}
|
||||
|
||||
if (insertIndex === -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
let newValue = Math.floor((sortedSteps[insertIndex] + sortedSteps[insertIndex + 1]) / 2);
|
||||
if (newValue > 100) {
|
||||
newValue = Math.floor(newValue / 10) * 10;
|
||||
}
|
||||
sortedSteps.splice(insertIndex + 1, 0, newValue);
|
||||
}
|
||||
|
||||
return sortedSteps;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建百家乐游戏面板 Alpine 组件。
|
||||
*
|
||||
* @returns {Record<string, any>}
|
||||
*/
|
||||
export function baccaratPanel() {
|
||||
return {
|
||||
show: false,
|
||||
phase: "idle",
|
||||
roundId: null,
|
||||
totalSeconds: DEFAULT_BACCARAT_TOTAL_SECONDS,
|
||||
countdown: DEFAULT_BACCARAT_TOTAL_SECONDS,
|
||||
countdownTimer: null,
|
||||
totalBetBig: 0,
|
||||
totalBetSmall: 0,
|
||||
totalBetTriple: 0,
|
||||
betCountBig: 0,
|
||||
betCountSmall: 0,
|
||||
betCountTriple: 0,
|
||||
myBet: false,
|
||||
myBetType: "",
|
||||
myBetAmount: 0,
|
||||
selectedType: "",
|
||||
betAmount: DEFAULT_BACCARAT_MIN_BET,
|
||||
minBet: DEFAULT_BACCARAT_MIN_BET,
|
||||
maxBet: DEFAULT_BACCARAT_MAX_BET,
|
||||
submitting: false,
|
||||
settledDice: [],
|
||||
settledTotal: 0,
|
||||
settledResult: "",
|
||||
resultLabel: "",
|
||||
diceEmoji: "",
|
||||
myWon: false,
|
||||
myPayout: 0,
|
||||
history: [],
|
||||
autoCloseTimer: null,
|
||||
autoCloseCountdown: 0,
|
||||
|
||||
/**
|
||||
* 同步全局聊天上下文中的金币余额,供弹窗右上角与其他面板共用。
|
||||
*
|
||||
* @param {number|string|null|undefined} jjb 最新金币余额
|
||||
* @returns {void}
|
||||
*/
|
||||
syncUserGold(jjb) {
|
||||
if (jjb === undefined || jjb === null || !window.chatContext) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.chatContext.userJjb = Number(jjb);
|
||||
window.chatContext.myGold = Number(jjb);
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取快捷下注金额数组。
|
||||
*
|
||||
* @returns {number[]}
|
||||
*/
|
||||
get quickBetAmounts() {
|
||||
return buildQuickBetAmounts(this.minBet || DEFAULT_BACCARAT_MIN_BET, this.maxBet || DEFAULT_BACCARAT_MAX_BET);
|
||||
},
|
||||
|
||||
/**
|
||||
* 从大厅或通知点击打开。
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
openFromHall() {
|
||||
this.show = true;
|
||||
this.loadCurrentRound();
|
||||
},
|
||||
|
||||
/**
|
||||
* 重置为未开局状态。
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
setIdleState() {
|
||||
clearInterval(this.countdownTimer);
|
||||
clearInterval(this.autoCloseTimer);
|
||||
this.countdownTimer = null;
|
||||
this.autoCloseTimer = null;
|
||||
this.phase = "idle";
|
||||
this.roundId = null;
|
||||
this.countdown = 0;
|
||||
this.autoCloseCountdown = 0;
|
||||
this.totalBetBig = 0;
|
||||
this.totalBetSmall = 0;
|
||||
this.totalBetTriple = 0;
|
||||
this.betCountBig = 0;
|
||||
this.betCountSmall = 0;
|
||||
this.betCountTriple = 0;
|
||||
this.myBet = false;
|
||||
this.myBetType = "";
|
||||
this.myBetAmount = 0;
|
||||
this.selectedType = "";
|
||||
this.settledDice = [];
|
||||
this.settledTotal = 0;
|
||||
this.settledResult = "";
|
||||
this.resultLabel = "";
|
||||
this.diceEmoji = "";
|
||||
this.myWon = false;
|
||||
this.myPayout = 0;
|
||||
this.updateFab(false);
|
||||
},
|
||||
|
||||
/**
|
||||
* 开局:填充局次数据并开始倒计时。
|
||||
*
|
||||
* @param {Record<string, any>} data 广播局次数据
|
||||
* @returns {void}
|
||||
*/
|
||||
openRound(data) {
|
||||
this.phase = "betting";
|
||||
this.roundId = data.round_id;
|
||||
this.countdown = data.bet_seconds || DEFAULT_BACCARAT_TOTAL_SECONDS;
|
||||
this.totalSeconds = this.countdown;
|
||||
this.myBet = false;
|
||||
this.myBetType = "";
|
||||
this.myBetAmount = 0;
|
||||
this.settledDice = [];
|
||||
this.selectedType = "";
|
||||
this.betAmount = this.minBet || DEFAULT_BACCARAT_MIN_BET;
|
||||
this.betCountBig = 0;
|
||||
this.betCountSmall = 0;
|
||||
this.betCountTriple = 0;
|
||||
this.show = true;
|
||||
|
||||
this.loadCurrentRound();
|
||||
this.startCountdown();
|
||||
this.updateFab(true);
|
||||
},
|
||||
|
||||
/**
|
||||
* 从接口获取当前局状态,包括我的下注和投注池。
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async loadCurrentRound() {
|
||||
try {
|
||||
const response = await fetch(baccaratUrls().current);
|
||||
const data = await response.json();
|
||||
this.syncUserGold(data.jjb);
|
||||
|
||||
if (data.round && (data.round.seconds_left || 0) > 0) {
|
||||
this.phase = "betting";
|
||||
this.roundId = data.round.id;
|
||||
this.countdown = data.round.seconds_left || this.countdown || 0;
|
||||
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;
|
||||
this.minBet = data.round.min_bet || DEFAULT_BACCARAT_MIN_BET;
|
||||
this.maxBet = data.round.max_bet || DEFAULT_BACCARAT_MAX_BET;
|
||||
|
||||
if (data.round.my_bet) {
|
||||
this.myBet = true;
|
||||
this.myBetType = data.round.my_bet.bet_type;
|
||||
this.myBetAmount = data.round.my_bet.amount;
|
||||
} else {
|
||||
this.myBet = false;
|
||||
this.myBetType = "";
|
||||
this.myBetAmount = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.setIdleState();
|
||||
} catch (error) {
|
||||
this.setIdleState();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 启动下注倒计时。
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
startCountdown() {
|
||||
clearInterval(this.countdownTimer);
|
||||
this.countdownTimer = setInterval(() => {
|
||||
this.countdown -= 1;
|
||||
if (this.countdown <= 0) {
|
||||
clearInterval(this.countdownTimer);
|
||||
this.countdownTimer = null;
|
||||
this.phase = "waiting";
|
||||
}
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
/**
|
||||
* 提交本局下注。
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async submitBet() {
|
||||
if (!this.roundId || !this.selectedType || this.betAmount < DEFAULT_BACCARAT_MIN_BET || this.submitting) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.submitting = true;
|
||||
|
||||
try {
|
||||
const response = await fetch(baccaratUrls().bet, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
"X-CSRF-TOKEN": csrfToken(),
|
||||
},
|
||||
body: JSON.stringify({
|
||||
round_id: this.roundId,
|
||||
bet_type: this.selectedType,
|
||||
amount: this.betAmount,
|
||||
}),
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok && data.ok) {
|
||||
this.myBet = true;
|
||||
this.myBetType = data.bet_type;
|
||||
this.myBetAmount = data.amount;
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.status === 422 && data.errors) {
|
||||
const firstError = Object.values(data.errors)[0][0];
|
||||
window.chatDialog?.alert(firstError, "下注验证失败", "#ef4444");
|
||||
return;
|
||||
}
|
||||
|
||||
window.chatDialog?.alert(data.message || "下注失败", "提示", "#ef4444");
|
||||
} catch (error) {
|
||||
window.chatDialog?.alert("网络异常,请稍后重试。", "错误", "#ef4444");
|
||||
} finally {
|
||||
this.submitting = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 显示开奖结果动画,并按本人是否下注决定是否弹出结算面板。
|
||||
*
|
||||
* @param {Record<string, any>} data 开奖广播数据
|
||||
* @returns {void}
|
||||
*/
|
||||
showResult(data) {
|
||||
clearInterval(this.countdownTimer);
|
||||
this.countdownTimer = null;
|
||||
this.settledDice = data.dice;
|
||||
this.settledTotal = data.total_points;
|
||||
this.settledResult = data.result;
|
||||
this.resultLabel = data.result_label;
|
||||
this.phase = "settled";
|
||||
|
||||
if (this.myBet) {
|
||||
this.show = true;
|
||||
}
|
||||
|
||||
if (this.myBet && this.myBetType === data.result && data.result !== "kill") {
|
||||
this.myWon = true;
|
||||
this.myPayout = this.myBetAmount * (data.result === "triple" ? 25 : 2);
|
||||
} else {
|
||||
this.myWon = false;
|
||||
this.myPayout = 0;
|
||||
}
|
||||
|
||||
this.updateFab(false);
|
||||
this.loadHistory();
|
||||
this.startAutoCloseCountdown();
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载历史趋势。
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async loadHistory() {
|
||||
try {
|
||||
const response = await fetch(baccaratUrls().history);
|
||||
const data = await response.json();
|
||||
this.history = (data.history || []).reverse();
|
||||
} catch (error) {
|
||||
// 历史趋势失败不阻塞下注主流程。
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新悬浮按钮显示状态。
|
||||
*
|
||||
* @param {boolean} visible 是否显示
|
||||
* @returns {void}
|
||||
*/
|
||||
updateFab(visible) {
|
||||
const fab = document.getElementById("baccarat-fab");
|
||||
|
||||
if (fab && typeof window.Alpine?.$data === "function") {
|
||||
window.Alpine.$data(fab).visible = visible;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 关闭面板;下注中保留悬浮按钮方便再次打开。
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
close() {
|
||||
clearInterval(this.autoCloseTimer);
|
||||
this.autoCloseTimer = null;
|
||||
this.autoCloseCountdown = 0;
|
||||
this.show = false;
|
||||
|
||||
if (this.phase === "betting") {
|
||||
this.updateFab(true);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 押注类型中文标签。
|
||||
*
|
||||
* @param {string} type 押注类型
|
||||
* @returns {string}
|
||||
*/
|
||||
betTypeLabel(type) {
|
||||
return RESULT_LABELS[type] || "";
|
||||
},
|
||||
|
||||
/**
|
||||
* 启动结算后的自动关闭倒计时。
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
startAutoCloseCountdown() {
|
||||
this.autoCloseCountdown = 10;
|
||||
clearInterval(this.autoCloseTimer);
|
||||
this.autoCloseTimer = setInterval(() => {
|
||||
this.autoCloseCountdown -= 1;
|
||||
if (this.autoCloseCountdown <= 0) {
|
||||
clearInterval(this.autoCloseTimer);
|
||||
this.autoCloseTimer = null;
|
||||
this.close();
|
||||
}
|
||||
}, 1000);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 挂载百家乐主面板全局组件名,兼容 Blade 的 x-data。
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
export function bindBaccaratPanelControls() {
|
||||
if (typeof window === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
window.baccaratPanel = baccaratPanel;
|
||||
}
|
||||
@@ -9,7 +9,13 @@
|
||||
--}}
|
||||
|
||||
{{-- 百家乐主面板 --}}
|
||||
<div id="baccarat-panel" x-data="baccaratPanel()" x-show="show" x-cloak>
|
||||
<div id="baccarat-panel"
|
||||
x-data="baccaratPanel()"
|
||||
x-show="show"
|
||||
x-cloak
|
||||
data-baccarat-current-url="{{ route('baccarat.current') }}"
|
||||
data-baccarat-bet-url="{{ route('baccarat.bet') }}"
|
||||
data-baccarat-history-url="{{ route('baccarat.history') }}">
|
||||
<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"
|
||||
@@ -371,350 +377,5 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 百家乐游戏面板 Alpine 组件
|
||||
*/
|
||||
function baccaratPanel() {
|
||||
return {
|
||||
show: false,
|
||||
phase: 'idle', // idle | betting | waiting | settled
|
||||
|
||||
roundId: null,
|
||||
totalSeconds: 60,
|
||||
countdown: 60,
|
||||
countdownTimer: null,
|
||||
|
||||
// 下注池统计
|
||||
totalBetBig: 0,
|
||||
totalBetSmall: 0,
|
||||
totalBetTriple: 0,
|
||||
|
||||
// 押注人数统计
|
||||
betCountBig: 0,
|
||||
betCountSmall: 0,
|
||||
betCountTriple: 0,
|
||||
|
||||
// 本人下注
|
||||
myBet: false,
|
||||
myBetType: '',
|
||||
myBetAmount: 0,
|
||||
|
||||
// 下注表单
|
||||
selectedType: '',
|
||||
betAmount: 100,
|
||||
minBet: 100,
|
||||
maxBet: 50000,
|
||||
submitting: false,
|
||||
|
||||
// 结算结果
|
||||
settledDice: [],
|
||||
settledTotal: 0,
|
||||
settledResult: '',
|
||||
resultLabel: '',
|
||||
diceEmoji: '',
|
||||
myWon: false,
|
||||
myPayout: 0,
|
||||
|
||||
// 历史记录
|
||||
history: [],
|
||||
|
||||
// 结算后定时自动关闭
|
||||
autoCloseTimer: null,
|
||||
autoCloseCountdown: 0,
|
||||
|
||||
/**
|
||||
* 同步全局聊天上下文中的金币余额,供弹窗右上角与其他面板共用。
|
||||
*/
|
||||
syncUserGold(jjb) {
|
||||
if (jjb === undefined || jjb === null) return;
|
||||
if (!window.chatContext) return;
|
||||
window.chatContext.userJjb = Number(jjb);
|
||||
window.chatContext.myGold = Number(jjb);
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取快捷下注金额数组
|
||||
*/
|
||||
get quickBetAmounts() {
|
||||
const min = this.minBet || 100;
|
||||
const max = this.maxBet || 50000;
|
||||
|
||||
// 预设候选倍数,尽量生成美观的数字
|
||||
const candidates = [1, 2, 5, 10, 20, 50, 100, 200, 500, 1000];
|
||||
let steps = candidates.map(m => min * m).filter(v => v >= min && v < max);
|
||||
steps.push(max);
|
||||
steps = [...new Set(steps)].sort((a, b) => a - b);
|
||||
|
||||
if (steps.length >= 5) {
|
||||
// 如果候选值足够多,均匀采样,确保首尾是最小值和最大值
|
||||
return [
|
||||
steps[0],
|
||||
steps[Math.floor((steps.length - 1) * 0.25)],
|
||||
steps[Math.floor((steps.length - 1) * 0.5)],
|
||||
steps[Math.floor((steps.length - 1) * 0.75)],
|
||||
steps[steps.length - 1]
|
||||
];
|
||||
} else {
|
||||
// 如果候选值不足5个(范围太小),通过线性插值补齐
|
||||
while (steps.length < 5) {
|
||||
let maxGap = 0;
|
||||
let insertIdx = -1;
|
||||
for (let i = 0; i < steps.length - 1; i++) {
|
||||
if (steps[i+1] - steps[i] > maxGap) {
|
||||
maxGap = steps[i+1] - steps[i];
|
||||
insertIdx = i;
|
||||
}
|
||||
}
|
||||
if (insertIdx === -1) break;
|
||||
let newVal = Math.floor((steps[insertIdx] + steps[insertIdx+1]) / 2);
|
||||
if (newVal > 100) newVal = Math.floor(newVal / 10) * 10; // 简单取整
|
||||
steps.splice(insertIdx + 1, 0, newVal);
|
||||
}
|
||||
return steps;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 从大厅或通知点击打开
|
||||
*/
|
||||
openFromHall() {
|
||||
this.show = true;
|
||||
this.loadCurrentRound();
|
||||
},
|
||||
|
||||
/**
|
||||
* 自动重置为未开局空状态
|
||||
*/
|
||||
setIdleState() {
|
||||
clearInterval(this.countdownTimer);
|
||||
clearInterval(this.autoCloseTimer);
|
||||
this.phase = 'idle';
|
||||
this.roundId = null;
|
||||
this.countdown = 0;
|
||||
this.autoCloseCountdown = 0;
|
||||
this.totalBetBig = 0;
|
||||
this.totalBetSmall = 0;
|
||||
this.totalBetTriple = 0;
|
||||
this.betCountBig = 0;
|
||||
this.betCountSmall = 0;
|
||||
this.betCountTriple = 0;
|
||||
this.myBet = false;
|
||||
this.myBetType = '';
|
||||
this.myBetAmount = 0;
|
||||
this.selectedType = '';
|
||||
this.settledDice = [];
|
||||
this.settledTotal = 0;
|
||||
this.settledResult = '';
|
||||
this.resultLabel = '';
|
||||
this.diceEmoji = '';
|
||||
this.myWon = false;
|
||||
this.myPayout = 0;
|
||||
this.updateFab(false);
|
||||
},
|
||||
|
||||
/**
|
||||
* 开局:填充局次数据并开始倒计时
|
||||
*/
|
||||
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.betCountBig = 0;
|
||||
this.betCountSmall = 0;
|
||||
this.betCountTriple = 0;
|
||||
this.show = true;
|
||||
|
||||
this.loadCurrentRound();
|
||||
this.startCountdown();
|
||||
this.updateFab(true);
|
||||
},
|
||||
|
||||
/**
|
||||
* 从接口获取当前局的状态(我的下注、投注池)
|
||||
*/
|
||||
async loadCurrentRound() {
|
||||
try {
|
||||
const res = await fetch('/baccarat/current');
|
||||
const data = await res.json();
|
||||
// 每次打开或刷新局次信息时,都用服务端最新金币覆盖右上角余额。
|
||||
this.syncUserGold(data.jjb);
|
||||
if (data.round && (data.round.seconds_left || 0) > 0) {
|
||||
this.phase = 'betting';
|
||||
this.roundId = data.round.id;
|
||||
this.countdown = data.round.seconds_left || this.countdown || 0;
|
||||
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;
|
||||
this.minBet = data.round.min_bet || 100;
|
||||
this.maxBet = data.round.max_bet || 50000;
|
||||
if (data.round.my_bet) {
|
||||
this.myBet = true;
|
||||
this.myBetType = data.round.my_bet.bet_type;
|
||||
this.myBetAmount = data.round.my_bet.amount;
|
||||
} else {
|
||||
this.myBet = false;
|
||||
this.myBetType = '';
|
||||
this.myBetAmount = 0;
|
||||
}
|
||||
} else {
|
||||
this.setIdleState();
|
||||
}
|
||||
} catch {
|
||||
this.setIdleState();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 启动倒计时
|
||||
*/
|
||||
startCountdown() {
|
||||
clearInterval(this.countdownTimer);
|
||||
this.countdownTimer = setInterval(() => {
|
||||
this.countdown--;
|
||||
if (this.countdown <= 0) {
|
||||
clearInterval(this.countdownTimer);
|
||||
this.phase = 'waiting';
|
||||
}
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
/**
|
||||
* 提交下注
|
||||
*/
|
||||
async submitBet() {
|
||||
if (!this.roundId || !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 (res.ok && data.ok) {
|
||||
this.myBet = true;
|
||||
this.myBetType = data.bet_type;
|
||||
this.myBetAmount = data.amount;
|
||||
} 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');
|
||||
}
|
||||
} 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';
|
||||
// 只有本局有押注的用户才弹出结算面板
|
||||
if (this.myBet) {
|
||||
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();
|
||||
|
||||
// 结算后 10 秒自动关闭
|
||||
this.autoCloseCountdown = 10;
|
||||
clearInterval(this.autoCloseTimer);
|
||||
this.autoCloseTimer = setInterval(() => {
|
||||
this.autoCloseCountdown--;
|
||||
if (this.autoCloseCountdown <= 0) {
|
||||
clearInterval(this.autoCloseTimer);
|
||||
this.close();
|
||||
}
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载历史趋势
|
||||
*/
|
||||
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() {
|
||||
// 手动关闭时清除自动关闭定时器
|
||||
clearInterval(this.autoCloseTimer);
|
||||
this.autoCloseCountdown = 0;
|
||||
this.show = false;
|
||||
if (this.phase === 'betting') {
|
||||
this.updateFab(true); // 还在下注阶段时保留悬浮按钮
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 押注类型中文标签
|
||||
*/
|
||||
betTypeLabel(type) {
|
||||
return {
|
||||
big: '大',
|
||||
small: '小',
|
||||
triple: '豹子'
|
||||
} [type] || '';
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
{{-- 乐彩百家乐广播监听和页面恢复逻辑已迁移到 resources/js/chat-room/baccarat-events.js --}}
|
||||
</script>
|
||||
{{-- 乐彩百家乐主面板脚本已迁移到 resources/js/chat-room/baccarat-panel.js --}}
|
||||
|
||||
Reference in New Issue
Block a user