2026-05-14 09:57:41 +08:00

187 lines
6.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import ContentCopyOutlined from "@mui/icons-material/ContentCopyOutlined";
import MeetingRoomOutlined from "@mui/icons-material/MeetingRoomOutlined";
import PersonOutlineOutlined from "@mui/icons-material/PersonOutlineOutlined";
import { useEffect, useState } from "react";
import { SideDrawer } from "@/shared/ui/SideDrawer.jsx";
import { formatMillis } from "@/shared/utils/time.js";
import { roomStatusLabels } from "@/features/rooms/constants.js";
import styles from "@/features/rooms/rooms.module.css";
export function RoomDetailDrawer({ onClose, open, room }) {
const owner = room?.owner || {};
const normalizedStatus = room?.status === "active" ? "active" : "closed";
const ownerDisplayId = ownerLongShortId(owner, room);
return (
<SideDrawer open={open} title="房间详情" width="wide" onClose={onClose}>
{room ? (
<>
<header className={styles.detailHero}>
<span className={styles.detailCover}>
{room.coverUrl ? <img src={room.coverUrl} alt="" /> : <MeetingRoomOutlined fontSize="medium" />}
</span>
<div className={styles.detailHeroText}>
<div className={styles.detailTitleRow}>
<h3>{room.title || "-"}</h3>
<StatusBadge status={normalizedStatus} />
</div>
<CopyableRoomId className={styles.detailMeta} prefix="房间 ID " roomId={room.roomId} />
</div>
</header>
<DetailSection
items={[
["房间名称", room.title],
["房间 ID", <CopyableRoomId key="room-id" roomId={room.roomId} />],
["房间状态", roomStatusLabels[normalizedStatus]],
["房间模式", room.mode],
["区域", room.regionName],
["创建时间", formatMillis(room.createdAtMs)],
["更新时间", formatMillis(room.updatedAtMs)]
]}
title="基础信息"
/>
<section className={styles.detailSection}>
<h3 className={styles.detailSectionTitle}>房主信息</h3>
<div className={styles.detailOwner}>
<span className={styles.detailOwnerAvatar}>
{owner.avatar ? <img src={owner.avatar} alt="" /> : <PersonOutlineOutlined fontSize="small" />}
</span>
<div className={styles.detailOwnerText}>
<strong>{owner.username || "-"}</strong>
<span>{ownerDisplayId}</span>
</div>
</div>
<div className={styles.detailGrid}>
<DetailItem label="房主长 ID(短 ID)" value={ownerDisplayId} />
</div>
</section>
<DetailSection
items={[
["在线人数", `${formatNumber(room.onlineCount)}`],
["座位数", `${formatNumber(room.seatCount)}`],
["已占座位", `${formatNumber(room.occupiedSeatCount)}`],
["房间贡献", formatNumber(roomContributionValue(room))],
["热度", formatNumber(room.heat)],
["排序分", formatNumber(room.sortScore)]
]}
title="房间数据"
/>
</>
) : null}
</SideDrawer>
);
}
function DetailSection({ items, title }) {
return (
<section className={styles.detailSection}>
<h3 className={styles.detailSectionTitle}>{title}</h3>
<div className={styles.detailGrid}>
{items.map(([label, value]) => (
<DetailItem key={label} label={label} value={value} />
))}
</div>
</section>
);
}
function DetailItem({ label, value }) {
return (
<div className={styles.detailItem}>
<span className={styles.detailLabel}>{label}</span>
<div className={styles.detailValue}>{displayValue(value)}</div>
</div>
);
}
function CopyableRoomId({ className = "", prefix = "", roomId }) {
const [copied, setCopied] = useState(false);
const value = displayValue(roomId);
useEffect(() => {
if (!copied) {
return undefined;
}
const timer = window.setTimeout(() => setCopied(false), 1200);
return () => window.clearTimeout(timer);
}, [copied]);
const copyRoomId = async () => {
if (!roomId) {
return;
}
await copyText(String(roomId));
setCopied(true);
};
return (
<button
aria-label={`复制房间 ID ${value}`}
className={[styles.copyValue, className].filter(Boolean).join(" ")}
disabled={!roomId}
type="button"
onClick={copyRoomId}
>
<span>{prefix}{value}</span>
<ContentCopyOutlined className={styles.copyIcon} fontSize="inherit" />
{copied ? <span className={styles.copyState}>已复制</span> : null}
</button>
);
}
function StatusBadge({ status }) {
const tone = status === "active" ? "running" : "stopped";
return (
<span className={`status-badge status-badge--${tone}`}>
<span className="status-point" />
{roomStatusLabels[status]}
</span>
);
}
function displayValue(value) {
return value === 0 || value ? value : "-";
}
function formatNumber(value) {
return Number(value || 0).toLocaleString("zh-CN");
}
function roomContributionValue(room) {
return room?.roomContribution ?? room?.heat ?? 0;
}
async function copyText(value) {
if (navigator.clipboard?.writeText) {
try {
await navigator.clipboard.writeText(value);
return;
} catch {
// 本地开发和部分浏览器策略会拒绝 Clipboard API退回同步复制路径。
}
}
const input = document.createElement("textarea");
input.value = value;
input.setAttribute("readonly", "");
input.style.position = "fixed";
input.style.opacity = "0";
document.body.appendChild(input);
input.select();
document.execCommand("copy");
input.remove();
}
function ownerLongShortId(owner, room) {
const longId = owner.userId || room?.ownerUserId || "";
const shortId = owner.displayUserId || "";
if (longId && shortId && longId !== shortId) {
return `${longId}(${shortId})`;
}
return longId || shortId || "-";
}