// 聊天室房间在线状态渲染工具,抽离右侧主面板与手机抽屉可共用的纯前端逻辑。
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);
}