产品详情页缩略图美化

This commit is contained in:
jingrow 2025-08-24 21:30:04 +08:00
parent 01f3c85526
commit d0982afc49

View File

@ -1,6 +1,6 @@
'use client'; 'use client';
import { useState, useEffect, useRef } from 'react'; import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
import { Swiper, SwiperSlide } from "swiper/react"; import { Swiper, SwiperSlide } from "swiper/react";
import { Navigation, Pagination, Thumbs, FreeMode } from "swiper/modules"; import { Navigation, Pagination, Thumbs, FreeMode } from "swiper/modules";
@ -21,8 +21,8 @@ const ProductImageGallery = ({ data }) => {
return null; return null;
} }
// attachments // 使useMemo
const getMainImageIndex = () => { const mainImageIndex = useMemo(() => {
if (!data.image) return 0; if (!data.image) return 0;
const mainImageIndex = data.attachments.findIndex( const mainImageIndex = data.attachments.findIndex(
@ -30,34 +30,34 @@ const ProductImageGallery = ({ data }) => {
); );
return mainImageIndex >= 0 ? mainImageIndex : 0; return mainImageIndex >= 0 ? mainImageIndex : 0;
}; }, [data.image, data.attachments]);
const mainImageIndex = getMainImageIndex(); // 使useMemoURL
const currentImage = data.attachments[currentImageIndex]?.file_url || data.attachments[0]?.file_url; const currentImage = useMemo(() => {
return data.attachments[currentImageIndex]?.file_url || data.attachments[0]?.file_url;
}, [data.attachments, currentImageIndex]);
// // 使useCallback
const scrollThumbnailToIndex = (index) => { const scrollThumbnailToIndex = useCallback((index) => {
console.log('滚动缩略图到索引:', index); // if (thumbsSwiperRef.current?.swiper) {
if (thumbsSwiperRef.current && thumbsSwiperRef.current.swiper) {
const swiper = thumbsSwiperRef.current.swiper;
console.log('Swiper实例存在开始滚动'); //
// 使
try { try {
swiper.slideTo(index, 300); const swiper = thumbsSwiperRef.current.swiper;
console.log('滚动成功,目标索引:', index);
if (index === 0) {
//
swiper.scrollTo(0, 300);
} else {
// 使slideTo
swiper.slideTo(index, 300);
}
} catch (error) { } catch (error) {
console.error('滚动失败:', error); //
} }
} else {
console.log('Swiper实例不存在thumbsSwiperRef:', thumbsSwiperRef.current);
} }
}; }, []);
const changeMainImage = (direction) => { // 使useCallback
console.log('changeMainImage 被调用,方向:', direction); // const changeMainImage = useCallback((direction) => {
if (data.attachments.length <= 1) return; if (data.attachments.length <= 1) return;
let newIndex; let newIndex;
@ -67,84 +67,106 @@ const ProductImageGallery = ({ data }) => {
newIndex = (currentImageIndex - 1 + data.attachments.length) % data.attachments.length; newIndex = (currentImageIndex - 1 + data.attachments.length) % data.attachments.length;
} }
console.log('新的索引:', newIndex, '当前索引:', currentImageIndex); //
setCurrentImageIndex(newIndex); setCurrentImageIndex(newIndex);
// //
setTimeout(() => { setTimeout(() => {
console.log('开始执行滚动,索引:', newIndex); //
scrollThumbnailToIndex(newIndex); scrollThumbnailToIndex(newIndex);
}, 100); }, 100);
}; }, [currentImageIndex, data.attachments.length, scrollThumbnailToIndex]);
const changeMainImageByIndex = (index) => { // 使useCallback
const changeMainImageByIndex = useCallback((index) => {
setCurrentImageIndex(index); setCurrentImageIndex(index);
// //
setTimeout(() => { setTimeout(() => {
scrollThumbnailToIndex(index); scrollThumbnailToIndex(index);
}, 100); }, 100);
}; }, [scrollThumbnailToIndex]);
// //
useEffect(() => { useEffect(() => {
setCurrentImageIndex(mainImageIndex); setCurrentImageIndex(mainImageIndex);
}, [mainImageIndex]); }, [mainImageIndex]);
// // - 使useCallback
useEffect(() => { const handleKeyDown = useCallback((event) => {
const handleKeyDown = (event) => { if (data.attachments.length <= 1) return;
if (data.attachments.length <= 1) return;
switch (event.key) {
switch (event.key) { case 'ArrowLeft':
case 'ArrowLeft': event.preventDefault();
event.preventDefault(); changeMainImage('prev');
changeMainImage('prev'); break;
break; case 'ArrowRight':
case 'ArrowRight': event.preventDefault();
event.preventDefault(); changeMainImage('next');
changeMainImage('next'); break;
break; case 'Home':
case 'Home': event.preventDefault();
event.preventDefault(); setCurrentImageIndex(0);
setCurrentImageIndex(0); setTimeout(() => scrollThumbnailToIndex(0), 100);
setTimeout(() => scrollThumbnailToIndex(0), 100); break;
break; case 'End':
case 'End': event.preventDefault();
event.preventDefault(); setCurrentImageIndex(data.attachments.length - 1);
setCurrentImageIndex(data.attachments.length - 1); setTimeout(() => scrollThumbnailToIndex(data.attachments.length - 1), 100);
setTimeout(() => scrollThumbnailToIndex(data.attachments.length - 1), 100); break;
break; default:
default: break;
break; }
} }, [data.attachments.length, changeMainImage, scrollThumbnailToIndex]);
};
// useEffect(() => {
document.addEventListener('keydown', handleKeyDown); document.addEventListener('keydown', handleKeyDown);
return () => { return () => {
document.removeEventListener('keydown', handleKeyDown); document.removeEventListener('keydown', handleKeyDown);
}; };
}, [data.attachments.length]); }, [handleKeyDown]);
// 使useMemo
const thumbnailSlides = useMemo(() => {
// - return data.attachments.map((attachment, index) => (
// const goToPrevThumbs = () => { <SwiperSlide
// if (thumbsSwiperRef.current && thumbsSwiperRef.current.swiper) { key={index}
// thumbsSwiperRef.current.swiper.slidePrev(); className="!w-16 !h-16"
// } onClick={() => changeMainImageByIndex(index)}
// }; >
<div
// const goToNextThumbs = () => { className={`w-full h-full rounded-lg overflow-hidden cursor-pointer transition-all duration-300 hover:scale-105 focus-within:ring-2 focus-within:ring-blue-500 focus-within:ring-offset-2 ${
// if (thumbsSwiperRef.current && thumbsSwiperRef.current.swiper) { index === currentImageIndex
// if (thumbsSwiperRef.current && thumbsSwiperRef.current.swiper) { ? 'border-2 border-blue-500 ring-2 ring-blue-200'
// thumbsSwiperRef.current.swiper.slideNext(); : 'border border-gray-200 hover:border-gray-300'
// } }`}
// } tabIndex={0}
// }; role="button"
aria-label={`查看图片 ${index + 1} / ${data.attachments.length}`}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
changeMainImageByIndex(index);
}
}}
>
<img
src={attachment.file_url}
alt={`${data?.title || 'Product'} - ${index + 1}`}
className="w-full h-full object-cover"
loading="lazy"
onError={(e) => {
e.target.style.display = 'none';
}}
/>
{/* 缩略图加载状态指示 */}
<div className="absolute inset-0 bg-gray-100 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-200">
<div className="w-4 h-4 border-2 border-gray-300 border-t-blue-500 rounded-full animate-spin"></div>
</div>
</div>
</SwiperSlide>
));
}, [data.attachments, currentImageIndex, data?.title, changeMainImageByIndex]);
return ( return (
<div className="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">
@ -218,57 +240,13 @@ const ProductImageGallery = ({ data }) => {
slidesPerView="auto" slidesPerView="auto"
freeMode={true} freeMode={true}
watchSlidesProgress={true} watchSlidesProgress={true}
modules={[FreeMode, Navigation, Pagination]} modules={[FreeMode, Navigation, Pagination, Thumbs]}
className="thumbs-swiper" className="thumbs-swiper"
grabCursor={true} grabCursor={true}
resistance={true} resistance={true}
resistanceRatio={0.85} resistanceRatio={0.85}
breakpoints={{
0: { slidesPerView: 3, spaceBetween: 12 },
480: { slidesPerView: 4, spaceBetween: 16 },
768: { slidesPerView: 5, spaceBetween: 16 },
1024: { slidesPerView: 6, spaceBetween: 16 }
}}
> >
{data.attachments.map((attachment, index) => ( {thumbnailSlides}
<SwiperSlide
key={index}
className="!w-16 !h-16"
onClick={() => changeMainImageByIndex(index)}
>
<div
className={`w-full h-full rounded-lg overflow-hidden cursor-pointer transition-all duration-300 hover:scale-105 focus-within:ring-2 focus-within:ring-blue-500 focus-within:ring-offset-2 ${
index === currentImageIndex
? 'border-2 border-blue-500 ring-2 ring-blue-200'
: 'border border-gray-200 hover:border-gray-300'
}`}
tabIndex={0}
role="button"
aria-label={`查看图片 ${index + 1} / ${data.attachments.length}`}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
changeMainImageByIndex(index);
}
}}
>
<img
src={attachment.file_url}
alt={`${data?.title || 'Product'} - ${index + 1}`}
className="w-full h-full object-cover"
loading="lazy"
onError={(e) => {
console.error('Image load error:', attachment.file_url);
e.target.style.display = 'none';
}}
/>
{/* 缩略图加载状态指示 */}
<div className="absolute inset-0 bg-gray-100 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-200">
<div className="w-4 h-4 border-2 border-gray-300 border-t-blue-500 rounded-full animate-spin"></div>
</div>
</div>
</SwiperSlide>
))}
</Swiper> </Swiper>
</div> </div>
)} )}
@ -279,12 +257,18 @@ const ProductImageGallery = ({ data }) => {
/* 缩略图悬停效果 */ /* 缩略图悬停效果 */
.thumbs-swiper .swiper-slide { .thumbs-swiper .swiper-slide {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
width: 64px !important; /* 确保宽度为64px */
margin-right: 16px !important; /* 确保右边距为16px */
} }
.thumbs-swiper .swiper-slide:hover { .thumbs-swiper .swiper-slide:hover {
transform: scale(1.05); transform: scale(1.05);
} }
.thumbs-swiper .swiper-slide:last-child {
margin-right: 0 !important; /* 最后一个缩略图不需要右边距 */
}
/* 当前选中的缩略图样式 */ /* 当前选中的缩略图样式 */
.border-2.border-blue-500 { .border-2.border-blue-500 {
border-color: #3b82f6; border-color: #3b82f6;