// 猜成语游戏前端模块 // 监听 IdiomGameStarted / IdiomGameAnswered 事件,提供答题弹窗功能 function csrf() { return document.querySelector('meta[name="csrf-token"]')?.content ?? ""; } let currentRoundId = 0; let currentRoomId = 0; /** * 查找当前回合是否已经有对应的聊天室消息节点。 */ function findIdiomRoundMessageNode(roundId) { if (roundId <= 0) { return null; } return document.querySelector(`[data-idiom-round-id="${roundId}"]`); } /** * 为指定回合创建统一样式的答题按钮。 */ function buildIdiomAnswerButton(roundId, hint, rewardGold, rewardExp) { const btn = document.createElement("button"); btn.type = "button"; btn.dataset.idiomAnswerBtn = String(roundId); btn.dataset.idiomHint = hint; btn.dataset.idiomGold = String(rewardGold); btn.dataset.idiomExp = String(rewardExp); btn.textContent = "🎯 答题"; btn.style.cssText = "margin-left:8px;padding:2px 12px;background:linear-gradient(135deg,#7c3aed,#a78bfa);" + "color:#fff;border:none;border-radius:999px;font-size:11px;cursor:pointer;" + "font-weight:bold;vertical-align:middle;"; return btn; } /** * 清理指定回合的所有答题按钮。 */ export function removeIdiomAnswerButtons(roundId = 0) { const selector = roundId > 0 ? `[data-idiom-answer-btn="${roundId}"]` : "[data-idiom-answer-btn]"; document.querySelectorAll(selector).forEach((button) => button.remove()); } /** * 把答题按钮挂到对应的消息节点上,而不是盲目追加到最后一条消息。 */ export function attachIdiomAnswerButton(messageNode, message) { if (!messageNode || !message) { return; } const roundId = Number.parseInt( String(message.idiom_game_round_id || message.idom_game_round_id || "0"), 10, ); if (roundId <= 0) { return; } if (Number.parseInt(String(message.idiom_game_round_ended_id || "0"), 10) > 0) { return; } if (message.from_user !== "星海小博士") { return; } if (messageNode.querySelector(`[data-idiom-answer-btn="${roundId}"]`)) { return; } const hint = String(message.content || ""); const rewardGold = Number.parseInt(String(message.idiom_reward_gold || "0"), 10); const rewardExp = Number.parseInt(String(message.idiom_reward_exp || "0"), 10); const button = buildIdiomAnswerButton(roundId, hint, rewardGold, rewardExp); const timeNode = messageNode.querySelector(".msg-time"); if (timeNode?.parentNode) { timeNode.parentNode.insertBefore(button, timeNode.nextSibling); return; } messageNode.appendChild(button); } /** * 根据当前服务端回合状态,清理刷新后残留的旧答题按钮。 */ async function syncCurrentIdiomRound() { const roomId = Number.parseInt(String(window.chatContext?.roomId || "0"), 10); if (roomId <= 0) { return; } currentRoomId = roomId; try { const response = await fetch(`/idiom-quiz/current?room_id=${roomId}`, { headers: { Accept: "application/json", }, }); const data = await response.json(); const activeRoundId = Number.parseInt(String(data?.data?.round_id || "0"), 10); currentRoundId = activeRoundId; if (activeRoundId <= 0) { removeIdiomAnswerButtons(); return; } document.querySelectorAll("[data-idiom-answer-btn]").forEach((button) => { if (button.dataset.idiomAnswerBtn !== String(activeRoundId)) { button.remove(); } }); } catch (_error) { // 当前回合同步失败时不打断聊天主流程,保留现有按钮状态。 } } /** * 收到猜成语出题事件时,在聊天窗口显示提示消息。 */ function handleIdiomGameStarted(e) { const { round_id, hint, reward_gold, reward_exp, message } = e.detail || {}; if (!round_id || !hint) return; currentRoundId = round_id; currentRoomId = window.chatContext?.roomId || 0; // 线上如果 MessageSent 补消息没有到达,这里主动补一条公屏消息兜底; // 本地或正常链路下若消息已存在,则只补挂答题按钮,避免重复渲染。 const existingMessageNode = findIdiomRoundMessageNode(round_id); if (existingMessageNode) { attachIdiomAnswerButton(existingMessageNode, { from_user: "星海小博士", content: message || `🧩 猜成语时间!${hint}`, idiom_game_round_id: round_id, idiom_reward_gold: reward_gold, idiom_reward_exp: reward_exp, }); console.log(`猜成语开始:${hint},奖励 ${reward_gold}金/${reward_exp}经验`); return; } const now = new Date(); const timeStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")} ${String(now.getHours()).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}:${String(now.getSeconds()).padStart(2, "0")}`; window.appendMessage?.({ id: `idiom-start-live-${round_id}`, room_id: currentRoomId || window.chatContext?.roomId || 0, from_user: "星海小博士", to_user: "大家", content: message || `🧩 猜成语时间!${hint}`, is_secret: false, font_color: "#7c3aed", action: "", idiom_game_round_id: round_id, idiom_reward_gold: reward_gold, idiom_reward_exp: reward_exp, sent_at: timeStr, }); console.log(`猜成语开始:${hint},奖励 ${reward_gold}金/${reward_exp}经验`); } /** * 收到猜成语结果事件。 */ function handleIdiomGameAnswered(e) { const { answer, winner_username, reward_gold, reward_exp, round_id } = e.detail || {}; if (!answer) return; currentRoundId = 0; removeIdiomAnswerButtons(round_id); // 关闭当前用户的答题弹窗(如果开着的话) const answerModal = document.getElementById("idiom-answer-modal"); if (answerModal && answerModal.style.display !== "none") { answerModal.style.display = "none"; } // 实时答题结果与刷新后的历史恢复统一走 appendMessage,避免两套分流逻辑跑偏。 const now = new Date(); const timeStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")} ${String(now.getHours()).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}:${String(now.getSeconds()).padStart(2, "0")}`; window.appendMessage?.({ id: `idiom-result-live-${round_id}-${Date.now()}`, room_id: currentRoomId || window.chatContext?.roomId || 0, from_user: "星海小博士", to_user: "大家", content: `🎉 【${winner_username}】率先答对成语「${answer}」,获得 ${reward_gold} 金币、${reward_exp} 经验!`, is_secret: false, font_color: "#16a34a", action: "idiom_result", winner_username, idiom_answer: answer, idiom_result_reward_gold: reward_gold, idiom_result_reward_exp: reward_exp, idiom_game_round_ended_id: round_id, sent_at: timeStr, }); // ── Toast 通知(所有用户都能看到) ── window.chatToast?.show({ title: "🧩 猜成语", message: `${winner_username} 答对了「${answer}」,获得 ${reward_gold}💰 + ${reward_exp}⭐!`, icon: "🎉", color: "#16a34a", duration: 6000, }); } /** * 打开答题弹窗。 */ function openIdiomAnswerModal(roundId, hint, rewardGold, rewardExp) { currentRoundId = roundId; currentRoomId = window.chatContext?.roomId || 0; const modal = document.getElementById("idiom-answer-modal"); if (!modal) return; const hintEl = document.getElementById("idiom-answer-hint"); const rewardEl = document.getElementById("idiom-answer-reward"); if (hintEl) hintEl.textContent = hint; if (rewardEl) rewardEl.textContent = `🎁 答对奖励:${rewardGold} 金币 + ${rewardExp} 经验`; modal.style.display = "flex"; const input = document.getElementById("idiom-answer-input"); if (input) { input.value = ""; input.focus(); input.disabled = false; } const submitBtn = document.getElementById("idiom-answer-submit"); if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = "提交答案"; } const feedbackEl = document.getElementById("idiom-answer-feedback"); if (feedbackEl) feedbackEl.textContent = ""; } /** * 关闭答题弹窗。 */ function closeIdiomAnswerModal() { const modal = document.getElementById("idiom-answer-modal"); if (modal) modal.style.display = "none"; } /** * 提交答案。 */ async function submitIdiomAnswer() { const input = document.getElementById("idiom-answer-input"); const feedbackEl = document.getElementById("idiom-answer-feedback"); const submitBtn = document.getElementById("idiom-answer-submit"); if (!input || !feedbackEl || !submitBtn) return; const answer = input.value.trim(); if (!answer) { feedbackEl.textContent = "请输入成语答案"; feedbackEl.style.color = "#ef4444"; return; } submitBtn.disabled = true; submitBtn.textContent = "提交中..."; try { const response = await fetch("/idiom-quiz/answer", { method: "POST", headers: { "X-CSRF-TOKEN": csrf(), "Content-Type": "application/json", "Accept": "application/json", }, body: JSON.stringify({ round_id: currentRoundId, answer: answer, room_id: currentRoomId, }), }); const data = await response.json(); if (data.status === "success") { feedbackEl.textContent = data.message || "🎉 回答正确!"; feedbackEl.style.color = "#16a34a"; input.disabled = true; // 延迟关闭弹窗 setTimeout(() => { closeIdiomAnswerModal(); }, 2000); } else { feedbackEl.textContent = data.message || "答案不正确"; feedbackEl.style.color = "#ef4444"; submitBtn.disabled = false; submitBtn.textContent = "提交答案"; input.focus(); input.select(); } } catch (error) { feedbackEl.textContent = "网络错误,请稍后重试"; feedbackEl.style.color = "#ef4444"; submitBtn.disabled = false; submitBtn.textContent = "提交答案"; } } // ── 事件绑定 ── export function bindIdiomQuizControls() { // 已经绑定的不再重复绑定 if (document.getElementById("idiom-answer-modal")?.dataset?.idiomBound) return; const modal = document.getElementById("idiom-answer-modal"); if (modal) modal.dataset.idiomBound = "1"; // 关闭按钮 document.addEventListener("click", (e) => { const closeBtn = e.target.closest("[data-idiom-answer-close]"); if (closeBtn) { closeIdiomAnswerModal(); return; } // 点击遮罩层关闭 const overlay = e.target.closest("#idiom-answer-modal"); if (overlay && e.target === overlay) { closeIdiomAnswerModal(); } }); // 提交按钮 document.addEventListener("click", (e) => { const submitBtn = e.target.closest("[data-idiom-answer-submit]"); if (submitBtn) { e.preventDefault(); submitIdiomAnswer(); } }); // 输入框 Enter 提交 document.addEventListener("keydown", (e) => { const input = e.target.closest("#idiom-answer-input"); if (input && e.key === "Enter" && !e.shiftKey) { e.preventDefault(); submitIdiomAnswer(); } }); // 聊天消息中的【答题】按钮点击 document.addEventListener("click", (e) => { const btn = e.target.closest("[data-idiom-answer-btn]"); if (!btn) return; const roundId = parseInt(btn.dataset.idiomAnswerBtn || "0", 10); const hint = btn.dataset.idiomHint || ""; const rewardGold = parseInt(btn.dataset.idiomGold || "0", 10); const rewardExp = parseInt(btn.dataset.idiomExp || "0", 10); if (roundId > 0) { openIdiomAnswerModal(roundId, hint, rewardGold, rewardExp); } }); // ── 猜成语结果消息中的用户名可点击 → 打开用户名片 // 注:单击/双击已由 right-panel.js 的全局 [data-chat-message-user] 事件委托统一处理 window.setTimeout(() => { syncCurrentIdiomRound(); }, 0); } // ── 挂载到 window ── window.openIdiomAnswerModal = openIdiomAnswerModal; window.closeIdiomAnswerModal = closeIdiomAnswerModal; window.submitIdiomAnswer = submitIdiomAnswer; window.handleIdiomGameStarted = handleIdiomGameStarted; window.handleIdiomGameAnswered = handleIdiomGameAnswered;