2026-01-24 16:54:44 +08:00
|
|
|
|
import { LOCALES } from "@/i18n/routing";
|
|
|
|
|
|
import { constructMetadata } from "@/lib/metadata";
|
|
|
|
|
|
import { getWorkShop, getWorkShops } from "@/lib/workshop";
|
|
|
|
|
|
import { WorkShop } from "@/types/workShop";
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
// 固定 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 workShop = await getWorkShop(locale, slug);
|
|
|
|
|
|
|
|
|
|
|
|
if (!workShop) {
|
|
|
|
|
|
return constructMetadata({
|
|
|
|
|
|
title: "404 - 车间不存在",
|
|
|
|
|
|
description: "请求的车间页面未找到",
|
|
|
|
|
|
noIndex: true,
|
|
|
|
|
|
locale: locale as Locale,
|
|
|
|
|
|
path: `/workshop/${slug}`,
|
|
|
|
|
|
canonicalUrl: `/workshop/${slug}`,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return constructMetadata({
|
|
|
|
|
|
title: workShop.title,
|
|
|
|
|
|
description: workShop.desc,
|
|
|
|
|
|
locale: locale as Locale,
|
|
|
|
|
|
path: `/workshop/${slug}`,
|
|
|
|
|
|
canonicalUrl: `/workshop/${slug}`,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 页面主组件 - 仅保留字段展示+修复 Hydration 错误
|
|
|
|
|
|
export default async function WorkshopDetailPage({ params }: { params: Params }) {
|
|
|
|
|
|
const { locale, slug } = await params;
|
|
|
|
|
|
const workShop = await getWorkShop(locale, slug);
|
2026-02-03 17:18:13 +08:00
|
|
|
|
const t = await getTranslations({ locale, namespace: "Workshop" });
|
2026-01-24 16:54:44 +08:00
|
|
|
|
|
|
|
|
|
|
if (!workShop) return null;
|
|
|
|
|
|
|
|
|
|
|
|
// 兜底处理:避免字段为空导致属性不匹配
|
|
|
|
|
|
const coverSrc = workShop.cover || "";
|
|
|
|
|
|
const coverAlt = workShop.title || "车间封面";
|
|
|
|
|
|
const workshopDesc = workShop.desc || "暂无车间描述";
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
2026-02-03 14:39:36 +08:00
|
|
|
|
<div className="min-h-screen bg-gray-50 py-16 px-6 sm:px-8 lg:px-10">
|
|
|
|
|
|
<div className="max-w-7xl mx-auto space-y-12">
|
|
|
|
|
|
{/* 封面图区域 - 强化背景效果 */}
|
|
|
|
|
|
<div className="relative rounded-xl overflow-hidden shadow-lg h-[500px] sm:h-[600px] mb-12 group">
|
2026-01-24 16:54:44 +08:00
|
|
|
|
<img
|
|
|
|
|
|
src={coverSrc}
|
|
|
|
|
|
alt={coverAlt}
|
2026-02-03 14:39:36 +08:00
|
|
|
|
className="absolute inset-0 w-full h-full object-cover transition-transform duration-500 group-hover:scale-110"
|
2026-01-24 16:54:44 +08:00
|
|
|
|
loading="lazy"
|
|
|
|
|
|
decoding="async"
|
|
|
|
|
|
/>
|
2026-02-03 14:39:36 +08:00
|
|
|
|
<div className="absolute inset-0 bg-gradient-to-t from-black opacity-40"></div>
|
2026-01-24 16:54:44 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 标题+描述区域 */}
|
2026-02-03 14:39:36 +08:00
|
|
|
|
<div className="bg-white rounded-xl p-8 shadow-xl">
|
|
|
|
|
|
<h1 className="text-3xl sm:text-4xl font-bold text-gray-900 mb-6">{workShop.title}</h1>
|
|
|
|
|
|
<p className="text-lg sm:text-xl text-gray-700 leading-relaxed mb-6">{workshopDesc}</p>
|
2026-01-24 16:54:44 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-02-03 14:39:36 +08:00
|
|
|
|
{/* 图片展示区域 */}
|
2026-01-24 16:54:44 +08:00
|
|
|
|
{workShop.images?.length > 0 && (
|
2026-02-03 14:39:36 +08:00
|
|
|
|
<div className="bg-white rounded-xl p-8 shadow-xl mt-12">
|
2026-01-24 16:54:44 +08:00
|
|
|
|
<h2 className="text-2xl font-semibold text-gray-800 mb-6 border-b pb-3 border-gray-200">
|
2026-02-03 17:18:13 +08:00
|
|
|
|
{t("pageImages")}
|
2026-01-24 16:54:44 +08:00
|
|
|
|
</h2>
|
|
|
|
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
|
|
|
|
{workShop.images.map((img, index) => {
|
|
|
|
|
|
const stableKey = `workshop-${slug}-img-${index}-${img.slice(-8)}`;
|
|
|
|
|
|
const imgAlt = `${workShop.title}-图片${index + 1}`;
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div
|
|
|
|
|
|
key={stableKey}
|
2026-02-03 14:39:36 +08:00
|
|
|
|
className="relative rounded-xl overflow-hidden shadow-lg hover:shadow-2xl transition-all duration-300"
|
2026-01-24 16:54:44 +08:00
|
|
|
|
>
|
|
|
|
|
|
<img
|
|
|
|
|
|
src={img}
|
|
|
|
|
|
alt={imgAlt}
|
2026-02-03 14:39:36 +08:00
|
|
|
|
className="w-full h-full object-cover transition-transform duration-500 hover:scale-105"
|
2026-01-24 16:54:44 +08:00
|
|
|
|
loading="lazy"
|
|
|
|
|
|
decoding="async"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
})}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export async function generateStaticParams() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const defaultLocale = LOCALES[0];
|
|
|
|
|
|
const workShops: WorkShop[] = await getWorkShops(defaultLocale);
|
|
|
|
|
|
|
|
|
|
|
|
return LOCALES.flatMap((locale) =>
|
|
|
|
|
|
workShops.map((workShop) => ({
|
|
|
|
|
|
locale,
|
|
|
|
|
|
slug: workShop.slug as string,
|
|
|
|
|
|
}))
|
|
|
|
|
|
);
|
|
|
|
|
|
} catch (error) {
|
2026-02-03 14:39:36 +08:00
|
|
|
|
console.error("生成车间静态参数失败:", error);
|
2026-01-24 16:54:44 +08:00
|
|
|
|
return [];
|
|
|
|
|
|
}
|
2026-02-03 14:39:36 +08:00
|
|
|
|
}
|