Files
sage-home/app/[locale]/workshop/page.tsx
砂糖 70f337bb92 init
2026-01-24 16:54:44 +08:00

118 lines
3.9 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 { Link as I18nLink, LOCALES } from "@/i18n/routing";
import { constructMetadata } from "@/lib/metadata";
import { getWorkShops } from "@/lib/workshop";
import { WorkShop } from "@/types/workShop";
import { Metadata } from "next";
import { getTranslations } from "next-intl/server";
// 强制静态生成
export const dynamic = "force-static";
// 明确 Params 类型(静态生成的参数)
type Params = { locale: string };
type MetadataProps = {
params: Params;
};
// 生成页面元数据(确保服务端/客户端翻译一致)
export async function generateMetadata({
params,
}: MetadataProps): Promise<Metadata> {
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>;
}
// 单个车间卡片组件Client Component 标记,避免 Hydration 冲突)
// 'use client'; // 仅当需要添加交互时启用,当前纯展示可不用
function WorkshopCard({ workshop }: { workshop: WorkShop }) {
// 为 Image 组件添加默认值,避免属性缺失导致不匹配
const coverSrc = workshop.cover || "/default-workshop-cover.png"; // 兜底封面图
const coverAlt = workshop.title || "车间封面";
return (
<I18nLink
href={`/workshop/${workshop.slug}`}
locale={workshop.locale}
>
<div className="border rounded-lg overflow-hidden shadow-sm hover:shadow-md transition-shadow">
{/* 封面图:优化属性,确保服务端/客户端渲染一致 */}
<div className="relative h-64 w-full">
<img
src={coverSrc}
alt={coverAlt}
sizes="100vw" // 简化 sizes避免解析差异
className="object-cover"
// 显式设置属性,避免隐式差异
loading="lazy"
/>
</div>
{/* 车间信息 */}
<div className="p-4">
<h2 className="text-xl font-bold mb-2">{workshop.title || "未命名车间"}</h2>
<p className="text-gray-600 mb-4">{workshop.desc || "暂无描述"}</p>
</div>
</div>
</I18nLink>
);
}
// 页面主组件Server Component
export default async function Page({
params,
}: {
params: Params;
}) {
const { locale } = await params;
// 获取翻译(确保服务端/客户端一致)
const t = await getTranslations({ locale, namespace: "Workshop" });
// 获取车间数据(顶层 awaitServer Component 原生支持)
const workShops: WorkShop[] = await getWorkShops(locale);
// 页面标题翻译兜底
const pageTitle = t("pageTitle", { defaultValue: "车间展示" });
return (
<main className="container mx-auto py-8 px-4">
{/* 页面标题(纯静态渲染,无动态属性) */}
<h1 className="text-3xl font-bold mb-8 text-center">{pageTitle}</h1>
{/* 移除不必要的 SuspenseServer Component 顶层 await 无需 Suspense */}
{workShops.length > 0 ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{workShops.map((workshop) => {
// 用稳定的 key优先用唯一标识如 id无 id 则用 title+locale 避免 index 导致的问题)
const stableKey = `${workshop.title}-${workshop.locale}`;
return <WorkshopCard key={stableKey} workshop={workshop} />;
})}
</div>
) : (
<EmptyState />
)}
</main>
);
}