feat(ui): 更新车间和产品页面UI设计并添加新图片资源
更新多个页面的UI设计,包括产品卡片、车间详情页和列表页,采用现代化卡片设计和动画效果 添加新的车间图片资源并更新相关页面的图片引用路径 优化响应式布局和交互体验,增强视觉吸引力
This commit is contained in:
@@ -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 [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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")} >
|
||||
<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>
|
||||
|
||||
@@ -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:移除不必要的 await(params 是同步对象)
|
||||
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 [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
{/* 移除不必要的 Suspense:Server 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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user