From 35a80279e6ee76edcc0857a97bd458a361756682 Mon Sep 17 00:00:00 2001 From: lkddi Date: Tue, 17 Mar 2026 17:49:14 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=81=8A=E5=A4=A9=E5=AE=A4=E6=89=8B?= =?UTF-8?q?=E6=9C=BA=E7=AB=AF=E8=87=AA=E9=80=82=E5=BA=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 mobile-drawer.blade.php:手机端浮动按钮 + 工具菜单抽屉 + 名单抽屉(独立维护) - frame.blade.php:手机端代码改为 @include 引入 - chat.css:添加 @media (max-width: 640px) 响应式样式 - 隐藏桌面端工具条和右侧名单面板 - 浮动按钮样式(位于屏幕中间偏右) - 抽屉组件从顶部向下展开 - 手机端隐藏房间介绍、输入栏动作/字色/字号/禁音/分屏控件 - 现有 modal 弹窗 max-width 自适应修复 - scripts.blade.php:重构 renderUserList 提取 _renderUserListToContainer - 修复代码损坏残留,补回 setAction/scrollToBottom/autoScrollEl --- public/css/chat.css | 221 +++++++++++++++ resources/views/chat/frame.blade.php | 6 + .../partials/layout/mobile-drawer.blade.php | 267 ++++++++++++++++++ .../views/chat/partials/scripts.blade.php | 83 ++++-- 4 files changed, 552 insertions(+), 25 deletions(-) create mode 100644 resources/views/chat/partials/layout/mobile-drawer.blade.php diff --git a/public/css/chat.css b/public/css/chat.css index 49d4218..80b6422 100644 --- a/public/css/chat.css +++ b/public/css/chat.css @@ -658,4 +658,225 @@ a:hover { 100% { transform: scale(1) rotate(0deg); } +} + +/* ═══════════════════════════════════════════════════ + 手机端响应式布局(≤ 640px) + ═══════════════════════════════════════════════════ */ +@media (max-width: 640px) { + + /* 隐藏中间工具条和右侧面板,让消息区占满全宽 */ + .chat-toolbar, + .chat-right { + display: none !important; + } + + /* 输入框最小宽度适配手机屏 */ + .input-bar .say-input { + min-width: 0; + } + + /* ── 右下角浮动按钮组 ── */ + #mobile-fabs { + position: fixed; + top: 35%; + right: 10px; + display: flex; + flex-direction: row; + gap: 8px; + z-index: 500; + } + + .mobile-fab { + width: 44px; + height: 44px; + border-radius: 50%; + border: none; + background: var(--bg-header); + color: #fff; + font-size: 18px; + cursor: pointer; + box-shadow: 0 3px 10px rgba(0, 0, 0, 0.35); + display: flex; + align-items: center; + justify-content: center; + transition: transform 0.15s, background 0.15s; + user-select: none; + -webkit-tap-highlight-color: transparent; + } + + .mobile-fab:active { + transform: scale(0.92); + background: var(--border-blue); + } + + /* ── 抽屉遮罩 ── */ + #mobile-drawer-mask { + display: none; + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.45); + z-index: 600; + } + + #mobile-drawer-mask.open { + display: block; + } + + /* ── 抽屉通用容器(顶部向下滑入) ── */ + .mobile-drawer { + position: fixed; + left: 0; + right: 0; + top: 0; + z-index: 700; + background: #fff; + border-bottom: 2px solid var(--border-blue); + border-radius: 0 0 14px 14px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25); + transform: translateY(-100%); + transition: transform 0.28s cubic-bezier(0.4, 0, 0.2, 1); + overflow: hidden; + display: flex; + flex-direction: column; + } + + .mobile-drawer.open { + transform: translateY(0); + } + + /* 抽屉顶部标题栏 */ + .mobile-drawer-header { + background: var(--bg-header); + color: #fff; + padding: 10px 14px; + font-size: 14px; + font-weight: bold; + display: flex; + justify-content: space-between; + align-items: center; + flex-shrink: 0; + } + + .mobile-drawer-close { + background: none; + border: none; + color: #fff; + font-size: 20px; + cursor: pointer; + line-height: 1; + opacity: 0.85; + padding: 0 4px; + } + + /* ── 工具条抽屉:横向网格排列按钮 ── */ + #mobile-drawer-toolbar .mobile-drawer-body { + display: grid; + grid-template-columns: repeat(5, 1fr); + gap: 1px; + background: var(--border-blue); + max-height: 55vh; + overflow-y: auto; + } + + #mobile-drawer-toolbar .mobile-tool-btn { + background: var(--bg-toolbar); + color: #ddd; + font-size: 13px; + padding: 14px 4px; + text-align: center; + cursor: pointer; + user-select: none; + -webkit-tap-highlight-color: transparent; + transition: background 0.15s, color 0.15s; + writing-mode: horizontal-tb; + letter-spacing: 0; + } + + #mobile-drawer-toolbar .mobile-tool-btn:active { + background: #5599cc; + color: #fff; + } + + /* ── 名单/房间抽屉:复用原 right-panel 结构 ── */ + #mobile-drawer-users .mobile-drawer-body { + max-height: 65vh; + overflow: hidden; + display: flex; + flex-direction: column; + flex: 1; + } + + #mobile-drawer-users .mobile-right-clone { + display: flex; + flex-direction: column; + flex: 1; + overflow: hidden; + } + + /* 抽屉内的 tabs 复用样式 */ + #mobile-drawer-users .right-tabs { + flex-shrink: 0; + } + + #mobile-drawer-users .user-list-content { + flex: 1; + overflow-y: auto; + max-height: calc(65vh - 80px); + } + + #mobile-drawer-users .online-stats { + flex-shrink: 0; + } + + /* ── 名单抽屉内用户列表 item 放大触摸区域 ── */ + #mob-online-users-list .user-item { + padding: 7px 8px; + font-size: 13px; + } + + /* ── 现有弹窗(settings / avatar / shop)手机端自适应 ── */ + /* 防止固定像素宽度的 modal 内容区超出手机屏宽 */ + #settings-modal > div, + #avatar-picker-modal > div { + width: 92vw !important; + max-width: 420px !important; + max-height: 85vh !important; + } + + #shop-modal { + padding: 12px 6px !important; + } + + #shop-modal-inner { + width: 92vw !important; + max-width: 420px !important; + max-height: 85vh !important; + overflow-y: auto !important; + } + + /* ── 手机端隐藏房间介绍 ── */ + .room-desc { + display: none !important; + } + + /* ── 手机端隐藏输入栏中不常用的控件(动作/字色/字号/禁音/分屏) ── */ + /* 用 :has() 选择器精准定位含对应 input/select 的 label */ + .input-row label:has(#action), + .input-row label:has(#font_color), + .input-row label:has(#font_size_select), + .input-row label:has(#sound_muted), + .input-row label:has(#split_screen) { + display: none !important; + } +} + +/* 桌面端(> 640px)不显示浮动按钮和抽屉组件 */ +@media (min-width: 641px) { + #mobile-fabs, + #mobile-drawer-mask, + #mobile-drawer-toolbar, + #mobile-drawer-users { + display: none !important; + } } \ No newline at end of file diff --git a/resources/views/chat/frame.blade.php b/resources/views/chat/frame.blade.php index 7baa7f7..9737a45 100644 --- a/resources/views/chat/frame.blade.php +++ b/resources/views/chat/frame.blade.php @@ -128,7 +128,13 @@ @include('chat.partials.layout.right-panel') + {{-- ═══════════ 手机端浮动按钮 + 抽屉(≤ 640px 屏幕有效,独立维护)═══════════ --}} + @include('chat.partials.layout.mobile-drawer') + + + {{-- ═══════════ 全局 UI 公共组件 ═══════════ --}} + {{-- 自定义弹窗(替代原生 alert/confirm/prompt,全页面可用) --}} @include('chat.partials.global-dialog') {{-- Toast 轻提示 --}} diff --git a/resources/views/chat/partials/layout/mobile-drawer.blade.php b/resources/views/chat/partials/layout/mobile-drawer.blade.php new file mode 100644 index 0000000..1461813 --- /dev/null +++ b/resources/views/chat/partials/layout/mobile-drawer.blade.php @@ -0,0 +1,267 @@ +{{-- + 文件功能:手机端自适应抽屉组件(≤ 640px 屏幕生效) + 包含: + 1. 浮动按钮组(右下角固定):🔧 工具 / 👥 名单 + 2. 工具条抽屉(底部滑入,横向网格排列工具按钮) + 3. 名单/房间抽屉(底部滑入,静态 HTML 结构,JS 动态填充) + 4. 遮罩层(点击关闭) + 5. JS 控制逻辑 + + 依赖: + - chat.css @media (max-width: 640px) 中的样式定义 + - scripts.blade.php 中的 onlineUsers / _renderUserListToContainer + + @author ChatRoom Laravel + @version 1.1.0 +--}} + +{{-- ── 浮动按钮组(右下角固定) ── --}} +
+ + +
+ +{{-- ── 遮罩(点击关闭当前抽屉) ── --}} +
+ +{{-- ── 工具条抽屉(底部滑入) ── --}} +
+
+ 🔧 工具菜单 + +
+
+
🛒
商店
+
💾
存点
+
🎮
娱乐
+
🏦
银行
+
💍
婚姻
+
👫
好友
+
🖼️
头像
+
⚙️
设置
+
📝
反馈
+
📬
留言
+
📖
规则
+
🏆
排行
+ @if ($user->id === 1 || $user->activePosition()->exists()) +
🛡️
管理
+ @endif +
🚪
离开
+
+
+ +{{-- ── 名单/房间抽屉(静态 HTML,JS 动态填充) ── --}} +
+
+ 👥 在线名单 + +
+ + {{-- Tab 切换条 --}} +
+ + +
+ + {{-- 名单面板 --}} +
+ {{-- 排序/搜索工具栏 --}} +
+ + +
+ + {{-- 用户列表容器 --}} +
+
加载中...
+
+ + {{-- 在线人数底栏 --}} +
+ 在线: 0 人 +
+
+ + {{-- 房间面板 --}} + +
+ +{{-- ── 手机端抽屉控制脚本 ── --}} + diff --git a/resources/views/chat/partials/scripts.blade.php b/resources/views/chat/partials/scripts.blade.php index bfc04e4..7144577 100644 --- a/resources/views/chat/partials/scripts.blade.php +++ b/resources/views/chat/partials/scripts.blade.php @@ -119,8 +119,12 @@ } - // ── 动作选择 ────────────────────────────────────── + /** + * 设置发言动作并聚焦输入框 + * + * @param {string} act 动作名称 + */ function setAction(act) { document.getElementById('action').value = act; switchTab('users'); @@ -136,6 +140,9 @@ } // ── 滚动到底部 ─────────────────────────────────── + /** + * 将公聊窗口滚动到最新消息(受 autoScroll 开关控制) + */ function scrollToBottom() { if (autoScroll) { container.scrollTop = container.scrollHeight; @@ -143,9 +150,16 @@ } // ── 渲染在线人员列表(支持排序) ────────────────── - function renderUserList() { - userList.innerHTML = ''; - toUserSelect.innerHTML = ''; + /** + * 核心渲染函数:将在线用户渲染到指定容器(桌面端名单区和手机端抽屉共用) + * + * @param {HTMLElement} targetContainer 目标 DOM 容器 + * @param {string} sortBy 排序方式:'default' | 'name' | 'level' + * @param {string} keyword 搜索关键词(小写) + */ + function _renderUserListToContainer(targetContainer, sortBy, keyword) { + if (!targetContainer) return; + targetContainer.innerHTML = ''; // 在列表顶部添加"大家"条目(原版风格) let allDiv = document.createElement('div'); @@ -154,7 +168,7 @@ allDiv.onclick = () => { toUserSelect.value = '大家'; }; - userList.appendChild(allDiv); + targetContainer.appendChild(allDiv); // ── AI 小助手(仅当全局开关开启时显示,与普通用户风格一致)── if (window.chatContext.chatBotEnabled) { @@ -168,19 +182,9 @@ toUserSelect.value = 'AI小班长'; document.getElementById('content').focus(); }; - userList.appendChild(botDiv); - - // 在发言对象下拉框中也添加 AI 小助手 - let botOption = document.createElement('option'); - botOption.value = 'AI小班长'; - botOption.textContent = '🤖 AI小班长'; - toUserSelect.appendChild(botOption); + targetContainer.appendChild(botDiv); } - // 获取排序方式 - const sortSelect = document.getElementById('user-sort-select'); - const sortBy = sortSelect ? sortSelect.value : 'default'; - // 构建用户数组并排序 let userArr = []; for (let username in onlineUsers) { @@ -196,21 +200,23 @@ userArr.sort((a, b) => (b.user_level || 0) - (a.user_level || 0)); } - let count = userArr.length; - userArr.forEach(user => { const username = user.username; + + // 搜索过滤 + if (keyword && !username.toLowerCase().includes(keyword)) return; + let item = document.createElement('div'); item.className = 'user-item'; item.dataset.username = username; const headface = (user.headface || '1.gif').toLowerCase(); - const headImgSrc = headface.startsWith('storage/') ? '/' + headface : '/images/headface/' + headface; + const headImgSrc = headface.startsWith('storage/') ? '/' + headface : '/images/headface/' + + headface; // 徽章优先级:职务图标 > 管理员 > VIP let badges = ''; if (user.position_icon) { - // 有职务:显示职务图标,hover 显示职务名称 const posTitle = (user.position_name || '在职') + ' · ' + username; badges += `${user.position_icon}`; @@ -235,23 +241,48 @@ }; // 双击打开用户名片弹窗(全局统一入口) item.ondblclick = () => openUserCard(username); - userList.appendChild(item); + targetContainer.appendChild(item); + }); + } + function renderUserList() { + userList.innerHTML = ''; + toUserSelect.innerHTML = ''; + + // 获取排序方式和搜索词 + const sortSelect = document.getElementById('user-sort-select'); + const sortBy = sortSelect ? sortSelect.value : 'default'; + const searchInput = document.getElementById('user-search-input'); + const keyword = searchInput ? searchInput.value.trim().toLowerCase() : ''; + + // 调用核心渲染(桌面端名单容器) + _renderUserListToContainer(userList, sortBy, keyword); + + // 重新填充发言对象下拉框(不过滤关键词,始终显示全部用户) + toUserSelect.innerHTML = ''; + if (window.chatContext.chatBotEnabled) { + let botOption = document.createElement('option'); + botOption.value = 'AI小班长'; + botOption.textContent = '🤖 AI小班长'; + toUserSelect.appendChild(botOption); + } + for (let username in onlineUsers) { if (username !== window.chatContext.username) { let option = document.createElement('option'); option.value = username; option.textContent = username; toUserSelect.appendChild(option); } - }); + } + const count = Object.keys(onlineUsers).length; onlineCount.innerText = count; onlineCountBottom.innerText = count; const footer = document.getElementById('online-count-footer'); - if (footer) footer.innerText = count; + if (footer) { footer.innerText = count; } - // 如果有搜索关键词,重新过滤 - filterUserList(); + // 派发用户列表更新事件,供手机端抽屉同步 + window.dispatchEvent(new Event('chatroom:users-updated')); } /** @@ -275,6 +306,8 @@ + + /** * 追加消息到聊天窗格(原版风格:非气泡模式,逐行显示) */