| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 |
- /**
- * 图片预览模态框组件
- *
- * 支持点击图片放大查看,带缩放、拖拽功能
- * 支持直接渲染 React 组件(用于显示 canvas 内容)
- */
- import React, { useState, useRef, useEffect } from 'react';
- import { X, RotateCw, Download } from 'lucide-react';
- interface ImagePreviewModalProps {
- imageUrl?: string;
- isOpen: boolean;
- onClose: () => void;
- title?: string;
- downloadFileName?: string;
- renderContent?: () => React.ReactNode; // 自定义渲染内容
- }
- const ImagePreviewModal: React.FC<ImagePreviewModalProps> = ({
- imageUrl,
- isOpen,
- onClose,
- title = '图片预览',
- downloadFileName = 'image.png',
- renderContent
- }) => {
- const [scale, setScale] = useState(1);
- const [rotation, setRotation] = useState(0);
- const [position, setPosition] = useState({ x: 0, y: 0 });
- const [isDragging, setIsDragging] = useState(false);
- const [dragStart, setDragStart] = useState({ x: 0, y: 0 });
- const imageRef = useRef<HTMLDivElement>(null);
- // 重置状态
- useEffect(() => {
- if (isOpen) {
- setScale(1);
- setRotation(0);
- setPosition({ x: 0, y: 0 });
- }
- }, [isOpen]);
- // ESC 键关闭
- useEffect(() => {
- const handleEsc = (e: KeyboardEvent) => {
- if (e.key === 'Escape' && isOpen) {
- onClose();
- }
- };
- window.addEventListener('keydown', handleEsc);
- return () => window.removeEventListener('keydown', handleEsc);
- }, [isOpen, onClose]);
- // 阻止背景滚动
- useEffect(() => {
- if (isOpen) {
- document.body.style.overflow = 'hidden';
- } else {
- document.body.style.overflow = '';
- }
- return () => {
- document.body.style.overflow = '';
- };
- }, [isOpen]);
- if (!isOpen) return null;
- // 缩放控制
- const handleZoomIn = () => setScale(prev => Math.min(prev + 0.25, 5));
- const handleZoomOut = () => setScale(prev => Math.max(prev - 0.25, 0.25));
- const handleResetZoom = () => {
- setScale(1);
- setPosition({ x: 0, y: 0 });
- };
- // 旋转控制
- const handleRotate = () => setRotation(prev => (prev + 90) % 360);
- // 下载图片
- const handleDownload = () => {
- if (!imageUrl) return;
-
- const link = document.createElement('a');
- link.href = imageUrl;
- link.download = downloadFileName;
- link.target = '_blank';
- document.body.appendChild(link);
- link.click();
- document.body.removeChild(link);
- };
- // 鼠标拖拽
- const handleMouseDown = (e: React.MouseEvent) => {
- if (scale > 1) {
- setIsDragging(true);
- setDragStart({
- x: e.clientX - position.x,
- y: e.clientY - position.y
- });
- }
- };
- const handleMouseMove = (e: React.MouseEvent) => {
- if (isDragging && scale > 1) {
- setPosition({
- x: e.clientX - dragStart.x,
- y: e.clientY - dragStart.y
- });
- }
- };
- const handleMouseUp = () => {
- setIsDragging(false);
- };
- // 鼠标滚轮缩放
- const handleWheel = (e: React.WheelEvent) => {
- e.preventDefault();
- if (e.deltaY < 0) {
- handleZoomIn();
- } else {
- handleZoomOut();
- }
- };
- return (
- <div
- className="fixed inset-0 z-50 flex items-center justify-center bg-black/90"
- onClick={onClose}
- >
- {/* 工具栏 */}
- <div className="absolute top-0 left-0 right-0 bg-black/50 backdrop-blur-sm p-4 flex items-center justify-between z-10">
- <h3 className="text-white font-medium">{title}</h3>
-
- <div className="flex items-center space-x-2">
- {/* 旋转 */}
- <button
- onClick={(e) => {
- e.stopPropagation();
- handleRotate();
- }}
- className="p-2 text-white hover:bg-white/20 rounded-lg transition-colors"
- title="旋转"
- >
- <RotateCw className="w-5 h-5" />
- </button>
- {/* 下载 */}
- {imageUrl && (
- <button
- onClick={(e) => {
- e.stopPropagation();
- handleDownload();
- }}
- className="p-2 text-white hover:bg-white/20 rounded-lg transition-colors"
- title="下载"
- >
- <Download className="w-5 h-5" />
- </button>
- )}
- {/* 关闭 */}
- <button
- onClick={(e) => {
- e.stopPropagation();
- onClose();
- }}
- className="p-2 text-white hover:bg-white/20 rounded-lg transition-colors"
- title="关闭 (ESC)"
- >
- <X className="w-5 h-5" />
- </button>
- </div>
- </div>
- {/* 图片容器 */}
- <div
- ref={imageRef}
- className="relative w-full h-full flex items-center justify-center overflow-hidden"
- onClick={(e) => e.stopPropagation()}
- onMouseDown={handleMouseDown}
- onMouseMove={handleMouseMove}
- onMouseUp={handleMouseUp}
- onMouseLeave={handleMouseUp}
- onWheel={handleWheel}
- style={{
- cursor: scale > 1 ? (isDragging ? 'grabbing' : 'grab') : 'default'
- }}
- >
- {renderContent ? (
- // 自定义内容渲染(用于 canvas 等)
- <div
- style={{
- transform: `translate(${position.x}px, ${position.y}px) scale(${scale}) rotate(${rotation}deg)`,
- transition: isDragging ? 'none' : 'transform 0.2s ease-out',
- maxHeight: '90vh',
- maxWidth: '90vw'
- }}
- >
- {renderContent()}
- </div>
- ) : imageUrl ? (
- // 普通图片渲染
- <img
- src={imageUrl}
- alt="预览"
- className="max-w-none select-none"
- draggable={false}
- style={{
- transform: `translate(${position.x}px, ${position.y}px) scale(${scale}) rotate(${rotation}deg)`,
- transition: isDragging ? 'none' : 'transform 0.2s ease-out',
- maxHeight: '90vh',
- maxWidth: '90vw'
- }}
- />
- ) : null}
- </div>
- </div>
- );
- };
- export default ImagePreviewModal;
|