diff --git a/docs/sections-inventory.md b/docs/sections-inventory.md index bb1dca500..1088ae3f6 100644 --- a/docs/sections-inventory.md +++ b/docs/sections-inventory.md @@ -44,8 +44,8 @@ | H8 | 热门嵌入式硬件 标题 | heading(Popular Embedded Hardware Products…) | ✅ v0.10.9(heading 40px/600/#000/行高 1 居中 x38/w1360;section 7df0244 gap=no→列 0px、margin-top 4%=57.4375px、bg #F4F5F8、padding 64px 0——section 级 margin/gap 渲染支持随此版块落地,slim 白名单补 gap) | | H9 | 热门硬件切换器(tabs) | Elementor 原生 tabs widget ×6 标签(内容为自包含 HTML 卡片网格) | ✅ v0.10.9(TabsWidget 忠实 DOM+React 点击切换/aria;widget-tabs.min.css 基础块+fd97340 生成规则(border_width=0 含伪元素)+custom_css(.ele-soms-chip-dif 边框/内容覆盖)三层取证;全局修正 body 行高为无单位 1.5、补 kit 链接字体 Roboto(卡片 经 .elementor-kit-15 a 规则用 Roboto,否则 Arial 截断致规格多折一行);实测仅 0.8px 页级漂移) | | H10 | Smart Apartment Gateway 规划模块 | html widget(17.9KB 自包含 HTML+CSS,无 JS) | ✅ v0.10.10(html widget 路径补 localSiteUrl 媒体改写、去掉内层 mb;补 Elementor 全局 figure margin 0(text-editor 例外)与 kit 标题排版(.elementor-kit-15 h1..h6 → Roboto 600/500,h2 38px)——H10 h2 由 kit Roboto 渲染换行点才与原站一致;sec h 674.8 与原站全等,3 图全载 | -| H11 | Industries & Use Cases | heading + eael-adv-tabs + button(多标签竖排) | ⬜ | -| H12 | The Difference Maker 段一 | heading+text+icon-list+text+button(左侧列表优势) | ⬜ | +| H11 | Dusun IoT Developer Center 三卡 | heading+text+内嵌 section 三列卡(heading/image/image-box 行/button) | ✅ v0.10.11(heading 40/600、text 20px/_padding 4rem;三列 453.33-margin→443.33(实测原站 populated flex 收缩语义,该三列 scoped width auto);image-box pos-left 行布局(flex 居中/图 18%/间距 15/标题底距 4px);按钮设置驱动(白底橙字/1px 边框/12px 48px/18px+悬停注入);节背景=电路板条图 bottom center+no-repeat+F4F5F8→透明渐变遮罩(.elementor-background-overlay 通用实现,行 relative 防蒙灰);全局:全零 _margin 不覆盖 kit-spacing、0.9rem p 边距、html widget localSiteUrl/去 mb、Elementor figure margin 0(text-editor 例外)、kit 标题 Roboto 600/500(h2 38px)、按钮 collapseBoxValues 边框) | +| H12 | Industries and Use Cases | heading + eael-adv-tabs(6 标签,内容自包含 HTML) + button(ef0fcd9) | ⬜ | | H13 | Difference Maker 产品卡 ×6 | image-box ×6 | ⬜ | | H14 | Why Choose Dusun IoT | heading + eael-info-box ×4 | ⬜ | | H15 | Trusted By 世界领先品牌 | heading + image ×11(Logo 墙) | ⬜ | diff --git a/package.json b/package.json index 242bc6bcb..4a418de46 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dusuniot-rebuild", - "version": "0.10.10", + "version": "0.10.11", "private": true, "type": "module", "scripts": { diff --git a/src/components/elementor/Renderer.tsx b/src/components/elementor/Renderer.tsx index be7554a06..6459d606a 100644 --- a/src/components/elementor/Renderer.tsx +++ b/src/components/elementor/Renderer.tsx @@ -200,9 +200,12 @@ function sectionBgStyle(settings: Record): CSSProperties { const img = (settings.background_image as { url?: string } | undefined)?.url; if (img) { style.backgroundImage = `url("${img}")`; - style.backgroundSize = "cover"; - // "center" 会被浏览器归一为 "center center",直接写全值保证水合一致 - style.backgroundPosition = "center center"; + // 位置/重复取设置值(H11 底图 1920×240 条图:bottom center + no-repeat, + // 只在节底部铺一条,尺寸 auto 不缩放——写死 cover/center 会平铺满节,实测对不上) + const posRaw = s(settings.background_position); + style.backgroundPosition = posRaw ? mapBgPosition(posRaw) : "center center"; + const rep = s(settings.background_repeat); + if (rep) style.backgroundRepeat = rep; } } else if (bgType === "gradient") { const c1 = s(settings.background_color) || s(settings.color_a) || "#ffffff"; @@ -331,6 +334,25 @@ function SectionNode({ el }: { el: ElElement }) { inner.maxWidth = width; } const gapPad = GAP_PAD[s(settings.gap) || "default"] || "10px"; + // 背景遮罩(Elementor .elementor-background-overlay div):盖在背景图上、内容之下 + // (H11 Developer Center:电路板底图 bottom center + F4F5F8→透明 180deg 渐变遮罩, + // 顶部近白、底部透出电路纹理,实测 2026-11-01) + const ovType = s((settings as Record).background_overlay_background); + let overlay: ReactNode = null; + if (ovType === "gradient") { + const c1 = s((settings as Record).background_overlay_color) || "transparent"; + const c2 = s((settings as Record).background_overlay_color_b) || "transparent"; + const angle = sizeCss((settings as Record).background_overlay_gradient_angle) || "180deg"; + overlay = ( +
+ ); + } else if (ovType === "classic") { + const c1 = s((settings as Record).background_overlay_color); + if (c1) overlay =
; + } // section 高度模式(Elementor: min-height / full),配合 custom_height; // 取证(原站 09fe4b7 实测):height 未设置时 custom_height 不生效(min-height 0px), // 故仅在显式 height 模式下应用 @@ -338,9 +360,12 @@ function SectionNode({ el }: { el: ElElement }) { const ch = sizeCss(settings.custom_height); if (heightMode === "min-height" && ch) style.minHeight = ch; else if (heightMode === "full" && ch) style.height = ch; + if (overlay) style.position = style.position || "relative"; return (
-
+ {overlay} + {/* 行加 relative:与绝对定位遮罩同属定位层,DOM 靠后绘制在上,内容不被蒙灰 */} +
{(el.elements || []).map((c, i) => ( ))} @@ -349,6 +374,20 @@ function SectionNode({ el }: { el: ElElement }) { ); } +/** # 函数 盒模型值是否非全零(全零 _margin 视为未设置,kit-spacing 的 20px 才能生效; + * 原站 H11 text-editor _margin 全零但实测 margin-bottom 20px) + */ +function boxNonZero(v: unknown): boolean { + if (typeof v === "number") return v !== 0; + if (typeof v === "string") return /[^0\s.]/.test(v.replace(/(px|rem|em|%)/g, "")); + if (Array.isArray(v)) return v.some((x) => boxNonZero(x)); + if (v && typeof v === "object") { + const o = v as Record; + return (["top", "right", "bottom", "left"] as const).some((k) => boxNonZero(o[k])); + } + return false; +} + /** # 组件 column:双层对应原站 .elementor-column(外层)+ .elementor-element-populated(内层) * 外层参与 flex 布局(按 _inline_size 百分比否则均分),不承载盒样式; * 内层承载 populated 的盒样式:_margin(margin,移动卡片间距且不影响 flex 宽度,与原站一致)、 @@ -368,10 +407,10 @@ function ColumnNode({ el, gapPad }: { el: ElElement; gapPad?: string }) { const inner: CSSProperties = {}; const mg = paddingCss(settings.margin); if (mg) { - // margin 写在 populated(与 Elementor 相同):width 取 100% 外层宽而不随 margin 收缩 - // (原站实测 populated rect 272px 与 1.6/6.4 margin 并存) - inner.width = "100%"; - inner.margin = mg; + // margin 写在 populated(与 Elementor 相同):宽保持 auto——块级填充会自动扣除自身外边距 + // (原站实测 H11 列 453.33 宽 + margin-right 10 → populated 443.33,与 auto 行为一致); + // 全零 margin 视为未设置(boxNonZero),避免覆盖 kit-spacing + if (boxNonZero(settings.margin)) inner.margin = mg; } inner.padding = paddingCss(settings.padding) || gapPad || "10px"; const bg = sectionBgStyle(settings); @@ -387,6 +426,8 @@ function ColumnNode({ el, gapPad }: { el: ElElement; gapPad?: string }) { } else inner.borderWidth = "1px"; } return ( + // 内层 width:100% 与列同宽(H4 卡 margin 内含、向右溢出,原站实测一致); + // H11 等少数列在 app.css 用 !important 覆写为 auto(原站 populated 扣除水平外边距 443.33)
{(el.elements || []).map((c, i) => ( @@ -410,7 +451,7 @@ function WidgetNode({ el }: { el: ElElement }) { const pad = paddingCss(settings._padding); if (pad) style.padding = pad; const mrg = paddingCss(settings._margin); - if (mrg) style.margin = mrg; + if (mrg && boxNonZero(settings._margin)) style.margin = mrg; if (s(settings._border_border) === "solid") { style.border = `1px solid ${s(settings.border_color) || s(settings._border_color) || "#e3e9f0"}`; const bw = paddingCss(settings._border_width); @@ -482,7 +523,7 @@ function WidgetNodeInner({ el }: { el: ElElement }) { case "image": return ; case "button": - return ; + return ; case "html": // 间距交由 kit-spacing 列规则(.el-col > div > [data-el]),内层不再加 mb; // 媒体地址经 localSiteUrl 改写为本地 /uploads(原站绝对地址 → 本地) @@ -763,24 +804,43 @@ function ContactFormWidget({ settings }: { settings: Record }) } /** # 组件 button widget */ -function ButtonWidget({ settings }: { settings: Record }) { +/** # 组件 Elementor button widget:样式全部来自设置(背景/文字色/边框/圆角/内边距/排版, + * globals 已由 resolveGlobals 解析为实色;text_padding/typography 等实测 H11 按钮 12px 48px/18px)。 + * 基础对齐 widget-button 默认:inline-block、line-height 1、圆角 3px;字重走 kit 链接 400 + * (.content-html a),不在此硬编码。悬停态(button_background_hover_color/hover_color) + * 经节点级 + ) : null} + {text}
@@ -819,11 +879,20 @@ function ImageBoxWidget({ settings }: { settings: Record }) { ? tagOpt : "h3") as "div"; const Tag = tag as "div"; + // position left/right:wrapper 为 flex 行(widget-image-box.min.css .elementor-position-left + // .elementor-image-box-wrapper{display:flex;align-items:center},实测 H11 卡内图标行); + // 图宽 image_size%(post-18 由 image_size 控件生成 .elementor-image-box-img{width:18%})、 + // 左侧布局 fig 右距 15px;标题底距 title_bottom_space(H11 实测 4px) + const isRow = pos === "left" || pos === "right"; + const imgW = sizeCss(settings.image_size); + const titleSpace = sizeCss(settings.title_bottom_space); + // flex/边距在 app.css 的 .el-imagebox-pos-* 规则(带 768 断点),这里只带 per-widget 图宽 + const figStyle: CSSProperties | undefined = isRow && imgW ? { width: imgW } : undefined; return (
{url ? ( -
+
{link ? ( {alt} ) : ( @@ -834,7 +903,7 @@ function ImageBoxWidget({ settings }: { settings: Record }) { {title || desc ? (
{title ? ( - + {link ? ( ) : ( diff --git a/src/components/elementor/slim.ts b/src/components/elementor/slim.ts index 806345f9a..c58ddd5c0 100644 --- a/src/components/elementor/slim.ts +++ b/src/components/elementor/slim.ts @@ -15,6 +15,9 @@ const SETTING_KEYS = new Set([ "background_color_b", "background_color_stop_b", "background_image", + // section 级背景定位/平铺(H11 底图 bottom center + no-repeat) + "background_position", + "background_repeat", "border_border", "border_color", "border_radius", @@ -30,9 +33,16 @@ const SETTING_KEYS = new Set([ "text_color", "title_color", "button_text_color", + "button_background_hover_color", + "hover_color", + "text_padding", // image-box 系列:图定位(position 默认 top)与标题标签(title_size 默认 h3) "position", "title_size", + // image-box 行布局(H11):图宽 image_size%、标题底距、内容垂直对齐 + "image_size", + "title_bottom_space", + "content_vertical_alignment", "gradient_angle", "color_a", "color_b", @@ -112,6 +122,12 @@ const SETTING_KEYS = new Set([ "_background_image", "_background_position", "_background_repeat", + // section 背景遮罩(.elementor-background-overlay,H11 电路板图+渐变) + "background_overlay_background", + "background_overlay_color", + "background_overlay_color_b", + "background_overlay_opacity", + "background_overlay_gradient_angle", "_border_border", "_border_color", "_border_width", diff --git a/src/styles/app.css b/src/styles/app.css index dfc9224ab..3a2f08102 100644 --- a/src/styles/app.css +++ b/src/styles/app.css @@ -247,11 +247,20 @@ .el-imagebox-description { margin: 0; } .el-imagebox-img img { transition: filter 0.3s ease; } @media (min-width: 768px) { - /* 图在左/右的横向布局(原站 min-width:768px 分支) */ + /* 图在左/右的横向布局(原站 min-width:768px 分支);图与文案间距 15px + * (post-18 对 position-left/right 各自生成 .elementor-image-box-img margin-*,实测 H11 行) */ .el-imagebox-pos-left .el-imagebox-wrapper { display: flex; text-align: start; } - .el-imagebox-pos-left .el-imagebox-img { margin-right: 0; } + .el-imagebox-pos-left .el-imagebox-img { margin-right: 15px; } .el-imagebox-pos-right .el-imagebox-wrapper { display: flex; flex-direction: row-reverse; text-align: end; } - .el-imagebox-pos-right .el-imagebox-img { margin-left: 0; } + .el-imagebox-pos-right .el-imagebox-img { margin-left: 15px; } + .el-imagebox-pos-left .el-imagebox-content, .el-imagebox-pos-right .el-imagebox-content { width: 100%; } + } + + /* ===== 首页 H11 Developer Center 三卡列(post-18.css 对应块;1436 实测 2026-11-01) + * 原站这三列 populated 宽 = 列 453.33 - 水平 margin 10 = 443.33(flex 收缩语义), + * 与 H4 卡(populated = 列宽 272 含 margin 溢出)不同——此处按实测覆写为 auto 扣边距 */ + [data-el="f43387e"] > div, [data-el="508f31a"] > div, [data-el="2b7b38c"] > div { + width: auto !important; } @media (max-width: 767px) { .el-imagebox-img { margin-inline: auto !important; margin-bottom: 15px; } @@ -291,10 +300,11 @@ .el-col > div > [data-el]:not(.el-w-auto) { width: 100%; } .el-col > div > [data-el]:not(:last-child):not(.el-w-auto) { margin-bottom: 20px; } .el-texteditor { display: flow-root; } - .el-texteditor p { margin: 0 0 0.9em; } + /* 0.9rem 固定值(原站 hello-elementor p 边距 14.4px @16 根字号;em 会随 widget 字号 20px 放大成 18px) */ + .el-texteditor p { margin: 0 0 0.9rem; } /* frontend.min.css:widget 内 figure 外边距清零(text-editor / post-content 例外保留 UA 边距); * H10 html 模块 figure 曾因 UA margin(1em 40px) 被挤窄 80px、多出 16px 底边距 */ - .content-html [data-el] figure { margin: 0; } + .content-html [data-el] figure:not(.el-imagebox-img) { margin: 0; } .content-html .el-texteditor figure { margin-block: 1em; margin-inline: 40px; } /* ===== Elementor widget-image 基础(widget-image.min.css 对应块)