v0.10.25: /solution/ 归档+落地页复刻(archive 组件/分页/摘录24词)、Header 全断点对齐、page_css 导入管线、reviews/populated 宽度/_css_classes/tabs HTML/图片变体修复
This commit is contained in:
@@ -6,6 +6,7 @@ import { createFileRoute } from "@tanstack/react-router";
|
||||
import { getLayoutData, Layout } from "~/components/website/Layout";
|
||||
import { ElementorRenderer, extractElementor } from "~/components/elementor/Renderer";
|
||||
import { getPublishedContent } from "~/server/data";
|
||||
import { resolveImageVariants } from "~/utils/image-variant";
|
||||
|
||||
export const Route = createFileRoute("/solution/$slug")({
|
||||
loader: async ({ params }) => {
|
||||
@@ -15,6 +16,8 @@ export const Route = createFileRoute("/solution/$slug")({
|
||||
]);
|
||||
if (!page || !extractElementor(page.meta)) return { layout, page: null };
|
||||
const meta = (page.meta ?? null) as Record<string, unknown> | null;
|
||||
// image widget 的 WP 尺寸变体(medium/thumbnail)解析为本地 -300x272 类文件
|
||||
resolveImageVariants(meta?.elementor);
|
||||
return { layout, page: { ...page, meta } };
|
||||
},
|
||||
head: ({ loaderData }) => ({
|
||||
@@ -42,7 +45,9 @@ function SolutionPage() {
|
||||
const elementor = extractElementor(data.page.meta);
|
||||
return (
|
||||
<Layout layout={data.layout}>
|
||||
{elementor ? <ElementorRenderer doc={elementor} /> : null}
|
||||
{elementor ? (
|
||||
<ElementorRenderer doc={elementor} pageCss={(data.page.meta as Record<string, unknown> | null)?.page_css as string | undefined} />
|
||||
) : null}
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* # 路由 方案归档 /solution/
|
||||
* 原站 Elementor archive-posts 归档一比一复刻(SolutionArchive 组件,第 1 页)。
|
||||
*/
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { SolutionArchive, loadSolutionArchive } from "~/components/website/SolutionArchive";
|
||||
|
||||
export const Route = createFileRoute("/solution/")({
|
||||
loader: () => loadSolutionArchive(1),
|
||||
head: () => ({
|
||||
meta: [
|
||||
{ title: "IoT Gateways & Devices Solution - DusunIoT" },
|
||||
{ name: "description", content: "Dusun IoT hardware solutions for smart home, smart apartment, energy storage, vehicle IoT, retail, building and more." },
|
||||
],
|
||||
links: [{ rel: "canonical", href: "https://www.dusuniot.com/solution/" }],
|
||||
}),
|
||||
component: SolutionArchivePage,
|
||||
});
|
||||
|
||||
/** # 组件 方案归档页(第 1 页) */
|
||||
function SolutionArchivePage() {
|
||||
const data = Route.useLoaderData();
|
||||
return <SolutionArchive data={data} />;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* # 路由 方案归档分页 /solution/page/$page/
|
||||
* 与原站一致的第 2+ 页归档(每页 12 篇,日期倒序);越界页返回空网格(原站 200)。
|
||||
*/
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { SolutionArchive, loadSolutionArchive } from "~/components/website/SolutionArchive";
|
||||
|
||||
export const Route = createFileRoute("/solution/page/$page")({
|
||||
loader: async ({ params }) => {
|
||||
const n = Number(params.page);
|
||||
return loadSolutionArchive(Number.isFinite(n) && n > 1 ? Math.floor(n) : 1);
|
||||
},
|
||||
head: ({ loaderData }) => ({
|
||||
meta: [
|
||||
{
|
||||
title: `IoT Gateways & Devices Solution - Page ${loaderData?.page ?? 2} of ${loaderData ? Math.max(1, Math.ceil(loaderData.list.total / loaderData.list.pageSize)) : 2} - DusunIoT`,
|
||||
},
|
||||
{ name: "description", content: "Dusun IoT hardware solutions for smart home, smart apartment, energy storage, vehicle IoT, retail, building and more." },
|
||||
],
|
||||
}),
|
||||
component: SolutionArchivePagedPage,
|
||||
});
|
||||
|
||||
/** # 组件 方案归档分页页 */
|
||||
function SolutionArchivePagedPage() {
|
||||
const data = Route.useLoaderData();
|
||||
return <SolutionArchive data={data} />;
|
||||
}
|
||||
+5
-56
@@ -1,59 +1,8 @@
|
||||
/**
|
||||
* # 路由 方案索引 /solution/
|
||||
* 原站形态:SOLUTION 大标题 + 3 列方案卡片网格(图 / 标题 / 摘要)。
|
||||
* 卡片数据 = listSolutionLandings()(同 slug 方案落地页)。
|
||||
* # 路由 /solution 布局
|
||||
* 原站 /solution/(归档)、/solution/page/N/(归档分页)、/solution/$slug/(方案落地页)
|
||||
* 共享该前缀。本路由只做布局挂载点(无 component 时 TanStack 直接渲染子路由 Outlet)。
|
||||
*/
|
||||
import { createFileRoute, Link } from "@tanstack/react-router";
|
||||
import { getLayoutData, Layout } from "~/components/website/Layout";
|
||||
import { listSolutionLandings } from "~/server/data";
|
||||
import { useT } from "~/i18n";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
export const Route = createFileRoute("/solution")({
|
||||
loader: async () => {
|
||||
const [layout, solutions] = await Promise.all([
|
||||
getLayoutData(),
|
||||
listSolutionLandings().catch(() => []),
|
||||
]);
|
||||
return { layout, solutions };
|
||||
},
|
||||
head: () => ({
|
||||
meta: [
|
||||
{ title: "IoT Solutions | Dusun IoT" },
|
||||
{ name: "description", content: "Dusun IoT hardware solutions for smart home, smart apartment, energy storage, vehicle IoT, retail, building and more." },
|
||||
],
|
||||
}),
|
||||
component: SolutionIndexPage,
|
||||
});
|
||||
|
||||
/** # 组件 方案索引页 */
|
||||
function SolutionIndexPage() {
|
||||
const t = useT();
|
||||
const data = Route.useLoaderData();
|
||||
return (
|
||||
<Layout layout={data.layout}>
|
||||
<div className="bg-[#f5f5f6] px-4 pt-[34px] pb-[30px] text-center">
|
||||
<h1 className="m-0 text-[34px] tracking-wide">{t("solution_title")}</h1>
|
||||
</div>
|
||||
<div className="site-container my-10">
|
||||
<div className="grid grid-cols-1 gap-6 min-[760px]:grid-cols-2 min-[1080px]:grid-cols-3">
|
||||
{data.solutions.map((s) => (
|
||||
<Link
|
||||
key={s.id}
|
||||
to="/solution/$slug/"
|
||||
params={{ slug: s.slug }}
|
||||
className="block overflow-hidden rounded-lg border border-line bg-white transition-[box-shadow,transform] duration-200 hover:-translate-y-0.5 hover:shadow-[0_8px_24px_rgba(16,42,76,0.1)]"
|
||||
>
|
||||
<div className="aspect-[4/3] overflow-hidden bg-gradient-to-b from-[#9aa3ad] to-[#c9ced4]">
|
||||
{s.imageUrl ? <img src={s.imageUrl} alt={s.imageAlt || s.title} loading="lazy" className="h-full w-full object-cover" /> : null}
|
||||
</div>
|
||||
<div className="p-5">
|
||||
<h2 className="mt-0 mb-2 text-[18px] font-semibold text-brand">{s.title}</h2>
|
||||
{s.excerpt ? <p className="m-0 text-sm leading-relaxed text-muted-foreground">{s.excerpt.slice(0, 160)}</p> : null}
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
export const Route = createFileRoute("/solution")({});
|
||||
|
||||
Reference in New Issue
Block a user