199 lines
4.7 KiB
TypeScript
199 lines
4.7 KiB
TypeScript
import { useAuth } from "@web/src/providers/auth-provider";
|
|
import { Avatar, Tag, theme, Tooltip } from "antd";
|
|
import React, {
|
|
ReactNode,
|
|
useEffect,
|
|
useState,
|
|
useRef,
|
|
CSSProperties,
|
|
} from "react";
|
|
import { SyncOutlined } from "@ant-design/icons";
|
|
import * as Y from "yjs";
|
|
import { stringToColor, YWsProvider } from "@nice/common";
|
|
import { lightenColor } from "@nice/client";
|
|
import { useLocalSettings } from "@web/src/hooks/useLocalSetting";
|
|
import Breadcrumb from "../element/breadcrumb";
|
|
|
|
interface AdminHeaderProps {
|
|
children?: ReactNode;
|
|
roomId?: string;
|
|
awarePlaceholder?: string;
|
|
borderless?: boolean;
|
|
style?: CSSProperties;
|
|
className?: string;
|
|
}
|
|
|
|
const AdminHeader: React.FC<AdminHeaderProps> = ({
|
|
className,
|
|
style,
|
|
borderless = false,
|
|
children,
|
|
roomId,
|
|
awarePlaceholder = "协作人员",
|
|
}) => {
|
|
const { user, sessionId, accessToken } = useAuth();
|
|
const [userStates, setUserStates] = useState<Map<string, any>>(new Map());
|
|
const { token } = theme.useToken();
|
|
const providerRef = useRef<YWsProvider | null>(null);
|
|
const { websocketUrl } = useLocalSettings();
|
|
|
|
useEffect(() => {
|
|
let cleanup: (() => void) | undefined;
|
|
// 如果已经连接或缺少必要参数,则返回
|
|
if (!user || !roomId || !websocketUrl) {
|
|
return;
|
|
}
|
|
// 设置延时,避免立即连接
|
|
const connectTimeout = setTimeout(() => {
|
|
try {
|
|
const ydoc = new Y.Doc();
|
|
const provider = new YWsProvider(
|
|
websocketUrl + "/yjs",
|
|
roomId,
|
|
ydoc,
|
|
{
|
|
params: {
|
|
userId: user?.id,
|
|
sessionId,
|
|
},
|
|
}
|
|
);
|
|
providerRef.current = provider;
|
|
const { awareness } = provider;
|
|
const updateAwarenessData = () => {
|
|
const uniqueStates = new Map<string, any>();
|
|
awareness.getStates().forEach((value, key) => {
|
|
const sessionId = value?.user?.sessionId;
|
|
if (sessionId) {
|
|
uniqueStates.set(sessionId, value);
|
|
}
|
|
});
|
|
setUserStates(uniqueStates);
|
|
};
|
|
|
|
const localState = {
|
|
user: {
|
|
id: user.id,
|
|
showname: user?.showname || user.username,
|
|
deptName: user.department?.name,
|
|
sessionId,
|
|
},
|
|
};
|
|
|
|
awareness.setLocalStateField("user", localState.user);
|
|
awareness.on("change", updateAwarenessData);
|
|
updateAwarenessData();
|
|
|
|
const handleBeforeUnload = () => {
|
|
awareness.setLocalState(null);
|
|
provider.disconnect();
|
|
};
|
|
|
|
window.addEventListener("beforeunload", handleBeforeUnload);
|
|
|
|
// 定义清理函数
|
|
cleanup = () => {
|
|
if (providerRef.current) {
|
|
awareness.off("change", updateAwarenessData);
|
|
awareness.setLocalState(null);
|
|
provider.disconnect();
|
|
providerRef.current = null;
|
|
}
|
|
|
|
setUserStates(new Map());
|
|
window.removeEventListener(
|
|
"beforeunload",
|
|
handleBeforeUnload
|
|
);
|
|
};
|
|
} catch (error) {
|
|
console.error("WebSocket connection error:", error);
|
|
}
|
|
}, 100);
|
|
|
|
// 返回清理函数
|
|
return () => {
|
|
clearTimeout(connectTimeout);
|
|
if (cleanup) {
|
|
cleanup();
|
|
}
|
|
};
|
|
}, [roomId, user, websocketUrl, sessionId]);
|
|
|
|
// 其余渲染代码保持不变...
|
|
const renderAvatars = () =>
|
|
Array.from(userStates.entries()).map(([key, value]) => (
|
|
<Tooltip
|
|
color="white"
|
|
title={
|
|
<span className="text-tertiary-300">
|
|
{value?.user.deptName && (
|
|
<span className="mr-2 text-primary">
|
|
{value?.user?.deptName}
|
|
</span>
|
|
)}
|
|
<span className="">
|
|
{value?.user?.showname || "匿名用户"}
|
|
</span>
|
|
</span>
|
|
}
|
|
key={key}>
|
|
<Avatar
|
|
className="cursor-pointer"
|
|
src={value?.user?.avatarUrl}
|
|
size={35}
|
|
style={{
|
|
borderColor: lightenColor(
|
|
stringToColor(value?.user?.sessionId),
|
|
30
|
|
),
|
|
borderWidth: 3,
|
|
color: lightenColor(
|
|
stringToColor(value?.user?.sessionId),
|
|
30
|
|
),
|
|
fontWeight: "bold",
|
|
background: stringToColor(value?.user?.sessionId),
|
|
}}>
|
|
{!value?.user?.avatarUrl &&
|
|
(value?.user?.showname?.toUpperCase() || "匿名用户")}
|
|
</Avatar>
|
|
</Tooltip>
|
|
));
|
|
|
|
return (
|
|
<div
|
|
className={`flex-shrink-0 p-2 border-gray-200 flex justify-between ${
|
|
borderless ? "" : "border-b"
|
|
} ${className}`}
|
|
style={{ height: "49px", ...style }}>
|
|
<div className="flex items-center gap-4">
|
|
<Breadcrumb />
|
|
<div className="flex items-center gap-2">
|
|
{roomId && (
|
|
<Tag
|
|
icon={<SyncOutlined spin />}
|
|
color={token.colorPrimaryHover}>
|
|
{awarePlaceholder}
|
|
</Tag>
|
|
)}
|
|
<Avatar.Group
|
|
max={{
|
|
count: 35,
|
|
style: {
|
|
backgroundColor: token.colorPrimaryBg,
|
|
color: token.colorPrimary,
|
|
padding: 10,
|
|
},
|
|
}}>
|
|
{renderAvatars()}
|
|
</Avatar.Group>
|
|
</div>
|
|
</div>
|
|
{children}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default AdminHeader;
|