迁移聊天室前端工具并优化消息渲染
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
// 聊天室房间在线状态渲染工具,抽离右侧主面板与手机抽屉可共用的纯前端逻辑。
|
||||
|
||||
import { escapeHtml } from "./html.js";
|
||||
|
||||
const EMPTY_ROOMS_HTML =
|
||||
'<div style="text-align:center;color:#bbb;padding:16px 0;font-size:11px;">暂无房间</div>';
|
||||
|
||||
/**
|
||||
* 转换接口房间数据,过滤异常房间编号。
|
||||
*
|
||||
* @param {Record<string, unknown>} 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}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成可放入 onclick 属性的安全跳转语句。
|
||||
*
|
||||
* @param {number} roomId
|
||||
* @param {(roomId:number) => string} [roomUrlResolver]
|
||||
* @returns {string}
|
||||
*/
|
||||
function buildRoomClickHandler(roomId, roomUrlResolver = undefined) {
|
||||
const safeUrlLiteral = escapeHtml(JSON.stringify(resolveRoomUrl(roomId, roomUrlResolver)));
|
||||
|
||||
return `onclick="location.href=${safeUrlLiteral}"`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染单个房间在线状态行。
|
||||
*
|
||||
* @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"
|
||||
? '<span style="font-size:9px;color:#7090b0;margin-left:3px;">当前</span>'
|
||||
: '<span style="font-size:9px;color:#336699;opacity:.7;margin-left:3px;">当前</span>')
|
||||
: "";
|
||||
const clickHandler = isCurrent ? "" : buildRoomClickHandler(room.id, options.roomUrlResolver);
|
||||
const badge = room.online > 0
|
||||
? `<span style="background:#e8f5e9;color:#2e7d32;border-radius:8px;padding:0 ${variant === "mobile" ? "6px" : "5px"};font-size:10px;font-weight:bold;white-space:nowrap;flex-shrink:0;">${room.online}${variant === "mobile" ? "" : " "}人</span>`
|
||||
: `<span style="background:#f5f5f5;color:#bbb;border-radius:8px;padding:0 ${variant === "mobile" ? "6px" : "5px"};font-size:10px;white-space:nowrap;flex-shrink:0;">空</span>`;
|
||||
|
||||
if (variant === "mobile") {
|
||||
return `<div ${clickHandler}
|
||||
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>`;
|
||||
}
|
||||
|
||||
const border = isCurrent ? "#aac5f0" : "#e0eaf5";
|
||||
|
||||
return `<div ${clickHandler}
|
||||
style="display:flex;align-items:center;justify-content:space-between;
|
||||
padding:5px 8px;margin:2px 3px;border-radius:5px;
|
||||
border:1px solid ${border};background:${bg};
|
||||
cursor:${isCurrent ? "default" : "pointer"};
|
||||
transition:background .15s;"
|
||||
onmouseover="if(${!isCurrent}) this.style.background='#ddeeff';"
|
||||
onmouseout="this.style.background='${bg}';">
|
||||
<span style="color:${nameColor};font-size:11px;font-weight:${isCurrent ? "bold" : "normal"};overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;margin-right:4px;">
|
||||
${safeRoomName}${currentTag}
|
||||
</span>
|
||||
${badge}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染房间在线状态列表 HTML。
|
||||
*
|
||||
* @param {{rooms?: Array<Record<string, unknown>>}} 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<Record<string, unknown>>}} 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);
|
||||
}
|
||||
Reference in New Issue
Block a user