310 lines
8.1 KiB
JavaScript
310 lines
8.1 KiB
JavaScript
/**
|
|
* 图片灯箱组件
|
|
* 点击图片放大查看
|
|
*/
|
|
|
|
import React, { useState } from 'react';
|
|
import {
|
|
Modal,
|
|
ModalOverlay,
|
|
ModalContent,
|
|
ModalBody,
|
|
ModalCloseButton,
|
|
Image,
|
|
Box,
|
|
IconButton,
|
|
HStack,
|
|
useDisclosure,
|
|
} from '@chakra-ui/react';
|
|
import { ChevronLeft, ChevronRight, X, ZoomIn } from 'lucide-react';
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
|
|
const MotionBox = motion(Box);
|
|
|
|
/**
|
|
* 单图片灯箱
|
|
*/
|
|
export const ImageLightbox = ({ src, alt, ...props }) => {
|
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
|
|
|
return (
|
|
<>
|
|
{/* 缩略图 */}
|
|
<Box
|
|
position="relative"
|
|
cursor="pointer"
|
|
onClick={onOpen}
|
|
_hover={{
|
|
'& .zoom-icon': {
|
|
opacity: 1,
|
|
},
|
|
}}
|
|
{...props}
|
|
>
|
|
<Image
|
|
src={src}
|
|
alt={alt}
|
|
w="100%"
|
|
h="100%"
|
|
objectFit="cover"
|
|
borderRadius="md"
|
|
transition="all 0.3s"
|
|
_hover={{
|
|
transform: 'scale(1.05)',
|
|
filter: 'brightness(0.8)',
|
|
}}
|
|
/>
|
|
|
|
{/* 放大图标 */}
|
|
<Box
|
|
className="zoom-icon"
|
|
position="absolute"
|
|
top="50%"
|
|
left="50%"
|
|
transform="translate(-50%, -50%)"
|
|
opacity={0}
|
|
transition="opacity 0.3s"
|
|
pointerEvents="none"
|
|
>
|
|
<Box
|
|
bg="blackAlpha.700"
|
|
borderRadius="full"
|
|
p="3"
|
|
>
|
|
<ZoomIn size={32} color="white" />
|
|
</Box>
|
|
</Box>
|
|
</Box>
|
|
|
|
{/* 灯箱模态框 */}
|
|
<Modal isOpen={isOpen} onClose={onClose} size="full" isCentered>
|
|
<ModalOverlay bg="blackAlpha.900" backdropFilter="blur(10px)" />
|
|
<ModalContent bg="transparent" boxShadow="none">
|
|
<ModalCloseButton
|
|
position="fixed"
|
|
top="4"
|
|
right="4"
|
|
size="lg"
|
|
color="white"
|
|
bg="blackAlpha.600"
|
|
_hover={{ bg: 'blackAlpha.800' }}
|
|
borderRadius="full"
|
|
zIndex={2}
|
|
/>
|
|
<ModalBody display="flex" alignItems="center" justifyContent="center" p="0">
|
|
<MotionBox
|
|
initial={{ opacity: 0, scale: 0.9 }}
|
|
animate={{ opacity: 1, scale: 1 }}
|
|
exit={{ opacity: 0, scale: 0.9 }}
|
|
transition={{ duration: 0.3 }}
|
|
maxW="90vw"
|
|
maxH="90vh"
|
|
>
|
|
<Image
|
|
src={src}
|
|
alt={alt}
|
|
maxW="100%"
|
|
maxH="90vh"
|
|
objectFit="contain"
|
|
borderRadius="lg"
|
|
/>
|
|
</MotionBox>
|
|
</ModalBody>
|
|
</ModalContent>
|
|
</Modal>
|
|
</>
|
|
);
|
|
};
|
|
|
|
/**
|
|
* 多图片轮播灯箱
|
|
*/
|
|
export const ImageGalleryLightbox = ({ images, initialIndex = 0, ...props }) => {
|
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
|
const [currentIndex, setCurrentIndex] = useState(initialIndex);
|
|
|
|
const handleOpen = (index) => {
|
|
setCurrentIndex(index);
|
|
onOpen();
|
|
};
|
|
|
|
const handlePrev = () => {
|
|
setCurrentIndex((prev) => (prev === 0 ? images.length - 1 : prev - 1));
|
|
};
|
|
|
|
const handleNext = () => {
|
|
setCurrentIndex((prev) => (prev === images.length - 1 ? 0 : prev + 1));
|
|
};
|
|
|
|
const handleKeyDown = (e) => {
|
|
if (e.key === 'ArrowLeft') handlePrev();
|
|
if (e.key === 'ArrowRight') handleNext();
|
|
if (e.key === 'Escape') onClose();
|
|
};
|
|
|
|
return (
|
|
<>
|
|
{/* 缩略图网格 */}
|
|
<HStack spacing="2" flexWrap="wrap" {...props}>
|
|
{images.map((image, index) => (
|
|
<Box
|
|
key={index}
|
|
position="relative"
|
|
cursor="pointer"
|
|
onClick={() => handleOpen(index)}
|
|
_hover={{
|
|
'& .zoom-icon': {
|
|
opacity: 1,
|
|
},
|
|
}}
|
|
>
|
|
<Image
|
|
src={image.src || image}
|
|
alt={image.alt || `图片 ${index + 1}`}
|
|
w="150px"
|
|
h="150px"
|
|
objectFit="cover"
|
|
borderRadius="md"
|
|
transition="all 0.3s"
|
|
_hover={{
|
|
transform: 'scale(1.05)',
|
|
filter: 'brightness(0.8)',
|
|
}}
|
|
/>
|
|
|
|
{/* 放大图标 */}
|
|
<Box
|
|
className="zoom-icon"
|
|
position="absolute"
|
|
top="50%"
|
|
left="50%"
|
|
transform="translate(-50%, -50%)"
|
|
opacity={0}
|
|
transition="opacity 0.3s"
|
|
pointerEvents="none"
|
|
>
|
|
<Box bg="blackAlpha.700" borderRadius="full" p="2">
|
|
<ZoomIn size={24} color="white" />
|
|
</Box>
|
|
</Box>
|
|
</Box>
|
|
))}
|
|
</HStack>
|
|
|
|
{/* 灯箱模态框(带轮播) */}
|
|
<Modal
|
|
isOpen={isOpen}
|
|
onClose={onClose}
|
|
size="full"
|
|
isCentered
|
|
onKeyDown={handleKeyDown}
|
|
>
|
|
<ModalOverlay bg="blackAlpha.900" backdropFilter="blur(10px)" />
|
|
<ModalContent bg="transparent" boxShadow="none">
|
|
{/* 关闭按钮 */}
|
|
<IconButton
|
|
icon={<X />}
|
|
position="fixed"
|
|
top="4"
|
|
right="4"
|
|
size="lg"
|
|
color="white"
|
|
bg="blackAlpha.600"
|
|
_hover={{ bg: 'blackAlpha.800' }}
|
|
borderRadius="full"
|
|
zIndex={2}
|
|
onClick={onClose}
|
|
/>
|
|
|
|
<ModalBody
|
|
display="flex"
|
|
alignItems="center"
|
|
justifyContent="center"
|
|
p="0"
|
|
position="relative"
|
|
>
|
|
{/* 左箭头 */}
|
|
{images.length > 1 && (
|
|
<IconButton
|
|
icon={<ChevronLeft />}
|
|
position="absolute"
|
|
left="4"
|
|
top="50%"
|
|
transform="translateY(-50%)"
|
|
size="lg"
|
|
color="white"
|
|
bg="blackAlpha.600"
|
|
_hover={{ bg: 'blackAlpha.800' }}
|
|
borderRadius="full"
|
|
zIndex={2}
|
|
onClick={handlePrev}
|
|
/>
|
|
)}
|
|
|
|
{/* 图片 */}
|
|
<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 }}
|
|
maxW="90vw"
|
|
maxH="90vh"
|
|
>
|
|
<Image
|
|
src={images[currentIndex].src || images[currentIndex]}
|
|
alt={images[currentIndex].alt || `图片 ${currentIndex + 1}`}
|
|
maxW="100%"
|
|
maxH="90vh"
|
|
objectFit="contain"
|
|
borderRadius="lg"
|
|
/>
|
|
</MotionBox>
|
|
</AnimatePresence>
|
|
|
|
{/* 右箭头 */}
|
|
{images.length > 1 && (
|
|
<IconButton
|
|
icon={<ChevronRight />}
|
|
position="absolute"
|
|
right="4"
|
|
top="50%"
|
|
transform="translateY(-50%)"
|
|
size="lg"
|
|
color="white"
|
|
bg="blackAlpha.600"
|
|
_hover={{ bg: 'blackAlpha.800' }}
|
|
borderRadius="full"
|
|
zIndex={2}
|
|
onClick={handleNext}
|
|
/>
|
|
)}
|
|
|
|
{/* 图片计数 */}
|
|
{images.length > 1 && (
|
|
<Box
|
|
position="absolute"
|
|
bottom="4"
|
|
left="50%"
|
|
transform="translateX(-50%)"
|
|
bg="blackAlpha.700"
|
|
color="white"
|
|
px="4"
|
|
py="2"
|
|
borderRadius="full"
|
|
fontSize="sm"
|
|
fontWeight="600"
|
|
>
|
|
{currentIndex + 1} / {images.length}
|
|
</Box>
|
|
)}
|
|
</ModalBody>
|
|
</ModalContent>
|
|
</Modal>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default ImageLightbox;
|