From d9a169d2e0b9de669a4dddda096c4189db111457 Mon Sep 17 00:00:00 2001 From: zzlgreat Date: Sun, 23 Nov 2025 22:27:57 +0800 Subject: [PATCH] update pay function --- src/components/ImagePreviewModal/index.js | 270 ++++++++++++++++++++++ src/views/ValueForum/PostDetail.js | 20 ++ 2 files changed, 290 insertions(+) create mode 100644 src/components/ImagePreviewModal/index.js diff --git a/src/components/ImagePreviewModal/index.js b/src/components/ImagePreviewModal/index.js new file mode 100644 index 00000000..04b6608b --- /dev/null +++ b/src/components/ImagePreviewModal/index.js @@ -0,0 +1,270 @@ +/** + * 图片预览弹窗组件 + * 支持多张图片左右切换、缩放、下载 + */ + +import React, { useState } from 'react'; +import { + Modal, + ModalOverlay, + ModalContent, + ModalBody, + ModalCloseButton, + Image, + IconButton, + HStack, + Text, + Box, +} from '@chakra-ui/react'; +import { ChevronLeft, ChevronRight, Download, ZoomIn, ZoomOut } from 'lucide-react'; +import { motion, AnimatePresence } from 'framer-motion'; + +const MotionBox = motion(Box); + +const ImagePreviewModal = ({ isOpen, onClose, images = [], initialIndex = 0 }) => { + const [currentIndex, setCurrentIndex] = useState(initialIndex); + const [scale, setScale] = useState(1); + + // 切换到上一张 + const handlePrevious = () => { + setCurrentIndex((prev) => (prev - 1 + images.length) % images.length); + setScale(1); // 重置缩放 + }; + + // 切换到下一张 + const handleNext = () => { + setCurrentIndex((prev) => (prev + 1) % images.length); + setScale(1); // 重置缩放 + }; + + // 放大 + const handleZoomIn = () => { + setScale((prev) => Math.min(prev + 0.25, 3)); + }; + + // 缩小 + const handleZoomOut = () => { + setScale((prev) => Math.max(prev - 0.25, 0.5)); + }; + + // 下载图片 + const handleDownload = () => { + const link = document.createElement('a'); + link.href = images[currentIndex]; + link.download = `image-${currentIndex + 1}.jpg`; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + }; + + // 键盘快捷键 + React.useEffect(() => { + const handleKeyDown = (e) => { + if (!isOpen) return; + + switch (e.key) { + case 'ArrowLeft': + handlePrevious(); + break; + case 'ArrowRight': + handleNext(); + break; + case 'Escape': + onClose(); + break; + case '+': + case '=': + handleZoomIn(); + break; + case '-': + handleZoomOut(); + break; + default: + break; + } + }; + + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, [isOpen, currentIndex]); + + // 关闭时重置状态 + const handleClose = () => { + setScale(1); + setCurrentIndex(initialIndex); + onClose(); + }; + + if (!images || images.length === 0) return null; + + return ( + + + + + + + {/* 图片显示区域 */} + + + {`图片 1 ? 'grab' : 'default'} + userSelect="none" + /> + + + + {/* 左右切换按钮(仅多张图片时显示) */} + {images.length > 1 && ( + <> + } + position="absolute" + left="20px" + top="50%" + transform="translateY(-50%)" + onClick={handlePrevious} + size="lg" + borderRadius="full" + bg="blackAlpha.600" + color="white" + _hover={{ bg: 'blackAlpha.800', transform: 'translateY(-50%) scale(1.1)' }} + _active={{ transform: 'translateY(-50%) scale(0.95)' }} + aria-label="上一张" + zIndex="2" + /> + + } + position="absolute" + right="20px" + top="50%" + transform="translateY(-50%)" + onClick={handleNext} + size="lg" + borderRadius="full" + bg="blackAlpha.600" + color="white" + _hover={{ bg: 'blackAlpha.800', transform: 'translateY(-50%) scale(1.1)' }} + _active={{ transform: 'translateY(-50%) scale(0.95)' }} + aria-label="下一张" + zIndex="2" + /> + + )} + + {/* 底部工具栏 */} + + + {/* 缩放控制 */} + + } + size="sm" + variant="ghost" + color="white" + onClick={handleZoomOut} + isDisabled={scale <= 0.5} + _hover={{ bg: 'whiteAlpha.200' }} + aria-label="缩小" + /> + + {Math.round(scale * 100)}% + + } + size="sm" + variant="ghost" + color="white" + onClick={handleZoomIn} + isDisabled={scale >= 3} + _hover={{ bg: 'whiteAlpha.200' }} + aria-label="放大" + /> + + + {/* 下载按钮 */} + } + size="sm" + variant="ghost" + color="white" + onClick={handleDownload} + _hover={{ bg: 'whiteAlpha.200' }} + aria-label="下载图片" + /> + + {/* 图片计数(仅多张图片时显示) */} + {images.length > 1 && ( + + {currentIndex + 1} / {images.length} + + )} + + + + {/* 快捷键提示 */} + + + 快捷键: ← → 切换 | + - 缩放 | ESC 关闭 + + + + + + ); +}; + +export default ImagePreviewModal; diff --git a/src/views/ValueForum/PostDetail.js b/src/views/ValueForum/PostDetail.js index 1e9bd5e7..23350bfb 100644 --- a/src/views/ValueForum/PostDetail.js +++ b/src/views/ValueForum/PostDetail.js @@ -40,6 +40,7 @@ import { } from '@services/elasticsearchService'; import EventTimeline from './components/EventTimeline'; import CommentSection from './components/CommentSection'; +import ImagePreviewModal from '@components/ImagePreviewModal'; const MotionBox = motion(Box); @@ -53,6 +54,10 @@ const PostDetail = () => { const [isLiked, setIsLiked] = useState(false); const [likes, setLikes] = useState(0); + // 图片预览相关状态 + const [isImagePreviewOpen, setIsImagePreviewOpen] = useState(false); + const [previewImageIndex, setPreviewImageIndex] = useState(0); + // 加载帖子数据 useEffect(() => { const loadPostData = async () => { @@ -91,6 +96,12 @@ const PostDetail = () => { } }; + // 打开图片预览 + const handleImageClick = (index) => { + setPreviewImageIndex(index); + setIsImagePreviewOpen(true); + }; + // 格式化时间 const formatTime = (dateString) => { const date = new Date(dateString); @@ -272,6 +283,7 @@ const PostDetail = () => { border="1px solid" borderColor={forumColors.border.default} cursor="pointer" + onClick={() => handleImageClick(index)} _hover={{ transform: 'scale(1.05)', boxShadow: forumColors.shadows.gold, @@ -363,6 +375,14 @@ const PostDetail = () => { + + {/* 图片预览弹窗 */} + setIsImagePreviewOpen(false)} + images={post?.images || []} + initialIndex={previewImageIndex} + /> ); };