69 lines
2.0 KiB
TypeScript
69 lines
2.0 KiB
TypeScript
|
|
import type { Metadata } from 'next';
|
||
|
|
import Link from 'next/link';
|
||
|
|
import { notFound } from 'next/navigation';
|
||
|
|
import { publicApi } from '@/lib/services';
|
||
|
|
import { resolveUploadUrl } from '@/lib/utils';
|
||
|
|
import { ContentView } from '@/components/front/ContentView';
|
||
|
|
|
||
|
|
export const revalidate = 60;
|
||
|
|
|
||
|
|
interface PageProps {
|
||
|
|
params: { id: string };
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
|
||
|
|
try {
|
||
|
|
const p = await publicApi.getProductDetail(Number(params.id));
|
||
|
|
return { title: p.name, description: p.desc ?? undefined };
|
||
|
|
} catch {
|
||
|
|
return { title: '产品详情' };
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export default async function ProductDetailPage({ params }: PageProps) {
|
||
|
|
let product;
|
||
|
|
try {
|
||
|
|
product = await publicApi.getProductDetail(Number(params.id));
|
||
|
|
} catch {
|
||
|
|
notFound();
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<article className="container-page py-12">
|
||
|
|
<nav className="mb-4 text-sm text-gray-500">
|
||
|
|
<Link href="/products" className="hover:text-brand-600">
|
||
|
|
产品中心
|
||
|
|
</Link>
|
||
|
|
<span className="mx-2">/</span>
|
||
|
|
<span>{product.name}</span>
|
||
|
|
</nav>
|
||
|
|
|
||
|
|
<div className="grid gap-8 md:grid-cols-2">
|
||
|
|
<div className="overflow-hidden rounded-lg bg-gray-100">
|
||
|
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||
|
|
<img
|
||
|
|
src={resolveUploadUrl(product.cover)}
|
||
|
|
alt={product.name}
|
||
|
|
className="aspect-[4/3] w-full object-cover"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
<div>
|
||
|
|
<h1 className="text-2xl font-bold text-gray-900">{product.name}</h1>
|
||
|
|
{product.category?.name && (
|
||
|
|
<span className="mt-2 inline-block rounded bg-brand-50 px-2 py-0.5 text-xs text-brand-700">
|
||
|
|
{product.category.name}
|
||
|
|
</span>
|
||
|
|
)}
|
||
|
|
{product.desc && (
|
||
|
|
<p className="mt-4 text-gray-700">{product.desc}</p>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{product.content && (
|
||
|
|
<ContentView content={product.content} className="mt-10 max-w-4xl" />
|
||
|
|
)}
|
||
|
|
</article>
|
||
|
|
);
|
||
|
|
}
|