Files
chatroom/resources/js/admin/game-configs.js
T

254 lines
8.7 KiB
JavaScript

// 游戏管理后台事件代理,逐步替代 game-configs Blade 内联脚本。
let adminGameConfigControlsBound = false;
/**
* 读取后台 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;
}
}
/**
* 绑定游戏管理页通用操作按钮。
*
* @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);
}
});
}