v0.10.23: 落地页收口(toggle/内嵌模板 27143/posts cards/info-box)+ dev 3311 修复
This commit is contained in:
+76
-4
@@ -10,24 +10,78 @@ import { useEffect, useState } from "react";
|
||||
import { createFileRoute, Link, notFound, redirect } from "@tanstack/react-router";
|
||||
import { getLayoutData, Layout, type LayoutData } from "~/components/website/Layout";
|
||||
import { Pagination } from "~/components/website/Pagination";
|
||||
import { ElementorRenderer, extractElementor } from "~/components/elementor/Renderer";
|
||||
import { ElementorRenderer, extractElementor, type ElElement } from "~/components/elementor/Renderer";
|
||||
import { InquiryForm } from "~/components/website/InquiryForm";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { Breadcrumb, BreadcrumbList, BreadcrumbItem, BreadcrumbLink, BreadcrumbPage, BreadcrumbSeparator } from "~/components/ui/breadcrumb";
|
||||
import { ProductCard } from "~/components/website/ProductCard";
|
||||
import { isLangCode, LANG_NAMES, pickContent, type CatchAllData } from "~/components/website/catchall-shared";
|
||||
import {
|
||||
getEmbeddedTemplate,
|
||||
getPublishedContent,
|
||||
getPostCategoryBySlug,
|
||||
getProductChildCategory,
|
||||
hasProductSlug,
|
||||
listPosts,
|
||||
listProducts,
|
||||
loadPostsWidgetsData,
|
||||
resolveRedirect,
|
||||
type PostCardItem,
|
||||
type ProductListResult,
|
||||
} from "~/server/data";
|
||||
import { useT } from "~/i18n";
|
||||
|
||||
/** # 函数 把 Elementor 文档归一化为元素数组(裸数组 / {elements} 两种形态) */
|
||||
function asElements(doc: unknown): ElElement[] {
|
||||
if (Array.isArray(doc)) return doc as ElElement[];
|
||||
const els = (doc as { elements?: unknown } | null)?.elements;
|
||||
return Array.isArray(els) ? (els as ElElement[]) : [];
|
||||
}
|
||||
|
||||
/** # 函数 扫描文档里的 [elementor-template id="N"] 短代码并加载模板
|
||||
* 参数 elements 主文档元素树
|
||||
* 返回值 templates: shortcode widget id → 模板元素树;elements: 模板元素树拼接
|
||||
* (供 posts widget 预取)
|
||||
*/
|
||||
async function loadEmbeddedTemplates(elements: ElElement[]): Promise<{ templates: Record<string, ElElement[]>; elements: ElElement[] }> {
|
||||
const templates: Record<string, ElElement[]> = {};
|
||||
const collected: ElElement[] = [];
|
||||
const ids = new Map<number, ElElement[]>();
|
||||
const walk = (els: ElElement[] | undefined) => {
|
||||
for (const el of els || []) {
|
||||
const st = (el.settings || {}) as Record<string, unknown>;
|
||||
const shortcode = typeof st.shortcode === "string" ? st.shortcode : "";
|
||||
const m = /\[elementor-template\s+id=["']?(\d+)/.exec(shortcode);
|
||||
if (m && el.id) ids.set(Number(m[1]), []);
|
||||
if (el.elements?.length) walk(el.elements);
|
||||
}
|
||||
};
|
||||
walk(elements);
|
||||
for (const id of ids.keys()) {
|
||||
const row = await getEmbeddedTemplate(id);
|
||||
const doc = asElements(row?.meta ? (row.meta as Record<string, unknown>).elementor : null);
|
||||
if (doc.length) {
|
||||
ids.set(id, doc);
|
||||
collected.push(...doc);
|
||||
}
|
||||
}
|
||||
// 按 widget id 回填模板(同一文档仅取第一个匹配的 widget)
|
||||
const walk2 = (els: ElElement[] | undefined) => {
|
||||
for (const el of els || []) {
|
||||
const st = (el.settings || {}) as Record<string, unknown>;
|
||||
const shortcode = typeof st.shortcode === "string" ? st.shortcode : "";
|
||||
const m = /\[elementor-template\s+id=["']?(\d+)/.exec(shortcode);
|
||||
if (m && el.id) {
|
||||
const doc = ids.get(Number(m[1]));
|
||||
if (doc?.length) templates[el.id] = doc;
|
||||
}
|
||||
if (el.elements?.length) walk2(el.elements);
|
||||
}
|
||||
};
|
||||
walk2(elements);
|
||||
return { templates, elements: collected };
|
||||
}
|
||||
|
||||
export const Route = createFileRoute("/$")({
|
||||
// 归档分页用 ?page=N
|
||||
validateSearch: (search: Record<string, unknown>): { page?: number } => {
|
||||
@@ -111,7 +165,7 @@ export const Route = createFileRoute("/$")({
|
||||
const cat = await getPostCategoryBySlug(last).catch(() => null);
|
||||
if (cat) {
|
||||
const posts = await listPosts(deps.page || 1, 12, cat.slug).catch(() => ({ items: [], total: 0, page: deps.page || 1, pageSize: 12 }));
|
||||
return { layout, content: null, lang, hotProducts, archive: { name: cat.name, slug: cat.slug, posts } } satisfies CatchAllData;
|
||||
return { layout, content: null, lang, hotProducts, archive: { name: cat.name, slug: cat.slug, posts }, templates: {}, postsData: {} } satisfies CatchAllData;
|
||||
}
|
||||
}
|
||||
// 未命中任何内容 → 404(SSR 返回 404 状态码,由 notFoundComponent 渲染)
|
||||
@@ -120,7 +174,12 @@ export const Route = createFileRoute("/$")({
|
||||
// 语言前缀首页等内容为空时的兜底
|
||||
if (!content) throw notFound();
|
||||
|
||||
return { layout, content, lang, hotProducts, archive: null } satisfies CatchAllData;
|
||||
// 内嵌 Elementor 模板([elementor-template id="N"])+ posts widget 数据预取
|
||||
const mainEls = asElements(extractElementor(content.meta));
|
||||
const embedded = await loadEmbeddedTemplates(mainEls);
|
||||
const postsData = await loadPostsWidgetsData([...mainEls, ...embedded.elements]);
|
||||
|
||||
return { layout, content, lang, hotProducts, archive: null, templates: embedded.templates, postsData } satisfies CatchAllData;
|
||||
},
|
||||
head: ({ loaderData }) => {
|
||||
const langLabel = loaderData?.lang ? ` (${LANG_NAMES[loaderData.lang] || loaderData.lang})` : "";
|
||||
@@ -249,7 +308,20 @@ function CatchAllPage() {
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<ElementorRenderer doc={elementor} />
|
||||
<ElementorRenderer
|
||||
doc={elementor}
|
||||
postsData={data.postsData}
|
||||
shortcodes={
|
||||
Object.keys(data.templates || {}).length
|
||||
? Object.fromEntries(
|
||||
Object.entries(data.templates).map(([id, doc]) => [
|
||||
id,
|
||||
<ElementorRenderer key={id} doc={doc} postsData={data.postsData} />,
|
||||
]),
|
||||
)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<section className="mx-auto mt-10 mb-14 max-w-[860px] px-4">
|
||||
<InquiryForm formKey="contact-us-form" productInterest={c.type === "post" ? "" : c.title} title="Get in Touch" />
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user