Files
sage-home/app/[locale]/line/page.tsx
砂糖 86c20e5ef7 feat(ui): 更新车间和产品页面UI设计并添加新图片资源
更新多个页面的UI设计,包括产品卡片、车间详情页和列表页,采用现代化卡片设计和动画效果
添加新的车间图片资源并更新相关页面的图片引用路径
优化响应式布局和交互体验,增强视觉吸引力
2026-02-03 14:39:36 +08:00

140 lines
4.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 { getLines } from "@/lib/lines";
import { constructMetadata } from "@/lib/metadata";
import { Line } from "@/types/line";
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 } = await 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: `/line`,
canonicalUrl: `/line`,
});
}
// 生成静态路由参数(多语言)
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 ProductCard({ product }: { product: Line }) {
// 为 Image 组件添加默认值,避免属性缺失导致不匹配
const coverSrc = product.cover || "/default-workshop-cover.png"; // 兜底封面图
const coverAlt = product.title || "产品线封面";
return (
<I18nLink
href={`/line/${product.slug}`}
locale={product.locale}
>
<div
className="mb-4 relative flex w-full flex-col rounded-xl bg-gradient-to-br from-white to-gray-50 bg-clip-border text-gray-700 shadow-lg hover:shadow-xl transition-all duration-300 hover:-translate-y-1"
>
<div
className="relative mx-4 -mt-6 h-40 overflow-hidden rounded-xl bg-clip-border shadow-lg group"
>
<div
className="absolute inset-0 bg-gradient-to-r from-blue-600 via-blue-500 to-indigo-600 opacity-90"
></div>
<div
className="absolute inset-0 bg-[linear-gradient(to_right,rgba(255,255,255,0.1)_1px,transparent_1px),linear-gradient(to_bottom,rgba(255,255,255,0.1)_1px,transparent_1px)] bg-[size:20px_20px] animate-pulse"
></div>
<div className="absolute inset-0 flex items-center justify-center">
<img
src={coverSrc}
alt={coverAlt}
className="w-full h-full text-white/90 transform transition-transform group-hover:scale-110 duration-300"
/>
</div>
</div>
<div className="p-6">
<h5
className="mb-2 block font-sans text-xl font-semibold leading-snug tracking-normal text-gray-900 antialiased group-hover:text-blue-600 transition-colors duration-300"
>
{product.title}
</h5>
{/* 最多四行,超过四行溢出隐藏 */}
<p
className="block font-sans text-base font-light leading-relaxed text-gray-700 antialiased overflow-hidden"
style={{
display: '-webkit-box',
WebkitBoxOrient: 'vertical',
WebkitLineClamp: 4,
overflow: 'hidden', // 确保文本溢出时隐藏
}}
>
{product.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 products: Line[] = await getLines(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 */}
{products.length > 0 ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{products.map((product) => {
// 用稳定的 key优先用唯一标识如 id无 id 则用 title+locale 避免 index 导致的问题)
const stableKey = `${product.title}-${product.locale}`;
return <ProductCard key={stableKey} product={product} />;
})}
</div>
) : (
<EmptyState />
)}
</main>
);
}