2026-01-24 16:54:44 +08:00
|
|
|
|
import { LOCALES } from "@/i18n/routing";
|
|
|
|
|
|
import { getLine, getLines } from "@/lib/lines";
|
|
|
|
|
|
import { constructMetadata } from "@/lib/metadata";
|
|
|
|
|
|
import { Line } from "@/types/line";
|
|
|
|
|
|
import { Metadata } from "next";
|
|
|
|
|
|
import { Locale } from "next-intl";
|
2026-02-03 17:18:13 +08:00
|
|
|
|
import { getTranslations } from "next-intl/server";
|
2026-01-24 16:54:44 +08:00
|
|
|
|
|
|
|
|
|
|
// 强制静态渲染
|
|
|
|
|
|
export const dynamic = "force-static";
|
|
|
|
|
|
|
|
|
|
|
|
// 固定 Params 类型为普通对象(Next.js 原生传参无异步)
|
|
|
|
|
|
type Params = {
|
|
|
|
|
|
locale: string;
|
|
|
|
|
|
slug: string;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
type MetadataProps = {
|
|
|
|
|
|
params: Params;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export async function generateMetadata({
|
|
|
|
|
|
params,
|
|
|
|
|
|
}: MetadataProps): Promise<Metadata> {
|
|
|
|
|
|
const { locale, slug } = await params;
|
|
|
|
|
|
const line = await getLine(locale, slug);
|
|
|
|
|
|
|
|
|
|
|
|
if (!line) {
|
|
|
|
|
|
return constructMetadata({
|
|
|
|
|
|
title: "404 - 产线不存在",
|
|
|
|
|
|
description: "请求的产线页面未找到",
|
|
|
|
|
|
noIndex: true,
|
|
|
|
|
|
locale: locale as Locale,
|
|
|
|
|
|
path: `/line/${slug}`,
|
|
|
|
|
|
canonicalUrl: `/line/${slug}`,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return constructMetadata({
|
|
|
|
|
|
title: line.title,
|
|
|
|
|
|
description: line.desc,
|
|
|
|
|
|
locale: locale as Locale,
|
|
|
|
|
|
path: `/line/${slug}`,
|
|
|
|
|
|
canonicalUrl: `/line/${slug}`,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 页面主组件 - 仅保留字段展示+修复 Hydration 错误
|
|
|
|
|
|
export default async function ProductDetailPage({ params }: { params: Params }) {
|
|
|
|
|
|
const { locale, slug } = await params;
|
|
|
|
|
|
const line = await getLine(locale, slug);
|
2026-02-03 17:18:13 +08:00
|
|
|
|
const t = await getTranslations({ locale, namespace: "Line" });
|
2026-01-24 16:54:44 +08:00
|
|
|
|
|
|
|
|
|
|
if (!line) return <div className="p-6">404 - 产线不存在</div>;
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
2026-02-03 14:39:36 +08:00
|
|
|
|
<div className="product-detail max-w-7xl mx-auto p-6 md:p-8">
|
|
|
|
|
|
{/* 标题和描述区域,分为左右布局 */}
|
|
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 mb-12">
|
|
|
|
|
|
{/* 左侧:标题和描述 */}
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h1 className="text-4xl md:text-5xl font-bold text-gray-800 mb-4">
|
|
|
|
|
|
{line.title}
|
|
|
|
|
|
</h1>
|
|
|
|
|
|
<p className="text-gray-700 text-lg md:text-xl mb-8 leading-relaxed">
|
|
|
|
|
|
{line.desc}
|
|
|
|
|
|
</p>
|
2026-01-24 16:54:44 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-02-03 14:39:36 +08:00
|
|
|
|
{/* 右侧:封面图 */}
|
|
|
|
|
|
{line.cover && (
|
|
|
|
|
|
<div className="relative rounded-lg overflow-hidden shadow-lg">
|
|
|
|
|
|
<img
|
|
|
|
|
|
src={line.cover}
|
|
|
|
|
|
alt={`${line.title} - 封面`}
|
|
|
|
|
|
className="w-full h-auto object-cover transition-transform duration-300 hover:scale-105"
|
|
|
|
|
|
loading="lazy"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 产品图片列表区域 */}
|
2026-01-24 16:54:44 +08:00
|
|
|
|
{line.images && line.images.length > 0 && (
|
2026-02-03 14:39:36 +08:00
|
|
|
|
<div className="mb-12">
|
|
|
|
|
|
<h2 className="text-2xl md:text-3xl font-semibold text-gray-800 mb-6">
|
2026-02-03 17:18:13 +08:00
|
|
|
|
{t("productImages")}
|
2026-01-24 16:54:44 +08:00
|
|
|
|
</h2>
|
2026-02-03 14:39:36 +08:00
|
|
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
2026-01-24 16:54:44 +08:00
|
|
|
|
{line.images.map((imgUrl, index) => (
|
2026-02-03 14:39:36 +08:00
|
|
|
|
<div key={index} className="rounded-xl overflow-hidden shadow-md transition-all duration-300 hover:shadow-xl">
|
2026-01-24 16:54:44 +08:00
|
|
|
|
<img
|
|
|
|
|
|
src={imgUrl}
|
|
|
|
|
|
alt={`${line.title} - 图片${index + 1}`}
|
|
|
|
|
|
className="w-full h-auto object-cover"
|
|
|
|
|
|
loading="lazy"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
2026-02-03 14:39:36 +08:00
|
|
|
|
{/* 产品特性/参数区域 */}
|
2026-01-24 16:54:44 +08:00
|
|
|
|
{line.properties && line.properties.length > 0 && (
|
2026-02-03 14:39:36 +08:00
|
|
|
|
<div className="mb-12">
|
|
|
|
|
|
<h2 className="text-2xl md:text-3xl font-semibold text-gray-800 mb-6">
|
2026-02-03 17:18:13 +08:00
|
|
|
|
{t("productProperties")}
|
2026-01-24 16:54:44 +08:00
|
|
|
|
</h2>
|
2026-02-03 14:39:36 +08:00
|
|
|
|
<ul className="space-y-4">
|
2026-01-24 16:54:44 +08:00
|
|
|
|
{line.properties.map((prop, index) => (
|
|
|
|
|
|
<li
|
|
|
|
|
|
key={index}
|
2026-02-03 14:39:36 +08:00
|
|
|
|
className="p-6 bg-gray-50 rounded-lg shadow-sm hover:shadow-lg transition-all duration-200"
|
2026-01-24 16:54:44 +08:00
|
|
|
|
>
|
|
|
|
|
|
{/* 处理参数中的换行符 \n */}
|
|
|
|
|
|
<span
|
|
|
|
|
|
dangerouslySetInnerHTML={{
|
|
|
|
|
|
__html: prop.replace(/\n/g, "<br />"),
|
|
|
|
|
|
}}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</li>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export async function generateStaticParams() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const defaultLocale = LOCALES[0];
|
2026-01-26 16:22:07 +08:00
|
|
|
|
const lines: Line[] = await getLines(defaultLocale);
|
2026-01-24 16:54:44 +08:00
|
|
|
|
|
|
|
|
|
|
return LOCALES.flatMap((locale) =>
|
2026-01-26 16:22:07 +08:00
|
|
|
|
lines.map((line) => ({
|
2026-01-24 16:54:44 +08:00
|
|
|
|
locale,
|
2026-01-26 16:22:07 +08:00
|
|
|
|
slug: line.slug as string,
|
2026-01-24 16:54:44 +08:00
|
|
|
|
}))
|
|
|
|
|
|
);
|
|
|
|
|
|
} catch (error) {
|
2026-01-26 16:22:07 +08:00
|
|
|
|
console.error("生成产线静态参数失败:", error);
|
2026-01-24 16:54:44 +08:00
|
|
|
|
return [];
|
|
|
|
|
|
}
|
2026-02-03 14:39:36 +08:00
|
|
|
|
}
|