feat: add avatar interaction and modal for user assistance

- Introduced an avatar element that opens a modal when clicked, providing users with assistance.
- Integrated IframeModal component to display external content within the modal.
- Enhanced user experience with hover effects on the avatar.
This commit is contained in:
zhongyidian committed 2025-10-26 13:15:41 +08:00
1 parent 2d66abc610
commit 13dd84a8a7
2 files changed
+175

No files matched your search

+143
View File
@@ -0,0 +1,143 @@
import clsx from "clsx";
import { useState } from "react";
interface ModalProps {
isOpen: boolean;
onClose: () => void;
children?: React.ReactNode;
className?: string;
}
export const Modal = ({ isOpen, onClose, children, className }: ModalProps) => {
if (!isOpen) return null;
return (
<div className="fixed inset-0 z-50 flex items-center justify-center">
{/* 背景遮罩 */}
<div className="absolute inset-0 bg-black/80 backdrop-blur-sm" />
{/* 弹窗内容 */}
<div
className={clsx(
"relative bg-white rounded-lg shadow-xl max-w-7xl max-h-[100vh] w-full mx-4",
className
)}
>
{/* 关闭按钮 */}
<button
onClick={onClose}
className="absolute top-4 right-4 z-10 w-8 h-8 flex items-center justify-center bg-gray-200 hover:bg-gray-300 rounded-full transition-colors"
>
<span className="text-gray-600 text-xl font-bold">×</span>
</button>
{/* 弹窗内容 */}
<div className="h-full">{children}</div>
</div>
</div>
);
};
interface IframeModalProps {
isOpen: boolean;
onClose: () => void;
url: string;
title?: string;
}
export const IframeModal = ({
isOpen,
onClose,
url,
title,
}: IframeModalProps) => {
const [hasError, setHasError] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [errorType, setErrorType] = useState<
"network" | "security" | "unknown"
>("unknown");
const handleIframeError = () => {
setHasError(true);
setIsLoading(false);
setErrorType("network");
};
const handleIframeLoad = () => {
setIsLoading(false);
setHasError(false);
// 监听iframe内部的错误
const iframe = document.querySelector("iframe");
if (iframe) {
try {
iframe.contentWindow?.addEventListener("error", (event) => {
console.error("Iframe内部错误:", event);
if (
event.message?.includes("VideoDecoder") ||
event.message?.includes("WebCodecs")
) {
setErrorType("security");
setHasError(true);
}
});
} catch {
console.log("无法访问iframe内容,这是正常的跨域限制");
}
}
};
return (
<Modal isOpen={isOpen} onClose={onClose} className="max-w-5xl">
<div className="h-[90vh] flex flex-col">
{title && (
<div className="px-6 py-4 border-b border-gray-200">
<h3 className="text-lg font-semibold text-gray-800">{title}</h3>
</div>
)}
<div className="flex-1 relative">
{isLoading && (
<div className="absolute inset-0 flex items-center justify-center bg-gray-50">
<div className="text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500 mx-auto mb-4"></div>
<p className="text-gray-600">正在加载...</p>
</div>
</div>
)}
{hasError ? (
<div className="absolute inset-0 flex items-center justify-center bg-gray-50">
<div className="text-center">
<div className="text-red-500 text-6xl mb-4">⚠️</div>
<h3 className="text-lg font-semibold text-gray-800 mb-2">
{errorType === "security" ? "页面功能受限" : "页面加载失败"}
</h3>
<p className="text-gray-600 mb-4">
{errorType === "security"
? "该页面使用了高级视频处理功能,在嵌入模式下可能无法正常工作"
: "由于安全策略限制,无法在此窗口中显示该页面"}
</p>
<p className="text-sm text-gray-500">
{errorType === "security"
? "建议在支持的浏览器中直接访问该页面"
: "请检查网络连接或稍后重试"}
</p>
</div>
</div>
) : (
<iframe
src={url}
className="w-full h-full border-0"
title={title || "嵌入页面"}
allow="microphone; camera; autoplay; fullscreen; encrypted-media; picture-in-picture; web-share; accelerometer; gyroscope; magnetometer; payment; usb; serial; bluetooth; xr-spatial-tracking"
referrerPolicy="no-referrer-when-downgrade"
loading="lazy"
onError={handleIframeError}
onLoad={handleIframeLoad}
/>
)}
</div>
</div>
</Modal>
);
};