Files
chatroom/resources/views/chat/partials/layout/mobile-drawer.blade.php

268 lines
13 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{{--
文件功能:手机端自适应抽屉组件(≤ 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
--}}
{{-- ── 浮动按钮组(右下角固定) ── --}}
<div id="mobile-fabs">
<button class="mobile-fab" id="fab-toolbar" onclick="openMobileDrawer('toolbar')" title="工具菜单">🔧</button>
<button class="mobile-fab" id="fab-users" onclick="openMobileDrawer('users')" title="在线名单">👥</button>
</div>
{{-- ── 遮罩(点击关闭当前抽屉) ── --}}
<div id="mobile-drawer-mask" onclick="closeMobileDrawer()"></div>
{{-- ── 工具条抽屉(底部滑入) ── --}}
<div id="mobile-drawer-toolbar" class="mobile-drawer">
<div class="mobile-drawer-header">
🔧 工具菜单
<button class="mobile-drawer-close" onclick="closeMobileDrawer()"></button>
</div>
<div class="mobile-drawer-body">
<div class="mobile-tool-btn" onclick="closeMobileDrawer();openShopModal();">🛒<br>商店</div>
<div class="mobile-tool-btn" onclick="closeMobileDrawer();saveExp();">💾<br>存点</div>
<div class="mobile-tool-btn" onclick="closeMobileDrawer();openGameHall();">🎮<br>娱乐</div>
<div class="mobile-tool-btn" onclick="closeMobileDrawer();window.chatDialog.alert('银行功能开发中,敬请期待!','开发中','#78716c');">🏦<br>银行</div>
<div class="mobile-tool-btn" onclick="closeMobileDrawer();openMarriageStatusModal();">💍<br>婚姻</div>
<div class="mobile-tool-btn" onclick="closeMobileDrawer();openFriendPanel();">👫<br>好友</div>
<div class="mobile-tool-btn" onclick="closeMobileDrawer();openAvatarPicker();">🖼️<br>头像</div>
<div class="mobile-tool-btn" onclick="closeMobileDrawer();document.getElementById('settings-modal').style.display='flex';">⚙️<br>设置</div>
<div class="mobile-tool-btn" onclick="closeMobileDrawer();window.open('{{ route('feedback.index') }}','_blank');">📝<br>反馈</div>
<div class="mobile-tool-btn" onclick="closeMobileDrawer();window.open('{{ route('guestbook.index') }}','_blank');">📬<br>留言</div>
<div class="mobile-tool-btn" onclick="closeMobileDrawer();window.open('{{ route('guide') }}','_blank');">📖<br>规则</div>
<div class="mobile-tool-btn" onclick="closeMobileDrawer();window.open('{{ route('leaderboard.index') }}','_blank');">🏆<br>排行</div>
@if ($user->id === 1 || $user->activePosition()->exists())
<div class="mobile-tool-btn" style="color:#ffcc00;" onclick="closeMobileDrawer();window.open('/admin','_blank');">🛡️<br>管理</div>
@endif
<div class="mobile-tool-btn" style="color:#ffaaaa;"
onclick="closeMobileDrawer();window.chatDialog.confirm('确定要离开聊天室吗?','离开聊天室').then(ok=>{if(ok)leaveRoom();});">🚪<br>离开</div>
</div>
</div>
{{-- ── 名单/房间抽屉(静态 HTMLJS 动态填充) ── --}}
<div id="mobile-drawer-users" class="mobile-drawer">
<div class="mobile-drawer-header">
<span id="mob-drawer-title">👥 在线名单</span>
<button class="mobile-drawer-close" onclick="closeMobileDrawer()"></button>
</div>
{{-- Tab 切换条 --}}
<div style="display:flex;background:#e8f0f8;border-bottom:1px solid #b0c8e0;flex-shrink:0;">
<button id="mob-tab-users"
style="flex:1;padding:7px 4px;font-size:12px;border:none;background:#336699;color:#fff;cursor:pointer;font-weight:bold;"
onclick="switchMobileTab('users')">👥 名单</button>
<button id="mob-tab-rooms"
style="flex:1;padding:7px 4px;font-size:12px;border:none;background:transparent;color:#336699;cursor:pointer;"
onclick="switchMobileTab('rooms')">🏠 房间</button>
</div>
{{-- 名单面板 --}}
<div id="mob-panel-users" style="display:flex;flex-direction:column;flex:1;overflow:hidden;min-height:0;">
{{-- 排序/搜索工具栏 --}}
<div style="display:flex;gap:4px;padding:5px 6px;background:#f0f4f8;border-bottom:1px solid #dde8f0;flex-shrink:0;">
<select id="mob-user-sort-select"
style="flex:1;font-size:11px;border:1px solid #b0c8e0;border-radius:3px;padding:2px 4px;color:#224466;"
onchange="renderMobileUserList()">
<option value="default">默认顺序</option>
<option value="level">按等级排</option>
<option value="name">按名字排</option>
</select>
<input id="mob-user-search-input" type="text" placeholder="搜索用户..."
style="flex:2;font-size:11px;border:1px solid #b0c8e0;border-radius:3px;padding:2px 6px;color:#333;"
oninput="renderMobileUserList()">
</div>
{{-- 用户列表容器 --}}
<div id="mob-online-users-list"
style="flex:1;overflow-y:auto;min-height:0;-webkit-overflow-scrolling:touch;">
<div style="text-align:center;color:#aaa;padding:20px;font-size:12px;">加载中...</div>
</div>
{{-- 在线人数底栏 --}}
<div id="mob-online-stats"
style="padding:4px 8px;font-size:10px;color:#669;text-align:right;background:#f0f4f8;border-top:1px solid #dde8f0;flex-shrink:0;">
在线: <span id="mob-online-count-footer">0</span>
</div>
</div>
{{-- 房间面板 --}}
<div id="mob-panel-rooms" style="display:none;flex:1;overflow-y:auto;min-height:0;">
<div id="mob-rooms-online-list" style="padding:4px 0;">
<div style="text-align:center;color:#aaa;padding:20px;font-size:12px;">加载中...</div>
</div>
</div>
</div>
{{-- ── 手机端抽屉控制脚本 ── --}}
<script>
/**
* 当前打开的抽屉名称:'toolbar' | 'users' | null
*
* @type {string|null}
*/
let _mobileDrawerOpen = null;
/**
* 打开指定抽屉
*
* @param {'toolbar'|'users'} which
*/
function openMobileDrawer(which) {
// 先关掉已有的抽屉(不重置状态,避免中间态闪烁)
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=true] 是否恢复 body overflow
*/
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'} tab
*/
function switchMobileTab(tab) {
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';
if (tabUsers) {
tabUsers.style.background = tab === 'users' ? '#336699' : 'transparent';
tabUsers.style.color = tab === 'users' ? '#fff' : '#336699';
tabUsers.style.fontWeight = tab === 'users' ? 'bold' : 'normal';
}
if (tabRooms) {
tabRooms.style.background = tab === 'rooms' ? '#336699' : 'transparent';
tabRooms.style.color = tab === 'rooms' ? '#fff' : '#336699';
tabRooms.style.fontWeight = tab === 'rooms' ? 'bold' : 'normal';
}
if (tab === 'rooms') {
_loadMobileRoomList();
}
}
/**
* 将在线用户渲染到手机端名单容器
*/
function renderMobileUserList() {
if (typeof onlineUsers === 'undefined') return;
const sortVal = 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 _renderUserListToContainer === 'function') {
_renderUserListToContainer(container, sortVal, keyword);
} else {
// 降级:简单渲染用户名列表
const users = Object.keys(onlineUsers);
container.innerHTML = users.length
? users.filter(u => !keyword || u.toLowerCase().includes(keyword))
.map(u => `<div class="user-item" style="padding:5px 8px;font-size:12px;border-bottom:1px solid #eee;">${u}</div>`).join('')
: '<div style="text-align:center;color:#aaa;padding:20px;font-size:12px;">暂无用户</div>';
}
// 更新在线人数
const count = Object.keys(onlineUsers).length;
const footerEl = document.getElementById('mob-online-count-footer');
if (footerEl) footerEl.textContent = count;
}
/**
* 拉取房间列表并渲染到手机端房间容器
*/
function _loadMobileRoomList() {
const container = document.getElementById('mob-rooms-online-list');
if (!container) return;
container.innerHTML = '<div style="text-align:center;color:#aaa;padding:16px;font-size:11px;">加载中...</div>';
fetch('{{ route('chat.rooms-online-status') }}')
.then(r => r.json())
.then(data => {
if (!data.rooms || !data.rooms.length) {
container.innerHTML = '<div style="text-align:center;color:#bbb;padding:16px;font-size:11px;">暂无房间</div>';
return;
}
const currentRoomId = window.chatContext?.roomId;
container.innerHTML = data.rooms.map(room => {
const isCurrent = room.id === currentRoomId;
const bg = isCurrent ? '#ecf4ff' : '#fff';
const nameColor = isCurrent ? '#336699' : (room.door_open ? '#444' : '#bbb');
const badge = room.online > 0
? `<span style="background:#e8f5e9;color:#2e7d32;border-radius:8px;padding:0 6px;font-size:10px;font-weight:bold;">${room.online}人</span>`
: `<span style="background:#f5f5f5;color:#bbb;border-radius:8px;padding:0 6px;font-size:10px;">空</span>`;
const currentTag = isCurrent ? `<span style="font-size:9px;color:#7090b0;margin-left:3px;">当前</span>` : '';
const clickAttr = isCurrent ? '' : `onclick="location.href='/room/${room.id}'"`;
return `<div ${clickAttr}
style="display:flex;align-items:center;justify-content:space-between;
padding:6px 10px;border-bottom:1px solid #eef2f8;background:${bg};
cursor:${isCurrent ? 'default' : 'pointer'};">
<span style="color:${nameColor};font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;margin-right:6px;">
${room.name}${currentTag}
</span>${badge}
</div>`;
}).join('');
})
.catch(() => {
container.innerHTML = '<div style="text-align:center;color:#f00;padding:10px;font-size:11px;">加载失败</div>';
});
}
/**
* 监听全局用户列表更新,若名单抽屉已打开则同步刷新
*/
window.addEventListener('chatroom:users-updated', () => {
if (_mobileDrawerOpen === 'users') {
renderMobileUserList();
}
});
</script>