// 游戏管理后台事件代理,逐步替代 game-configs Blade 内联脚本。 let adminGameConfigControlsBound = false; const MYSTERY_BOX_META = { normal: { name: "普通箱", icon: "📦" }, rare: { name: "稀有箱", icon: "💎" }, trap: { name: "黑化箱", icon: "☠️" }, }; /** * 读取后台 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 `
${card.icon} ${card.title}
${card.items.map((item) => `
${item.label} ${item.value}
`).join("")}
`; } /** * 将游戏统计接口响应转换为顶部统计卡片。 * * @param {Record>} 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} */ 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 = '
⏳ 加载中...
'; 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 = '
❌ 加载失败,请重试
'; } } /** * 根据开关接口响应更新游戏卡片状态。 * * @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} */ 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; } } /** * 管理员手动投放神秘箱子。 * * @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( `确定要向 #1 房间 投放一个「${meta.name}」吗?
箱子投放后将立即在公屏广播暗号,用户限时领取。`, `投放${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} */ 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 = '⏳ 加载中…'; try { const response = await fetch(currentUrl, { headers: { "Accept": "application/json" }, }); const data = await response.json(); if (!data.issue) { statusPanel.innerHTML = '⚠️ 当前无进行中期次,可手动开新期'; return; } const issue = data.issue; const pool = Number(issue.pool_amount).toLocaleString(); const statusMap = { open: "🟢 购票中", closed: "🔴 已停售", settled: "✅ 已开奖", }; const superTag = issue.is_super_issue ? " 🎊超级期" : ""; const drawAt = issue.draw_at ? issue.draw_at.replace("T", " ") : "--"; statusPanel.innerHTML = `第 ${issue.issue_no} 期${superTag}  ·  状态:${statusMap[issue.status] || issue.status}  ·  奖池:💰 ${pool} 金币  ·  预计开奖:${drawAt}  ·  已购:${issue.total_tickets || 0} 注`; } catch (error) { statusPanel.innerHTML = '❌ 加载失败'; } } /** * 手动开启新一期双色球。 * * @param {HTMLButtonElement} button 开期按钮 * @returns {void} */ function openLotteryIssue(button) { const openUrl = button.getAttribute("data-game-lottery-open-url") || ""; if (!openUrl) { return; } window.adminDialog?.confirm( '确定要手动开启新一期双色球吗?
仅在无进行中期次时生效,开奖时间将使用当前配置的 draw_hour:draw_minute。', "手动开新期", () => { 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( '确定要立即强制开奖吗?
将对当前 closed 或 open 期次立即执行开奖,此操作不可撤销!', "强制开奖确认", () => { 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("网络错误,请重试", "网络错误", "🌐")); }, "🎊", ); } /** * 绑定游戏管理页通用操作按钮。 * * @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); 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"; } }); }