// 手机端抽屉业务模块,承接工具菜单、在线名单和房间列表的 Blade 内联脚本。 import { escapeHtml } from "./html.js"; import { renderRoomsOnlineStatusToContainer } from "./rooms.js"; let mobileDrawerEventsBound = false; // 模块级状态用于维持抽屉互斥、搜索 RAF 节流和房间列表短缓存。 let mobileDrawerOpen = null; let mobileUserListRenderTimer = null; let mobileRoomsOnlineStatusCache = null; let mobileRoomsOnlineStatusCacheAt = 0; const MOBILE_ROOMS_ONLINE_STATUS_CACHE_TTL = 10000; /** * 判断点击是否来自手机抽屉区域。 * document 代理只处理浮动按钮、遮罩和抽屉内部,避免其它区域复用 data 属性时误触发。 * * @param {Element} target 事件目标 * @returns {boolean} */ function isMobileDrawerEventTarget(target) { return Boolean(target.closest("#mobile-fabs, #mobile-drawer-mask, .mobile-drawer")); } /** * 执行手机抽屉工具入口动作。 * * @param {string} action 工具动作 * @returns {void} */ function runMobileToolAction(action) { // 抽屉工具只负责分发到现有全局函数,业务实现仍留在原模块中。 const actions = { "daily-sign-in": () => window.quickDailySignIn?.(), shop: () => window.openShopModal?.(), vip: () => window.openVipModal?.(), "save-exp": () => window.saveExp?.(), game: () => window.openGameHall?.(), bank: () => window.openBankModal?.(), marriage: () => window.openMarriageStatusModal?.(), friend: () => window.openFriendPanel?.(), avatar: () => window.openAvatarPicker?.(), settings: () => { if (typeof window.openSettingsModal === "function") { window.openSettingsModal(); return; } // 兼容设置弹窗尚未迁移为全局函数的页面。 const settingsModal = document.getElementById("settings-modal"); if (settingsModal) { settingsModal.style.display = "flex"; } }, }; actions[action]?.(); } /** * 确认并执行手机端离开房间动作。 * * @returns {void} */ function confirmMobileLeaveRoom() { // 离开房间需要保留二次确认,避免手机误触直接退出。 window.chatDialog ?.confirm("确定要离开聊天室吗?", "离开聊天室") .then((ok) => { if (ok) { window.leaveRoom?.(); } }); } /** * 打开指定手机端抽屉。 * * @param {"toolbar"|"users"|string} which 抽屉名称 * @returns {void} */ export function openMobileDrawer(which) { if (!["toolbar", "users"].includes(which)) { return; } // 切换抽屉时只关闭旧面板,不恢复 body overflow,避免中间态闪烁。 if (mobileDrawerOpen && mobileDrawerOpen !== which) { document.getElementById(`mobile-drawer-${mobileDrawerOpen}`)?.classList.remove("open"); } const drawer = document.getElementById(`mobile-drawer-${which}`); const mask = document.getElementById("mobile-drawer-mask"); if (!drawer || !mask) { return; } drawer.classList.add("open"); mask.classList.add("open"); mobileDrawerOpen = which; document.body.style.overflow = "hidden"; if (which === "users") { renderMobileUserList(); } } /** * 关闭当前打开的手机端抽屉。 * * @param {boolean} resetOverflow 是否恢复 body overflow * @returns {void} */ export function closeMobileDrawer(resetOverflow = true) { if (mobileDrawerOpen) { document.getElementById(`mobile-drawer-${mobileDrawerOpen}`)?.classList.remove("open"); mobileDrawerOpen = null; } document.getElementById("mobile-drawer-mask")?.classList.remove("open"); if (resetOverflow) { document.body.style.overflow = ""; } } /** * 切换名单抽屉内的用户/房间 Tab。 * * @param {"users"|"rooms"|string} tab Tab 名称 * @returns {void} */ export function switchMobileTab(tab) { if (!["users", "rooms"].includes(tab)) { return; } const panelUsers = document.getElementById("mob-panel-users"); const panelRooms = document.getElementById("mob-panel-rooms"); const tabUsers = document.getElementById("mob-tab-users"); const tabRooms = document.getElementById("mob-tab-rooms"); if (panelUsers) { panelUsers.style.display = tab === "users" ? "flex" : "none"; } if (panelRooms) { panelRooms.style.display = tab === "rooms" ? "block" : "none"; } syncMobileTabButton(tabUsers, tab === "users"); syncMobileTabButton(tabRooms, tab === "rooms"); if (tab === "rooms") { void loadMobileRoomList(); } } /** * 同步手机抽屉 Tab 按钮选中样式。 * * @param {HTMLElement|null} button 按钮元素 * @param {boolean} active 是否选中 * @returns {void} */ function syncMobileTabButton(button, active) { if (!button) { return; } button.style.background = active ? "#336699" : "transparent"; button.style.color = active ? "#fff" : "#336699"; button.style.fontWeight = active ? "bold" : "normal"; } /** * 将在线用户渲染到手机端名单容器。 * * @returns {void} */ export function renderMobileUserList() { const onlineUsers = window.onlineUsers; if (!onlineUsers || typeof onlineUsers !== "object") { return; } const sortValue = document.getElementById("mob-user-sort-select")?.value || "default"; const keyword = (document.getElementById("mob-user-search-input")?.value || "").trim().toLowerCase(); const container = document.getElementById("mob-online-users-list"); if (!container) { return; } if (typeof window._renderUserListToContainer === "function") { window._renderUserListToContainer(container, sortValue, keyword); } else { // 降级渲染只展示用户名,保障主渲染函数异常缺失时手机名单仍可用。 const users = Object.keys(onlineUsers); container.innerHTML = users.length ? users .filter((username) => !keyword || username.toLowerCase().includes(keyword)) .map((username) => `
${escapeHtml(username)}
`) .join("") : '
暂无用户
'; } const footerElement = document.getElementById("mob-online-count-footer"); if (footerElement) { footerElement.textContent = Object.keys(onlineUsers).length; } } /** * 调度手机端在线名单渲染,避免搜索输入时同步重建整份名单。 * * @returns {void} */ export function scheduleRenderMobileUserList() { if (mobileUserListRenderTimer !== null) { return; } const scheduleRender = window.requestAnimationFrame || ((callback) => window.setTimeout(callback, 16)); mobileUserListRenderTimer = scheduleRender(() => { mobileUserListRenderTimer = null; renderMobileUserList(); }); } /** * 拉取房间列表并渲染到手机端房间容器。 * * @returns {Promise} */ export async function loadMobileRoomList() { const container = document.getElementById("mob-rooms-online-list"); if (!container) { return; } if (mobileRoomsOnlineStatusCache && Date.now() - mobileRoomsOnlineStatusCacheAt < MOBILE_ROOMS_ONLINE_STATUS_CACHE_TTL) { // 切换手机房间 tab 可能高频触发,10 秒短缓存用来减少接口压力,允许轻微延迟。 renderMobileRoomList(mobileRoomsOnlineStatusCache, container); return; } const statusUrl = container.getAttribute("data-mobile-rooms-status-url") || ""; if (!statusUrl) { container.innerHTML = '
缺少房间接口
'; return; } container.innerHTML = '
加载中...
'; try { const response = await fetch(statusUrl); const data = await response.json(); mobileRoomsOnlineStatusCache = data; mobileRoomsOnlineStatusCacheAt = Date.now(); renderMobileRoomList(data, container); } catch (error) { container.innerHTML = '
加载失败
'; } } /** * 渲染手机端房间列表。 * * @param {Record} data 接口返回数据 * @param {HTMLElement} container 目标容器 * @returns {void} */ export function renderMobileRoomList(data, container) { renderRoomsOnlineStatusToContainer(data, container, { currentRoomId: window.chatContext?.roomId, variant: "mobile", emptyHtml: '
暂无房间
', }); } /** * 绑定手机端抽屉基础控件事件。 * * @returns {void} */ export function bindMobileDrawerControls() { if (mobileDrawerEventsBound || typeof document === "undefined") { return; } mobileDrawerEventsBound = true; document.addEventListener("click", (event) => { if (!(event.target instanceof Element)) { return; } // 手机抽屉内多数节点由 Blade 与后续 AJAX 动态生成,统一使用 document 事件代理。 if (!isMobileDrawerEventTarget(event.target)) { return; } const drawerTrigger = event.target.closest("[data-mobile-drawer-open]"); if (drawerTrigger) { event.preventDefault(); openMobileDrawer(drawerTrigger.dataset.mobileDrawerOpen || ""); return; } if (event.target.closest("[data-mobile-drawer-close]")) { event.preventDefault(); closeMobileDrawer(); return; } const toolAction = event.target.closest("[data-mobile-tool-action]"); if (toolAction) { event.preventDefault(); closeMobileDrawer(); const action = toolAction.getAttribute("data-mobile-tool-action") || ""; if (action === "leave") { confirmMobileLeaveRoom(); return; } runMobileToolAction(action); return; } const toolUrl = event.target.closest("[data-mobile-tool-url]"); if (toolUrl) { event.preventDefault(); closeMobileDrawer(); const url = toolUrl.getAttribute("data-mobile-tool-url"); if (url) { // 工具外链统一新窗口打开,并禁止新窗口反向访问聊天室页面。 window.open(url, "_blank", "noopener,noreferrer"); } return; } const tabTrigger = event.target.closest("[data-mobile-drawer-tab]"); if (tabTrigger) { event.preventDefault(); switchMobileTab(tabTrigger.dataset.mobileDrawerTab || ""); } }); document.addEventListener("change", (event) => { if (!(event.target instanceof HTMLSelectElement) || event.target.id !== "mob-user-sort-select") { return; } renderMobileUserList(); }); document.addEventListener("input", (event) => { if (!(event.target instanceof HTMLInputElement) || event.target.id !== "mob-user-search-input") { return; } scheduleRenderMobileUserList(); }); // 只有名单抽屉打开时才立即重渲染,关闭状态下等下次打开再读取最新 onlineUsers。 window.addEventListener("chatroom:users-updated", () => { if (mobileDrawerOpen === "users") { renderMobileUserList(); } }); }