Files
if.u.clients.web/src/components/ImageModal.tsx

190 lines
5.2 KiB
TypeScript
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 React, { useState, useEffect } from 'react';
import { Modal, Spin } from 'antd';
import { CloseOutlined } from '@ant-design/icons';
import './ImageModal.css';
interface ImageModalProps {
visible: boolean;
imageUrl: string;
onClose: () => void;
}
// 图片缓存
const imageCache = new Set<string>();
const ImageModal: React.FC<ImageModalProps> = ({ visible, imageUrl, onClose }) => {
const [loading, setLoading] = useState(false);
const [imageLoaded, setImageLoaded] = useState(false);
const [imageError, setImageError] = useState(false);
const [isMobile, setIsMobile] = useState(false);
const [imageDimensions, setImageDimensions] = useState<{ width: number; height: number } | null>(null);
// 检测是否为移动端
useEffect(() => {
const checkMobile = () => {
setIsMobile(window.innerWidth <= 768);
};
checkMobile();
window.addEventListener('resize', checkMobile);
return () => window.removeEventListener('resize', checkMobile);
}, []);
// 预加载图片
useEffect(() => {
if (visible && imageUrl) {
// 如果图片已缓存,直接显示
if (imageCache.has(imageUrl)) {
setImageLoaded(true);
setLoading(false);
return;
}
setLoading(true);
setImageLoaded(false);
setImageError(false);
const img = new Image();
img.onload = () => {
imageCache.add(imageUrl);
setImageDimensions({ width: img.naturalWidth, height: img.naturalHeight });
setImageLoaded(true);
setLoading(false);
};
img.onerror = () => {
setImageError(true);
setLoading(false);
};
img.src = imageUrl;
}
}, [visible, imageUrl]);
// 重置状态当弹窗关闭时
useEffect(() => {
if (!visible) {
setLoading(false);
setImageLoaded(false);
setImageError(false);
setImageDimensions(null);
}
}, [visible]);
// 计算移动端弹窗高度
const getMobileModalHeight = () => {
if (imageLoaded && imageDimensions) {
// 如果图片已加载,根据图片比例自适应高度
const availableHeight = window.innerHeight - 64; // 减去标题栏高度
const availableWidth = window.innerWidth;
// 计算图片按宽度100%显示时的高度
const aspectRatio = imageDimensions.height / imageDimensions.width;
const calculatedHeight = availableWidth * aspectRatio;
// 确保高度不超过可用空间的90%
const maxHeight = availableHeight * 0.9;
const finalHeight = Math.min(calculatedHeight, maxHeight);
return `${finalHeight}px`;
}
// 图片未加载时使用默认高度除标题栏外的33%
return 'calc((100vh - 64px) * 0.33)';
};
const modalStyle = isMobile ? {
// 移动端居中显示不设置top
paddingBottom: 0,
margin: 0,
} : {
// PC端不设置top让centered属性处理居中
};
const modalBodyStyle = isMobile ? {
padding: 0,
height: getMobileModalHeight(),
minHeight: 'calc((100vh - 64px) * 0.33)', // 最小高度为33%
maxHeight: 'calc(100vh - 64px)', // 最大高度不超过可视区域
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#000',
} : {
padding: 0,
height: '66vh',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#000',
};
return (
<Modal
open={visible}
onCancel={onClose}
footer={null}
closable={false}
width={isMobile ? '100vw' : '66vw'}
style={modalStyle}
styles={{
body: modalBodyStyle,
mask: { backgroundColor: 'rgba(0, 0, 0, 0.8)' },
}}
centered={true} // 移动端和PC端都居中显示
destroyOnHidden
wrapClassName={isMobile ? 'mobile-image-modal' : 'desktop-image-modal'}
>
{/* 自定义关闭按钮 */}
<div
style={{
position: 'absolute',
top: 16,
right: 16,
zIndex: 1000,
cursor: 'pointer',
color: '#fff',
fontSize: 20,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
borderRadius: '50%',
width: 32,
height: 32,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
transition: 'all 0.3s',
}}
onClick={onClose}
onMouseEnter={(e) => {
e.currentTarget.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';
}}
>
<CloseOutlined />
</div>
{/* 图片内容 */}
<div className="image-modal-container">
{loading && (
<Spin size="large" style={{ color: '#fff' }} />
)}
{imageError && (
<div style={{ color: '#fff', textAlign: 'center' }}>
<div style={{ fontSize: 48, marginBottom: 16 }}>📷</div>
<div></div>
</div>
)}
{imageLoaded && !loading && !imageError && (
<img
src={imageUrl}
alt="预览图片"
/>
)}
</div>
</Modal>
);
};
export default ImageModal;