diff --git a/resources/js/chat-room/right-panel.js b/resources/js/chat-room/right-panel.js
index 3b903d7..6260a6e 100644
--- a/resources/js/chat-room/right-panel.js
+++ b/resources/js/chat-room/right-panel.js
@@ -1,9 +1,75 @@
// 聊天室右侧名单/房间面板事件绑定,逐步替代 Blade 内联事件。
let rightPanelEventsBound = false;
+// 用户名单由 Blade 主脚本动态重建,点击/双击状态必须放在模块层避免 DOM 重建后残留闭包。
+const userListClickTimers = new WeakMap();
+const userListLastTapTimes = new WeakMap();
+const USER_LIST_CLICK_DELAY = 250;
+const USER_LIST_DOUBLE_TAP_DELAY = 300;
/**
- * 绑定右侧在线面板的 tab、刷新、排序和搜索事件。
+ * 选择聊天目标并聚焦输入框。
+ *
+ * @param {string} username 目标用户名
+ * @returns {void}
+ */
+function selectChatTarget(username) {
+ const toUserSelect = document.getElementById("to_user");
+ if (toUserSelect instanceof HTMLSelectElement) {
+ toUserSelect.value = username;
+ }
+
+ // 手机抽屉里的名单点击后需要收起抽屉,桌面端没有该函数时自然跳过。
+ window.closeMobileDrawer?.();
+ document.getElementById("content")?.focus();
+}
+
+/**
+ * 打开用户资料卡并取消待执行的单击选择。
+ *
+ * @param {HTMLElement} item 用户列表项
+ * @returns {void}
+ */
+function openUserCardFromList(item) {
+ const username = item.dataset.username || "";
+ const clickTimer = userListClickTimers.get(item);
+
+ if (!username) {
+ return;
+ }
+
+ if (clickTimer) {
+ window.clearTimeout(clickTimer);
+ userListClickTimers.delete(item);
+ }
+
+ window.closeMobileDrawer?.();
+ window.openUserCard?.(username);
+}
+
+/**
+ * 调度用户列表单击选择,延迟执行用于和双击打开名片互斥。
+ *
+ * @param {HTMLElement} item 用户列表项
+ * @returns {void}
+ */
+function scheduleUserListSelection(item) {
+ const username = item.dataset.username || "";
+
+ if (!username || userListClickTimers.has(item)) {
+ return;
+ }
+
+ const clickTimer = window.setTimeout(() => {
+ userListClickTimers.delete(item);
+ selectChatTarget(username);
+ }, USER_LIST_CLICK_DELAY);
+
+ userListClickTimers.set(item, clickTimer);
+}
+
+/**
+ * 绑定右侧在线面板的 tab、刷新、排序、搜索与用户列表选择事件。
*
* @returns {void}
*/
@@ -31,9 +97,58 @@ export function bindChatRightPanelControls() {
if (refreshButton) {
event.preventDefault();
window.renderUserList?.();
+ return;
+ }
+
+ const everyoneItem = event.target.closest("[data-user-list-everyone]");
+ if (everyoneItem) {
+ event.preventDefault();
+ selectChatTarget("大家");
+ return;
+ }
+
+ const userItem = event.target.closest("[data-user-list-entry]");
+ if (userItem instanceof HTMLElement) {
+ event.preventDefault();
+ scheduleUserListSelection(userItem);
}
});
+ document.addEventListener("dblclick", (event) => {
+ if (!(event.target instanceof Element)) {
+ return;
+ }
+
+ const userItem = event.target.closest("[data-user-list-entry]");
+ if (userItem instanceof HTMLElement) {
+ event.preventDefault();
+ openUserCardFromList(userItem);
+ }
+ });
+
+ document.addEventListener("touchend", (event) => {
+ if (!(event.target instanceof Element)) {
+ return;
+ }
+
+ const userItem = event.target.closest("[data-user-list-entry]");
+ if (!(userItem instanceof HTMLElement)) {
+ return;
+ }
+
+ // 移动端双触发沿用旧的 300ms 口径,单触摸仍交给 click 事件选择聊天目标。
+ const now = Date.now();
+ const lastTapTime = userListLastTapTimes.get(userItem) || 0;
+ if (now - lastTapTime < USER_LIST_DOUBLE_TAP_DELAY) {
+ event.preventDefault();
+ userListLastTapTimes.delete(userItem);
+ openUserCardFromList(userItem);
+ return;
+ }
+
+ userListLastTapTimes.set(userItem, now);
+ }, { passive: false });
+
document.addEventListener("change", (event) => {
if (!(event.target instanceof HTMLSelectElement) || event.target.id !== "user-sort-select") {
return;
diff --git a/resources/views/chat/partials/scripts.blade.php b/resources/views/chat/partials/scripts.blade.php
index 4bb7494..86de7c9 100644
--- a/resources/views/chat/partials/scripts.blade.php
+++ b/resources/views/chat/partials/scripts.blade.php
@@ -1931,10 +1931,8 @@
// 在列表顶部添加"大家"条目(原版风格)
let allDiv = document.createElement('div');
allDiv.className = 'user-item';
+ allDiv.dataset.userListEveryone = '1';
allDiv.innerHTML = '大家';
- allDiv.onclick = () => {
- toUserSelect.value = '大家';
- };
fragment.appendChild(allDiv);
// ── AI 小助手(已在 onlineUsers 中动态维护,移除硬编码)──
@@ -1963,6 +1961,7 @@
let item = document.createElement('div');
item.className = 'user-item';
item.dataset.username = username;
+ item.dataset.userListEntry = '1';
const headface = (user.headface || '1.gif');
const headImgSrc = headface.startsWith('storage/') ? '/' + headface : '/images/headface/' +
@@ -1978,41 +1977,7 @@
${badges}
`;
- // 单击/双击互斥:单击延迟 250ms 执行,双击取消单击定时器后直接执行双击逻辑
- let _clickTimer = null;
- item.onclick = () => {
- if (_clickTimer) {
- return;
- }
- _clickTimer = setTimeout(() => {
- _clickTimer = null;
- toUserSelect.value = username;
- // 手机端:点击名字时关闭名单抽屉
- if (typeof closeMobileDrawer === 'function') {
- closeMobileDrawer();
- }
- document.getElementById('content').focus();
- }, 250);
- };
- // 双击 / 手机端双触发 打开用户名片弹窗
- const _openCardFromList = () => {
- if (_clickTimer) { clearTimeout(_clickTimer); _clickTimer = null; }
- if (typeof closeMobileDrawer === 'function') { closeMobileDrawer(); }
- openUserCard(username);
- };
- item.ondblclick = _openCardFromList;
- // 手机端:检测 300ms 内两次 touchend 触发双击
- let _tapTime = 0;
- item.addEventListener('touchend', (e) => {
- const now = Date.now();
- if (now - _tapTime < 300) {
- e.preventDefault();
- _openCardFromList();
- _tapTime = 0;
- } else {
- _tapTime = now;
- }
- }, { passive: false });
+ // 具体点击、双击与手机双触发由 Vite 的 right-panel.js 统一事件委托处理。
fragment.appendChild(item);
});