220 lines
7.4 KiB
JavaScript
220 lines
7.4 KiB
JavaScript
'use client';
|
||
|
||
import { useState, useEffect, useRef } from 'react';
|
||
|
||
const ProductImageGallery = ({ data }) => {
|
||
const [currentImageIndex, setCurrentImageIndex] = useState(0);
|
||
const thumbnailContainerRef = useRef(null);
|
||
|
||
// 如果没有attachments,不显示组件
|
||
if (!data?.attachments || data.attachments.length === 0) {
|
||
return null;
|
||
}
|
||
|
||
// 找到主图在attachments中的索引
|
||
const getMainImageIndex = () => {
|
||
if (!data.image) return 0;
|
||
|
||
const mainImageIndex = data.attachments.findIndex(
|
||
attachment => attachment.file_url === data.image
|
||
);
|
||
|
||
return mainImageIndex >= 0 ? mainImageIndex : 0;
|
||
};
|
||
|
||
const mainImageIndex = getMainImageIndex();
|
||
const currentImage = data.attachments[currentImageIndex]?.file_url || data.attachments[0]?.file_url;
|
||
|
||
// 初始化当前索引为主图索引
|
||
useEffect(() => {
|
||
setCurrentImageIndex(mainImageIndex);
|
||
}, [mainImageIndex]);
|
||
|
||
const changeMainImage = (direction) => {
|
||
if (data.attachments.length <= 1) return;
|
||
|
||
if (direction === 'next') {
|
||
setCurrentImageIndex((prev) => (prev + 1) % data.attachments.length);
|
||
} else {
|
||
setCurrentImageIndex((prev) => (prev - 1 + data.attachments.length) % data.attachments.length);
|
||
}
|
||
};
|
||
|
||
const changeMainImageByIndex = (index) => {
|
||
setCurrentImageIndex(index);
|
||
};
|
||
|
||
// 智能滚动到缩略图 - 业内最佳实践
|
||
const scrollToThumbnail = (index) => {
|
||
if (!thumbnailContainerRef.current) return;
|
||
|
||
const container = thumbnailContainerRef.current;
|
||
const containerWidth = container.clientWidth;
|
||
const thumbnailWidth = 64; // 64px 缩略图宽度
|
||
const gap = 16; // 16px 间距
|
||
const totalThumbnailWidth = thumbnailWidth + gap;
|
||
|
||
// 计算一次能显示多少个缩略图(类似Swiper的slidesPerView)
|
||
const visibleCount = Math.floor(containerWidth / totalThumbnailWidth);
|
||
|
||
// 计算目标滚动位置
|
||
let scrollLeft;
|
||
|
||
if (index < visibleCount / 2) {
|
||
// 如果是前几个,滚动到开头
|
||
scrollLeft = 0;
|
||
} else if (index >= data.attachments.length - visibleCount / 2) {
|
||
// 如果是后几个,滚动到末尾
|
||
scrollLeft = container.scrollWidth - containerWidth;
|
||
} else {
|
||
// 居中显示
|
||
scrollLeft = (index - Math.floor(visibleCount / 2)) * totalThumbnailWidth;
|
||
}
|
||
|
||
// 确保滚动位置在有效范围内
|
||
scrollLeft = Math.max(0, Math.min(scrollLeft, container.scrollWidth - containerWidth));
|
||
|
||
container.scrollTo({
|
||
left: scrollLeft,
|
||
behavior: 'smooth'
|
||
});
|
||
};
|
||
|
||
return (
|
||
<div className="w-full md:w-120 flex justify-center md:justify-start mb-4 md:mb-0 max-w-full">
|
||
<div className="relative w-full max-w-[320px] md:max-w-full">
|
||
{/* 主图显示区域 */}
|
||
<div className="relative overflow-hidden rounded-xl shadow-lg mb-6">
|
||
<img
|
||
src={currentImage}
|
||
alt={data?.title || 'Product Image'}
|
||
className="w-full max-w-[320px] md:max-w-full object-contain"
|
||
/>
|
||
|
||
{/* 导航箭头 - 简约大气设计 */}
|
||
{data.attachments.length > 1 && (
|
||
<>
|
||
<button
|
||
className="absolute left-3 top-1/2 transform -translate-y-1/2 w-10 h-10 bg-white/90 backdrop-blur-sm rounded-full border border-gray-100 flex items-center justify-center shadow-lg hover:bg-white hover:shadow-xl transition-all duration-300 z-10 group"
|
||
onClick={() => changeMainImage('prev')}
|
||
aria-label="Previous image"
|
||
>
|
||
<svg
|
||
className="w-4 h-4 text-gray-600 group-hover:text-gray-800 transition-colors duration-200"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
viewBox="0 0 24 24"
|
||
strokeWidth={2}
|
||
>
|
||
<path strokeLinecap="round" strokeLinejoin="round" d="M15 19l-7-7 7-7" />
|
||
</svg>
|
||
</button>
|
||
<button
|
||
className="absolute right-3 top-1/2 transform -translate-y-1/2 w-10 h-10 bg-white/90 backdrop-blur-sm rounded-full border border-gray-100 flex items-center justify-center shadow-lg hover:bg-white hover:shadow-xl transition-all duration-300 z-10 group"
|
||
onClick={() => changeMainImage('next')}
|
||
aria-label="Next image"
|
||
>
|
||
<svg
|
||
className="w-4 h-4 text-gray-600 group-hover:text-gray-800 transition-colors duration-200"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
viewBox="0 0 24 24"
|
||
strokeWidth={2}
|
||
>
|
||
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
|
||
</svg>
|
||
</button>
|
||
</>
|
||
)}
|
||
</div>
|
||
|
||
{/* 缩略图列表 - 业内最佳实践,确保完整显示 */}
|
||
<div className="relative">
|
||
<div
|
||
ref={thumbnailContainerRef}
|
||
className="flex gap-4 overflow-x-auto pb-2"
|
||
style={{
|
||
scrollbarWidth: 'none',
|
||
msOverflowStyle: 'none',
|
||
WebkitOverflowScrolling: 'touch'
|
||
}}
|
||
>
|
||
{data.attachments.map((attachment, index) => (
|
||
<div
|
||
key={index}
|
||
className={`flex-shrink-0 w-16 h-16 rounded-lg overflow-hidden cursor-pointer transition-all duration-300 hover:scale-105 ${
|
||
index === currentImageIndex
|
||
? 'border-2 border-blue-500'
|
||
: 'border border-gray-200 hover:border-gray-300'
|
||
}`}
|
||
onClick={() => {
|
||
changeMainImageByIndex(index);
|
||
scrollToThumbnail(index);
|
||
}}
|
||
>
|
||
<img
|
||
src={attachment.file_url}
|
||
alt={`${data?.title || 'Product'} - ${index + 1}`}
|
||
className="w-full h-full object-cover"
|
||
onError={(e) => {
|
||
console.error('Image load error:', attachment.file_url);
|
||
e.target.style.display = 'none';
|
||
}}
|
||
/>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 内联样式 */}
|
||
<style jsx>{`
|
||
/* 隐藏滚动条 */
|
||
.overflow-x-auto::-webkit-scrollbar {
|
||
display: none;
|
||
}
|
||
|
||
.overflow-x-auto {
|
||
-ms-overflow-style: none;
|
||
scrollbar-width: none;
|
||
}
|
||
|
||
/* 缩略图悬停效果 */
|
||
.flex-shrink-0 {
|
||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||
}
|
||
|
||
.flex-shrink-0:hover {
|
||
transform: scale(1.05);
|
||
}
|
||
|
||
/* 当前选中的缩略图样式 */
|
||
.border-2.border-blue-500 {
|
||
border-color: #3b82f6;
|
||
}
|
||
|
||
/* 导航按钮样式增强 */
|
||
.backdrop-blur-sm {
|
||
backdrop-filter: blur(8px);
|
||
-webkit-backdrop-filter: blur(8px);
|
||
}
|
||
|
||
.group:hover .backdrop-blur-sm {
|
||
backdrop-filter: blur(12px);
|
||
-webkit-backdrop-filter: blur(12px);
|
||
}
|
||
|
||
/* 响应式优化 */
|
||
@media (max-width: 768px) {
|
||
.w-16.h-16 {
|
||
width: 4rem;
|
||
height: 4rem;
|
||
}
|
||
}
|
||
`}</style>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default ProductImageGallery;
|