v0.10.5 首页 H2 产品线入口 ×6:ImageBoxWidget 重构为 Elementor 忠实 DOM + slim 白名单补齐 + H2 scoped CSS(post-18 对齐,桌面/移动/hover 实测一致)

This commit is contained in:
BigCode
2026-11-01 00:15:29 +08:00
commit 5b83e71ad9
128 changed files with 20008 additions and 0 deletions
+157
View File
@@ -0,0 +1,157 @@
/**
* # 模块 Elementor 文档瘦身
* WordPress 导入的 Elementor JSON 包含大量桌面/平板/移动端响应式字段与空值,
* 整份文档会随 TanStack Router 脱水序列化进 SSR HTML(首页曾达 391KB),
* 严重影响 SEO 与首字节体积。本模块只保留渲染器实际读取的字段。
*/
/** # 成员变量 渲染器(Renderer.tsx)实际读取的 settings 字段白名单 **/
const SETTING_KEYS = new Set([
// 通用布局/背景/边框
"__globals__",
"align",
"background_background",
"background_color",
"background_color_b",
"background_color_stop_b",
"background_image",
"border_border",
"border_color",
"border_radius",
"border_width",
"content_width",
"custom_height",
"padding",
"layout",
"height",
"size",
"space",
"color",
"text_color",
"title_color",
"button_text_color",
// image-box 系列:图定位(position 默认 top)与标题标签(title_size 默认 h3)
"position",
"title_size",
"gradient_angle",
"color_a",
"color_b",
"header_size",
// 列结构
"_column_size",
"_inline_size",
// 各 widget 数据
"heading_title",
"title",
"title_text",
"description",
"description_text",
"selected_icon",
"url",
"width",
"text",
"editor",
"html",
"source_code",
"image",
"link",
"prefix",
"suffix",
"ending_number",
"icon_list",
"social_icon_list",
"slides",
"slides_height",
"tabs",
"video_url",
"youtube_url",
"map",
"posts_per_page",
"cards_posts_per_page",
"post_type",
"eael_ticker_tag_text",
// EAEL Infobox / flip-box / 高级标签页 / 数据表 / 评价等(Renderer 对应组件读取)
"custom_css",
"_css_classes",
"eael_infobox_title",
"eael_infobox_text",
"eael_infobox_title_tag",
"title_text_a",
"description_text_a",
"button_text",
"background_a_image",
"eael_adv_tabs_tab",
"eael_data_table_header_cols_data",
"eael_data_table_content_rows",
"eael_testimonial_name",
"eael_testimonial_company_title",
"eael_testimonial_description",
]);
/** # 函数 收窄对象型 settings 值(渲染器只读 url/alt/target 等少数字段)
* 参数 key settings 键名;val 原始值
* 返回值 收窄后的值;非命中键原样返回
*/
function slimSettingValue(key: string, val: unknown): unknown {
if (!val || typeof val !== "object" || Array.isArray(val)) return val;
const v = val as Record<string, unknown>;
if (key === "image" || key === "background_image" || key === "background_a_image") {
// 渲染器只读 url / alt
return { url: v.url, alt: v.alt };
}
if (key === "link") {
// 渲染器只读 url / target / is_external(is_external=on → target=_blank,与原站 link_attributes 一致)
return { url: v.url, target: v.target, is_external: v.is_external };
}
return val;
}
/** # 函数 递归瘦身单个 Elementor 元素
* 参数 el 原始元素(section/column/widget 通用结构)
* 返回值 仅含 id/elType/widgetType/isInner/白名单 settings/elements 的新对象
*/
function slimElement(el: unknown): unknown {
if (!el || typeof el !== "object") return el;
const src = el as Record<string, unknown>;
const out: Record<string, unknown> = {};
if (src.id != null) out.id = src.id;
if (src.elType != null) out.elType = src.elType;
if (src.widgetType != null) out.widgetType = src.widgetType;
if (src.isInner) out.isInner = true;
const st = src.settings;
if (st && typeof st === "object" && !Array.isArray(st)) {
const slim: Record<string, unknown> = {};
for (const key of Object.keys(st as Record<string, unknown>)) {
if (SETTING_KEYS.has(key)) slim[key] = slimSettingValue(key, (st as Record<string, unknown>)[key]);
}
if (Object.keys(slim).length > 0) out.settings = slim;
}
if (Array.isArray(src.elements) && src.elements.length > 0) {
out.elements = src.elements.map(slimElement);
}
return out;
}
/** # 函数 瘦身整份 Elementor 文档(数组或 {elements:[]} 包装均支持)
* 参数 doc 原始文档(来自 meta.elementor)
* 返回值 瘦身后的文档;非对象/数组输入原样返回
*/
export function slimElementorDoc(doc: unknown): unknown {
if (Array.isArray(doc)) return doc.map(slimElement);
if (doc && typeof doc === "object" && Array.isArray((doc as Record<string, unknown>).elements)) {
const wrapped = doc as Record<string, unknown>;
return { elements: (wrapped.elements as unknown[]).map(slimElement) };
}
return doc;
}
/** # 函数 瘦身内容 meta:替换 meta.elementor 为瘦身版(原 meta 其余字段保留)
* 参数 meta 原始 meta JSON(可能为 null)
* 返回值 elementor 已瘦身的 meta;无 elementor 时原样返回
*/
export function slimMetaElementor<T>(meta: T): T {
if (!meta || typeof meta !== "object") return meta;
const m = meta as Record<string, unknown>;
if (!m.elementor) return meta;
return { ...m, elementor: slimElementorDoc(m.elementor) } as T;
}