// 聊天室在线用户列表渲染:名单、搜索、徽标轮换。 // 从 Blade 内联脚本 scripts.blade.php 迁移至 Vite 模块。 import { escapeHtml } from "./html.js"; import { normalizeDailyStatus } from "./preferences-status.js"; // ── 每日状态解析 ── function resolveUserDailyStatus(user) { return normalizeDailyStatus(user); } // ── 构建职务 / 管理员徽标 ── function buildUserPrimaryBadgeHtml(user, username) { if (user.position_icon) { const posTitle = (user.position_name || "在职") + " · " + username; const safePosTitle = escapeHtml(String(posTitle)); const safePositionIcon = escapeHtml(String(user.position_icon || "🎖️")); return `${safePositionIcon}`; } if (user.is_admin) { return `🎖️`; } return ""; } // ── 构建用户 VIP 徽标 ── function buildUserVipBadgeHtml(user) { if (!user.vip_icon) { return ""; } const vipColor = user.vip_color || "#f59e0b"; const safeVipTitle = escapeHtml(String(user.vip_name || "VIP")); const safeVipIcon = escapeHtml(String(user.vip_icon || "👑")); return `${safeVipIcon}`; } // ── 构建状态徽标 ── function buildUserStatusBadgeHtml(user) { const status = resolveUserDailyStatus(user); if (!status) { return ""; } const safeIcon = escapeHtml(status.icon); const safeTooltip = escapeHtml(`${status.group} · ${status.label}`); return `${safeIcon}`; } // ── 构建签到身份徽标 ── function buildUserSignIdentityBadgeHtml(user) { const identityKey = String(user.sign_identity_key ?? user.sign_identity ?? ""); const identityIcon = String(user.sign_identity_icon ?? ""); if (!identityKey || !identityIcon) { return ""; } const identityLabel = String(user.sign_identity_label ?? user.sign_identity_name ?? ""); const safeIcon = escapeHtml(identityIcon); const safeTooltip = escapeHtml(identityLabel ? `签到 · ${identityLabel}` : "签到身份"); return `${safeIcon}`; } /** * 按轮换节奏在签到身份、状态、职务/管理、VIP 徽标之间轮换。 */ export function buildUserBadgeHtml(user, username) { const state = window.chatState; const tick = state ? state.userBadgeRotationTick : 0; const badges = [ buildUserSignIdentityBadgeHtml(user), buildUserStatusBadgeHtml(user), buildUserPrimaryBadgeHtml(user, username), buildUserVipBadgeHtml(user), ].filter(Boolean); if (badges.length === 0) { return ""; } return badges[tick % badges.length]; } /** * 仅刷新当前已渲染用户行的徽标槽位,避免重建头像节点造成闪烁。 */ export function refreshRenderedUserBadges(scope = document) { const state = window.chatState; const onlineUsers = state ? state.onlineUsers : (window.onlineUsers || {}); scope.querySelectorAll(".user-item[data-username]").forEach((item) => { const username = item.dataset.username; const badgeSlot = item.querySelector(".user-badge-slot"); if (!username || !badgeSlot) { return; } badgeSlot.innerHTML = buildUserBadgeHtml(onlineUsers[username] || {}, username); }); } /** * 核心渲染函数:将在线用户渲染到指定容器(桌面端名单区和手机端抽屉共用)。 */ export function renderUserListToContainer(targetContainer, sortBy, keyword) { if (!targetContainer) return; const state = window.chatState; const onlineUsers = state ? state.onlineUsers : (window.onlineUsers || {}); const fragment = document.createDocumentFragment(); // 在列表顶部添加"大家"条目 const allDiv = document.createElement("div"); allDiv.className = "user-item"; allDiv.dataset.userListEveryone = "1"; allDiv.innerHTML = '大家'; fragment.appendChild(allDiv); // 构建用户数组并排序 let userArr = []; for (let username in onlineUsers) { userArr.push({ username, ...onlineUsers[username] }); } if (sortBy === "name") { userArr.sort((a, b) => a.username.localeCompare(b.username, "zh")); } else if (sortBy === "level") { userArr.sort((a, b) => (b.user_level || 0) - (a.user_level || 0)); } userArr.forEach((user) => { const username = user.username; // 搜索过滤 if (keyword && !username.toLowerCase().includes(keyword)) return; const item = document.createElement("div"); item.className = "user-item"; item.dataset.username = username; item.dataset.userListEntry = "1"; const headface = (user.headface || "1.gif"); const headImgSrc = headface.startsWith("storage/") ? "/" + headface : "/images/headface/" + headface; const badges = buildUserBadgeHtml(user, username); // 女生名字使用玫粉色 const nameColor = (user.sex == 2) ? "color:#e91e8c;" : ""; // 昵称颜色装扮 let userNameExtraClass = ""; if (user.name_color) { userNameExtraClass = " msg-name--" + user.name_color.replace(/^msg_name_/, ""); } // 头像框装扮 let avatarHtml = ""; if (user.avatar_frame) { const frameClass = "avatar-frame--" + user.avatar_frame.replace(/^avatar_frame_/, ""); avatarHtml = '' + '' + '' + ''; } else { avatarHtml = ''; } item.innerHTML = ` ${avatarHtml} ${username} ${badges} `; // 具体点击、双击与手机双触发由 Vite 的 right-panel.js 统一事件委托处理 fragment.appendChild(item); }); targetContainer.replaceChildren(fragment); refreshRenderedUserBadges(targetContainer); } /** * 渲染完整用户列表(含下拉选单与在线计数)。 */ export function renderUserList() { const state = window.chatState; if (!state) return; if (state.userListRenderTimer) { window.clearTimeout(state.userListRenderTimer); state.userListRenderTimer = null; } const userList = state.userList; const toUserSelect = state.toUserSelect; // 获取排序方式和搜索词 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() : ""; // 调用核心渲染 if (userList) { renderUserListToContainer(userList, sortBy, keyword); } // 下拉框重建 if (toUserSelect) { const selectFragment = document.createDocumentFragment(); const everyoneOption = document.createElement("option"); everyoneOption.value = "大家"; everyoneOption.textContent = "大家"; selectFragment.appendChild(everyoneOption); for (let username in state.onlineUsers) { if (username !== window.chatContext?.username) { const option = document.createElement("option"); option.value = username; option.textContent = username === "AI小班长" ? "🤖 AI小班长" : username; selectFragment.appendChild(option); } } toUserSelect.replaceChildren(selectFragment); } // 在线计数 const count = Object.keys(state.onlineUsers).length; const onlineCount = state.onlineCount; const onlineCountBottom = state.onlineCountBottom; if (onlineCount) onlineCount.innerText = count; if (onlineCountBottom) onlineCountBottom.innerText = count; const footer = document.getElementById("online-count-footer"); if (footer) footer.innerText = count; // 派发用户列表更新事件,供手机端抽屉同步 window.dispatchEvent(new Event("chatroom:users-updated")); } /** * 合并高频在线名单变动,避免 Presence 连续进出时重复重建名单 DOM。 */ export function scheduleRenderUserList(delay = 120) { const state = window.chatState; if (!state) return; if (state.userListRenderTimer) { window.clearTimeout(state.userListRenderTimer); } state.userListRenderTimer = window.setTimeout(() => { state.userListRenderTimer = null; renderUserList(); }, delay); } /** * 搜索/过滤用户列表(仅操作 DOM 可见性,不重建)。 */ export function filterUserList() { const searchInput = document.getElementById("user-search-input"); const keyword = searchInput ? searchInput.value.trim().toLowerCase() : ""; const state = window.chatState; const userList = state?.userList || document.getElementById("online-users-list"); if (!userList) return; const items = userList.querySelectorAll(".user-item"); items.forEach((item) => { if (!keyword) { item.style.display = ""; return; } const name = (item.dataset.username || item.textContent || "").toLowerCase(); item.style.display = name.includes(keyword) ? "" : "none"; }); } /** * 调度用户列表搜索过滤,避免每个按键都同步扫描名单 DOM。 */ export function scheduleFilterUserList() { const state = window.chatState; if (!state) return; if (state.userFilterRenderTimer !== null) { return; } const scheduleFilter = window.requestAnimationFrame || ((callback) => window.setTimeout(callback, 16)); state.userFilterRenderTimer = scheduleFilter(() => { state.userFilterRenderTimer = null; filterUserList(); }); } // ── 徽标旋转定时器 ── let badgeRotationInterval = null; export function startBadgeRotation() { if (badgeRotationInterval) return; badgeRotationInterval = window.setInterval(() => { if (document.hidden) return; const state = window.chatState; if (!state) return; state.userBadgeRotationTick = (state.userBadgeRotationTick + 1) % 4; if (state.userList) { refreshRenderedUserBadges(state.userList); } const mobileUsersList = document.getElementById("mob-online-users-list"); if (mobileUsersList?.offsetParent !== null) { refreshRenderedUserBadges(mobileUsersList); } // 同步每日状态 UI if (typeof window.syncDailyStatusUi === "function") { window.syncDailyStatusUi(); } }, 3000); } export function stopBadgeRotation() { if (badgeRotationInterval) { window.clearInterval(badgeRotationInterval); badgeRotationInterval = null; } } // ── 挂载到 window ── window.renderUserList = renderUserList; window.renderUserListToContainer = renderUserListToContainer; window.filterUserList = filterUserList; window.scheduleFilterUserList = scheduleFilterUserList; window.scheduleRenderUserList = scheduleRenderUserList; window.refreshRenderedUserBadges = refreshRenderedUserBadges; window.buildUserBadgeHtml = buildUserBadgeHtml; window._renderUserListToContainer = renderUserListToContainer; export { buildUserPrimaryBadgeHtml, buildUserVipBadgeHtml, buildUserStatusBadgeHtml, buildUserSignIdentityBadgeHtml, resolveUserDailyStatus, };