135 lines
3.8 KiB
TypeScript
135 lines
3.8 KiB
TypeScript
import type { Metadata } from 'next';
|
|
import { publicApi } from '@/lib/services';
|
|
import { ProductCard } from '@/components/front/ProductCard';
|
|
import type { ProductCategory } from '@/lib/types';
|
|
|
|
export const metadata: Metadata = {
|
|
title: '产品中心',
|
|
description: '查看我们的全部产品与解决方案。',
|
|
};
|
|
|
|
export const revalidate = 60;
|
|
|
|
interface PageProps {
|
|
searchParams: { categoryId?: string; keyword?: string; page?: string };
|
|
}
|
|
|
|
export default async function ProductsPage({ searchParams }: PageProps) {
|
|
const page = Number(searchParams.page ?? 1);
|
|
const categoryId = searchParams.categoryId ? Number(searchParams.categoryId) : undefined;
|
|
|
|
const [categories, productsRes] = await Promise.all([
|
|
publicApi.getProductCategories().catch(() => [] as ProductCategory[]),
|
|
publicApi
|
|
.getProducts({
|
|
page,
|
|
pageSize: 12,
|
|
categoryId,
|
|
keyword: searchParams.keyword,
|
|
})
|
|
.catch(() => ({ list: [], total: 0, page, pageSize: 12 })),
|
|
]);
|
|
|
|
return (
|
|
<div className="container-page py-12">
|
|
<h1 className="text-3xl font-bold text-gray-900">产品中心</h1>
|
|
<p className="mt-2 text-sm text-gray-500">
|
|
共 {productsRes.total} 款产品
|
|
</p>
|
|
|
|
{/* 分类筛选 */}
|
|
<div className="mt-6 flex flex-wrap gap-2">
|
|
<a
|
|
href="/products"
|
|
className={`rounded-full border px-3 py-1 text-sm ${
|
|
!categoryId
|
|
? 'border-brand-600 bg-brand-50 text-brand-700'
|
|
: 'border-gray-200 text-gray-600 hover:border-brand-300'
|
|
}`}
|
|
>
|
|
全部
|
|
</a>
|
|
{categories.map((c) => (
|
|
<a
|
|
key={c.id}
|
|
href={`/products?categoryId=${c.id}`}
|
|
className={`rounded-full border px-3 py-1 text-sm ${
|
|
categoryId === c.id
|
|
? 'border-brand-600 bg-brand-50 text-brand-700'
|
|
: 'border-gray-200 text-gray-600 hover:border-brand-300'
|
|
}`}
|
|
>
|
|
{c.name}
|
|
</a>
|
|
))}
|
|
</div>
|
|
|
|
{/* 列表 */}
|
|
<div className="mt-8 grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
|
{productsRes.list.map((p) => (
|
|
<ProductCard key={p.id} product={p} />
|
|
))}
|
|
</div>
|
|
{productsRes.list.length === 0 && (
|
|
<div className="mt-8 flex h-32 items-center justify-center rounded-md border border-dashed text-sm text-gray-400">
|
|
暂无产品
|
|
</div>
|
|
)}
|
|
|
|
{/* 分页 */}
|
|
{productsRes.total > 12 && (
|
|
<Pagination
|
|
page={page}
|
|
total={productsRes.total}
|
|
pageSize={12}
|
|
baseQuery={{ categoryId: searchParams.categoryId, keyword: searchParams.keyword }}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function Pagination({
|
|
page,
|
|
total,
|
|
pageSize,
|
|
baseQuery,
|
|
}: {
|
|
page: number;
|
|
total: number;
|
|
pageSize: number;
|
|
baseQuery: Record<string, string | undefined>;
|
|
}) {
|
|
const totalPages = Math.ceil(total / pageSize);
|
|
const buildHref = (p: number) => {
|
|
const qs = new URLSearchParams({
|
|
...(baseQuery as Record<string, string>),
|
|
page: String(p),
|
|
}).toString();
|
|
return `/products?${qs}`;
|
|
};
|
|
return (
|
|
<div className="mt-8 flex items-center justify-center gap-2">
|
|
<a
|
|
href={buildHref(Math.max(1, page - 1))}
|
|
className={`rounded border px-3 py-1 text-sm ${
|
|
page <= 1 ? 'pointer-events-none opacity-50' : 'hover:bg-gray-50'
|
|
}`}
|
|
>
|
|
上一页
|
|
</a>
|
|
<span className="text-sm text-gray-600">
|
|
{page} / {totalPages}
|
|
</span>
|
|
<a
|
|
href={buildHref(Math.min(totalPages, page + 1))}
|
|
className={`rounded border px-3 py-1 text-sm ${
|
|
page >= totalPages ? 'pointer-events-none opacity-50' : 'hover:bg-gray-50'
|
|
}`}
|
|
>
|
|
下一页
|
|
</a>
|
|
</div>
|
|
);
|
|
}
|