2026-01-24 16:54:44 +08:00
|
|
|
|
import { Link as I18nLink, LOCALES } from "@/i18n/routing";
|
|
|
|
|
|
import { constructMetadata } from "@/lib/metadata";
|
|
|
|
|
|
import { getWorkShops } from "@/lib/workshop";
|
|
|
|
|
|
import { WorkShop } from "@/types/workShop";
|
2026-02-03 14:39:36 +08:00
|
|
|
|
import clsx from "clsx";
|
2026-01-24 16:54:44 +08:00
|
|
|
|
import { Metadata } from "next";
|
|
|
|
|
|
import { getTranslations } from "next-intl/server";
|
|
|
|
|
|
|
|
|
|
|
|
// 强制静态生成
|
|
|
|
|
|
export const dynamic = "force-static";
|
|
|
|
|
|
|
2026-02-03 14:39:36 +08:00
|
|
|
|
// 明确 Params 类型(静态生成的参数)
|
2026-01-24 16:54:44 +08:00
|
|
|
|
type Params = { locale: string };
|
|
|
|
|
|
|
|
|
|
|
|
type MetadataProps = {
|
|
|
|
|
|
params: Params;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 生成页面元数据(确保服务端/客户端翻译一致)
|
2026-02-03 14:39:36 +08:00
|
|
|
|
export async function generateMetadata({ params }: MetadataProps): Promise<Metadata> {
|
2026-01-24 16:54:44 +08:00
|
|
|
|
const { locale } = params;
|
|
|
|
|
|
const t = await getTranslations({ locale, namespace: "Workshop" });
|
|
|
|
|
|
|
|
|
|
|
|
return constructMetadata({
|
|
|
|
|
|
page: "Workshop",
|
|
|
|
|
|
title: t("pageTitle", { defaultValue: "车间展示" }), // 使用 next-intl 官方的 defaultValue
|
|
|
|
|
|
description: t("pageDesc", { defaultValue: "公司车间展示" }),
|
|
|
|
|
|
locale: locale,
|
|
|
|
|
|
path: `/workshop`,
|
|
|
|
|
|
canonicalUrl: `/workshop`,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 生成静态路由参数(多语言)
|
|
|
|
|
|
export async function generateStaticParams() {
|
|
|
|
|
|
return LOCALES.map((locale) => ({
|
|
|
|
|
|
locale,
|
|
|
|
|
|
}));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 空数据组件(纯静态,无动态属性)
|
|
|
|
|
|
function EmptyState() {
|
|
|
|
|
|
return <div className="py-10 text-center text-gray-500">暂无车间数据</div>;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function WorkshopCard({ workshop }: { workshop: WorkShop }) {
|
2026-02-03 14:39:36 +08:00
|
|
|
|
// 为 img 标签添加默认值,避免属性缺失导致不匹配
|
2026-01-24 16:54:44 +08:00
|
|
|
|
const coverSrc = workshop.cover || "/default-workshop-cover.png"; // 兜底封面图
|
|
|
|
|
|
const coverAlt = workshop.title || "车间封面";
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
2026-02-03 14:39:36 +08:00
|
|
|
|
<I18nLink href={`/workshop/${workshop.slug}`} locale={workshop.locale}>
|
|
|
|
|
|
<div className="border rounded-xl overflow-hidden shadow-lg hover:shadow-2xl transition-shadow">
|
|
|
|
|
|
{/* 封面图:使用原生 img 标签,确保图片尺寸一致 */}
|
2026-01-24 16:54:44 +08:00
|
|
|
|
<div className="relative h-64 w-full">
|
|
|
|
|
|
<img
|
|
|
|
|
|
src={coverSrc}
|
|
|
|
|
|
alt={coverAlt}
|
2026-02-03 14:39:36 +08:00
|
|
|
|
className="object-cover w-full h-full"
|
2026-01-24 16:54:44 +08:00
|
|
|
|
loading="lazy"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 车间信息 */}
|
2026-02-03 14:39:36 +08:00
|
|
|
|
<div className="p-6 bg-white">
|
|
|
|
|
|
<h2 className="text-2xl font-semibold mb-2 text-gray-800">{workshop.title || "未命名车间"}</h2>
|
2026-01-24 16:54:44 +08:00
|
|
|
|
<p className="text-gray-600 mb-4">{workshop.desc || "暂无描述"}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</I18nLink>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 页面主组件(Server Component)
|
2026-02-03 14:39:36 +08:00
|
|
|
|
export default async function Page({ params }: { params: Params }) {
|
2026-01-24 16:54:44 +08:00
|
|
|
|
const { locale } = await params;
|
|
|
|
|
|
// 获取翻译(确保服务端/客户端一致)
|
|
|
|
|
|
const t = await getTranslations({ locale, namespace: "Workshop" });
|
|
|
|
|
|
|
|
|
|
|
|
// 获取车间数据(顶层 await,Server Component 原生支持)
|
|
|
|
|
|
const workShops: WorkShop[] = await getWorkShops(locale);
|
|
|
|
|
|
|
|
|
|
|
|
// 页面标题翻译兜底
|
|
|
|
|
|
const pageTitle = t("pageTitle", { defaultValue: "车间展示" });
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
2026-02-03 14:39:36 +08:00
|
|
|
|
<main className="container mx-auto py-16 px-6 max-w-screen-xl">
|
|
|
|
|
|
{/* 页面标题 */}
|
|
|
|
|
|
<h1 className="text-4xl font-extrabold text-center text-gray-900 mb-12">{pageTitle}</h1>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 展示车间卡片 */}
|
|
|
|
|
|
<div className={clsx("grid gap-8", workShops.length ? "grid-cols-1 md:grid-cols-2 lg:grid-cols-3" : "")}>
|
|
|
|
|
|
{workShops.length > 0 ? (
|
|
|
|
|
|
workShops.map((workshop) => {
|
2026-01-24 16:54:44 +08:00
|
|
|
|
const stableKey = `${workshop.title}-${workshop.locale}`;
|
|
|
|
|
|
return <WorkshopCard key={stableKey} workshop={workshop} />;
|
2026-02-03 14:39:36 +08:00
|
|
|
|
})
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<EmptyState />
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
2026-01-24 16:54:44 +08:00
|
|
|
|
</main>
|
|
|
|
|
|
);
|
2026-02-03 14:39:36 +08:00
|
|
|
|
}
|