产品详情页缩略图美化
This commit is contained in:
parent
01f3c85526
commit
d0982afc49
@ -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();
|
// 使用useMemo优化当前图片URL
|
||||||
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;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user