迁移游戏配置统计与开关
This commit is contained in:
@@ -0,0 +1,253 @@
|
||||
// 游戏管理后台事件代理,逐步替代 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
import './bootstrap';
|
||||
import { bindAdminAutoactControls } from './admin/autoact.js';
|
||||
import { bindAdminFishingEventsControls } from './admin/fishing-events.js';
|
||||
import { bindAdminGameConfigControls } from './admin/game-configs.js';
|
||||
import { bindAdminSignInRulesControls } from './admin/sign-in-rules.js';
|
||||
|
||||
// 后台共用入口只注册轻量事件代理,具体页面通过 data-* 属性决定是否响应。
|
||||
bindAdminAutoactControls();
|
||||
bindAdminFishingEventsControls();
|
||||
bindAdminGameConfigControls();
|
||||
bindAdminSignInRulesControls();
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<h2 class="text-lg font-bold text-gray-800">🎮 游戏管理</h2>
|
||||
<p class="text-xs text-gray-500 mt-1">统一管理聊天室所有娱乐游戏的开关状态与核心参数,所有游戏默认关闭。</p>
|
||||
</div>
|
||||
<button onclick="loadGameStats()"
|
||||
<button type="button" data-game-stats-url="{{ route('admin.game-history.stats') }}"
|
||||
class="px-4 py-2 bg-indigo-50 text-indigo-700 rounded-lg text-sm font-bold hover:bg-indigo-100 transition">
|
||||
📊 加载实时统计
|
||||
</button>
|
||||
@@ -70,7 +70,8 @@
|
||||
</a>
|
||||
@endif
|
||||
{{-- 大开关按钮 --}}
|
||||
<button onclick="toggleGame('{{ $game->game_key }}', {{ $game->id }})"
|
||||
<button type="button" data-game-toggle-url="{{ route('admin.game-configs.toggle', $game) }}"
|
||||
data-game-key="{{ $game->game_key }}"
|
||||
id="toggle-btn-{{ $game->game_key }}"
|
||||
class="px-5 py-2 rounded-lg font-bold text-sm transition shadow-sm
|
||||
{{ $game->enabled ? 'bg-red-500 hover:bg-red-600 text-white' : 'bg-emerald-500 hover:bg-emerald-600 text-white' }}">
|
||||
@@ -219,50 +220,6 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 切换游戏开启/关闭状态
|
||||
*/
|
||||
function toggleGame(gameKey, gameId) {
|
||||
fetch(`/admin/game-configs/${gameId}/toggle`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (!data.ok) return;
|
||||
|
||||
const enabled = data.enabled;
|
||||
const card = document.getElementById(`game-card-${gameKey}`);
|
||||
const badge = document.getElementById(`badge-${gameKey}`);
|
||||
const btn = document.getElementById(`toggle-btn-${gameKey}`);
|
||||
const header = card?.querySelector('.flex.items-center.justify-between');
|
||||
|
||||
// 更新徽章
|
||||
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'}`;
|
||||
|
||||
// 更新按钮
|
||||
btn.textContent = enabled ? '⏸ 关闭游戏' : '▶ 开启游戏';
|
||||
btn.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);
|
||||
}
|
||||
|
||||
// 全局弹窗提示
|
||||
window.adminDialog.alert(data.message, enabled ? '游戏已开启' : '游戏已关闭', enabled ? '✅' : '⏸');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理员手动投放神秘箱子
|
||||
*
|
||||
@@ -396,174 +353,6 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 加载各游戏实时统计摘要并渲染到顶部面板
|
||||
*/
|
||||
function loadGameStats() {
|
||||
const panel = document.getElementById('game-stats-panel');
|
||||
const grid = document.getElementById('game-stats-grid');
|
||||
|
||||
grid.innerHTML = '<div class="col-span-5 text-center text-gray-400 text-sm py-4">⏳ 加载中...</div>';
|
||||
panel.classList.remove('hidden');
|
||||
|
||||
fetch('{{ route('admin.game-history.stats') }}', {
|
||||
headers: {
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(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',
|
||||
}
|
||||
];
|
||||
|
||||
grid.innerHTML = cards.map(card => `
|
||||
<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>
|
||||
`).join('');
|
||||
})
|
||||
.catch(() => {
|
||||
grid.innerHTML = '<div class="col-span-5 text-center text-red-400 text-sm py-4">❌ 加载失败,请重试</div>';
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@endsection
|
||||
|
||||
@php
|
||||
|
||||
Reference in New Issue
Block a user