135 lines
4.2 KiB
TypeScript
135 lines
4.2 KiB
TypeScript
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";
|
||
|
||
// 强制静态渲染
|
||
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);
|
||
console.log(line);
|
||
|
||
console.log(line?.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 }) {
|
||
// 🔴 核心修复1:移除不必要的 await(params 是同步对象)
|
||
const { locale, slug } = await params;
|
||
const line = await getLine(locale, slug);
|
||
|
||
if (!line) return null;
|
||
|
||
// 兜底处理:避免字段为空导致属性不匹配
|
||
const coverSrc = line.cover || "";
|
||
const coverAlt = line.title || "产线封面";
|
||
const lineDesc = line.desc || "暂无产线描述";
|
||
|
||
return (
|
||
<div className="min-h-screen bg-gray-50 py-10 px-4 sm:px-6 lg:px-8">
|
||
<div className="max-w-6xl mx-auto">
|
||
{/* 封面图区域 - 🔴 修复布局属性不一致 */}
|
||
<div className="relative rounded-2xl overflow-hidden shadow-lg h-[400px] sm:h-[500px] mb-8">
|
||
<img
|
||
src={coverSrc}
|
||
alt={coverAlt}
|
||
className="absolute inset-0 w-full h-full object-cover transition-transform hover:scale-105 duration-700"
|
||
// 显式设置属性,确保服务端/客户端一致
|
||
loading="lazy"
|
||
decoding="async"
|
||
/>
|
||
</div>
|
||
|
||
{/* 标题+描述区域 */}
|
||
<div className="bg-white rounded-xl p-8 shadow-sm mb-10">
|
||
<h1 className="text-3xl sm:text-4xl font-bold text-gray-900 mb-6">
|
||
{line.title}
|
||
</h1>
|
||
<p className="text-lg text-gray-700 leading-relaxed">
|
||
{lineDesc}
|
||
</p>
|
||
</div>
|
||
|
||
{line.images.length > 0 && (
|
||
<div className="bg-white rounded-xl p-8 shadow-sm">
|
||
<h2 className="text-2xl font-semibold text-gray-800 mb-6 border-b pb-3 border-gray-200">
|
||
车间图片
|
||
</h2>
|
||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||
{line.images.map((img, index) => {
|
||
const stableKey = `line-${slug}-img-${index}-${img.slice(-8)}`;
|
||
const imgAlt = `${line.title}-图片${index + 1}`;
|
||
return (
|
||
<div
|
||
key={stableKey}
|
||
className="relative rounded-lg overflow-hidden shadow-md h-64 hover:shadow-xl transition-all duration-300"
|
||
>
|
||
<img
|
||
src={img}
|
||
alt={imgAlt}
|
||
className="absolute inset-0 w-full h-full object-cover transition-transform hover:scale-110 duration-500"
|
||
loading="lazy"
|
||
decoding="async"
|
||
/>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export async function generateStaticParams() {
|
||
try {
|
||
const defaultLocale = LOCALES[0];
|
||
const workShops: Line[] = await getLines(defaultLocale);
|
||
|
||
return LOCALES.flatMap((locale) =>
|
||
workShops.map((workShop) => ({
|
||
locale,
|
||
slug: workShop.slug as string,
|
||
}))
|
||
);
|
||
} catch (error) {
|
||
console.error("生成产品静态参数失败:", error);
|
||
return [];
|
||
}
|
||
} |