355 lines
11 KiB
JavaScript
355 lines
11 KiB
JavaScript
// 猜成语游戏前端模块
|
|
// 监听 IdiomGameStarted / IdiomGameAnswered 事件,提供答题弹窗功能
|
|
|
|
function csrf() {
|
|
return document.querySelector('meta[name="csrf-token"]')?.content ?? "";
|
|
}
|
|
|
|
let currentRoundId = 0;
|
|
let currentRoomId = 0;
|
|
|
|
/**
|
|
* 为指定回合创建统一样式的答题按钮。
|
|
*/
|
|
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 事件负责渲染,不重复添加)
|
|
// 这里只存储当前回合信息
|
|
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: `<b>${winner_username}</b> 答对了「${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;
|