From 367214098757e8d083f7b0d0fbc0cc3c4179afd5 Mon Sep 17 00:00:00 2001 From: pllx Date: Wed, 29 Apr 2026 14:35:52 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BB=9F=E4=B8=80=E8=81=8A=E5=A4=A9=E5=AE=A4?= =?UTF-8?q?=E6=B8=B8=E6=88=8F=E9=80=9A=E7=9F=A5=E8=83=B6=E5=9B=8A=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/js/chat-room/message-renderer.js | 345 ++++++++++++++++++--- resources/js/chat-room/riddle-quiz.js | 18 +- 2 files changed, 318 insertions(+), 45 deletions(-) diff --git a/resources/js/chat-room/message-renderer.js b/resources/js/chat-room/message-renderer.js index 2a1f7ee..ec5fb85 100644 --- a/resources/js/chat-room/message-renderer.js +++ b/resources/js/chat-room/message-renderer.js @@ -56,6 +56,13 @@ function parseBracketUsers(content, color = "#000099") { }); } +/** + * 构建统一的猜谜活动标题与题型标签。 + */ +function buildGameLabelChipHtml(label, accentColor) { + return `${escapeHtml(label)}`; +} + /** * 构建统一的猜谜活动标题与题型标签。 */ @@ -64,12 +71,239 @@ function buildQuizBadgeHtml(msg, accentColor = "#7c3aed") { return ` - ${escapeHtml(activityLabel)} - ${escapeHtml(typeLabel)} + ${buildGameLabelChipHtml(activityLabel, accentColor)} + ${escapeHtml(typeLabel)} `; } +/** + * 判断当前消息是否应该使用统一的游戏通知卡片。 + */ +function resolveGameNotificationCardMeta(msg) { + const normalizedContent = String(msg?.content || ""); + const fromUser = String(msg?.from_user || ""); + + if ( + normalizedContent.includes("【百家乐】") + || (normalizedContent.includes("开局:") && normalizedContent.includes("点收割")) + || (normalizedContent.startsWith("🎲") && normalizedContent.includes("点")) + || (normalizedContent.includes("快速参与") && normalizedContent.includes("1:24")) + ) { + return { + label: "百家乐", + icon: "🎲", + accent: "#2563eb", + background: "linear-gradient(135deg,#eff6ff,#f8fbff)", + border: "rgba(37,99,235,.16)", + text: "#1e3a8a", + chipBg: "#dbeafe", + }; + } + + if ( + normalizedContent.includes("【赛马】") + || normalizedContent.startsWith("🐎 开赛:") + || normalizedContent.startsWith("🏇 比赛开始:") + || normalizedContent.startsWith("🏆 冠军:") + ) { + return { + label: "赛马", + icon: "🏇", + accent: "#0f766e", + background: "linear-gradient(135deg,#ecfeff,#f0fdfa)", + border: "rgba(15,118,110,.16)", + text: "#115e59", + chipBg: "#ccfbf1", + }; + } + + if ( + normalizedContent.includes("神秘箱子") + || (normalizedContent.includes("暗号") && normalizedContent.includes("《")) + || normalizedContent.includes("抢到") + || normalizedContent.includes("箱子消失") + ) { + return { + label: "神秘箱子", + icon: "📦", + accent: "#7c3aed", + background: "linear-gradient(135deg,#faf5ff,#fdf4ff)", + border: "rgba(124,58,237,.16)", + text: "#6b21a8", + chipBg: "#ede9fe", + }; + } + + if ( + normalizedContent.includes("双色球") + || /购买\s+\d+\s*期/u.test(normalizedContent) + || /\d+\s*期:\s*🔴/u.test(normalizedContent) + || normalizedContent.includes("超级期") + ) { + return { + label: "双色球彩票", + icon: "🎟️", + accent: "#dc2626", + background: "linear-gradient(135deg,#fef2f2,#fff7ed)", + border: "rgba(220,38,38,.16)", + text: "#991b1b", + chipBg: "#fee2e2", + }; + } + + if (normalizedContent.includes("【五子棋】")) { + return { + label: "五子棋", + icon: "♟️", + accent: "#475569", + background: "linear-gradient(135deg,#f8fafc,#f1f5f9)", + border: "rgba(71,85,105,.16)", + text: "#334155", + chipBg: "#e2e8f0", + }; + } + + if (normalizedContent.includes("老虎机")) { + return { + label: "老虎机", + icon: "🎰", + accent: "#d97706", + background: "linear-gradient(135deg,#fff7ed,#fffbeb)", + border: "rgba(217,119,6,.16)", + text: "#9a3412", + chipBg: "#fed7aa", + }; + } + + if (fromUser === "钓鱼播报") { + return { + label: "钓鱼", + icon: "🎣", + accent: "#059669", + background: "linear-gradient(135deg,#ecfdf5,#f0fdf4)", + border: "rgba(5,150,105,.16)", + text: "#065f46", + chipBg: "#a7f3d0", + }; + } + + return null; +} + +/** + * 提炼系统传音卡片正文,去掉和标签重复的前缀。 + */ +function extractSystemGameCardSummary(content, meta) { + const normalizedContent = String(content || "").trim(); + + if (!meta) { + return normalizedContent; + } + + if (meta.label === "神秘箱子") { + return normalizedContent + .replace(/^[📦💎☠️]\s*/u, "") + .replace(/^【神秘箱子】/u, "") + .replace(/^开箱播报[::]\s*/u, "") + .replace(/投放了一个神秘箱子/u, "投放") + .replace(/抢到了神秘/u, "抢到") + .replace(/即可开箱!?/u, "开箱") + .trim(); + } + + if (meta.label === "百家乐") { + return normalizedContent + .replace(/^[🎲🎉]\s*/u, "") + .replace(/^【百家乐】/u, "") + .replace(/^开局:/u, "") + .replace(/^第\s*#?\d+\s*局开奖!?\s*/u, "") + .replace(/^第\s*#?\d+\s*局开始!?\s*/u, "") + .replace(/总点\s*(\d+)\s*→\s*/u, "$1点 · ") + .replace(/本局无人获奖。?/u, "无人获奖") + .replace(/\s+/gu, " ") + .trim(); + } + + if (meta.label === "赛马") { + return normalizedContent + .replace(/^[🐎🏇🏆]\s*/u, "") + .replace(/^【赛马】/u, "") + .replace(/^第\s*#?\d+\s*场开始!?\s*/u, "") + .replace(/^第\s*#?\d+\s*场押注截止!?\s*/u, "") + .replace(/^第\s*#?\d+\s*场结束!?\s*/u, "") + .replace(/马匹已进入跑道,比赛开始!?/u, "比赛开始") + .trim(); + } + + if (meta.label === "双色球彩票") { + return normalizedContent + .replace(/^[🎟️🎊]+\s*/u, "") + .replace(/^【双色球[^】]*】/u, "") + .trim(); + } + + if (meta.label === "五子棋") { + return normalizedContent + .replace(/^[♟️🏆]+\s*/u, "") + .replace(/^【五子棋】/u, "") + .replace(/^玩家对战结果!/u, "") + .replace(/^棋神降临!/u, "") + .replace(/^AI 大获全胜!/u, "") + .trim(); + } + + if (meta.label === "老虎机") { + return normalizedContent + .replace(/^[🎰🎉]+\s*/u, "") + .replace(/^【老虎机大奖】/u, "") + .trim(); + } + + return normalizedContent; +} + +/** + * 统一系统游戏卡片中的内嵌按钮样式,避免不同游戏沿用旧尺寸。 + */ +function normalizeSystemGameCardActions(content, meta) { + const normalizedContent = String(content || ""); + + return normalizedContent.replace(/]*)>([\s\S]*?)<\/button>/giu, (_match, attributes, label) => { + const onclickMatch = String(attributes || "").match(/\bonclick=(["'])([\s\S]*?)\1/iu); + const onclickAttr = onclickMatch ? ` onclick="${escapeHtml(onclickMatch[2])}"` : ""; + const safeLabel = String(label || "").trim(); + + return ``; + }); +} + +/** + * 将系统传音中的游戏通知渲染为和猜谜活动同级的紧凑卡片。 + */ +function buildSystemGameNotificationHtml(msg, timeStr) { + const content = String(msg.content || ""); + const meta = resolveGameNotificationCardMeta(msg); + if (!meta) { + return ""; + } + + const summary = normalizeSystemGameCardActions(extractSystemGameCardSummary(content, meta), meta); + + return ` +
+
${meta.icon}
+
+ ${buildGameLabelChipHtml(meta.label, meta.accent)} +
+ ${parseBracketUsers(summary, meta.text)} + (${timeStr}) +
+
+
+ `; +} + /** * 猜谜活动开题消息统一渲染为卡片。 */ @@ -86,18 +320,18 @@ function buildQuizStartHtml(msg, timeStr) { const safeHint = escapeHtml(rawHint); return ` -
-
🧩
-
-
${buildQuizBadgeHtml(msg)}
-
+
+
🧩
+
+
${buildQuizBadgeHtml(msg)}
+
${safeHint} - (${timeStr}) + (${timeStr})
-
- 💰 ${quizMeta.rewardGold} 金币 - ⭐ ${quizMeta.rewardExp} 经验 +
+ 💰 ${quizMeta.rewardGold} 金币 + ⭐ ${quizMeta.rewardExp} 经验
@@ -105,26 +339,59 @@ function buildQuizStartHtml(msg, timeStr) { } /** - * 猜谜活动结算消息统一渲染为结果卡片。 + * 提取猜谜活动结束消息里的正确答案,兼容中奖与超时文案。 + */ +function resolveQuizResultAnswerText(msg) { + const quizMeta = normalizeQuizRoundPayload(msg); + const explicitAnswer = String(quizMeta.answer || "").trim(); + if (explicitAnswer) { + return explicitAnswer; + } + + const content = String(msg.content || ""); + const matchedAnswer = content.match(/正确答案:(.+?)(?:!|。|$)/u); + + return matchedAnswer?.[1]?.trim() || content.trim(); +} + +/** + * 猜谜活动结束消息统一渲染为和开题通知同级的紧凑卡片。 */ function buildQuizResultHtml(msg, timeStr) { const quizMeta = normalizeQuizRoundPayload(msg); - const winnerHtml = clickableUser(String(msg.winner_username || ""), "#15803d"); - const answerText = escapeHtml(quizMeta.answer || String(msg.content || "")); + const winnerUsername = String(msg.winner_username || "").trim(); + const answerText = escapeHtml(resolveQuizResultAnswerText(msg)); + const isAnsweredResult = winnerUsername !== ""; + const accentColor = isAnsweredResult ? "#16a34a" : "#d97706"; + const accentBackground = isAnsweredResult + ? "linear-gradient(135deg,#f0fdf4,#ecfccb)" + : "linear-gradient(135deg,#fff7ed,#fffbeb)"; + const accentBorder = isAnsweredResult ? "rgba(22,163,74,.18)" : "rgba(217,119,6,.18)"; + const textColor = isAnsweredResult ? "#166534" : "#9a3412"; + const icon = isAnsweredResult ? "🎉" : "⏳"; + const iconBackground = isAnsweredResult + ? "linear-gradient(135deg,#16a34a,#4ade80)" + : "linear-gradient(135deg,#f59e0b,#f97316)"; + const badgeColor = isAnsweredResult ? "#16a34a" : "#d97706"; + const summaryHtml = isAnsweredResult + ? `【${clickableUser(winnerUsername, "#15803d")}】率先答对「${answerText}」` + : `第 #${quizMeta.endedRoundId || quizMeta.roundId || 0} 题已超时结束,正确答案:${answerText}`; return ` -
-
🎉
-
-
- ${buildQuizBadgeHtml(msg, "#16a34a")} - (${timeStr}) -
-
【${winnerHtml}】率先答对「${answerText}」
-
- 💰 ${quizMeta.rewardGold} 金币 - ⭐ ${quizMeta.rewardExp} 经验 +
+
${icon}
+
+
${buildQuizBadgeHtml(msg, badgeColor)}
+
+ ${summaryHtml} + (${timeStr})
+ ${isAnsweredResult ? ` +
+ 💰 ${quizMeta.rewardGold} 金币 + ⭐ ${quizMeta.rewardExp} 经验 +
+ ` : ""}
`; @@ -289,8 +556,8 @@ export function appendMessage(msg, renderBatch = null) { div.dataset.idiomResult = "1"; div.dataset.quizRoundEndedId = String(quizMeta.endedRoundId || quizMeta.roundId || 0); div.dataset.quizWinnerUsername = String(msg.winner_username || ""); - const parsedContent = parseBracketUsers(msg.content); - html = `${headImg}${clickableUser(msg.from_user, fontColor, nameClass)}:${parsedContent}`; + html = buildQuizResultHtml(msg, timeStr); + timeStrOverride = true; } else if (isIdiomStartMessage) { html = buildQuizStartHtml(msg, timeStr); timeStrOverride = true; @@ -356,17 +623,15 @@ export function appendMessage(msg, renderBatch = null) { const isRedPacketClaimNotification = content.includes("抢到了") && content.includes("礼包"); const isBaccaratLossCoverNotification = content.includes("【你玩游戏我买单】") || content.includes("金币补偿"); const isDailySignInNotification = content.includes("完成今日签到") || content.includes("使用补签卡补签"); - const isQuizStartNotification = isIdiomStartMessage || content.includes("猜谜活动") || content.includes("猜成语时间"); - const isPlainNotification = - content.includes("【百家乐】") || - content.includes("【赛马】") || - content.includes("神秘箱子") || - content.includes("【双色球") || - content.includes("【五子棋】") || - content.includes("【老虎机】") || - content.includes("购买了"); + const isQuizEndNotification = content.includes("猜谜活动") && (content.includes("已超时结束") || content.includes("正确答案")); + const isQuizStartNotification = !isQuizEndNotification && (isIdiomStartMessage || content.includes("猜谜活动") || content.includes("猜成语时间")); + const systemGameCardMeta = resolveGameNotificationCardMeta(msg); + const isPlainNotification = content.includes("购买了"); - if (isQuizStartNotification) { + if (isQuizEndNotification) { + html = buildQuizResultHtml(msg, timeStr); + timeStrOverride = true; + } else if (isQuizStartNotification) { div.style.cssText = "background:linear-gradient(135deg,#fff7ed,#fffbeb);border:1px solid rgba(245,158,11,.28);border-left:4px solid #f59e0b;border-radius:12px;padding:8px 12px;margin:4px 0;box-shadow:0 10px 24px rgba(245,158,11,.14);"; html = ` @@ -385,6 +650,9 @@ export function appendMessage(msg, renderBatch = null) { } else if (isRedPacketClaimNotification || isBaccaratLossCoverNotification || isDailySignInNotification) { let plainAccentContent = parseBracketUsers(msg.content); html = `🌟 ${plainAccentContent}`; + } else if (systemGameCardMeta) { + html = buildSystemGameNotificationHtml(msg, timeStr); + timeStrOverride = true; } else if (isPlainNotification) { let parsedContent = parseBracketUsers(msg.content); html = `${headImg}${clickableUser(msg.from_user, fontColor, nameClass)}:${parsedContent}`; @@ -394,6 +662,9 @@ export function appendMessage(msg, renderBatch = null) { let sysTranContent = parseBracketUsers(msg.content); html = `🌟 ${sysTranContent}`; } + } else if (resolveGameNotificationCardMeta(msg)) { + html = buildSystemGameNotificationHtml(msg, timeStr); + timeStrOverride = true; } else if (msg.from_user === "系统" && msg.to_user && msg.to_user !== "大家") { div.style.cssText = "background:#f0fdf4;border-left:3px solid #16a34a;border-radius:4px;padding:3px 8px;margin:2px 0;"; diff --git a/resources/js/chat-room/riddle-quiz.js b/resources/js/chat-room/riddle-quiz.js index 7b4a6e1..002dffd 100644 --- a/resources/js/chat-room/riddle-quiz.js +++ b/resources/js/chat-room/riddle-quiz.js @@ -140,9 +140,9 @@ function buildIdiomAnswerButton(roundId, hint, rewardGold, rewardExp, typeLabel, btn.dataset.quizEnded = "0"; btn.textContent = "🎯 立即答题"; btn.style.cssText = - "padding:4px 12px;background:linear-gradient(135deg,#7c3aed,#a78bfa);" + - "color:#fff;border:none;border-radius:999px;font-size:11px;cursor:pointer;" + - "font-weight:700;line-height:1.2;vertical-align:middle;box-shadow:0 4px 10px rgba(124,58,237,.18);"; + "display:inline-flex;align-items:center;gap:4px;padding:2px 9px;background:linear-gradient(135deg,#7c3aed,#a78bfa);" + + "color:#fff;border:1px solid #7c3aed;border-radius:999px;font-size:11px;cursor:pointer;" + + "font-weight:700;line-height:1;vertical-align:middle;box-shadow:0 2px 6px rgba(124,58,237,.14);"; return btn; } @@ -211,12 +211,13 @@ export function disableIdiomAnswerButtons(roundId = 0, endedText = "本回合已 button.dataset.quizEnded = "1"; button.style.background = "linear-gradient(135deg,#94a3b8,#cbd5e1)"; button.style.color = "#f8fafc"; + button.style.border = "1px solid #94a3b8"; button.style.cursor = "not-allowed"; button.style.boxShadow = "none"; button.style.opacity = ".92"; - button.style.padding = "4px 12px"; + button.style.padding = "2px 9px"; button.style.fontSize = "11px"; - button.style.lineHeight = "1.2"; + button.style.lineHeight = "1"; button.title = endedText; button.textContent = "已结束"; syncQuizWinnerLabel(button, winnerUsername); @@ -236,12 +237,13 @@ function syncQuizAnswerButtons(activeRoundIds) { button.dataset.quizEnded = "0"; button.style.background = "linear-gradient(135deg,#7c3aed,#a78bfa)"; button.style.color = "#fff"; + button.style.border = "1px solid #7c3aed"; button.style.cursor = "pointer"; - button.style.boxShadow = "0 4px 10px rgba(124,58,237,.18)"; + button.style.boxShadow = "0 2px 6px rgba(124,58,237,.14)"; button.style.opacity = "1"; - button.style.padding = "4px 12px"; + button.style.padding = "2px 9px"; button.style.fontSize = "11px"; - button.style.lineHeight = "1.2"; + button.style.lineHeight = "1"; button.title = ""; button.textContent = "🎯 立即答题"; syncQuizWinnerLabel(button, "");