优化产品详情页缩略图效果

This commit is contained in:
jingrow 2025-08-24 19:42:16 +08:00
parent 74699a172b
commit bf0958db93

View File

@ -1,9 +1,10 @@
'use client';
import { useState, useEffect } from 'react';
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) {
@ -43,64 +44,174 @@ const ProductImageGallery = ({ data }) => {
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;
// SwiperslidesPerView
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="flex-shrink-0 w-full md:w-120 flex justify-center md:justify-start mb-4 md:mb-0 max-w-full">
<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-4">
<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-2 top-1/2 transform -translate-y-1/2 w-8 h-8 bg-white rounded-full border border-gray-300 flex items-center justify-center shadow-md hover:bg-gray-50 transition-colors z-10"
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" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
<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-2 top-1/2 transform -translate-y-1/2 w-8 h-8 bg-white rounded-full border border-gray-300 flex items-center justify-center shadow-md hover:bg-gray-50 transition-colors z-10"
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" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
<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>
{/* 缩略图列表 - 直接显示attachments */}
<div className="flex flex-wrap gap-1 justify-center md:justify-start">
{data.attachments.map((attachment, index) => (
<div
key={index}
className={`w-16 h-16 border-1 rounded-lg overflow-hidden cursor-pointer transition-all hover:border-blue-500 ${
index === currentImageIndex ? 'border-blue-500' : 'border-gray-200'
}`}
onClick={() => changeMainImageByIndex(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 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);
}}
/>
</div>
))}
>
<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>
);
};