feat: enhance modal functionality and integrate new components

- Added keyframes and animation for a smooth scale-in effect in the Tailwind configuration.
- Updated Image component to improve quality handling and style definitions.
- Introduced WordViewer and WordViewerModal components for document viewing capabilities.
- Enhanced Modal component to support embedded mode with customizable position and dimensions.
- Integrated IframeModal across various pages for user assistance and content display.
- Updated Content and Menu pages to utilize new components and improve user interaction.
- Added utility function to retrieve public paths for resources.
This commit is contained in:
zhongyidian committed 2025-10-29 00:18:06 +08:00
1 parent 13dd84a8a7
commit 4ce7520066
44 files changed
+1209 -100

No files matched your search

+112 -10
View File
@@ -1,27 +1,110 @@
import clsx from "clsx";
import { useState } from "react";
type DisplayMode = "modal" | "embedded";
type EmbeddedPosition =
| "bottom-right"
| "bottom-left"
| "top-right"
| "top-left";
interface ModalProps {
isOpen: boolean;
onClose: () => void;
children?: React.ReactNode;
className?: string;
mode?: DisplayMode;
position?: EmbeddedPosition;
width?: string | number;
height?: string | number;
showMask?: boolean;
}
export const Modal = ({ isOpen, onClose, children, className }: ModalProps) => {
export const Modal = ({
isOpen,
onClose,
children,
className,
mode = "modal",
position = "bottom-right",
width,
height,
showMask = true,
}: ModalProps) => {
if (!isOpen) return null;
// 嵌入式客服对话框模式
if (mode === "embedded") {
const getPosition = () => {
switch (position) {
case "bottom-right":
return "bottom-6 right-6";
case "bottom-left":
return "bottom-6 left-6";
case "top-right":
return "top-6 right-6";
case "top-left":
return "top-6 left-6";
}
};
const defaultWidth = width || "400px";
const defaultHeight = height || "600px";
return (
<div
className={clsx(
"fixed z-[9999] bg-transparent overflow-hidden border border-transparent",
"transition-all duration-300 ease-out",
"animate-scale-in",
getPosition(),
className
)}
style={{
width: defaultWidth,
height: defaultHeight,
maxHeight: "calc(100vh - 3rem)",
maxWidth: "calc(100vw - 3rem)",
}}
>
{/* 关闭/最小化按钮 */}
<button
onClick={onClose}
className="absolute top-3 right-3 z-10 w-7 h-7 flex items-center justify-center bg-white hover:bg-gray-100 rounded-full transition-colors shadow-sm border border-gray-200"
title="最小化"
>
<span className="text-gray-500 text-lg font-bold leading-none">
−
</span>
</button>
{/* 内容 */}
<div className="h-full w-full">{children}</div>
</div>
);
}
// 传统弹窗模式
return (
<div className="fixed inset-0 z-50 flex items-center justify-center">
{/* 背景遮罩 */}
<div className="absolute inset-0 bg-black/80 backdrop-blur-sm" />
{showMask && (
<div
className="absolute inset-0 bg-black/80 backdrop-blur-sm"
onClick={onClose}
/>
)}
{/* 弹窗内容 */}
<div
className={clsx(
"relative bg-white rounded-lg shadow-xl max-w-7xl max-h-[100vh] w-full mx-4",
"relative bg-white rounded-lg shadow-xl max-w-7xl max-h-[100vh] w-full mx-4",
className
)}
style={{
width: width,
height: height,
}}
>
{/* 关闭按钮 */}
<button
@@ -43,6 +126,11 @@ interface IframeModalProps {
onClose: () => void;
url: string;
title?: string;
mode?: DisplayMode;
position?: EmbeddedPosition;
width?: string | number;
height?: string | number;
showMask?: boolean;
}
export const IframeModal = ({
@@ -50,6 +138,11 @@ export const IframeModal = ({
onClose,
url,
title,
mode = "modal",
position = "bottom-right",
width,
height,
showMask = true,
}: IframeModalProps) => {
const [hasError, setHasError] = useState(false);
const [isLoading, setIsLoading] = useState(true);
@@ -88,16 +181,25 @@ export const IframeModal = ({
};
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">
<Modal
isOpen={isOpen}
onClose={onClose}
mode={mode}
position={position}
width={width}
height={height}
showMask={showMask}
className={mode === "modal" ? "max-w-5xl" : ""}
>
<div className="h-full flex flex-col">
{title && mode === "modal" && (
<div className="px-6 py-4 border-b border-transparent">
<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="absolute inset-0 flex items-center justify-center bg-transparent">
<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>
@@ -106,7 +208,7 @@ export const IframeModal = ({
)}
{hasError ? (
<div className="absolute inset-0 flex items-center justify-center bg-gray-50">
<div className="absolute inset-0 flex items-center justify-center bg-transparent">
<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">
@@ -127,7 +229,7 @@ export const IframeModal = ({
) : (
<iframe
src={url}
className="w-full h-full border-0"
className="w-full h-full border-0 bg-transparent"
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"