diff --git a/app/products/[...slug]/page.jsx b/app/products/[...slug]/page.jsx index 0ad6e9f..b2f7d79 100644 --- a/app/products/[...slug]/page.jsx +++ b/app/products/[...slug]/page.jsx @@ -136,7 +136,7 @@ export default async function Page({ params, searchParams }) { )} {/* 产品副标题 */} {data.subtitle && ( -
+
{data.subtitle}
)} diff --git a/components/products/ProductImageGallery.jsx b/components/products/ProductImageGallery.jsx index 3a77523..d99abc9 100644 --- a/components/products/ProductImageGallery.jsx +++ b/components/products/ProductImageGallery.jsx @@ -1,10 +1,20 @@ 'use client'; import { useState, useEffect, useRef } from 'react'; +import { Swiper, SwiperSlide } from "swiper/react"; +import { Navigation, Pagination, Thumbs, FreeMode } from "swiper/modules"; + +// 导入Swiper样式 +import "swiper/css"; +import "swiper/css/navigation"; +import "swiper/css/pagination"; +import "swiper/css/thumbs"; +import "swiper/css/free-mode"; const ProductImageGallery = ({ data }) => { const [currentImageIndex, setCurrentImageIndex] = useState(0); - const thumbnailContainerRef = useRef(null); + const [thumbsSwiper, setThumbsSwiper] = useState(null); + const thumbsSwiperRef = useRef(null); // 如果没有attachments,不显示组件 if (!data?.attachments || data.attachments.length === 0) { @@ -30,6 +40,41 @@ const ProductImageGallery = ({ data }) => { setCurrentImageIndex(mainImageIndex); }, [mainImageIndex]); + // 键盘导航支持 + useEffect(() => { + const handleKeyDown = (event) => { + if (data.attachments.length <= 1) return; + + switch (event.key) { + case 'ArrowLeft': + event.preventDefault(); + changeMainImage('prev'); + break; + case 'ArrowRight': + event.preventDefault(); + changeMainImage('next'); + break; + case 'Home': + event.preventDefault(); + setCurrentImageIndex(0); + break; + case 'End': + event.preventDefault(); + setCurrentImageIndex(data.attachments.length - 1); + break; + default: + break; + } + }; + + // 添加键盘事件监听器 + document.addEventListener('keydown', handleKeyDown); + + return () => { + document.removeEventListener('keydown', handleKeyDown); + }; + }, [data.attachments.length]); + const changeMainImage = (direction) => { if (data.attachments.length <= 1) return; @@ -42,149 +87,201 @@ const ProductImageGallery = ({ data }) => { const changeMainImageByIndex = (index) => { setCurrentImageIndex(index); + + // 智能滚动缩略图到可见区域 + if (thumbsSwiperRef.current && thumbsSwiperRef.current.swiper) { + const swiper = thumbsSwiperRef.current.swiper; + const slideIndex = index; + + // 计算目标滚动位置 + const slideWidth = 64; // 64px 缩略图宽度 + const spaceBetween = 16; // 16px 间距 + const totalSlideWidth = slideWidth + spaceBetween; + + // 获取当前可见的slides数量 + const visibleSlides = swiper.params.slidesPerView; + + // 计算当前滚动位置 + const currentTranslate = swiper.translate; + const currentSlideIndex = Math.round(Math.abs(currentTranslate) / totalSlideWidth); + + // 判断需要向左还是向右滚动 + let targetSlideIndex; + + if (slideIndex < currentSlideIndex) { + // 向左滚动:让选中的缩略图显示在左侧 + targetSlideIndex = Math.max(0, slideIndex - 1); + } else if (slideIndex >= currentSlideIndex + visibleSlides) { + // 向右滚动:让选中的缩略图显示在右侧 + targetSlideIndex = slideIndex - visibleSlides + 1; + } else { + // 当前缩略图已经在可见区域内,不需要滚动 + return; + } + + // 计算目标滚动位置 + const targetTranslate = targetSlideIndex * totalSlideWidth; + + // 确保不超出边界 + const maxTranslate = swiper.maxTranslate(); + const finalTranslate = Math.max(0, Math.min(targetTranslate, maxTranslate)); + + // 平滑滚动到目标位置 + swiper.slideTo(Math.round(finalTranslate / totalSlideWidth), 300); + } }; - // 智能滚动到缩略图 - 业内最佳实践 - 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' - }); - }; + // 缩略图导航控制 - 移除这些函数,不再需要 + // const goToPrevThumbs = () => { + // if (thumbsSwiperRef.current && thumbsSwiperRef.current.swiper) { + // thumbsSwiperRef.current.swiper.slidePrev(); + // } + // }; + + // const goToNextThumbs = () => { + // if (thumbsSwiperRef.current && thumbsSwiperRef.current.swiper) { + // if (thumbsSwiperRef.current && thumbsSwiperRef.current.swiper) { + // thumbsSwiperRef.current.swiper.slideNext(); + // } + // } + // }; return (
{/* 主图显示区域 */} -
+
{data?.title - {/* 导航箭头 - 简约大气设计 */} + {/* 图片计数器 */} + {data.attachments.length > 1 && ( +
+ {currentImageIndex + 1} / {data.attachments.length} +
+ )} + + {/* 导航箭头 - 悬浮时显示 */} {data.attachments.length > 1 && ( <> )}
- {/* 缩略图列表 - 业内最佳实践,确保完整显示 */} -
-
- {data.attachments.map((attachment, index) => ( -
{ - changeMainImageByIndex(index); - scrollToThumbnail(index); - }} - > - {`${data?.title { - console.error('Image load error:', attachment.file_url); - e.target.style.display = 'none'; - }} - /> -
- ))} + {/* 缩略图列表 - 使用Swiper实现最佳滚动效果 */} + {data.attachments.length > 1 && ( +
+ + {data.attachments.map((attachment, index) => ( + changeMainImageByIndex(index)} + > +
{ + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + changeMainImageByIndex(index); + } + }} + > + {`${data?.title { + console.error('Image load error:', attachment.file_url); + e.target.style.display = 'none'; + }} + /> + {/* 缩略图加载状态指示 */} +
+
+
+
+
+ ))} +
-
+ )}
{/* 内联样式 */}