更新多个页面的UI设计,包括产品卡片、车间详情页和列表页,采用现代化卡片设计和动画效果 添加新的车间图片资源并更新相关页面的图片引用路径 优化响应式布局和交互体验,增强视觉吸引力
140 lines
4.9 KiB
TypeScript
140 lines
4.9 KiB
TypeScript
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" });
|
||
|
||
// 获取产品线数据(顶层 await,Server 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>
|
||
|
||
{/* 移除不必要的 Suspense:Server 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>
|
||
);
|
||
} |