// 聊天室房间在线状态渲染工具,抽离右侧主面板与手机抽屉可共用的纯前端逻辑。 import { escapeHtml } from "./html.js"; // 默认空态供右侧面板和手机抽屉共用,调用方仍可通过 options.emptyHtml 覆盖。 const EMPTY_ROOMS_HTML = '
暂无房间
'; // 事件委托只需要注册一次,避免房间在线状态定时刷新后重复绑定。 let roomStatusControlsBound = false; /** * 转换接口房间数据,过滤异常房间编号。 * * @param {Record} room * @returns {{id:number,name:string,online:number,doorOpen:boolean}|null} */ export function normalizeRoomStatus(room) { const roomId = Number.parseInt(room?.id, 10); if (!Number.isInteger(roomId)) { return null; } return { id: roomId, name: String(room?.name ?? ""), online: Math.max(Number.parseInt(room?.online, 10) || 0, 0), doorOpen: Boolean(room?.door_open), }; } /** * 生成房间跳转地址,默认保持现有 `/room/{id}` 路径。 * * @param {number} roomId * @param {(roomId:number) => string} [roomUrlResolver] * @returns {string} */ export function resolveRoomUrl(roomId, roomUrlResolver = undefined) { return typeof roomUrlResolver === "function" ? String(roomUrlResolver(roomId)) : `/room/${roomId}`; } /** * 生成房间跳转数据属性,实际跳转由事件委托统一处理。 * 只输出 data 属性,避免在线房间列表继续混入内联 onclick。 * * @param {number} roomId * @param {(roomId:number) => string} [roomUrlResolver] * @returns {string} */ function buildRoomClickAttributes(roomId, roomUrlResolver = undefined) { const safeUrl = escapeHtml(resolveRoomUrl(roomId, roomUrlResolver)); return `data-room-url="${safeUrl}"`; } /** * 生成桌面房间行悬停样式数据,交给事件委托恢复背景色。 * * @param {boolean} isCurrent 是否当前房间 * @param {string} bg 默认背景色 * @returns {string} */ function buildRoomHoverAttributes(isCurrent, bg) { if (isCurrent) { return ""; } return `data-room-hover-bg="#ddeeff" data-room-normal-bg="${escapeHtml(bg)}"`; } /** * 绑定房间列表跳转事件。 * 右侧面板和手机抽屉都复用 data-room-url,刷新 HTML 后无需重新绑定。 * * @returns {void} */ export function bindRoomStatusControls() { if (roomStatusControlsBound || typeof document === "undefined") { return; } roomStatusControlsBound = true; document.addEventListener("click", (event) => { if (!(event.target instanceof Element)) { return; } const roomLink = event.target.closest("[data-room-url]"); if (!roomLink) { return; } const roomUrl = roomLink.getAttribute("data-room-url"); if (!roomUrl) { return; } event.preventDefault(); window.location.href = roomUrl; }); document.addEventListener("mouseover", (event) => { if (!(event.target instanceof Element)) { return; } const roomRow = event.target.closest("[data-room-hover-bg]"); if (roomRow instanceof HTMLElement) { roomRow.style.background = roomRow.getAttribute("data-room-hover-bg") || ""; } }); document.addEventListener("mouseout", (event) => { if (!(event.target instanceof Element)) { return; } const roomRow = event.target.closest("[data-room-normal-bg]"); if (roomRow instanceof HTMLElement) { roomRow.style.background = roomRow.getAttribute("data-room-normal-bg") || ""; } }); } /** * 渲染单个房间在线状态行。 * * @param {{id:number,name:string,online:number,doorOpen:boolean}} room * @param {{currentRoomId?:number|null, variant?:'desktop'|'mobile', roomUrlResolver?:(roomId:number)=>string}} options * @returns {string} */ export function renderRoomStatusRow(room, options = {}) { const currentRoomId = Number.parseInt(options.currentRoomId, 10); const isCurrent = Number.isInteger(currentRoomId) && room.id === currentRoomId; const variant = options.variant === "mobile" ? "mobile" : "desktop"; const safeRoomName = escapeHtml(room.name); const bg = isCurrent ? "#ecf4ff" : "#fff"; const nameColor = isCurrent ? "#336699" : (room.doorOpen ? "#444" : "#bbb"); const currentTag = isCurrent ? (variant === "mobile" ? '当前' : '当前') : ""; // 当前房间不生成跳转事件,避免重复进入同一房间触发无意义刷新。 const clickAttributes = isCurrent ? "" : buildRoomClickAttributes(room.id, options.roomUrlResolver); const badge = room.online > 0 ? `${room.online}${variant === "mobile" ? "" : " "}人` : ``; // 手机和桌面只区分容器尺寸与视觉密度,房间数据口径保持同一套。 if (variant === "mobile") { return `
${safeRoomName}${currentTag} ${badge}
`; } const border = isCurrent ? "#aac5f0" : "#e0eaf5"; const hoverAttributes = buildRoomHoverAttributes(isCurrent, bg); return `
${safeRoomName}${currentTag} ${badge}
`; } /** * 渲染房间在线状态列表 HTML。 * * @param {{rooms?: Array>}} data * @param {{currentRoomId?:number|null, variant?:'desktop'|'mobile', emptyHtml?:string, roomUrlResolver?:(roomId:number)=>string}} options * @returns {string} */ export function renderRoomsOnlineStatus(data, options = {}) { const rooms = Array.isArray(data?.rooms) ? data.rooms : []; const rows = rooms .map((room) => normalizeRoomStatus(room)) .filter(Boolean) .map((room) => renderRoomStatusRow(room, options)) .join(""); return rows || options.emptyHtml || EMPTY_ROOMS_HTML; } /** * 将房间在线状态列表渲染到指定容器。 * * @param {{rooms?: Array>}} data * @param {HTMLElement} container * @param {{currentRoomId?:number|null, variant?:'desktop'|'mobile', emptyHtml?:string, roomUrlResolver?:(roomId:number)=>string}} options * @returns {void} */ export function renderRoomsOnlineStatusToContainer(data, container, options = {}) { container.innerHTML = renderRoomsOnlineStatus(data, options); }