v0.10.5 首页 H2 产品线入口 ×6:ImageBoxWidget 重构为 Elementor 忠实 DOM + slim 白名单补齐 + H2 scoped CSS(post-18 对齐,桌面/移动/hover 实测一致)
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* # 路由 产品列表 /collection/
|
||||
* 列出全部产品(无分类限定)+ 顶部分类卡片;带分页。
|
||||
*/
|
||||
import { createFileRoute, Link } from "@tanstack/react-router";
|
||||
import { getLayoutData, Layout } from "~/components/website/Layout";
|
||||
import { ProductCard } from "~/components/website/ProductCard";
|
||||
import { Pagination } from "~/components/website/Pagination";
|
||||
import { Breadcrumb, BreadcrumbList, BreadcrumbItem, BreadcrumbLink, BreadcrumbPage, BreadcrumbSeparator } from "~/components/ui/breadcrumb";
|
||||
import { parseCollectionSearch, searchToFilters, type CollectionSearch } from "~/components/website/collection-shared";
|
||||
import { getProductCategoryTree, listProducts, type ProductListResult } from "~/server/data";
|
||||
import { useT } from "~/i18n";
|
||||
|
||||
export const Route = createFileRoute("/collection/")({
|
||||
validateSearch: parseCollectionSearch as (search: Record<string, unknown>) => CollectionSearch,
|
||||
loaderDeps: ({ search }) => ({ search }),
|
||||
loader: async ({ deps }) => {
|
||||
const [layout, cats, list] = await Promise.all([
|
||||
getLayoutData(),
|
||||
getProductCategoryTree().catch(() => []),
|
||||
listProducts({ page: deps.search.page, filters: searchToFilters(deps.search), pageSize: 24 }).catch(
|
||||
() => ({ items: [], total: 0, page: 1, pageSize: 24 }) as ProductListResult,
|
||||
),
|
||||
]);
|
||||
return { layout, cats, list };
|
||||
},
|
||||
head: () => ({
|
||||
meta: [
|
||||
{ title: "All Products | IoT Gateways & Devices | Dusun IoT" },
|
||||
{ name: "description", content: "Browse all Dusun IoT products: industrial IoT gateways, single board computers, wireless modules and smart IoT devices." },
|
||||
],
|
||||
}),
|
||||
component: CollectionIndexPage,
|
||||
});
|
||||
|
||||
/** # 组件 全部产品列表页 */
|
||||
function CollectionIndexPage() {
|
||||
const t = useT();
|
||||
const data = Route.useLoaderData();
|
||||
const topCats = data.cats.filter((c) => !c.parentId && c.slug !== "uncategorized");
|
||||
const page = data.list.page;
|
||||
return (
|
||||
<Layout layout={data.layout}>
|
||||
<div className="site-container">
|
||||
<Breadcrumb aria-label="Breadcrumb">
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem><BreadcrumbLink to="/">{t("nav_home")}</BreadcrumbLink></BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem><BreadcrumbPage>{t("nav_products")}</BreadcrumbPage></BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
<h1 className="text-[28px]">{t("nav_all_products")}</h1>
|
||||
<p className="text-muted-foreground">{t("collection_products_count", { total: data.list.total })}</p>
|
||||
|
||||
<section className="mt-5 mb-8">
|
||||
<div className="grid grid-cols-2 gap-[18px] min-[960px]:grid-cols-4">
|
||||
{topCats.map((c) => (
|
||||
<Link key={c.id} to="/collection/$slug/" params={{ slug: c.slug }} className="block overflow-hidden rounded-lg border border-line bg-white text-brand transition-[box-shadow,transform] duration-200 hover:-translate-y-0.5 hover:shadow-[0_8px_24px_rgba(16,42,76,0.1)]">
|
||||
<div className="px-4 pt-3.5 pb-[18px]">
|
||||
<div className="mb-1.5 text-[15px] leading-[1.45] font-semibold">{c.name}</div>
|
||||
<div className="text-[13px] text-muted-foreground">{t("collection_products_count", { total: Number(c.count) })}</div>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
{topCats.length === 0 ? <p className="py-[60px] text-center text-muted-foreground">Product categories are being updated.</p> : null}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="grid grid-cols-2 gap-[18px] min-[960px]:grid-cols-4">
|
||||
{data.list.items.map((p) => (
|
||||
<ProductCard key={p.id} p={p} />
|
||||
))}
|
||||
{data.list.items.length === 0 ? <p className="py-[60px] text-center text-muted-foreground">No products found. They may still be syncing — please check back soon.</p> : null}
|
||||
</div>
|
||||
<Pagination
|
||||
page={page}
|
||||
total={data.list.total}
|
||||
pageSize={data.list.pageSize}
|
||||
makeHref={(p) => `/collection/?page=${p}`}
|
||||
/>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user