2026-04-25 13:21:45 +08:00
|
|
|
|
// 游戏管理后台事件代理,逐步替代 game-configs Blade 内联脚本。
|
|
|
|
|
|
|
|
|
|
|
|
let adminGameConfigControlsBound = false;
|
|
|
|
|
|
|
2026-04-25 13:23:20 +08:00
|
|
|
|
const MYSTERY_BOX_META = {
|
|
|
|
|
|
normal: { name: "普通箱", icon: "📦" },
|
|
|
|
|
|
rare: { name: "稀有箱", icon: "💎" },
|
|
|
|
|
|
trap: { name: "黑化箱", icon: "☠️" },
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-04-25 13:21:45 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 读取后台 layout 注入的 CSRF token。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @returns {string}
|
|
|
|
|
|
*/
|
|
|
|
|
|
function getCsrfToken() {
|
|
|
|
|
|
return document.querySelector('meta[name="csrf-token"]')?.getAttribute("content") || "";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 生成单个统计卡片 HTML。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {{icon:string,title:string,color:string,items:Array<{label:string,value:string|number}>}} card 统计卡片
|
|
|
|
|
|
* @returns {string}
|
|
|
|
|
|
*/
|
|
|
|
|
|
function renderStatsCard(card) {
|
|
|
|
|
|
return `
|
|
|
|
|
|
<div class="bg-white rounded-xl shadow-sm border ${card.color} p-4">
|
|
|
|
|
|
<div class="flex items-center gap-2 mb-3">
|
|
|
|
|
|
<span class="text-xl">${card.icon}</span>
|
|
|
|
|
|
<span class="font-bold text-gray-700 text-sm">${card.title}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="space-y-1.5">
|
|
|
|
|
|
${card.items.map((item) => `
|
|
|
|
|
|
<div class="flex justify-between text-xs">
|
|
|
|
|
|
<span class="text-gray-500">${item.label}</span>
|
|
|
|
|
|
<span class="font-bold text-gray-700">${item.value}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
`).join("")}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 将游戏统计接口响应转换为顶部统计卡片。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {Record<string, Record<string, number>>} data 统计接口响应
|
|
|
|
|
|
* @returns {string}
|
|
|
|
|
|
*/
|
|
|
|
|
|
function renderGameStats(data) {
|
|
|
|
|
|
const cards = [
|
|
|
|
|
|
{
|
|
|
|
|
|
icon: "🎲",
|
|
|
|
|
|
title: "百家乐",
|
|
|
|
|
|
items: [
|
|
|
|
|
|
{ label: "总局数", value: data.baccarat.total_rounds.toLocaleString() },
|
|
|
|
|
|
{ label: "总下注", value: `${data.baccarat.total_bets.toLocaleString()} 笔` },
|
|
|
|
|
|
{ label: "今日局数", value: data.baccarat.today_rounds.toLocaleString() },
|
|
|
|
|
|
],
|
|
|
|
|
|
color: "border-red-200 bg-red-50",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
icon: "🎰",
|
|
|
|
|
|
title: "老虎机",
|
|
|
|
|
|
items: [
|
|
|
|
|
|
{ label: "总转动", value: `${data.slot.total_spins.toLocaleString()} 次` },
|
|
|
|
|
|
{ label: "三7大奖", value: `${data.slot.jackpot_count.toLocaleString()} 次` },
|
|
|
|
|
|
{ label: "今日转动", value: data.slot.today_spins.toLocaleString() },
|
|
|
|
|
|
],
|
|
|
|
|
|
color: "border-amber-200 bg-amber-50",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
icon: "🐎",
|
|
|
|
|
|
title: "赛马竞猜",
|
|
|
|
|
|
items: [
|
|
|
|
|
|
{ label: "总场次", value: data.horse.total_races.toLocaleString() },
|
|
|
|
|
|
{ label: "总注池", value: `${data.horse.total_pool.toLocaleString()} 金` },
|
|
|
|
|
|
{ label: "今日场次", value: data.horse.today_races.toLocaleString() },
|
|
|
|
|
|
],
|
|
|
|
|
|
color: "border-emerald-200 bg-emerald-50",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
icon: "📦",
|
|
|
|
|
|
title: "神秘箱子",
|
|
|
|
|
|
items: [
|
|
|
|
|
|
{ label: "总投放", value: data.mystery_box.total_dropped.toLocaleString() },
|
|
|
|
|
|
{ label: "已领取", value: data.mystery_box.total_claimed.toLocaleString() },
|
|
|
|
|
|
{ label: "今日投放", value: data.mystery_box.today_dropped.toLocaleString() },
|
|
|
|
|
|
],
|
|
|
|
|
|
color: "border-purple-200 bg-purple-50",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
icon: "🔮",
|
|
|
|
|
|
title: "神秘占卜",
|
|
|
|
|
|
items: [
|
|
|
|
|
|
{ label: "总占卜", value: `${data.fortune.total_times.toLocaleString()} 次` },
|
|
|
|
|
|
{ label: "吉签/凶签", value: `${data.fortune.jackpot_count} / ${data.fortune.curse_count}` },
|
|
|
|
|
|
{ label: "今日占卜", value: data.fortune.today_times.toLocaleString() },
|
|
|
|
|
|
],
|
|
|
|
|
|
color: "border-fuchsia-200 bg-fuchsia-50",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
icon: "🎟️",
|
|
|
|
|
|
title: "双色球彩票",
|
|
|
|
|
|
items: [
|
|
|
|
|
|
{ label: "总期数", value: `${data.lottery.total_issues.toLocaleString()} 期` },
|
|
|
|
|
|
{ label: "历史彩票", value: `${data.lottery.total_bets.toLocaleString()} 张` },
|
|
|
|
|
|
{ label: "累计奖池", value: `${data.lottery.total_pool.toLocaleString()} 金` },
|
|
|
|
|
|
],
|
|
|
|
|
|
color: "border-rose-200 bg-rose-50",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
icon: "♟️",
|
|
|
|
|
|
title: "五子棋",
|
|
|
|
|
|
items: [
|
|
|
|
|
|
{ label: "总对局", value: `${data.gomoku.total_games.toLocaleString()} 局` },
|
|
|
|
|
|
{ label: "人机/对战", value: `${data.gomoku.pve_count} / ${data.gomoku.pvp_count}` },
|
|
|
|
|
|
{ label: "今日对局", value: data.gomoku.today_games.toLocaleString() },
|
|
|
|
|
|
],
|
|
|
|
|
|
color: "border-blue-200 bg-blue-50",
|
|
|
|
|
|
},
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
return cards.map((card) => renderStatsCard(card)).join("");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 加载各游戏实时统计摘要并渲染到顶部面板。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {HTMLButtonElement} button 触发按钮
|
|
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function loadGameStats(button) {
|
|
|
|
|
|
const statsUrl = button.getAttribute("data-game-stats-url") || "";
|
|
|
|
|
|
const panel = document.getElementById("game-stats-panel");
|
|
|
|
|
|
const grid = document.getElementById("game-stats-grid");
|
|
|
|
|
|
|
|
|
|
|
|
if (!statsUrl || !panel || !grid) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
grid.innerHTML = '<div class="col-span-5 text-center text-gray-400 text-sm py-4">⏳ 加载中...</div>';
|
|
|
|
|
|
panel.classList.remove("hidden");
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch(statsUrl, {
|
|
|
|
|
|
headers: { "Accept": "application/json" },
|
|
|
|
|
|
});
|
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
|
|
grid.innerHTML = renderGameStats(data);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
grid.innerHTML = '<div class="col-span-5 text-center text-red-400 text-sm py-4">❌ 加载失败,请重试</div>';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 根据开关接口响应更新游戏卡片状态。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {HTMLButtonElement} button 开关按钮
|
|
|
|
|
|
* @param {boolean} enabled 是否启用
|
|
|
|
|
|
* @returns {void}
|
|
|
|
|
|
*/
|
|
|
|
|
|
function updateGameToggleState(button, enabled) {
|
|
|
|
|
|
const gameKey = button.getAttribute("data-game-key") || "";
|
|
|
|
|
|
const card = document.getElementById(`game-card-${gameKey}`);
|
|
|
|
|
|
const badge = document.getElementById(`badge-${gameKey}`);
|
|
|
|
|
|
const header = card?.querySelector(".flex.items-center.justify-between");
|
|
|
|
|
|
|
|
|
|
|
|
if (badge) {
|
|
|
|
|
|
badge.textContent = enabled ? "运行中" : "已关闭";
|
|
|
|
|
|
badge.className = `text-xs px-2 py-0.5 rounded-full font-bold ${
|
|
|
|
|
|
enabled ? "bg-emerald-100 text-emerald-700" : "bg-gray-200 text-gray-500"
|
|
|
|
|
|
}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
button.textContent = enabled ? "⏸ 关闭游戏" : "▶ 开启游戏";
|
|
|
|
|
|
button.className = `px-5 py-2 rounded-lg font-bold text-sm transition shadow-sm ${
|
|
|
|
|
|
enabled ? "bg-red-500 hover:bg-red-600 text-white" : "bg-emerald-500 hover:bg-emerald-600 text-white"
|
|
|
|
|
|
}`;
|
|
|
|
|
|
|
|
|
|
|
|
// 卡片头部背景是游戏状态的主要视觉反馈,需要和按钮、徽章同步。
|
|
|
|
|
|
if (header) {
|
|
|
|
|
|
header.classList.toggle("bg-emerald-50", enabled);
|
|
|
|
|
|
header.classList.toggle("bg-gray-50", !enabled);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 切换游戏开启/关闭状态。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {HTMLButtonElement} button 开关按钮
|
|
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function toggleGame(button) {
|
|
|
|
|
|
const toggleUrl = button.getAttribute("data-game-toggle-url") || "";
|
|
|
|
|
|
|
|
|
|
|
|
if (!toggleUrl || button.disabled) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
button.disabled = true;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch(toggleUrl, {
|
|
|
|
|
|
method: "POST",
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
"X-CSRF-TOKEN": getCsrfToken(),
|
|
|
|
|
|
"Accept": "application/json",
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
|
|
if (!data?.ok) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
updateGameToggleState(button, Boolean(data.enabled));
|
|
|
|
|
|
window.adminDialog?.alert(
|
|
|
|
|
|
data.message,
|
|
|
|
|
|
data.enabled ? "游戏已开启" : "游戏已关闭",
|
|
|
|
|
|
data.enabled ? "✅" : "⏸",
|
|
|
|
|
|
);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
button.disabled = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-25 13:23:20 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 管理员手动投放神秘箱子。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {HTMLButtonElement} button 投放按钮
|
|
|
|
|
|
* @returns {void}
|
|
|
|
|
|
*/
|
|
|
|
|
|
function dropMysteryBox(button) {
|
|
|
|
|
|
const boxType = button.getAttribute("data-game-drop-box-type") || "normal";
|
|
|
|
|
|
const dropUrl = button.getAttribute("data-game-drop-box-url") || "";
|
|
|
|
|
|
const meta = MYSTERY_BOX_META[boxType] || MYSTERY_BOX_META.normal;
|
|
|
|
|
|
|
|
|
|
|
|
if (!dropUrl) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
window.adminDialog?.confirm(
|
|
|
|
|
|
`确定要向 <b>#1 房间</b> 投放一个「${meta.name}」吗?<br><span style="color:#64748b; font-size:12px;">箱子投放后将立即在公屏广播暗号,用户限时领取。</span>`,
|
|
|
|
|
|
`投放${meta.name}`,
|
|
|
|
|
|
() => {
|
|
|
|
|
|
fetch(dropUrl, {
|
|
|
|
|
|
method: "POST",
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
"X-CSRF-TOKEN": getCsrfToken(),
|
|
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
|
|
"Accept": "application/json",
|
|
|
|
|
|
},
|
|
|
|
|
|
body: JSON.stringify({ box_type: boxType }),
|
|
|
|
|
|
})
|
|
|
|
|
|
.then((response) => response.json())
|
|
|
|
|
|
.then((data) => {
|
|
|
|
|
|
window.adminDialog?.alert(
|
|
|
|
|
|
data.message || (data.ok ? "投放成功!" : "投放失败"),
|
|
|
|
|
|
data.ok ? "投放成功" : "投放失败",
|
|
|
|
|
|
data.ok ? meta.icon : "❌",
|
|
|
|
|
|
);
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch(() => window.adminDialog?.alert("网络错误,请重试", "网络错误", "🌐"));
|
|
|
|
|
|
},
|
|
|
|
|
|
meta.icon,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 加载双色球当前期次状态。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {HTMLButtonElement} button 状态加载按钮
|
|
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function loadLotteryStatus(button) {
|
|
|
|
|
|
const currentUrl = button.getAttribute("data-game-lottery-current-url") || "";
|
|
|
|
|
|
const statusPanel = document.getElementById("lottery-issue-status");
|
|
|
|
|
|
|
|
|
|
|
|
if (!currentUrl || !statusPanel) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
statusPanel.innerHTML = '<span class="text-gray-400">⏳ 加载中…</span>';
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch(currentUrl, {
|
|
|
|
|
|
headers: { "Accept": "application/json" },
|
|
|
|
|
|
});
|
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
|
|
if (!data.issue) {
|
|
|
|
|
|
statusPanel.innerHTML = '<span class="text-orange-500">⚠️ 当前无进行中期次,可手动开新期</span>';
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const issue = data.issue;
|
|
|
|
|
|
const pool = Number(issue.pool_amount).toLocaleString();
|
|
|
|
|
|
const statusMap = {
|
|
|
|
|
|
open: "🟢 购票中",
|
|
|
|
|
|
closed: "🔴 已停售",
|
|
|
|
|
|
settled: "✅ 已开奖",
|
|
|
|
|
|
};
|
|
|
|
|
|
const superTag = issue.is_super_issue ? " 🎊<b>超级期</b>" : "";
|
|
|
|
|
|
const drawAt = issue.draw_at ? issue.draw_at.replace("T", " ") : "--";
|
|
|
|
|
|
|
|
|
|
|
|
statusPanel.innerHTML = `第 <b>${issue.issue_no}</b> 期${superTag} · 状态:${statusMap[issue.status] || issue.status}
|
|
|
|
|
|
· 奖池:<b style="color:#dc2626">💰 ${pool} 金币</b>
|
|
|
|
|
|
· 预计开奖:${drawAt}
|
|
|
|
|
|
· 已购:${issue.total_tickets || 0} 注`;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
statusPanel.innerHTML = '<span class="text-red-400">❌ 加载失败</span>';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 手动开启新一期双色球。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {HTMLButtonElement} button 开期按钮
|
|
|
|
|
|
* @returns {void}
|
|
|
|
|
|
*/
|
|
|
|
|
|
function openLotteryIssue(button) {
|
|
|
|
|
|
const openUrl = button.getAttribute("data-game-lottery-open-url") || "";
|
|
|
|
|
|
|
|
|
|
|
|
if (!openUrl) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
window.adminDialog?.confirm(
|
|
|
|
|
|
'确定要手动开启新一期双色球吗?<br><span style="color:#64748b;font-size:12px;">仅在无进行中期次时生效,开奖时间将使用当前配置的 draw_hour:draw_minute。</span>',
|
|
|
|
|
|
"手动开新期",
|
|
|
|
|
|
() => {
|
|
|
|
|
|
fetch(openUrl, {
|
|
|
|
|
|
method: "POST",
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
"X-CSRF-TOKEN": getCsrfToken(),
|
|
|
|
|
|
"Accept": "application/json",
|
|
|
|
|
|
},
|
|
|
|
|
|
})
|
|
|
|
|
|
.then((response) => response.json())
|
|
|
|
|
|
.then((data) => {
|
|
|
|
|
|
window.adminDialog?.alert(data.message, data.ok ? "操作成功" : "操作失败", data.ok ? "✅" : "❌");
|
|
|
|
|
|
if (data.ok) {
|
|
|
|
|
|
const statusButton = document.querySelector("[data-game-lottery-current-url]");
|
|
|
|
|
|
if (statusButton instanceof HTMLButtonElement) {
|
|
|
|
|
|
void loadLotteryStatus(statusButton);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch(() => window.adminDialog?.alert("网络错误,请重试", "网络错误", "🌐"));
|
|
|
|
|
|
},
|
|
|
|
|
|
"➕",
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 强制提前开奖,用于测试或管理需要。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {HTMLButtonElement} button 强制开奖按钮
|
|
|
|
|
|
* @returns {void}
|
|
|
|
|
|
*/
|
|
|
|
|
|
function forceLotteryDraw(button) {
|
|
|
|
|
|
const drawUrl = button.getAttribute("data-game-lottery-force-url") || "";
|
|
|
|
|
|
|
|
|
|
|
|
if (!drawUrl) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
window.adminDialog?.confirm(
|
|
|
|
|
|
'确定要<b style="color:red">立即强制开奖</b>吗?<br><span style="color:#64748b;font-size:12px;">将对当前 closed 或 open 期次立即执行开奖,此操作不可撤销!</span>',
|
|
|
|
|
|
"强制开奖确认",
|
|
|
|
|
|
() => {
|
|
|
|
|
|
fetch(drawUrl, {
|
|
|
|
|
|
method: "POST",
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
"X-CSRF-TOKEN": getCsrfToken(),
|
|
|
|
|
|
"Accept": "application/json",
|
|
|
|
|
|
},
|
|
|
|
|
|
})
|
|
|
|
|
|
.then((response) => response.json())
|
|
|
|
|
|
.then((data) => {
|
|
|
|
|
|
window.adminDialog?.alert(data.message, data.ok ? "开奖完成" : "操作失败", data.ok ? "🎊" : "❌");
|
|
|
|
|
|
if (data.ok) {
|
|
|
|
|
|
const statusButton = document.querySelector("[data-game-lottery-current-url]");
|
|
|
|
|
|
if (statusButton instanceof HTMLButtonElement) {
|
|
|
|
|
|
void loadLotteryStatus(statusButton);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch(() => window.adminDialog?.alert("网络错误,请重试", "网络错误", "🌐"));
|
|
|
|
|
|
},
|
|
|
|
|
|
"🎊",
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-25 13:21:45 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 绑定游戏管理页通用操作按钮。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @returns {void}
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function bindAdminGameConfigControls() {
|
|
|
|
|
|
if (adminGameConfigControlsBound || typeof document === "undefined") {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
adminGameConfigControlsBound = true;
|
|
|
|
|
|
document.addEventListener("click", (event) => {
|
|
|
|
|
|
if (!(event.target instanceof Element)) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const statsButton = event.target.closest("[data-game-stats-url]");
|
|
|
|
|
|
if (statsButton instanceof HTMLButtonElement) {
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
void loadGameStats(statsButton);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const toggleButton = event.target.closest("[data-game-toggle-url]");
|
|
|
|
|
|
if (toggleButton instanceof HTMLButtonElement) {
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
void toggleGame(toggleButton);
|
2026-04-25 13:23:20 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const dropBoxButton = event.target.closest("[data-game-drop-box-type]");
|
|
|
|
|
|
if (dropBoxButton instanceof HTMLButtonElement) {
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
dropMysteryBox(dropBoxButton);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const lotteryStatusButton = event.target.closest("[data-game-lottery-current-url]");
|
|
|
|
|
|
if (lotteryStatusButton instanceof HTMLButtonElement) {
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
void loadLotteryStatus(lotteryStatusButton);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const lotteryOpenButton = event.target.closest("[data-game-lottery-open-url]");
|
|
|
|
|
|
if (lotteryOpenButton instanceof HTMLButtonElement) {
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
openLotteryIssue(lotteryOpenButton);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const lotteryForceButton = event.target.closest("[data-game-lottery-force-url]");
|
|
|
|
|
|
if (lotteryForceButton instanceof HTMLButtonElement) {
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
forceLotteryDraw(lotteryForceButton);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener("mouseover", (event) => {
|
|
|
|
|
|
if (!(event.target instanceof Element)) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const hoverButton = event.target.closest("[data-game-hover-opacity]");
|
|
|
|
|
|
if (hoverButton instanceof HTMLElement) {
|
|
|
|
|
|
hoverButton.style.opacity = hoverButton.getAttribute("data-game-hover-opacity") || "";
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener("mouseout", (event) => {
|
|
|
|
|
|
if (!(event.target instanceof Element)) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const hoverButton = event.target.closest("[data-game-hover-opacity]");
|
|
|
|
|
|
if (hoverButton instanceof HTMLElement) {
|
|
|
|
|
|
hoverButton.style.opacity = "1";
|
2026-04-25 13:21:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|