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:
1 parent
2d66abc610
commit
13dd84a8a7
2 files changed
+175
No files matched your search
@@ -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>
|
||||
);
|
||||
};
|
||||
Reference in new issue
Block a user