迁移手机抽屉脚本到Vite模块

This commit is contained in:
2026-04-25 08:21:30 +08:00
parent 2f09d5e2ed
commit 308e5690fe
4 changed files with 266 additions and 238 deletions
@@ -98,235 +98,8 @@
{{-- 房间面板 --}}
<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 id="mob-rooms-online-list" data-mobile-rooms-status-url="{{ route('chat.rooms-online-status') }}" style="padding:4px 0;">
<div style="text-align:center;color:#aaa;padding:20px;font-size:12px;">加载中...</div>
</div>
</div>
</div>
{{-- ── 手机端抽屉控制脚本 ── --}}
<script>
/**
* 对手机端抽屉中的动态文本做 HTML 转义,避免直接拼入 innerHTML。
*
* @param {string} text
* @returns {string}
*/
function escapeMobileDrawerHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
/**
* 当前打开的抽屉名称:'toolbar' | 'users' | null
*
* @type {string|null}
*/
let _mobileDrawerOpen = null;
let _mobileUserListRenderTimer = null;
let _mobileRoomsOnlineStatusCache = null;
let _mobileRoomsOnlineStatusCacheAt = 0;
const MOBILE_ROOMS_ONLINE_STATUS_CACHE_TTL = 10000;
/**
* 打开指定抽屉
*
* @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;">${escapeMobileDrawerHtml(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 scheduleRenderMobileUserList() {
if (_mobileUserListRenderTimer !== null) {
return;
}
const scheduleRender = window.requestAnimationFrame || ((callback) => window.setTimeout(callback, 16));
_mobileUserListRenderTimer = scheduleRender(() => {
_mobileUserListRenderTimer = null;
renderMobileUserList();
});
}
/**
* 拉取房间列表并渲染到手机端房间容器
*/
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;
}
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 => {
_mobileRoomsOnlineStatusCache = data;
_mobileRoomsOnlineStatusCacheAt = Date.now();
renderMobileRoomList(data, container);
})
.catch(() => {
container.innerHTML = '<div style="text-align:center;color:#f00;padding:10px;font-size:11px;">加载失败</div>';
});
}
/**
* 渲染手机端房间列表。
*
* @param {Object} data 接口返回数据
* @param {HTMLElement} container 目标容器
*/
function renderMobileRoomList(data, container) {
if (window.ChatRoomTools?.renderRoomsOnlineStatusToContainer) {
window.ChatRoomTools.renderRoomsOnlineStatusToContainer(data, container, {
currentRoomId: window.chatContext?.roomId,
variant: 'mobile',
emptyHtml: '<div style="text-align:center;color:#bbb;padding:16px;font-size:11px;">暂无房间</div>',
});
return;
}
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;
const roomRows = data.rooms.map(room => {
const roomId = Number.parseInt(room.id, 10);
if (!Number.isInteger(roomId)) {
return '';
}
const isCurrent = roomId === currentRoomId;
const bg = isCurrent ? '#ecf4ff' : '#fff';
const nameColor = isCurrent ? '#336699' : (room.door_open ? '#444' : '#bbb');
const safeRoomName = escapeMobileDrawerHtml(String(room.name ?? ''));
const safeOnlineCount = Math.max(Number.parseInt(room.online, 10) || 0, 0);
const badge = safeOnlineCount > 0
? `<span style="background:#e8f5e9;color:#2e7d32;border-radius:8px;padding:0 6px;font-size:10px;font-weight:bold;">${safeOnlineCount}人</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/${roomId}'"`;
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;">
${safeRoomName}${currentTag}
</span>${badge}
</div>`;
}).filter(Boolean).join('');
container.innerHTML = roomRows || '<div style="text-align:center;color:#bbb;padding:16px;font-size:11px;">暂无房间</div>';
}
/**
* 监听全局用户列表更新,若名单抽屉已打开则同步刷新
*/
window.addEventListener('chatroom:users-updated', () => {
if (_mobileDrawerOpen === 'users') {
renderMobileUserList();
}
});
</script>
@@ -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);
});