Files
fad-trade-next/app/[locale]/product/[slug]/page.tsx
砂糖 f25989b88f feat: 更新产品类型和内容,优化产品展示功能
- 重构产品类型定义,新增desc、models、content字段
- 更新所有语言的产品内容,包括详细规格和化学/机械性能
- 修改产品展示组件,支持MDX内容渲染和模型列表展示
- 调整产品详情页布局,优化信息展示方式
- 更新i18n翻译文件,同步产品名称变更
- 修改默认主题配置为light模式
- 修复公司简介中的格式问题
2026-01-27 12:57:27 +08:00

194 lines
6.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import MDXComponents from "@/components/mdx/MDXComponents";
import ProductTabsClient from "@/components/ProductTabsClient";
import { Locale, LOCALES } from "@/i18n/routing";
import { getProducts } from "@/lib/getProducts";
import { constructMetadata } from "@/lib/metadata";
import { Product } from "@/types/product";
import { Metadata } from "next";
import { getTranslations } from "next-intl/server";
import { MDXRemote } from "next-mdx-remote-client/rsc";
import { notFound } from "next/navigation";
import remarkGfm from "remark-gfm"; // 引入表格解析插件
type Params = {
locale: string;
slug: string;
};
type MetadataProps = {
params: Promise<Params>;
};
export async function generateMetadata({
params,
}: MetadataProps): Promise<Metadata> {
const { locale, slug } = await params;
try {
const { products }: { products: Product[] } = await getProducts(locale);
const product = products.find((product) => product.slug == slug);
if (!product) {
return constructMetadata({
title: "404 - 产品不存在",
description: "请求的产品页面未找到",
noIndex: true,
locale: locale as Locale,
path: `/products/${slug}`,
canonicalUrl: `/products/${slug}`,
});
}
return constructMetadata({
page: "products",
title: product.title,
description: `${product.title} - ${product.model} - ${product.place}`,
images: product.images.length ? [product.images[0]] : [],
locale: locale as Locale,
path: `/products/${slug}`,
canonicalUrl: `/products/${slug}`,
});
} catch (error) {
console.error("生成产品元数据失败:", error);
return constructMetadata({
title: "产品详情",
description: "产品详情页面",
locale: (await params).locale as Locale,
path: `/products/${(await params).slug}`,
});
}
}
export default async function ProductPage({ params }: { params: Promise<Params> }) {
const { locale, slug } = await params;
const { products }: { products: Product[] } = await getProducts(locale);
const product = products.find((item) => item.slug == slug);
if (!product) return notFound();
const t = await getTranslations({ locale, namespace: "Product" });
// 获取所有产品图片
const allImages = product.images || [];
const formattedPublishTime = (() => {
if (!product.publishedTime) return "暂无";
const publishDate = typeof product.publishedTime === 'string'
? new Date(product.publishedTime)
: product.publishedTime;
return isNaN(publishDate.getTime())
? "暂无"
: publishDate.toLocaleDateString("zh-CN", {
year: "numeric",
month: "2-digit",
day: "2-digit",
});
})();
return (
<div>
<img src="http://kelunpuzhonggong.com/upload/img/20251015111553.jpg" className="w-screen h-auto" alt="" />
<div className="w-full max-w-6xl mx-auto px-4 md:px-8 py-8">
<div className="text-center mb-6">
<h1 className="text-purple-600 text-xl font-bold">{t("detailTitle")}</h1>
</div>
{/* 调整后:左侧大图+下方缩略图 + 右侧信息 */}
<div className="border rounded p-4 mb-6">
<div className="flex flex-col md:flex-row">
{/* 左侧图片区域(大图+缩略图) */}
<div className="md:w-1/3 mb-4 md:mb-0">
{/* 第一张大图 */}
{allImages.length > 0 ? (
<div className="relative w-full h-auto max-h-[500px]">
<img
src={allImages[0]}
alt={`${product.title || "产品"}-主图`}
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 66vw, 66vw"
className="object-contain"
/>
</div>
) : (
<div className="w-full h-60 bg-gray-100 flex items-center justify-center text-gray-500">
</div>
)}
</div>
{/* 右侧产品信息区域 */}
<div className="md:w-2/3 md:ml-4">
<h1 className="text-xl font-bold mb-3">{product.title || "未命名产品"}</h1>
<div className="space-y-2 mb-6">
<div>
<span className="font-medium">{t('productDetail')}</span>
<span>{product.desc || "暂无"}</span>
</div>
<div>
<span className="font-medium">{t("productPlace")}</span>
<span>{product.place || "暂无"}</span>
</div>
<div>
<span className="font-medium">{t("productPublishedTime")}</span>
<span>{formattedPublishTime}</span>
</div>
</div>
</div>
</div>
{/* 所有图片缩略图(横向滚动) */}
{allImages.length > 0 && (
<div className="flex flex-nowrap gap-2 mt-4 overflow-x-auto pb-2">
{allImages.map((img, index) => (
<div
key={index}
className="relative w-20 h-20 cursor-pointer border border-gray-200 rounded"
>
<img
src={img}
alt={`${product.title || "产品"}-图${index + 1}`}
className="object-cover"
sizes="80px"
/>
</div>
))}
</div>
)}
</div>
<ProductTabsClient
locale={locale}
detail={product.desc}
models={product.models}
spec={product.spec}
packaging={product.packaging}
/>
<MDXRemote source={product.content || ""} components={MDXComponents} options={{
mdxOptions: {
remarkPlugins: [remarkGfm], // 加入表格解析插件
},
}} />
</div>
</div>
);
}
export async function generateStaticParams() {
try {
const defaultLocale = LOCALES[0];
const { products }: { products: Product[] } = await getProducts(defaultLocale);
const validProducts = products.filter((product) => product.slug.toString() && product.title);
return LOCALES.flatMap((locale) =>
validProducts.map((product) => ({
locale,
slug: product.slug.toString(),
}))
);
} catch (error) {
console.error("生成产品静态参数失败:", error);
return [];
}
}