update pay function
This commit is contained in:
270
src/components/ImagePreviewModal/index.js
Normal file
270
src/components/ImagePreviewModal/index.js
Normal file
@@ -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 (
|
||||||
|
<Modal isOpen={isOpen} onClose={handleClose} size="full" isCentered>
|
||||||
|
<ModalOverlay bg="blackAlpha.900" backdropFilter="blur(10px)" />
|
||||||
|
<ModalContent bg="transparent" boxShadow="none" m="0">
|
||||||
|
<ModalCloseButton
|
||||||
|
size="lg"
|
||||||
|
color="white"
|
||||||
|
bg="blackAlpha.600"
|
||||||
|
_hover={{ bg: 'blackAlpha.800' }}
|
||||||
|
zIndex="2"
|
||||||
|
top="20px"
|
||||||
|
right="20px"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ModalBody
|
||||||
|
display="flex"
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="center"
|
||||||
|
position="relative"
|
||||||
|
p="0"
|
||||||
|
>
|
||||||
|
{/* 图片显示区域 */}
|
||||||
|
<AnimatePresence mode="wait">
|
||||||
|
<MotionBox
|
||||||
|
key={currentIndex}
|
||||||
|
initial={{ opacity: 0, scale: 0.9 }}
|
||||||
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
|
exit={{ opacity: 0, scale: 0.9 }}
|
||||||
|
transition={{ duration: 0.3 }}
|
||||||
|
display="flex"
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="center"
|
||||||
|
maxH="90vh"
|
||||||
|
maxW="90vw"
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
src={images[currentIndex]}
|
||||||
|
alt={`图片 ${currentIndex + 1}`}
|
||||||
|
maxH="90vh"
|
||||||
|
maxW="90vw"
|
||||||
|
objectFit="contain"
|
||||||
|
transform={`scale(${scale})`}
|
||||||
|
transition="transform 0.3s"
|
||||||
|
cursor={scale > 1 ? 'grab' : 'default'}
|
||||||
|
userSelect="none"
|
||||||
|
/>
|
||||||
|
</MotionBox>
|
||||||
|
</AnimatePresence>
|
||||||
|
|
||||||
|
{/* 左右切换按钮(仅多张图片时显示) */}
|
||||||
|
{images.length > 1 && (
|
||||||
|
<>
|
||||||
|
<IconButton
|
||||||
|
icon={<ChevronLeft size={32} />}
|
||||||
|
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"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
icon={<ChevronRight size={32} />}
|
||||||
|
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"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 底部工具栏 */}
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
bottom="30px"
|
||||||
|
left="50%"
|
||||||
|
transform="translateX(-50%)"
|
||||||
|
bg="blackAlpha.700"
|
||||||
|
borderRadius="full"
|
||||||
|
px="6"
|
||||||
|
py="3"
|
||||||
|
backdropFilter="blur(10px)"
|
||||||
|
zIndex="2"
|
||||||
|
>
|
||||||
|
<HStack spacing="4">
|
||||||
|
{/* 缩放控制 */}
|
||||||
|
<HStack spacing="2">
|
||||||
|
<IconButton
|
||||||
|
icon={<ZoomOut size={18} />}
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
color="white"
|
||||||
|
onClick={handleZoomOut}
|
||||||
|
isDisabled={scale <= 0.5}
|
||||||
|
_hover={{ bg: 'whiteAlpha.200' }}
|
||||||
|
aria-label="缩小"
|
||||||
|
/>
|
||||||
|
<Text color="white" fontSize="sm" fontWeight="500" minW="60px" textAlign="center">
|
||||||
|
{Math.round(scale * 100)}%
|
||||||
|
</Text>
|
||||||
|
<IconButton
|
||||||
|
icon={<ZoomIn size={18} />}
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
color="white"
|
||||||
|
onClick={handleZoomIn}
|
||||||
|
isDisabled={scale >= 3}
|
||||||
|
_hover={{ bg: 'whiteAlpha.200' }}
|
||||||
|
aria-label="放大"
|
||||||
|
/>
|
||||||
|
</HStack>
|
||||||
|
|
||||||
|
{/* 下载按钮 */}
|
||||||
|
<IconButton
|
||||||
|
icon={<Download size={18} />}
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
color="white"
|
||||||
|
onClick={handleDownload}
|
||||||
|
_hover={{ bg: 'whiteAlpha.200' }}
|
||||||
|
aria-label="下载图片"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 图片计数(仅多张图片时显示) */}
|
||||||
|
{images.length > 1 && (
|
||||||
|
<Text color="white" fontSize="sm" fontWeight="500">
|
||||||
|
{currentIndex + 1} / {images.length}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</HStack>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* 快捷键提示 */}
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
top="80px"
|
||||||
|
left="20px"
|
||||||
|
bg="blackAlpha.600"
|
||||||
|
borderRadius="md"
|
||||||
|
px="4"
|
||||||
|
py="2"
|
||||||
|
backdropFilter="blur(10px)"
|
||||||
|
>
|
||||||
|
<Text color="whiteAlpha.800" fontSize="xs">
|
||||||
|
快捷键: ← → 切换 | + - 缩放 | ESC 关闭
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
</ModalBody>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ImagePreviewModal;
|
||||||
@@ -40,6 +40,7 @@ import {
|
|||||||
} from '@services/elasticsearchService';
|
} from '@services/elasticsearchService';
|
||||||
import EventTimeline from './components/EventTimeline';
|
import EventTimeline from './components/EventTimeline';
|
||||||
import CommentSection from './components/CommentSection';
|
import CommentSection from './components/CommentSection';
|
||||||
|
import ImagePreviewModal from '@components/ImagePreviewModal';
|
||||||
|
|
||||||
const MotionBox = motion(Box);
|
const MotionBox = motion(Box);
|
||||||
|
|
||||||
@@ -53,6 +54,10 @@ const PostDetail = () => {
|
|||||||
const [isLiked, setIsLiked] = useState(false);
|
const [isLiked, setIsLiked] = useState(false);
|
||||||
const [likes, setLikes] = useState(0);
|
const [likes, setLikes] = useState(0);
|
||||||
|
|
||||||
|
// 图片预览相关状态
|
||||||
|
const [isImagePreviewOpen, setIsImagePreviewOpen] = useState(false);
|
||||||
|
const [previewImageIndex, setPreviewImageIndex] = useState(0);
|
||||||
|
|
||||||
// 加载帖子数据
|
// 加载帖子数据
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadPostData = async () => {
|
const loadPostData = async () => {
|
||||||
@@ -91,6 +96,12 @@ const PostDetail = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 打开图片预览
|
||||||
|
const handleImageClick = (index) => {
|
||||||
|
setPreviewImageIndex(index);
|
||||||
|
setIsImagePreviewOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
// 格式化时间
|
// 格式化时间
|
||||||
const formatTime = (dateString) => {
|
const formatTime = (dateString) => {
|
||||||
const date = new Date(dateString);
|
const date = new Date(dateString);
|
||||||
@@ -272,6 +283,7 @@ const PostDetail = () => {
|
|||||||
border="1px solid"
|
border="1px solid"
|
||||||
borderColor={forumColors.border.default}
|
borderColor={forumColors.border.default}
|
||||||
cursor="pointer"
|
cursor="pointer"
|
||||||
|
onClick={() => handleImageClick(index)}
|
||||||
_hover={{
|
_hover={{
|
||||||
transform: 'scale(1.05)',
|
transform: 'scale(1.05)',
|
||||||
boxShadow: forumColors.shadows.gold,
|
boxShadow: forumColors.shadows.gold,
|
||||||
@@ -363,6 +375,14 @@ const PostDetail = () => {
|
|||||||
</Box>
|
</Box>
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
|
{/* 图片预览弹窗 */}
|
||||||
|
<ImagePreviewModal
|
||||||
|
isOpen={isImagePreviewOpen}
|
||||||
|
onClose={() => setIsImagePreviewOpen(false)}
|
||||||
|
images={post?.images || []}
|
||||||
|
initialIndex={previewImageIndex}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user