feat(ui): 更新车间和产品页面UI设计并添加新图片资源

更新多个页面的UI设计,包括产品卡片、车间详情页和列表页,采用现代化卡片设计和动画效果
添加新的车间图片资源并更新相关页面的图片引用路径
优化响应式布局和交互体验,增强视觉吸引力
This commit is contained in:
砂糖
2026-02-03 14:39:36 +08:00
parent 8600a01ce4
commit 86c20e5ef7
20 changed files with 179 additions and 141 deletions

View File

@@ -46,48 +46,47 @@ export async function generateMetadata({
// 页面主组件 - 仅保留字段展示+修复 Hydration 错误
export default async function ProductDetailPage({ params }: { params: Params }) {
// 🔴 修复params 是同步对象,移除不必要的 await
const { locale, slug } = await params;
const line = await getLine(locale, slug);
if (!line) return <div className="p-6">404 - 线</div>;
return (
<div className="product-detail max-w-5xl mx-auto p-6 md:p-8">
{/* 标题 */}
<h1 className="text-3xl md:text-4xl font-bold text-gray-800 mb-4">
{line.title}
</h1>
{/* 产品描述 */}
<p className="text-gray-700 text-lg mb-8 leading-relaxed">
{line.desc}
</p>
{/* 封面图 */}
{line.cover && (
<div className="mb-8 rounded-lg overflow-hidden shadow-md">
<img
src={line.cover}
alt={`${line.title} - 封面`}
className="w-full h-auto object-cover"
loading="lazy"
/>
<div className="product-detail max-w-7xl mx-auto p-6 md:p-8">
{/* 标题和描述区域,分为左右布局 */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 mb-12">
{/* 左侧:标题和描述 */}
<div>
<h1 className="text-4xl md:text-5xl font-bold text-gray-800 mb-4">
{line.title}
</h1>
<p className="text-gray-700 text-lg md:text-xl mb-8 leading-relaxed">
{line.desc}
</p>
</div>
)}
{/* 产品图片列表 */}
{/* 右侧:封面图 */}
{line.cover && (
<div className="relative rounded-lg overflow-hidden shadow-lg">
<img
src={line.cover}
alt={`${line.title} - 封面`}
className="w-full h-auto object-cover transition-transform duration-300 hover:scale-105"
loading="lazy"
/>
</div>
)}
</div>
{/* 产品图片列表区域 */}
{line.images && line.images.length > 0 && (
<div className="mb-8">
<h2 className="text-2xl md:text-3xl font-semibold text-gray-800 mb-4">
<div className="mb-12">
<h2 className="text-2xl md:text-3xl font-semibold text-gray-800 mb-6">
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 md:gap-6">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{line.images.map((imgUrl, index) => (
<div
key={index}
className="rounded-lg overflow-hidden shadow-sm"
>
<div key={index} className="rounded-xl overflow-hidden shadow-md transition-all duration-300 hover:shadow-xl">
<img
src={imgUrl}
alt={`${line.title} - 图片${index + 1}`}
@@ -100,17 +99,17 @@ export default async function ProductDetailPage({ params }: { params: Params })
</div>
)}
{/* 产品特性/参数 */}
{/* 产品特性/参数区域 */}
{line.properties && line.properties.length > 0 && (
<div className="mb-4">
<h2 className="text-2xl md:text-3xl font-semibold text-gray-800 mb-4">
<div className="mb-12">
<h2 className="text-2xl md:text-3xl font-semibold text-gray-800 mb-6">
</h2>
<ul className="space-y-3">
<ul className="space-y-4">
{line.properties.map((prop, index) => (
<li
key={index}
className="p-4 bg-gray-50 rounded-lg text-gray-700"
className="p-6 bg-gray-50 rounded-lg shadow-sm hover:shadow-lg transition-all duration-200"
>
{/* 处理参数中的换行符 \n */}
<span
@@ -142,4 +141,4 @@ export async function generateStaticParams() {
console.error("生成产线静态参数失败:", error);
return [];
}
}
}

View File

@@ -57,25 +57,47 @@ function ProductCard({ product }: { product: Line }) {
href={`/line/${product.slug}`}
locale={product.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
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-4">
<h2 className="text-xl font-bold mb-2">{product.title || "未命名产品线"}</h2>
<p className="text-gray-600 mb-4">{product.desc || "暂无描述"}</p>
<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>
);
}

View File

@@ -89,17 +89,55 @@ export default async function Page({
prefetch={false}
className="block bg-white border border-gray-100 rounded-lg overflow-hidden shadow-sm hover:shadow-md transition-all duration-300"
>
<div className="aspect-square flex items-center justify-center p-6">
<img
src={product.images?.[0] || "/placeholder.svg"}
alt={product.title}
className="max-w-full max-h-full object-contain hover:scale-105 transition-transform duration-300"
/>
</div>
<div className="p-6 text-center">
<h3 className="text-lg font-medium text-gray-800 mb-4">{product.title}</h3>
<div className="inline-block text-red-600 font-medium hover:underline">
{t("learnMore")} &gt;
<div
className="relative flex w-80 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 className="w-20 h-20" src={product.images[0]} alt="" />
</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"
>
{product.detail}
</p>
</div>
<div className="p-6 pt-0">
<button
className="group relative w-full inline-flex items-center justify-center px-6 py-3 font-bold text-white rounded-lg bg-gradient-to-r from-blue-600 to-blue-500 hover:from-blue-700 hover:to-blue-600 shadow-lg shadow-blue-500/30 hover:shadow-blue-500/40 transition-all duration-300 hover:-translate-y-0.5"
>
<span className="relative flex items-center gap-2">
Read More
<svg
viewBox="0 0 24 24"
stroke="currentColor"
fill="none"
className="w-5 h-5 transform transition-transform group-hover:translate-x-1"
>
<path
d="M17 8l4 4m0 0l-4 4m4-4H3"
stroke-width="2"
stroke-linejoin="round"
stroke-linecap="round"
></path>
</svg>
</span>
</button>
</div>
</div>
</I18nLink>

View File

@@ -20,9 +20,6 @@ export async function generateMetadata({
}: MetadataProps): Promise<Metadata> {
const { locale, slug } = await params;
const workShop = await getWorkShop(locale, slug);
console.log(workShop);
console.log(workShop?.slug);
if (!workShop) {
return constructMetadata({
@@ -46,7 +43,6 @@ export async function generateMetadata({
// 页面主组件 - 仅保留字段展示+修复 Hydration 错误
export default async function WorkshopDetailPage({ params }: { params: Params }) {
// 🔴 核心修复1移除不必要的 awaitparams 是同步对象)
const { locale, slug } = await params;
const workShop = await getWorkShop(locale, slug);
@@ -58,32 +54,29 @@ export default async function WorkshopDetailPage({ params }: { params: Params })
const workshopDesc = workShop.desc || "暂无车间描述";
return (
<div className="min-h-screen bg-gray-50 py-10 px-4 sm:px-6 lg:px-8">
<div className="max-w-6xl mx-auto">
{/* 封面图区域 - 🔴 修复布局属性不一致 */}
<div className="relative rounded-2xl overflow-hidden shadow-lg h-[400px] sm:h-[500px] mb-8">
<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">
<img
src={coverSrc}
alt={coverAlt}
className="absolute inset-0 w-full h-full object-cover transition-transform hover:scale-105 duration-700"
// 显式设置属性,确保服务端/客户端一致
className="absolute inset-0 w-full h-full object-cover transition-transform duration-500 group-hover:scale-110"
loading="lazy"
decoding="async"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black opacity-40"></div>
</div>
{/* 标题+描述区域 */}
<div className="bg-white rounded-xl p-8 shadow-sm mb-10">
<h1 className="text-3xl sm:text-4xl font-bold text-gray-900 mb-6">
{workShop.title}
</h1>
<p className="text-lg text-gray-700 leading-relaxed">
{workshopDesc}
</p>
<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>
</div>
{/* 图片展示区域 */}
{workShop.images?.length > 0 && (
<div className="bg-white rounded-xl p-8 shadow-sm">
<div className="bg-white rounded-xl p-8 shadow-xl mt-12">
<h2 className="text-2xl font-semibold text-gray-800 mb-6 border-b pb-3 border-gray-200">
</h2>
@@ -94,12 +87,12 @@ export default async function WorkshopDetailPage({ params }: { params: Params })
return (
<div
key={stableKey}
className="relative rounded-lg overflow-hidden shadow-md h-64 hover:shadow-xl transition-all duration-300"
className="relative rounded-xl overflow-hidden shadow-lg hover:shadow-2xl transition-all duration-300"
>
<img
src={img}
alt={imgAlt}
className="absolute inset-0 w-full h-full object-cover transition-transform hover:scale-110 duration-500"
className="w-full h-full object-cover transition-transform duration-500 hover:scale-105"
loading="lazy"
decoding="async"
/>
@@ -126,7 +119,7 @@ export async function generateStaticParams() {
}))
);
} catch (error) {
console.error("生成产品静态参数失败:", error);
console.error("生成车间静态参数失败:", error);
return [];
}
}
}

View File

@@ -2,13 +2,14 @@ import { Link as I18nLink, LOCALES } from "@/i18n/routing";
import { constructMetadata } from "@/lib/metadata";
import { getWorkShops } from "@/lib/workshop";
import { WorkShop } from "@/types/workShop";
import clsx from "clsx";
import { Metadata } from "next";
import { getTranslations } from "next-intl/server";
// 强制静态生成
export const dynamic = "force-static";
// 明确 Params 类型(静态生成的参数)
// 明确 Params 类型(静态生成的参数)
type Params = { locale: string };
type MetadataProps = {
@@ -16,9 +17,7 @@ type MetadataProps = {
};
// 生成页面元数据(确保服务端/客户端翻译一致)
export async function generateMetadata({
params,
}: MetadataProps): Promise<Metadata> {
export async function generateMetadata({ params }: MetadataProps): Promise<Metadata> {
const { locale } = params;
const t = await getTranslations({ locale, namespace: "Workshop" });
@@ -44,35 +43,27 @@ function EmptyState() {
return <div className="py-10 text-center text-gray-500"></div>;
}
// 单个车间卡片组件Client Component 标记,避免 Hydration 冲突)
// 'use client'; // 仅当需要添加交互时启用,当前纯展示可不用
function WorkshopCard({ workshop }: { workshop: WorkShop }) {
// 为 Image 组件添加默认值,避免属性缺失导致不匹配
// 为 img 标签添加默认值,避免属性缺失导致不匹配
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">
{/* 封面图:优化属性,确保服务端/客户端渲染一致 */}
<I18nLink href={`/workshop/${workshop.slug}`} locale={workshop.locale}>
<div className="border rounded-xl overflow-hidden shadow-lg hover:shadow-2xl transition-shadow">
{/* 封面图:使用原生 img 标签,确保图片尺寸一致 */}
<div className="relative h-64 w-full">
<img
src={coverSrc}
alt={coverAlt}
sizes="100vw" // 简化 sizes避免解析差异
className="object-cover"
// 显式设置属性,避免隐式差异
className="object-cover w-full h-full"
loading="lazy"
/>
</div>
{/* 车间信息 */}
<div className="p-4">
<h2 className="text-xl font-bold mb-2">{workshop.title || "未命名车间"}</h2>
<div className="p-6 bg-white">
<h2 className="text-2xl font-semibold mb-2 text-gray-800">{workshop.title || "未命名车间"}</h2>
<p className="text-gray-600 mb-4">{workshop.desc || "暂无描述"}</p>
</div>
</div>
@@ -81,11 +72,7 @@ function WorkshopCard({ workshop }: { workshop: WorkShop }) {
}
// 页面主组件Server Component
export default async function Page({
params,
}: {
params: Params;
}) {
export default async function Page({ params }: { params: Params }) {
const { locale } = await params;
// 获取翻译(确保服务端/客户端一致)
const t = await getTranslations({ locale, namespace: "Workshop" });
@@ -97,22 +84,21 @@ export default async function Page({
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>
<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>
{/* 移除不必要的 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 导致的问题)
{/* 展示车间卡片 */}
<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) => {
const stableKey = `${workshop.title}-${workshop.locale}`;
return <WorkshopCard key={stableKey} workshop={workshop} />;
})}
</div>
) : (
<EmptyState />
)}
})
) : (
<EmptyState />
)}
</div>
</main>
);
}
}

View File

@@ -1,9 +1,9 @@
---
title: Assembly Workshop
desc: An assembly workshop is a specialized facility responsible for the quality inspection, assembly, pre-installation and commissioning of equipment components. It plays an important role in the production process.
cover: machine.jpg
cover: /images/workshop/assembly1.png
slug: assembly
images:
- machine.jpg
- machine2.jpg
- /images/workshop/assembly1.png
- /images/workshop/assembly2.png
---

View File

@@ -1,9 +1,9 @@
---
title: Heat Treatment Workshop
desc: A heat treatment workshop is a specialized facility dedicated to the heat treatment of metal materials. Its main processes include quenching, tempering, normalizing, isothermal quenching, annealing and hardening.
cover: machine.jpg
cover: /images/workshop/treatment1.png
slug: heat-treatment
images:
- machine.jpg
- machine2.jpg
- /images/workshop/treatment1.png
- /images/workshop/treatment2.png
---

View File

@@ -1,9 +1,9 @@
---
title: Machining Workshop
desc: The machining workshop of this machinery factory covers an area of 4,000 square meters and is equipped with dozens of processing equipment, including milling machines, lathes, grinding machines, drilling machines and so on.
cover: machine.jpg
cover: /images/workshop/machine1.png
slug: machine
images:
- machine.jpg
- machine2.jpg
- /images/workshop/machine1.png
- /images/workshop/machine2.png
---

View File

@@ -1,9 +1,9 @@
---
title: 装配车间
desc: 装配车间是一种专门的设施,负责设备组件的质量检验、组装、预安装和调试。在生产过程中起着重要作用。
cover: machine.jpg
cover: /images/workshop/assembly1.png
slug: assembly
images:
- machine.jpg
- machine2.jpg
- /images/workshop/assembly1.png
- /images/workshop/assembly2.png
---

View File

@@ -1,9 +1,9 @@
---
title: 热处理车间
desc: 热处理车间是专门从事金属材料热处理的工厂。主要工艺包括淬火、回火、正火、等温淬火、退火和硬化。
cover: machine.jpg
cover: /images/workshop/treatment1.png
slug: heat-treatment
images:
- machine.jpg
- machine2.jpg
- /images/workshop/treatment1.png
- /images/workshop/treatment2.png
---

View File

@@ -1,9 +1,9 @@
---
title: 机械加工车间
desc: 该机械加工厂的机械车间占地面积4000平方米配备有数十台加工设备包括铣床、车床、磨床、钻床等。
cover: machine.jpg
cover: /images/workshop/machine1.png
slug: machine
images:
- machine.jpg
- machine2.jpg
- /images/workshop/machine1.png
- /images/workshop/machine2.png
---

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB