迁移娱乐大厅脚本
This commit is contained in:
@@ -44,7 +44,7 @@ export {
|
||||
openBaccaratLossCoverModal,
|
||||
switchBaccaratLossCoverTab,
|
||||
} from "./chat-room/baccarat-loss-cover.js";
|
||||
export { bindGameHallControls } from "./chat-room/game-hall.js";
|
||||
export { bindGameHallControls, closeGameHall, openGameHall } from "./chat-room/game-hall.js";
|
||||
export { bindGameBootstrapControls, deferChatGameBootstrap } from "./chat-room/game-bootstrap.js";
|
||||
export { bindGamePanelControls } from "./chat-room/game-panels.js";
|
||||
export { bindHolidayModalControls, openHolidayRunFromSystemMessage } from "./chat-room/holiday-modal.js";
|
||||
@@ -134,7 +134,7 @@ import {
|
||||
openBaccaratLossCoverModal,
|
||||
switchBaccaratLossCoverTab,
|
||||
} from "./chat-room/baccarat-loss-cover.js";
|
||||
import { bindGameHallControls } from "./chat-room/game-hall.js";
|
||||
import { bindGameHallControls, closeGameHall, openGameHall } from "./chat-room/game-hall.js";
|
||||
import { bindGameBootstrapControls, deferChatGameBootstrap } from "./chat-room/game-bootstrap.js";
|
||||
import { bindGamePanelControls } from "./chat-room/game-panels.js";
|
||||
import { bindHolidayModalControls, openHolidayRunFromSystemMessage } from "./chat-room/holiday-modal.js";
|
||||
@@ -234,6 +234,8 @@ if (typeof window !== "undefined") {
|
||||
openBaccaratLossCoverModal,
|
||||
switchBaccaratLossCoverTab,
|
||||
bindGameHallControls,
|
||||
closeGameHall,
|
||||
openGameHall,
|
||||
bindGameBootstrapControls,
|
||||
deferChatGameBootstrap,
|
||||
bindGamePanelControls,
|
||||
@@ -324,8 +326,10 @@ if (typeof window !== "undefined") {
|
||||
window.bankLoadInfo = bankLoadInfo;
|
||||
window.bankShowMsg = bankShowMsg;
|
||||
window.closeBankModal = closeBankModal;
|
||||
window.closeGameHall = closeGameHall;
|
||||
window.fetchBankRanking = fetchBankRanking;
|
||||
window.deferChatGameBootstrap = deferChatGameBootstrap;
|
||||
window.openGameHall = openGameHall;
|
||||
window.openBankModal = openBankModal;
|
||||
window.switchBankTab = switchBankTab;
|
||||
window.toggleBankRankSort = toggleBankRankSort;
|
||||
|
||||
@@ -1,16 +1,555 @@
|
||||
// 娱乐大厅弹窗事件代理,替代静态关闭按钮内联 onclick。
|
||||
// 聊天室娱乐大厅弹窗逻辑,集中加载游戏状态并渲染入口卡片。
|
||||
|
||||
import { escapeHtml } from "./html.js";
|
||||
|
||||
const GAME_HALL_CACHE_TTL = 15000;
|
||||
|
||||
let gameHallEventsBound = false;
|
||||
let gameHallStatusCache = null;
|
||||
let gameHallStatusCacheAt = 0;
|
||||
|
||||
/**
|
||||
* 关闭娱乐大厅弹窗。
|
||||
* 获取游戏大厅弹窗元素。
|
||||
*
|
||||
* @returns {HTMLElement|null}
|
||||
*/
|
||||
function getGameHallModal() {
|
||||
return document.getElementById("game-hall-modal");
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取服务端注入的游戏开关快照。
|
||||
*
|
||||
* @returns {Record<string, boolean>}
|
||||
*/
|
||||
function readGameEnabledSnapshot() {
|
||||
const modal = getGameHallModal();
|
||||
if (!modal?.dataset.gameEnabled) {
|
||||
return {};
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(modal.dataset.gameEnabled);
|
||||
} catch (error) {
|
||||
console.warn("[游戏大厅] 游戏开关数据解析失败:", error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开 Alpine 管理的游戏面板。
|
||||
*
|
||||
* @param {string} panelId
|
||||
* @param {string} [method]
|
||||
* @returns {void}
|
||||
*/
|
||||
function openAlpinePanel(panelId, method = "show") {
|
||||
const panel = document.getElementById(panelId);
|
||||
if (!panel || !window.Alpine) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = window.Alpine.$data(panel);
|
||||
if (typeof data?.[method] === "function") {
|
||||
data[method]();
|
||||
return;
|
||||
}
|
||||
|
||||
data[method] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 游戏大厅配置定义。
|
||||
*
|
||||
* @returns {Array<object>}
|
||||
*/
|
||||
function createGameDefinitions() {
|
||||
return [
|
||||
{
|
||||
id: "baccarat",
|
||||
name: "🎲 百家乐",
|
||||
desc: "猜骰子大小,1:1 赔率,豹子 1:24",
|
||||
accentColor: "#336699",
|
||||
fetchUrl: "/baccarat/current",
|
||||
openFn: () => {
|
||||
closeGameHall();
|
||||
openAlpinePanel("baccarat-panel");
|
||||
},
|
||||
renderStatus: (data) => {
|
||||
if (!data?.round) {
|
||||
return {
|
||||
badge: "⏸ 等待开局",
|
||||
badgeStyle: "background:#e8f0f8; color:#336699; border:1px solid #b8d0e8",
|
||||
detail: "下局即将开始,稍后再来",
|
||||
};
|
||||
}
|
||||
|
||||
const round = data.round;
|
||||
if (round.status === "betting") {
|
||||
return {
|
||||
badge: "🟢 押注中",
|
||||
badgeStyle: "background:#d1fae5; color:#065f46; border:1px solid #6ee7b7",
|
||||
detail: `⏱ 剩余 ${round.seconds_left || 0} 秒 | 注池:${Number((round.total_bet_big || 0) + (round.total_bet_small || 0) + (round.total_bet_triple || 0)).toLocaleString()} 金`,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
badge: "⏳ 开奖中",
|
||||
badgeStyle: "background:#fef3c7; color:#92400e; border:1px solid #fcd34d",
|
||||
detail: "正在摇骰子…",
|
||||
};
|
||||
},
|
||||
btnLabel: (data) => data?.round?.status === "betting" ? "🎲 立即下注" : "📊 查看详情",
|
||||
},
|
||||
{
|
||||
id: "baccarat_loss_cover",
|
||||
name: "🎁 买单活动",
|
||||
desc: "查看“你玩游戏我买单”活动,补偿领取和个人历史记录",
|
||||
accentColor: "#16a34a",
|
||||
fetchUrl: "/baccarat-loss-cover/summary",
|
||||
openFn: () => {
|
||||
closeGameHall();
|
||||
void window.openBaccaratLossCoverModal?.("overview");
|
||||
},
|
||||
renderStatus: (data) => {
|
||||
const event = data?.event;
|
||||
if (!event) {
|
||||
return {
|
||||
badge: "📭 暂无活动",
|
||||
badgeStyle: "background:#e8f0f8; color:#336699; border:1px solid #b8d0e8",
|
||||
detail: "当前没有进行中或待领取的买单活动",
|
||||
};
|
||||
}
|
||||
|
||||
const myStatus = event.my_record?.claim_status_label || "未参与";
|
||||
const total = Number(event.total_claimed_amount || 0).toLocaleString();
|
||||
|
||||
if (event.status === "active") {
|
||||
return {
|
||||
badge: "🟢 进行中",
|
||||
badgeStyle: "background:#d1fae5; color:#065f46; border:1px solid #6ee7b7",
|
||||
detail: `我的状态:${myStatus} | 开启人:${event.creator_username || ""}`,
|
||||
};
|
||||
}
|
||||
|
||||
if (event.status === "settlement_pending") {
|
||||
return {
|
||||
badge: "⏳ 结算中",
|
||||
badgeStyle: "background:#fef3c7; color:#92400e; border:1px solid #fcd34d",
|
||||
detail: `活动已结束,等待最后几局结算 | 我的状态:${myStatus}`,
|
||||
};
|
||||
}
|
||||
|
||||
if (event.status === "claimable") {
|
||||
return {
|
||||
badge: "💰 可领取",
|
||||
badgeStyle: "background:#dcfce7; color:#166534; border:1px solid #86efac",
|
||||
detail: `我的状态:${myStatus} | 已发补偿 ${total} 金币`,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
badge: "🕒 即将开始",
|
||||
badgeStyle: "background:#ede9fe; color:#5b21b6; border:1px solid #c4b5fd",
|
||||
detail: `开启人:${event.creator_username || ""} | 我的状态:${myStatus}`,
|
||||
};
|
||||
},
|
||||
btnLabel: (data) => data?.event?.status === "claimable" ? "💰 查看并领取" : "📜 查看活动",
|
||||
},
|
||||
{
|
||||
id: "slot_machine",
|
||||
name: "🎰 老虎机",
|
||||
desc: "每日限额旋转,中奖即时到账",
|
||||
accentColor: "#0891b2",
|
||||
fetchUrl: null,
|
||||
openFn: () => {
|
||||
closeGameHall();
|
||||
openAlpinePanel("slot-panel");
|
||||
},
|
||||
renderStatus: () => ({
|
||||
badge: "✅ 随时可玩",
|
||||
badgeStyle: "background:#d1fae5; color:#065f46; border:1px solid #6ee7b7",
|
||||
detail: "每日限额抽奖,旋转即可",
|
||||
}),
|
||||
btnLabel: () => "🎰 开始旋转",
|
||||
},
|
||||
{
|
||||
id: "mystery_box",
|
||||
name: "📦 神秘箱子",
|
||||
desc: "管理员随机投放,抢到即开奖",
|
||||
accentColor: "#b45309",
|
||||
fetchUrl: "/mystery-box/status",
|
||||
openFn: () => {
|
||||
closeGameHall();
|
||||
window.dispatchEvent(new CustomEvent("open-mystery-box"));
|
||||
},
|
||||
renderStatus: (data) => {
|
||||
const count = data?.available_count ?? 0;
|
||||
|
||||
return count > 0 ? {
|
||||
badge: `🎁 ${count} 个待领`,
|
||||
badgeStyle: "background:#fef3c7; color:#92400e; border:1px solid #fcd34d",
|
||||
detail: "箱子已投放!快去领取",
|
||||
} : {
|
||||
badge: "📭 暂无箱子",
|
||||
badgeStyle: "background:#e8f0f8; color:#336699; border:1px solid #b8d0e8",
|
||||
detail: "等待管理员投放",
|
||||
};
|
||||
},
|
||||
btnLabel: (data) => (data?.available_count ?? 0) > 0 ? "🎁 立即领取" : "📭 等待投放",
|
||||
},
|
||||
{
|
||||
id: "horse_racing",
|
||||
name: "🐎 赛马竞猜",
|
||||
desc: "彩池制赛马,押注马匹赢取奖金",
|
||||
accentColor: "#336699",
|
||||
fetchUrl: "/horse-race/current",
|
||||
openFn: () => {
|
||||
closeGameHall();
|
||||
openAlpinePanel("horse-race-panel", "openFromHall");
|
||||
},
|
||||
renderStatus: (data) => {
|
||||
if (!data?.race) {
|
||||
return {
|
||||
badge: "⏸ 等待开赛",
|
||||
badgeStyle: "background:#e8f0f8; color:#336699; border:1px solid #b8d0e8",
|
||||
detail: "下场赛马即将开始",
|
||||
};
|
||||
}
|
||||
|
||||
const race = data.race;
|
||||
if (race.status === "betting") {
|
||||
return {
|
||||
badge: "🟢 押注中",
|
||||
badgeStyle: "background:#d1fae5; color:#065f46; border:1px solid #6ee7b7",
|
||||
detail: `⏱ 剩余 ${race.seconds_left || 0} 秒 | 注池:${Number(race.total_pool || 0).toLocaleString()} 金`,
|
||||
};
|
||||
}
|
||||
|
||||
if (race.status === "running") {
|
||||
return {
|
||||
badge: "🏇 跑马中",
|
||||
badgeStyle: "background:#fef3c7; color:#92400e; border:1px solid #fcd34d",
|
||||
detail: "比赛进行中…",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
badge: "🏆 已结算",
|
||||
badgeStyle: "background:#e8f0f8; color:#336699; border:1px solid #b8d0e8",
|
||||
detail: "下场即将开始",
|
||||
};
|
||||
},
|
||||
btnLabel: (data) => data?.race?.status === "betting" ? "🐎 立即押注" : "📊 查看赛况",
|
||||
},
|
||||
{
|
||||
id: "fortune_telling",
|
||||
name: "🔮 神秘占卜",
|
||||
desc: "每日签文,开启今日运势加成",
|
||||
accentColor: "#6d28d9",
|
||||
fetchUrl: "/fortune/today",
|
||||
openFn: () => {
|
||||
closeGameHall();
|
||||
openAlpinePanel("fortune-panel");
|
||||
},
|
||||
renderStatus: (data) => {
|
||||
if (!data?.enabled) {
|
||||
return {
|
||||
badge: "🔒 未开启",
|
||||
badgeStyle: "background:#e8f0f8; color:#336699; border:1px solid #b8d0e8",
|
||||
detail: "此游戏暂未开启",
|
||||
};
|
||||
}
|
||||
|
||||
const used = data.free_used ?? 0;
|
||||
const total = data.free_count ?? 1;
|
||||
|
||||
return data.has_free_left ? {
|
||||
badge: "✨ 免费可占",
|
||||
badgeStyle: "background:#ede9fe; color:#5b21b6; border:1px solid #c4b5fd",
|
||||
detail: `今日已占 ${used}/${total} 次,还有免费次数`,
|
||||
} : {
|
||||
badge: "💰 付费可占",
|
||||
badgeStyle: "background:#e8f0f8; color:#336699; border:1px solid #b8d0e8",
|
||||
detail: `今日免费次数已用完(${data.extra_cost} 金/次)`,
|
||||
};
|
||||
},
|
||||
btnLabel: (data) => data?.has_free_left ? "🔮 免费占卜" : "🔮 付费占卜",
|
||||
},
|
||||
{
|
||||
id: "fishing",
|
||||
name: "🎣 钓鱼",
|
||||
desc: "消耗鱼饵钓取金币和道具。背包需有鱼饵才能出竿。",
|
||||
accentColor: "#0d9488",
|
||||
fetchUrl: null,
|
||||
openFn: () => {
|
||||
closeGameHall();
|
||||
window.startFishing?.();
|
||||
},
|
||||
renderStatus: () => ({
|
||||
badge: "🎣 随时可钓",
|
||||
badgeStyle: "background:#d1fae5; color:#065f46; border:1px solid #6ee7b7",
|
||||
detail: "① 点击发言框上方【🎣 钓鱼】按钮 → ② 等待浮漂出现 → ③ 看到 🪝 后立刻点击收竿!",
|
||||
}),
|
||||
btnLabel: () => "🎣 去钓鱼",
|
||||
},
|
||||
{
|
||||
id: "lottery",
|
||||
name: "🎟️ 双色球",
|
||||
desc: "每日20:00开奖,选3红1蓝,按奖池比例派奖,无一等奖滚存累积",
|
||||
accentColor: "#dc2626",
|
||||
fetchUrl: "/lottery/current",
|
||||
openFn: () => {
|
||||
closeGameHall();
|
||||
window.openLotteryPanel?.();
|
||||
},
|
||||
renderStatus: (data) => {
|
||||
if (!data?.issue) {
|
||||
return {
|
||||
badge: "⏸ 等待开期",
|
||||
badgeStyle: "background:#e8f0f8; color:#336699; border:1px solid #b8d0e8",
|
||||
detail: "暂无进行中期次",
|
||||
};
|
||||
}
|
||||
|
||||
const issue = data.issue;
|
||||
const pool = Number(issue.pool_amount || 0).toLocaleString();
|
||||
if (data.is_open) {
|
||||
const hours = Math.floor(issue.seconds_left / 3600);
|
||||
const minutes = Math.floor((issue.seconds_left % 3600) / 60);
|
||||
const timeText = hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
|
||||
|
||||
return {
|
||||
badge: issue.is_super_issue ? "🎊 超级期购票中" : "🟢 购票中",
|
||||
badgeStyle: "background:#fef2f2; color:#b91c1c; border:1px solid #fca5a5",
|
||||
detail: `💰 奖池 ${pool} 金 | 距开奖 ${timeText} | 第 ${issue.issue_no} 期`,
|
||||
};
|
||||
}
|
||||
|
||||
if (issue.status === "settled") {
|
||||
return {
|
||||
badge: "✅ 已开奖",
|
||||
badgeStyle: "background:#d1fae5; color:#065f46; border:1px solid #6ee7b7",
|
||||
detail: "本期已开奖,下期购票中",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
badge: "🔴 已停售",
|
||||
badgeStyle: "background:#fee2e2; color:#b91c1c; border:1px solid #fecaca",
|
||||
detail: `💰 奖池 ${pool} 金 | 等待开奖中…`,
|
||||
};
|
||||
},
|
||||
btnLabel: (data) => data?.is_open ? "🎟️ 立即购票" : "📊 查看结果",
|
||||
},
|
||||
{
|
||||
id: "gomoku",
|
||||
name: "♟️ 五子棋",
|
||||
desc: "益智对弈,支持 PvP 随机对战和 AI 人机对战(4档难度)",
|
||||
accentColor: "#1e3a5f",
|
||||
fetchUrl: null,
|
||||
openFn: () => {
|
||||
closeGameHall();
|
||||
window.openGomokuPanel?.();
|
||||
},
|
||||
renderStatus: () => ({
|
||||
badge: "♟️ 随时对弈",
|
||||
badgeStyle: "background:#e8eef8; color:#1e3a5f; border:1px solid #9db3d4",
|
||||
detail: "PvP 胜利 +80 金币 | AI 专家难度胜利 +300 金币",
|
||||
}),
|
||||
btnLabel: () => "♟️ 开始对弈",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载游戏大厅状态,短时间内复用结果以减少重复请求。
|
||||
*
|
||||
* @returns {Promise<{enabledGames:Array<object>, statuses:Record<string, unknown>}>}
|
||||
*/
|
||||
async function loadGameHallStatus() {
|
||||
const now = Date.now();
|
||||
if (gameHallStatusCache && now - gameHallStatusCacheAt < GAME_HALL_CACHE_TTL) {
|
||||
return gameHallStatusCache;
|
||||
}
|
||||
|
||||
const modal = getGameHallModal();
|
||||
let enabledMap = readGameEnabledSnapshot();
|
||||
|
||||
try {
|
||||
const response = await fetch(modal?.dataset.gameEnabledUrl || "/games/enabled", {
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
},
|
||||
});
|
||||
if (response.ok) {
|
||||
enabledMap = await response.json();
|
||||
}
|
||||
} catch (error) {
|
||||
// 网络异常时降级使用页面注入的开关快照。
|
||||
}
|
||||
|
||||
const enabledGames = createGameDefinitions().filter((game) => enabledMap[game.id] !== false);
|
||||
const statuses = {};
|
||||
|
||||
await Promise.all(enabledGames.filter((game) => game.fetchUrl).map(async (game) => {
|
||||
try {
|
||||
const response = await fetch(game.fetchUrl);
|
||||
statuses[game.id] = await response.json();
|
||||
} catch (error) {
|
||||
statuses[game.id] = null;
|
||||
}
|
||||
}));
|
||||
|
||||
gameHallStatusCache = { enabledGames, statuses };
|
||||
gameHallStatusCacheAt = Date.now();
|
||||
|
||||
return gameHallStatusCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭游戏大厅弹窗。
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function closeGameHallThroughGlobal() {
|
||||
// 游戏大厅加载、缓存和卡片行为仍在 Blade 旧脚本内,模块阶段只统一关闭入口。
|
||||
if (typeof window.closeGameHall === "function") {
|
||||
window.closeGameHall();
|
||||
export function closeGameHall() {
|
||||
const modal = getGameHallModal();
|
||||
if (modal) {
|
||||
modal.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染所有游戏卡片。
|
||||
*
|
||||
* @param {Array<object>} games
|
||||
* @param {Record<string, unknown>} statuses
|
||||
* @returns {void}
|
||||
*/
|
||||
function renderGameCards(games, statuses) {
|
||||
const container = document.getElementById("game-hall-cards");
|
||||
const loading = document.getElementById("game-hall-loading");
|
||||
const empty = document.getElementById("game-hall-empty");
|
||||
if (!container || !loading || !empty) {
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = "";
|
||||
|
||||
if (games.length === 0) {
|
||||
loading.style.display = "none";
|
||||
empty.style.display = "block";
|
||||
return;
|
||||
}
|
||||
|
||||
games.forEach((game) => {
|
||||
try {
|
||||
const data = statuses[game.id] ?? null;
|
||||
const status = game.renderStatus ? game.renderStatus(data) : {
|
||||
badge: "✅ 可用",
|
||||
badgeStyle: "background:#d1fae5; color:#065f46; border:1px solid #6ee7b7",
|
||||
detail: "",
|
||||
};
|
||||
const buttonLabel = game.btnLabel ? game.btnLabel(data) : "🎮 进入";
|
||||
|
||||
const card = document.createElement("div");
|
||||
card.style.cssText = `
|
||||
background:#fff;
|
||||
border:1px solid #d0e4f5;
|
||||
border-left:4px solid ${game.accentColor};
|
||||
border-radius:6px; padding:12px 14px;
|
||||
cursor:default; transition:border-color .2s, box-shadow .2s;
|
||||
display:flex; flex-direction:column; gap:8px;
|
||||
`;
|
||||
|
||||
card.innerHTML = `
|
||||
<div style="display:flex; align-items:flex-start; justify-content:space-between; gap:8px;">
|
||||
<div style="flex:1;">
|
||||
<div style="color:#225588; font-weight:bold; font-size:13px; margin-bottom:3px;">${escapeHtml(game.name)}</div>
|
||||
<div style="color:#666; font-size:11px; line-height:1.4;">${escapeHtml(game.desc)}</div>
|
||||
</div>
|
||||
<span style="padding:2px 8px; border-radius:10px; font-size:10px; font-weight:bold; white-space:nowrap; ${status.badgeStyle}">
|
||||
${escapeHtml(status.badge)}
|
||||
</span>
|
||||
</div>
|
||||
<div style="color:#888; font-size:10px; line-height:1.4; min-height:14px; border-top:1px dashed #e0ecf8; padding-top:6px;">${status.detail ? escapeHtml(status.detail) : " "}</div>
|
||||
<button type="button"
|
||||
style="width:100%; border:none; border-radius:4px; padding:7px 8px; font-size:12px; font-weight:bold;
|
||||
cursor:pointer; color:#fff; transition:opacity .15s;
|
||||
background:linear-gradient(135deg,${game.accentColor},${game.accentColor}cc);">
|
||||
${escapeHtml(buttonLabel)}
|
||||
</button>
|
||||
`;
|
||||
|
||||
card.querySelector("button")?.addEventListener("click", (event) => {
|
||||
event.stopPropagation();
|
||||
game.openFn();
|
||||
});
|
||||
|
||||
card.addEventListener("mouseenter", () => {
|
||||
card.style.borderColor = game.accentColor;
|
||||
card.style.boxShadow = "0 2px 8px rgba(51,102,153,.18)";
|
||||
});
|
||||
card.addEventListener("mouseleave", () => {
|
||||
card.style.borderColor = "#d0e4f5";
|
||||
card.style.borderLeftColor = game.accentColor;
|
||||
card.style.boxShadow = "";
|
||||
});
|
||||
|
||||
container.appendChild(card);
|
||||
} catch (error) {
|
||||
console.warn(`[游戏大厅] 游戏 ${game.id} 卡片渲染失败:`, error);
|
||||
}
|
||||
});
|
||||
|
||||
loading.style.display = "none";
|
||||
container.style.display = "grid";
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开游戏大厅弹窗并加载各游戏状态。
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function openGameHall() {
|
||||
const modal = getGameHallModal();
|
||||
if (!modal) {
|
||||
console.error("[游戏大厅] game-hall-modal 元素不存在,请检查模板加载");
|
||||
return;
|
||||
}
|
||||
|
||||
modal.style.display = "flex";
|
||||
|
||||
const loading = document.getElementById("game-hall-loading");
|
||||
const cards = document.getElementById("game-hall-cards");
|
||||
const empty = document.getElementById("game-hall-empty");
|
||||
if (loading) {
|
||||
loading.style.display = "block";
|
||||
}
|
||||
if (cards) {
|
||||
cards.style.display = "none";
|
||||
}
|
||||
if (empty) {
|
||||
empty.style.display = "none";
|
||||
}
|
||||
|
||||
const jjb = document.getElementById("game-hall-jjb");
|
||||
if (jjb && window.chatContext?.userJjb !== undefined) {
|
||||
jjb.textContent = Number(window.chatContext.userJjb).toLocaleString();
|
||||
}
|
||||
|
||||
const { enabledGames, statuses } = await loadGameHallStatus();
|
||||
|
||||
try {
|
||||
renderGameCards(enabledGames, statuses);
|
||||
} catch (error) {
|
||||
console.error("[游戏大厅] 渲染游戏卡片失败:", error);
|
||||
if (loading) {
|
||||
loading.style.display = "none";
|
||||
}
|
||||
if (empty) {
|
||||
empty.style.display = "block";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,12 +564,23 @@ export function bindGameHallControls() {
|
||||
}
|
||||
|
||||
gameHallEventsBound = true;
|
||||
window.openGameHall = openGameHall;
|
||||
window.closeGameHall = closeGameHall;
|
||||
|
||||
document.addEventListener("click", (event) => {
|
||||
if (!(event.target instanceof Element) || !event.target.closest("[data-game-hall-close]")) {
|
||||
if (!(event.target instanceof Element)) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
closeGameHallThroughGlobal();
|
||||
if (event.target.closest("[data-game-hall-close]")) {
|
||||
event.preventDefault();
|
||||
closeGameHall();
|
||||
return;
|
||||
}
|
||||
|
||||
const modal = event.target.closest("#game-hall-modal");
|
||||
if (modal && event.target === modal) {
|
||||
closeGameHall();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,11 +9,12 @@
|
||||
- 神秘占卜:今日占卜次数 + 直接打开按钮
|
||||
- 钓鱼:状态 + 打开按钮
|
||||
|
||||
JS 逻辑已迁移到 resources/js/chat-room/game-hall.js。
|
||||
|
||||
@author ChatRoom Laravel
|
||||
@version 1.0.0
|
||||
--}}
|
||||
|
||||
{{-- ─── 服务端注入各游戏开关状态(避免前端额外请求)─── --}}
|
||||
@php
|
||||
$gameEnabled = [
|
||||
'baccarat' => \App\Models\GameConfig::isEnabled('baccarat'),
|
||||
@@ -26,13 +27,11 @@
|
||||
'gomoku' => \App\Models\GameConfig::isEnabled('gomoku'),
|
||||
];
|
||||
@endphp
|
||||
<script>
|
||||
/** 后台游戏开关状态(Blade 服务端注入,1分钟缓存) */
|
||||
window.GAME_ENABLED = @json($gameEnabled);
|
||||
</script>
|
||||
|
||||
{{-- ═══════════ 游戏大厅弹窗遮罩 ═══════════ --}}
|
||||
<div id="game-hall-modal"
|
||||
data-game-enabled='@json($gameEnabled)'
|
||||
data-game-enabled-url="{{ url('/games/enabled') }}"
|
||||
style="display:none; position:fixed; inset:0; background:rgba(0,0,0,.55);
|
||||
z-index:9998; justify-content:center; align-items:center;">
|
||||
<div id="game-hall-inner"
|
||||
@@ -87,474 +86,3 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 游戏大厅模块(使用 IIFE 隔离全局作用域,防止 const 重复初始化导致脚本块失败)
|
||||
*/
|
||||
(function() {
|
||||
/** 游戏大厅状态短缓存,避免用户频繁开关弹窗时重复打多个状态接口 */
|
||||
const GAME_HALL_CACHE_TTL = 15000;
|
||||
let gameHallStatusCache = null;
|
||||
let gameHallStatusCacheAt = 0;
|
||||
|
||||
/** 游戏大厅配置定义(ID → 展示配置) */
|
||||
const GAME_HALL_GAMES = [{
|
||||
id: 'baccarat',
|
||||
name: '🎲 百家乐',
|
||||
desc: '猜骰子大小,1:1 赔率,豹子 1:24',
|
||||
accentColor: '#336699',
|
||||
fetchUrl: '/baccarat/current',
|
||||
openFn: () => {
|
||||
closeGameHall();
|
||||
const panel = document.getElementById('baccarat-panel');
|
||||
if (panel) Alpine.$data(panel).show = true;
|
||||
},
|
||||
renderStatus: (data) => {
|
||||
if (!data?.round) return {
|
||||
badge: '⏸ 等待开局',
|
||||
badgeStyle: 'background:#e8f0f8; color:#336699; border:1px solid #b8d0e8',
|
||||
detail: '下局即将开始,稍后再来'
|
||||
};
|
||||
const r = data.round;
|
||||
if (r.status === 'betting') {
|
||||
return {
|
||||
badge: '🟢 押注中',
|
||||
badgeStyle: 'background:#d1fae5; color:#065f46; border:1px solid #6ee7b7',
|
||||
detail: `⏱ 剩余 ${r.seconds_left || 0} 秒 | 注池:${Number((r.total_bet_big||0)+(r.total_bet_small||0)+(r.total_bet_triple||0)).toLocaleString()} 金`
|
||||
};
|
||||
}
|
||||
return {
|
||||
badge: '⏳ 开奖中',
|
||||
badgeStyle: 'background:#fef3c7; color:#92400e; border:1px solid #fcd34d',
|
||||
detail: '正在摇骰子…'
|
||||
};
|
||||
},
|
||||
btnLabel: (data) => data?.round?.status === 'betting' ? '🎲 立即下注' : '📊 查看详情',
|
||||
},
|
||||
{
|
||||
id: 'baccarat_loss_cover',
|
||||
name: '🎁 买单活动',
|
||||
desc: '查看“你玩游戏我买单”活动,补偿领取和个人历史记录',
|
||||
accentColor: '#16a34a',
|
||||
fetchUrl: '/baccarat-loss-cover/summary',
|
||||
openFn: () => {
|
||||
closeGameHall();
|
||||
if (typeof openBaccaratLossCoverModal === 'function') {
|
||||
openBaccaratLossCoverModal('overview');
|
||||
}
|
||||
},
|
||||
renderStatus: (data) => {
|
||||
const event = data?.event;
|
||||
if (!event) {
|
||||
return {
|
||||
badge: '📭 暂无活动',
|
||||
badgeStyle: 'background:#e8f0f8; color:#336699; border:1px solid #b8d0e8',
|
||||
detail: '当前没有进行中或待领取的买单活动'
|
||||
};
|
||||
}
|
||||
|
||||
const myStatus = event.my_record?.claim_status_label || '未参与';
|
||||
const total = Number(event.total_claimed_amount || 0).toLocaleString();
|
||||
|
||||
if (event.status === 'active') {
|
||||
return {
|
||||
badge: '🟢 进行中',
|
||||
badgeStyle: 'background:#d1fae5; color:#065f46; border:1px solid #6ee7b7',
|
||||
detail: `我的状态:${myStatus} | 开启人:${event.creator_username}`
|
||||
};
|
||||
}
|
||||
|
||||
if (event.status === 'settlement_pending') {
|
||||
return {
|
||||
badge: '⏳ 结算中',
|
||||
badgeStyle: 'background:#fef3c7; color:#92400e; border:1px solid #fcd34d',
|
||||
detail: `活动已结束,等待最后几局结算 | 我的状态:${myStatus}`
|
||||
};
|
||||
}
|
||||
|
||||
if (event.status === 'claimable') {
|
||||
return {
|
||||
badge: '💰 可领取',
|
||||
badgeStyle: 'background:#dcfce7; color:#166534; border:1px solid #86efac',
|
||||
detail: `我的状态:${myStatus} | 已发补偿 ${total} 金币`
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
badge: '🕒 即将开始',
|
||||
badgeStyle: 'background:#ede9fe; color:#5b21b6; border:1px solid #c4b5fd',
|
||||
detail: `开启人:${event.creator_username} | 我的状态:${myStatus}`
|
||||
};
|
||||
},
|
||||
btnLabel: (data) => data?.event?.status === 'claimable' ? '💰 查看并领取' : '📜 查看活动',
|
||||
},
|
||||
{
|
||||
id: 'slot_machine',
|
||||
name: '🎰 老虎机',
|
||||
desc: '每日限额旋转,中奖即时到账',
|
||||
accentColor: '#0891b2',
|
||||
fetchUrl: null,
|
||||
openFn: () => {
|
||||
closeGameHall();
|
||||
const panel = document.getElementById('slot-panel');
|
||||
if (panel) Alpine.$data(panel).show = true;
|
||||
},
|
||||
renderStatus: () => ({
|
||||
badge: '✅ 随时可玩',
|
||||
badgeStyle: 'background:#d1fae5; color:#065f46; border:1px solid #6ee7b7',
|
||||
detail: '每日限额抽奖,旋转即可'
|
||||
}),
|
||||
btnLabel: () => '🎰 开始旋转',
|
||||
},
|
||||
{
|
||||
id: 'mystery_box',
|
||||
name: '📦 神秘箱子',
|
||||
desc: '管理员随机投放,抢到即开奖',
|
||||
accentColor: '#b45309',
|
||||
fetchUrl: '/mystery-box/status',
|
||||
openFn: () => {
|
||||
closeGameHall();
|
||||
window.dispatchEvent(new CustomEvent('open-mystery-box'));
|
||||
},
|
||||
renderStatus: (data) => {
|
||||
const count = data?.available_count ?? 0;
|
||||
return count > 0 ? {
|
||||
badge: `🎁 ${count} 个待领`,
|
||||
badgeStyle: 'background:#fef3c7; color:#92400e; border:1px solid #fcd34d',
|
||||
detail: '箱子已投放!快去领取'
|
||||
} : {
|
||||
badge: '📭 暂无箱子',
|
||||
badgeStyle: 'background:#e8f0f8; color:#336699; border:1px solid #b8d0e8',
|
||||
detail: '等待管理员投放'
|
||||
};
|
||||
},
|
||||
btnLabel: (data) => (data?.available_count ?? 0) > 0 ? '🎁 立即领取' : '📭 等待投放',
|
||||
},
|
||||
{
|
||||
id: 'horse_racing',
|
||||
name: '🐎 赛马竞猜',
|
||||
desc: '彩池制赛马,押注马匹赢取奖金',
|
||||
accentColor: '#336699',
|
||||
fetchUrl: '/horse-race/current',
|
||||
openFn: () => {
|
||||
closeGameHall();
|
||||
const panel = document.getElementById('horse-race-panel');
|
||||
if (panel) Alpine.$data(panel).openFromHall();
|
||||
},
|
||||
renderStatus: (data) => {
|
||||
if (!data?.race) return {
|
||||
badge: '⏸ 等待开赛',
|
||||
badgeStyle: 'background:#e8f0f8; color:#336699; border:1px solid #b8d0e8',
|
||||
detail: '下场赛马即将开始'
|
||||
};
|
||||
const r = data.race;
|
||||
if (r.status === 'betting') {
|
||||
return {
|
||||
badge: '🟢 押注中',
|
||||
badgeStyle: 'background:#d1fae5; color:#065f46; border:1px solid #6ee7b7',
|
||||
detail: `⏱ 剩余 ${r.seconds_left || 0} 秒 | 注池:${Number(r.total_pool || 0).toLocaleString()} 金`
|
||||
};
|
||||
}
|
||||
if (r.status === 'running') {
|
||||
return {
|
||||
badge: '🏇 跑马中',
|
||||
badgeStyle: 'background:#fef3c7; color:#92400e; border:1px solid #fcd34d',
|
||||
detail: '比赛进行中…'
|
||||
};
|
||||
}
|
||||
return {
|
||||
badge: '🏆 已结算',
|
||||
badgeStyle: 'background:#e8f0f8; color:#336699; border:1px solid #b8d0e8',
|
||||
detail: '下场即将开始'
|
||||
};
|
||||
},
|
||||
btnLabel: (data) => data?.race?.status === 'betting' ? '🐎 立即押注' : '📊 查看赛况',
|
||||
},
|
||||
{
|
||||
id: 'fortune_telling',
|
||||
name: '🔮 神秘占卜',
|
||||
desc: '每日签文,开启今日运势加成',
|
||||
accentColor: '#6d28d9',
|
||||
fetchUrl: '/fortune/today',
|
||||
openFn: () => {
|
||||
closeGameHall();
|
||||
const panel = document.getElementById('fortune-panel');
|
||||
if (panel) Alpine.$data(panel).show = true;
|
||||
},
|
||||
renderStatus: (data) => {
|
||||
if (!data?.enabled) return {
|
||||
badge: '🔒 未开启',
|
||||
badgeStyle: 'background:#e8f0f8; color:#336699; border:1px solid #b8d0e8',
|
||||
detail: '此游戏暂未开启'
|
||||
};
|
||||
const used = data.free_used ?? 0;
|
||||
const total = data.free_count ?? 1;
|
||||
return data.has_free_left ? {
|
||||
badge: '✨ 免费可占',
|
||||
badgeStyle: 'background:#ede9fe; color:#5b21b6; border:1px solid #c4b5fd',
|
||||
detail: `今日已占 ${used}/${total} 次,还有免费次数`
|
||||
} : {
|
||||
badge: '💰 付费可占',
|
||||
badgeStyle: 'background:#e8f0f8; color:#336699; border:1px solid #b8d0e8',
|
||||
detail: `今日免费次数已用完(${data.extra_cost} 金/次)`
|
||||
};
|
||||
},
|
||||
btnLabel: (data) => data?.has_free_left ? '🔮 免费占卜' : '🔮 付费占卜',
|
||||
},
|
||||
{
|
||||
id: 'fishing',
|
||||
name: '🎣 钓鱼',
|
||||
desc: '消耗鱼饵钓取金币和道具。背包需有鱼饵才能出竿。',
|
||||
accentColor: '#0d9488',
|
||||
fetchUrl: null,
|
||||
openFn: () => {
|
||||
closeGameHall();
|
||||
// 直接触发钓鱼,无需手动输入指令
|
||||
if (typeof startFishing === 'function') {
|
||||
startFishing();
|
||||
}
|
||||
},
|
||||
renderStatus: () => ({
|
||||
badge: '🎣 随时可钓',
|
||||
badgeStyle: 'background:#d1fae5; color:#065f46; border:1px solid #6ee7b7',
|
||||
detail: '① 点击发言框上方【🎣 钓鱼】按钮 → ② 等待浮漂出现 → ③ 看到 🪝 后立刻点击收竿!'
|
||||
}),
|
||||
btnLabel: () => '🎣 去钓鱼',
|
||||
},
|
||||
{
|
||||
id: 'lottery',
|
||||
name: '🎟️ 双色球',
|
||||
desc: '每日20:00开奖,选3红1蓝,按奖池比例派奖,无一等奖滚存累积',
|
||||
accentColor: '#dc2626',
|
||||
fetchUrl: '/lottery/current',
|
||||
openFn: () => {
|
||||
closeGameHall();
|
||||
if (typeof openLotteryPanel === 'function') openLotteryPanel();
|
||||
},
|
||||
renderStatus: (data) => {
|
||||
if (!data?.issue) return {
|
||||
badge: '⏸ 等待开期',
|
||||
badgeStyle: 'background:#e8f0f8; color:#336699; border:1px solid #b8d0e8',
|
||||
detail: '暂无进行中期次'
|
||||
};
|
||||
const iss = data.issue;
|
||||
const pool = Number(iss.pool_amount || 0).toLocaleString();
|
||||
if (data.is_open) {
|
||||
const h = Math.floor(iss.seconds_left / 3600);
|
||||
const m = Math.floor((iss.seconds_left % 3600) / 60);
|
||||
const timeStr = h > 0 ? `${h}h ${m}m` : `${m}m`;
|
||||
return {
|
||||
badge: iss.is_super_issue ? '🎊 超级期购票中' : '🟢 购票中',
|
||||
badgeStyle: 'background:#fef2f2; color:#b91c1c; border:1px solid #fca5a5',
|
||||
detail: `💰 奖池 ${pool} 金 | 距开奖 ${timeStr} | 第 ${iss.issue_no} 期`
|
||||
};
|
||||
}
|
||||
if (iss.status === 'settled') return {
|
||||
badge: '✅ 已开奖',
|
||||
badgeStyle: 'background:#d1fae5; color:#065f46; border:1px solid #6ee7b7',
|
||||
detail: '本期已开奖,下期购票中'
|
||||
};
|
||||
return {
|
||||
badge: '🔴 已停售',
|
||||
badgeStyle: 'background:#fee2e2; color:#b91c1c; border:1px solid #fecaca',
|
||||
detail: `💰 奖池 ${pool} 金 | 等待开奖中…`
|
||||
};
|
||||
},
|
||||
btnLabel: (data) => data?.is_open ? '🎟️ 立即购票' : '📊 查看结果',
|
||||
},
|
||||
|
||||
{
|
||||
id: 'gomoku',
|
||||
name: '♟️ 五子棋',
|
||||
desc: '益智对弈,支持 PvP 随机对战和 AI 人机对战(4档难度)',
|
||||
accentColor: '#1e3a5f',
|
||||
fetchUrl: null,
|
||||
openFn: () => {
|
||||
closeGameHall();
|
||||
if (typeof openGomokuPanel === 'function') openGomokuPanel();
|
||||
},
|
||||
renderStatus: () => ({
|
||||
badge: '♟️ 随时对弈',
|
||||
badgeStyle: 'background:#e8eef8; color:#1e3a5f; border:1px solid #9db3d4',
|
||||
detail: 'PvP 胜利 +80 金币 | AI 专家难度胜利 +300 金币',
|
||||
}),
|
||||
btnLabel: () => '♟️ 开始对弈',
|
||||
},
|
||||
|
||||
|
||||
];
|
||||
|
||||
/**
|
||||
* 打开游戏大厅弹窗,加载各游戏状态
|
||||
*/
|
||||
window.openGameHall = async function() {
|
||||
const modal = document.getElementById('game-hall-modal');
|
||||
if (!modal) {
|
||||
console.error('[游戏大厅] game-hall-modal 元素不存在,请检查模板加载');
|
||||
return;
|
||||
}
|
||||
modal.style.display = 'flex';
|
||||
document.getElementById('game-hall-loading').style.display = 'block';
|
||||
document.getElementById('game-hall-cards').style.display = 'none';
|
||||
document.getElementById('game-hall-empty').style.display = 'none';
|
||||
|
||||
const jjbEl = document.getElementById('game-hall-jjb');
|
||||
if (jjbEl && window.chatContext?.userJjb !== undefined) {
|
||||
jjbEl.textContent = Number(window.chatContext.userJjb).toLocaleString();
|
||||
}
|
||||
|
||||
const { enabledGames, statuses } = await loadGameHallStatus();
|
||||
|
||||
try {
|
||||
renderGameCards(enabledGames, statuses);
|
||||
} catch (err) {
|
||||
console.error('[游戏大厅] 渲染游戏卡片失败:', err);
|
||||
document.getElementById('game-hall-loading').style.display = 'none';
|
||||
document.getElementById('game-hall-empty').style.display = 'block';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 加载游戏大厅状态,短时间内复用结果以减少重复请求。
|
||||
*
|
||||
* @returns {Promise<{enabledGames:Array, statuses:Object}>}
|
||||
*/
|
||||
async function loadGameHallStatus() {
|
||||
const now = Date.now();
|
||||
if (gameHallStatusCache && now - gameHallStatusCacheAt < GAME_HALL_CACHE_TTL) {
|
||||
return gameHallStatusCache;
|
||||
}
|
||||
|
||||
// 打开大厅时拉取后台开关状态;短缓存能兼顾配置同步和重复打开速度。
|
||||
let enabledMap = window.GAME_ENABLED ?? {};
|
||||
try {
|
||||
const r = await fetch('/games/enabled', {
|
||||
headers: {
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
if (r.ok) enabledMap = await r.json();
|
||||
} catch {
|
||||
/* 网络异常时降级使用页面注入值 */
|
||||
}
|
||||
|
||||
// 过滤出后台已开启的游戏
|
||||
const enabledGames = GAME_HALL_GAMES.filter(g => enabledMap[g.id] !== false);
|
||||
|
||||
// 并行请求有状态接口的游戏
|
||||
const statuses = {};
|
||||
await Promise.all(
|
||||
enabledGames.filter(g => g.fetchUrl).map(async g => {
|
||||
try {
|
||||
const res = await fetch(g.fetchUrl);
|
||||
statuses[g.id] = await res.json();
|
||||
} catch {
|
||||
statuses[g.id] = null;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
gameHallStatusCache = { enabledGames, statuses };
|
||||
gameHallStatusCacheAt = Date.now();
|
||||
|
||||
return gameHallStatusCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭游戏大厅弹窗
|
||||
*/
|
||||
window.closeGameHall = function() {
|
||||
document.getElementById('game-hall-modal').style.display = 'none';
|
||||
};
|
||||
|
||||
/**
|
||||
* 渲染所有游戏卡片(海军蓝风格)
|
||||
*
|
||||
* @param {Array} games 已过滤的游戏配置列表
|
||||
* @param {Object} statuses 各游戏的 API 返回数据
|
||||
*/
|
||||
function renderGameCards(games, statuses) {
|
||||
const container = document.getElementById('game-hall-cards');
|
||||
container.innerHTML = '';
|
||||
|
||||
if (games.length === 0) {
|
||||
document.getElementById('game-hall-loading').style.display = 'none';
|
||||
document.getElementById('game-hall-empty').style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
games.forEach(game => {
|
||||
try {
|
||||
const data = statuses[game.id] ?? null;
|
||||
const status = game.renderStatus ? game.renderStatus(data) : {
|
||||
badge: '✅ 可用',
|
||||
badgeStyle: 'background:#d1fae5; color:#065f46; border:1px solid #6ee7b7',
|
||||
detail: ''
|
||||
};
|
||||
const btnLabel = game.btnLabel ? game.btnLabel(data) : '🎮 进入';
|
||||
|
||||
const card = document.createElement('div');
|
||||
card.style.cssText = `
|
||||
background:#fff;
|
||||
border:1px solid #d0e4f5;
|
||||
border-left:4px solid ${game.accentColor};
|
||||
border-radius:6px; padding:12px 14px;
|
||||
cursor:default; transition:border-color .2s, box-shadow .2s;
|
||||
display:flex; flex-direction:column; gap:8px;
|
||||
`;
|
||||
|
||||
card.innerHTML = `
|
||||
<div style="display:flex; align-items:flex-start; justify-content:space-between; gap:8px;">
|
||||
<div style="flex:1;">
|
||||
<div style="color:#225588; font-weight:bold; font-size:13px; margin-bottom:3px;">${game.name}</div>
|
||||
<div style="color:#666; font-size:11px; line-height:1.4;">${game.desc}</div>
|
||||
</div>
|
||||
<span style="padding:2px 8px; border-radius:10px; font-size:10px; font-weight:bold; white-space:nowrap; ${status.badgeStyle}">
|
||||
${status.badge}
|
||||
</span>
|
||||
</div>
|
||||
<div style="color:#888; font-size:10px; line-height:1.4; min-height:14px; border-top:1px dashed #e0ecf8; padding-top:6px;">${status.detail || ' '}</div>
|
||||
<button
|
||||
style="width:100%; border:none; border-radius:4px; padding:7px 8px; font-size:12px; font-weight:bold;
|
||||
cursor:pointer; color:#fff; transition:opacity .15s;
|
||||
background:linear-gradient(135deg,${game.accentColor},${game.accentColor}cc);"
|
||||
onmouseover="this.style.opacity='.85'"
|
||||
onmouseout="this.style.opacity='1'">
|
||||
${btnLabel}
|
||||
</button>
|
||||
`;
|
||||
|
||||
card.querySelector('button').addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
game.openFn();
|
||||
});
|
||||
|
||||
card.addEventListener('mouseenter', () => {
|
||||
card.style.borderColor = game.accentColor;
|
||||
card.style.boxShadow = `0 2px 8px rgba(51,102,153,.18)`;
|
||||
});
|
||||
card.addEventListener('mouseleave', () => {
|
||||
card.style.borderColor = '#d0e4f5';
|
||||
card.style.borderLeftColor = game.accentColor;
|
||||
card.style.boxShadow = '';
|
||||
});
|
||||
|
||||
container.appendChild(card);
|
||||
} catch (err) {
|
||||
// 单个游戏卡片渲染失败不影响其他游戏展示
|
||||
console.warn(`[游戏大厅] 游戏 ${game.id} 卡片渲染失败:`, err);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('game-hall-loading').style.display = 'none';
|
||||
container.style.display = 'grid';
|
||||
}
|
||||
|
||||
// 点击遮罩关闭弹窗
|
||||
document.getElementById('game-hall-modal').addEventListener('click', function(e) {
|
||||
if (e.target === this) closeGameHall();
|
||||
});
|
||||
|
||||
})(); // end IIFE
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user