From 6cb63a98e26dbb457db859e3889a94d1e79f2f46 Mon Sep 17 00:00:00 2001 From: lkddi Date: Sat, 25 Apr 2026 11:03:40 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BF=81=E7=A7=BB=E6=B6=88=E6=81=AF=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E5=90=8D=E7=82=B9=E5=87=BB=E5=A7=94=E6=89=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/js/chat-room/right-panel.js | 106 ++++++++++++++++++ .../views/chat/partials/scripts.blade.php | 60 +--------- 2 files changed, 112 insertions(+), 54 deletions(-) diff --git a/resources/js/chat-room/right-panel.js b/resources/js/chat-room/right-panel.js index 6260a6e..b46cdf6 100644 --- a/resources/js/chat-room/right-panel.js +++ b/resources/js/chat-room/right-panel.js @@ -4,8 +4,12 @@ let rightPanelEventsBound = false; // 用户名单由 Blade 主脚本动态重建,点击/双击状态必须放在模块层避免 DOM 重建后残留闭包。 const userListClickTimers = new WeakMap(); const userListLastTapTimes = new WeakMap(); +const messageUserClickTimers = new WeakMap(); +const messageUserLastTapTimes = new WeakMap(); const USER_LIST_CLICK_DELAY = 250; const USER_LIST_DOUBLE_TAP_DELAY = 300; +const MESSAGE_USER_CLICK_DELAY = 250; +const MESSAGE_USER_DOUBLE_TAP_DELAY = 300; /** * 选择聊天目标并聚焦输入框。 @@ -47,6 +51,78 @@ function openUserCardFromList(item) { window.openUserCard?.(username); } +/** + * 读取消息区用户名,过滤掉系统播报和游戏标签等不可交互内容。 + * + * @param {HTMLElement} item 消息区用户名节点 + * @returns {string} + */ +function getMessageUsername(item) { + return (item.dataset.u || "").trim(); +} + +/** + * 消息区用户名单击选择聊天目标,保留旧 switchTarget 的离线用户兜底逻辑。 + * + * @param {string} username 目标用户名 + * @returns {void} + */ +function selectMessageChatTarget(username) { + if (!username) { + return; + } + + if (typeof window.switchTarget === "function") { + window.switchTarget(username); + return; + } + + selectChatTarget(username); +} + +/** + * 消息区用户名双击打开名片,并取消待执行的单击选择。 + * + * @param {HTMLElement} item 消息区用户名节点 + * @returns {void} + */ +function openUserCardFromMessage(item) { + const username = getMessageUsername(item); + const clickTimer = messageUserClickTimers.get(item); + + if (!username) { + return; + } + + if (clickTimer) { + window.clearTimeout(clickTimer); + messageUserClickTimers.delete(item); + } + + window.openUserCard?.(username); +} + +/** + * 调度消息区用户名单击选择,延迟执行用于和双击打开名片互斥。 + * + * @param {HTMLElement} item 消息区用户名节点 + * @returns {void} + */ +function scheduleMessageUserSelection(item) { + const username = getMessageUsername(item); + + if (!username || messageUserClickTimers.has(item)) { + return; + } + + const clickTimer = window.setTimeout(() => { + messageUserClickTimers.delete(item); + selectMessageChatTarget(username); + }, MESSAGE_USER_CLICK_DELAY); + + messageUserClickTimers.set(item, clickTimer); +} + /** * 调度用户列表单击选择,延迟执行用于和双击打开名片互斥。 * @@ -111,6 +187,13 @@ export function bindChatRightPanelControls() { if (userItem instanceof HTMLElement) { event.preventDefault(); scheduleUserListSelection(userItem); + return; + } + + const messageUser = event.target.closest("[data-chat-message-user]"); + if (messageUser instanceof HTMLElement) { + event.preventDefault(); + scheduleMessageUserSelection(messageUser); } }); @@ -123,6 +206,13 @@ export function bindChatRightPanelControls() { if (userItem instanceof HTMLElement) { event.preventDefault(); openUserCardFromList(userItem); + return; + } + + const messageUser = event.target.closest("[data-chat-message-user]"); + if (messageUser instanceof HTMLElement) { + event.preventDefault(); + openUserCardFromMessage(messageUser); } }); @@ -133,6 +223,22 @@ export function bindChatRightPanelControls() { const userItem = event.target.closest("[data-user-list-entry]"); if (!(userItem instanceof HTMLElement)) { + const messageUser = event.target.closest("[data-chat-message-user]"); + if (!(messageUser instanceof HTMLElement)) { + return; + } + + // 消息区用户名由 Blade 渲染,移动端双触发统一在 Vite 模块内处理。 + const now = Date.now(); + const lastTapTime = messageUserLastTapTimes.get(messageUser) || 0; + if (now - lastTapTime < MESSAGE_USER_DOUBLE_TAP_DELAY) { + event.preventDefault(); + messageUserLastTapTimes.delete(messageUser); + openUserCardFromMessage(messageUser); + return; + } + + messageUserLastTapTimes.set(messageUser, now); return; } diff --git a/resources/views/chat/partials/scripts.blade.php b/resources/views/chat/partials/scripts.blade.php index 0caeee0..edeb493 100644 --- a/resources/views/chat/partials/scripts.blade.php +++ b/resources/views/chat/partials/scripts.blade.php @@ -43,33 +43,6 @@ const hoverTooltip = document.getElementById('chat-hover-tooltip'); let activeTooltipTrigger = null; - // ── 消息区:手机端双触发打开用户名片(PC 端靠 ondblclick 内联属性)── - // span[data-u] 由 clickableUser() 生成,touchend 委托至容器避免每条消息单独绑定 - (function _bindMsgDoubleTap() { - let _lastTapTarget = null; - let _lastTapTime = 0; - - /** touchend 委托处理函数 */ - function _onMsgTouch(e) { - const span = e.target.closest('[data-u]'); - if (!span) return; - const now = Date.now(); - if (span === _lastTapTarget && now - _lastTapTime < 300) { - e.preventDefault(); - openUserCard(span.dataset.u); - _lastTapTarget = null; - _lastTapTime = 0; - } else { - _lastTapTarget = span; - _lastTapTime = now; - } - } - - // 两个聊天容器(公屏 + 包厢)都绑定 - [container, container2].forEach(c => { - if (c) { c.addEventListener('touchend', _onMsgTouch, { passive: false }); } - }); - })(); let onlineUsers = {}; // 暂时暴露给已迁移的手机抽屉 Vite 模块读取,后续在线名单整体迁移后可移除。 window.onlineUsers = onlineUsers; @@ -2357,15 +2330,16 @@ return false; }; - // 用户名(单击切换发言对象,双击查看资料;系统用户或游戏标签仅显示文本) + // 用户名(单击切换发言对象,双击查看资料;事件委托已迁至 Vite right-panel.js) const clickableUser = (uName, color) => { + const safeName = escapeHtml(uName); if (uName === 'AI小班长') { - return `${uName}`; + return `${safeName}`; } if (systemUsers.includes(uName) || isGameLabel(uName)) { - return `${uName}`; + return `${safeName}`; } - return `${uName}`; + return `${safeName}`; }; // 普通用户(包括 AI小班长)用数据库头像,播报类用特殊喇叭图标 @@ -2702,29 +2676,7 @@ window.initChat(window.chatContext.roomId); } - // 手机端:在公屏(say1)和包厢(say2)容器上注册 touchend 委托 - // 检测 300ms 内两次触摸同一 [data-u] 用户名 span,触发 openUserCard - let _msgTapTarget = null; - let _msgTapTime = 0; - ['say1', 'say2'].forEach(id => { - const el = document.getElementById(id); - if (!el) { return; } - el.addEventListener('touchend', (e) => { - const span = e.target.closest('[data-u]'); - if (!span) { return; } - const uName = span.dataset.u; - const now = Date.now(); - if (uName === _msgTapTarget && now - _msgTapTime < 300) { - e.preventDefault(); - openUserCard(uName); - _msgTapTarget = null; - _msgTapTime = 0; - } else { - _msgTapTarget = uName; - _msgTapTime = now; - } - }, { passive: false }); - }); + // 消息区用户名点击/双击/移动端双触发已迁至 Vite 的 right-panel.js 统一委托。 }); // ── WebSocket 事件监听 ────────────────────────────