/** * # 路由 产品列表 /collection/(原站 /shop/ 的等价页) * 原站 /shop/ 使用 WooCommerce 归档模板(elementor_library post 1398): * - a2ab15c:面包屑 hero(bg #F4F5F8) * - 65176b9:左侧 WPC 筛选(20% 列)+ 右侧 “IoT Products” 标题、排序、24 商品 4 列网格、分页 * 本路由以 ElementorRenderer 直出该模板外壳,按 widget id 注入: * - 98d2008 → ShopFilters(属性复选筛选,URL query 驱动) * - d761846 → ShopProducts(WooCommerce 归档网格 + 排序 + 分页) * 模板缺失时降级为内置分类卡片 + 简单网格。 */ import { createFileRoute, Link } from "@tanstack/react-router"; import { createServerFn } from "@tanstack/react-start"; import { getLayoutData, Layout } from "~/components/website/Layout"; import { ElementorRenderer, extractElementor } from "~/components/elementor/Renderer"; 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 { ShopFilters } from "~/components/website/ShopFilters"; import { ShopProducts, orderByToSort } from "~/components/website/ShopProducts"; import { getFilterConfig, getProductCategoryTree, getShopTemplate, listProducts, type ProductListResult } from "~/server/data"; import { useT } from "~/i18n"; export const Route = createFileRoute("/collection/")({ validateSearch: parseCollectionSearch as (search: Record) => CollectionSearch, loaderDeps: ({ search }) => ({ search }), loader: async ({ deps }) => loadCollection({ data: deps.search }), 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, }); /** # 函数 产品归档页数据加载(Server Function:客户端导航时由服务端执行, * 避免把 db/查询层打进浏览器包;返回纯 JSON) */ const loadCollection = createServerFn({ method: "GET" }) .validator((d: CollectionSearch) => d) .handler(async ({ data: search }) => { const filters = searchToFilters(search); const [layout, cats, list, shop, filterConfig] = await Promise.all([ getLayoutData(), getProductCategoryTree().catch(() => []), listProducts({ page: search.page, filters, pageSize: 24, sort: orderByToSort(search.orderby) }).catch( () => ({ items: [], total: 0, page: 1, pageSize: 24 }) as ProductListResult, ), getShopTemplate().catch(() => null), getFilterConfig().catch(() => []), ]); // Elementor 文档是递归 JSON,TanStack 序列化校验无法展开;运行时为纯 JSON return { layout, cats, list, shop, filters, filterConfig, search } as any; }); /** # 类型 归档页 loader 数据 */ type CollectionData = { layout: any; cats: { id: number; name: string; slug: string; parentId: number | null; count: number }[]; list: ProductListResult; shop: { meta?: unknown } | null; filters: Record; filterConfig: { name: string; slug: string; values: { value: string; slug: string; count: number }[] }[]; search: CollectionSearch; }; /** # 组件 全部产品列表页 */ function CollectionIndexPage() { const data = Route.useLoaderData(); const shopDoc = extractElementor(data.shop?.meta); if (shopDoc) { return ( ), "d761846": ( ), }} /> ); } return ; } /** # 组件 降级列表页(归档模板缺失时) */ function FallbackCollectionPage(props: { data: CollectionData }) { const t = useT(); const data = props.data; const topCats = data.cats.filter((c) => !c.parentId && c.slug !== "uncategorized"); return (
{t("nav_home")} {t("nav_products")}

{t("nav_all_products")}

{t("collection_products_count", { total: data.list.total })}

{topCats.map((c) => (
{c.name}
{t("collection_products_count", { total: Number(c.count) })}
))}
{data.list.items.map((p) => ( ))} {data.list.items.length === 0 ?

No products found.

: null}
`/collection/?page=${p}`} />
); }