产品详情页实现多图展示,processDataItem函数增加支持下载附件到本地服务器

This commit is contained in:
jingrow 2025-08-24 18:06:33 +08:00
parent 5c184b8906
commit 59b2daa196
17 changed files with 123 additions and 11 deletions

View File

@ -5,6 +5,7 @@ import { notFound } from 'next/navigation';
import DynamicListPage from "@/components/common/DynamicListPage";
import { Suspense } from 'react';
import { getPageData } from "@/utils/data";
import ProductImageGallery from "@/components/products/ProductImageGallery";
const baseSlug = 'products';
@ -123,15 +124,9 @@ export default async function Page({ params, searchParams }) {
{/* 图片和附加信息并排显示,响应式优化 */}
{(data.image || data.subtitle) && (
<div className="flex flex-col md:flex-row gap-4 md:gap-8 mb-6 md:mb-8 items-center md:items-start w-full max-w-full">
{data.image && (
<div className="flex-shrink-0 w-full md:w-72 flex justify-center md:justify-start mb-4 md:mb-0 max-w-full">
<img
src={data.image}
alt={data.title}
className="w-full max-w-[320px] md:max-w-full max-h-60 md:max-h-72 rounded-xl shadow-lg object-contain"
/>
</div>
)}
{/* 图片轮播区块 */}
<ProductImageGallery data={data} />
<div className="flex-1 min-w-0">
{/* 产品标题 */}
{data.title && (
@ -141,7 +136,7 @@ export default async function Page({ params, searchParams }) {
)}
{/* 产品副标题 */}
{data.subtitle && (
<div className="bg-gray-50 whitespace-pre-line text-gray-700 w-full max-w-full prose prose-sm">
<div className="bg-gray-50 whitespace-pre-line text-gray-700 w-full max-w-full prose prose-sm p-4 rounded-lg">
{data.subtitle}
</div>
)}
@ -186,4 +181,4 @@ export default async function Page({ params, searchParams }) {
} else {
notFound();
}
}
}

View File

@ -0,0 +1,108 @@
'use client';
import { useState, useEffect } from 'react';
const ProductImageGallery = ({ data }) => {
const [currentImageIndex, setCurrentImageIndex] = useState(0);
// 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);
};
return (
<div className="flex-shrink-0 w-full md:w-72 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">
<img
src={currentImage}
alt={data?.title || 'Product Image'}
className="w-full max-w-[320px] md:max-w-full max-h-60 md:max-h-72 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"
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>
</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"
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>
</button>
</>
)}
</div>
{/* 缩略图列表 - 直接显示attachments */}
<div className="flex flex-wrap gap-2 justify-center md:justify-start">
{data.attachments.map((attachment, index) => (
<div
key={index}
className={`w-16 h-16 border-2 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>
))}
</div>
</div>
</div>
);
};
export default ProductImageGallery;

Binary file not shown.

After

Width:  |  Height:  |  Size: 856 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 741 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 799 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 971 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 760 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@ -82,6 +82,15 @@ export async function processDataItem(item, downloadFiles) {
item.file_src = await downloadToLocal(item.file_src);
}
// 处理attachments字段
if (item.attachments && Array.isArray(item.attachments)) {
for (const attachment of item.attachments) {
if (attachment.file_url) {
attachment.file_url = await downloadToLocal(attachment.file_url);
}
}
}
if (item.items && Array.isArray(item.items)) {
for (const subItem of item.items) {
if (subItem.item_image) {