From 308e5690feddd13352167872ebdb193f869c55d7 Mon Sep 17 00:00:00 2001 From: lkddi Date: Sat, 25 Apr 2026 08:21:30 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BF=81=E7=A7=BB=E6=89=8B=E6=9C=BA=E6=8A=BD?= =?UTF-8?q?=E5=B1=89=E8=84=9A=E6=9C=AC=E5=88=B0Vite=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/js/chat-room.js | 36 ++- resources/js/chat-room/mobile-drawer.js | 235 +++++++++++++++++- .../partials/layout/mobile-drawer.blade.php | 229 +---------------- .../views/chat/partials/scripts.blade.php | 4 + 4 files changed, 266 insertions(+), 238 deletions(-) diff --git a/resources/js/chat-room.js b/resources/js/chat-room.js index 55c9a83..9c9ae61 100644 --- a/resources/js/chat-room.js +++ b/resources/js/chat-room.js @@ -7,7 +7,16 @@ export { applyFontSize, bindChatFontSizeControl, CHAT_FONT_SIZE_STORAGE_KEY, res export { bindChatImageUploadControl } from "./chat-room/image-upload.js"; export { bindFriendPanelControls, closeFriendPanel, friendSearch, loadFriends, openFriendPanel } from "./chat-room/friend-panel.js"; export { closeChatImageLightbox, initChatImageLightboxEvents, openChatImageLightbox } from "./chat-room/lightbox.js"; -export { bindMobileDrawerControls } from "./chat-room/mobile-drawer.js"; +export { + bindMobileDrawerControls, + closeMobileDrawer, + loadMobileRoomList, + openMobileDrawer, + renderMobileRoomList, + renderMobileUserList, + scheduleRenderMobileUserList, + switchMobileTab, +} from "./chat-room/mobile-drawer.js"; export { bindMarriageStatusControls } from "./chat-room/marriage-status.js"; export { bindToolbarControls } from "./chat-room/toolbar.js"; export { bindWelcomeMenuControls } from "./chat-room/welcome-menu.js"; @@ -66,7 +75,16 @@ import { applyFontSize, bindChatFontSizeControl, CHAT_FONT_SIZE_STORAGE_KEY, res import { bindChatImageUploadControl } from "./chat-room/image-upload.js"; import { bindFriendPanelControls, closeFriendPanel, friendSearch, loadFriends, openFriendPanel } from "./chat-room/friend-panel.js"; import { closeChatImageLightbox, initChatImageLightboxEvents, openChatImageLightbox } from "./chat-room/lightbox.js"; -import { bindMobileDrawerControls } from "./chat-room/mobile-drawer.js"; +import { + bindMobileDrawerControls, + closeMobileDrawer, + loadMobileRoomList, + openMobileDrawer, + renderMobileRoomList, + renderMobileUserList, + scheduleRenderMobileUserList, + switchMobileTab, +} from "./chat-room/mobile-drawer.js"; import { bindMarriageStatusControls } from "./chat-room/marriage-status.js"; import { bindToolbarControls } from "./chat-room/toolbar.js"; import { bindWelcomeMenuControls } from "./chat-room/welcome-menu.js"; @@ -134,6 +152,13 @@ if (typeof window !== "undefined") { loadFriends, openFriendPanel, bindMobileDrawerControls, + closeMobileDrawer, + loadMobileRoomList, + openMobileDrawer, + renderMobileRoomList, + renderMobileUserList, + scheduleRenderMobileUserList, + switchMobileTab, bindToolbarControls, bindWelcomeMenuControls, bindAdminMenuControls, @@ -190,6 +215,13 @@ if (typeof window !== "undefined") { window.closeFriendPanel = closeFriendPanel; window.friendSearch = friendSearch; window.openFriendPanel = openFriendPanel; + window.closeMobileDrawer = closeMobileDrawer; + window.loadMobileRoomList = loadMobileRoomList; + window.openMobileDrawer = openMobileDrawer; + window.renderMobileRoomList = renderMobileRoomList; + window.renderMobileUserList = renderMobileUserList; + window.scheduleRenderMobileUserList = scheduleRenderMobileUserList; + window.switchMobileTab = switchMobileTab; window.closeAdminBaccaratLossCoverModal = closeAdminBaccaratLossCoverModal; window.closeCurrentBaccaratLossCoverEvent = closeCurrentBaccaratLossCoverEvent; window.openAdminBaccaratLossCoverModal = openAdminBaccaratLossCoverModal; diff --git a/resources/js/chat-room/mobile-drawer.js b/resources/js/chat-room/mobile-drawer.js index 4f3f187..797f928 100644 --- a/resources/js/chat-room/mobile-drawer.js +++ b/resources/js/chat-room/mobile-drawer.js @@ -1,6 +1,15 @@ -// 手机端抽屉基础控件事件绑定,替代浮动按钮、关闭按钮与名单 tab 的内联事件。 +// 手机端抽屉业务模块,承接工具菜单、在线名单和房间列表的 Blade 内联脚本。 + +import { escapeHtml } from "./html.js"; +import { renderRoomsOnlineStatusToContainer } from "./rooms.js"; let mobileDrawerEventsBound = false; +let mobileDrawerOpen = null; +let mobileUserListRenderTimer = null; +let mobileRoomsOnlineStatusCache = null; +let mobileRoomsOnlineStatusCacheAt = 0; + +const MOBILE_ROOMS_ONLINE_STATUS_CACHE_TTL = 10000; /** * 执行手机抽屉工具入口动作。 @@ -26,6 +35,7 @@ function runMobileToolAction(action) { return; } + // 兼容设置弹窗尚未迁移为全局函数的页面。 const settingsModal = document.getElementById("settings-modal"); if (settingsModal) { settingsModal.style.display = "flex"; @@ -52,6 +62,208 @@ function confirmMobileLeaveRoom() { }); } +/** + * 打开指定手机端抽屉。 + * + * @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) { + 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: '
暂无房间
', + }); +} + /** * 绑定手机端抽屉基础控件事件。 * @@ -68,23 +280,24 @@ export function bindMobileDrawerControls() { return; } + // 手机抽屉内多数节点由 Blade 与后续 AJAX 动态生成,统一使用 document 事件代理。 const drawerTrigger = event.target.closest("[data-mobile-drawer-open]"); if (drawerTrigger) { event.preventDefault(); - window.openMobileDrawer?.(drawerTrigger.dataset.mobileDrawerOpen); + openMobileDrawer(drawerTrigger.dataset.mobileDrawerOpen || ""); return; } if (event.target.closest("[data-mobile-drawer-close]")) { event.preventDefault(); - window.closeMobileDrawer?.(); + closeMobileDrawer(); return; } const toolAction = event.target.closest("[data-mobile-tool-action]"); if (toolAction) { event.preventDefault(); - window.closeMobileDrawer?.(); + closeMobileDrawer(); const action = toolAction.getAttribute("data-mobile-tool-action") || ""; if (action === "leave") { @@ -99,7 +312,7 @@ export function bindMobileDrawerControls() { const toolUrl = event.target.closest("[data-mobile-tool-url]"); if (toolUrl) { event.preventDefault(); - window.closeMobileDrawer?.(); + closeMobileDrawer(); const url = toolUrl.getAttribute("data-mobile-tool-url"); if (url) { @@ -112,7 +325,7 @@ export function bindMobileDrawerControls() { const tabTrigger = event.target.closest("[data-mobile-drawer-tab]"); if (tabTrigger) { event.preventDefault(); - window.switchMobileTab?.(tabTrigger.dataset.mobileDrawerTab); + switchMobileTab(tabTrigger.dataset.mobileDrawerTab || ""); } }); @@ -121,7 +334,7 @@ export function bindMobileDrawerControls() { return; } - window.renderMobileUserList?.(); + renderMobileUserList(); }); document.addEventListener("input", (event) => { @@ -129,6 +342,12 @@ export function bindMobileDrawerControls() { return; } - window.scheduleRenderMobileUserList?.(); + scheduleRenderMobileUserList(); + }); + + window.addEventListener("chatroom:users-updated", () => { + if (mobileDrawerOpen === "users") { + renderMobileUserList(); + } }); } diff --git a/resources/views/chat/partials/layout/mobile-drawer.blade.php b/resources/views/chat/partials/layout/mobile-drawer.blade.php index 8fd363d..5d4883f 100644 --- a/resources/views/chat/partials/layout/mobile-drawer.blade.php +++ b/resources/views/chat/partials/layout/mobile-drawer.blade.php @@ -98,235 +98,8 @@ {{-- 房间面板 --}} - -{{-- ── 手机端抽屉控制脚本 ── --}} - diff --git a/resources/views/chat/partials/scripts.blade.php b/resources/views/chat/partials/scripts.blade.php index 0185b3d..d8ca2be 100644 --- a/resources/views/chat/partials/scripts.blade.php +++ b/resources/views/chat/partials/scripts.blade.php @@ -71,6 +71,8 @@ }); })(); let onlineUsers = {}; + // 暂时暴露给已迁移的手机抽屉 Vite 模块读取,后续在线名单整体迁移后可移除。 + window.onlineUsers = onlineUsers; let autoScroll = true; let userBadgeRotationTick = 0; let userListRenderTimer = null; @@ -2805,6 +2807,8 @@ window.addEventListener('chat:here', (e) => { const users = e.detail; onlineUsers = {}; + // onlineUsers 使用 let 重建对象时,需要同步 window 引用给 Vite 模块。 + window.onlineUsers = onlineUsers; users.forEach(u => { hydrateOnlineUserPayload(u.username, u); });