v0.10.11 首页 H11 Developer Center 三卡 1:1:节背景遮罩通用实现(电路板条图+渐变 overlay,行 relative);image-box pos-left 行布局;按钮设置驱动+悬停注入;列宽 margin 语义(全零 _margin 跳过/0.9rem/figure margin 0/kit 标题 Roboto);H4/H9/H10 全量回归零差异
This commit is contained in:
@@ -200,9 +200,12 @@ function sectionBgStyle(settings: Record<string, unknown>): 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<string, unknown>).background_overlay_background);
|
||||
let overlay: ReactNode = null;
|
||||
if (ovType === "gradient") {
|
||||
const c1 = s((settings as Record<string, unknown>).background_overlay_color) || "transparent";
|
||||
const c2 = s((settings as Record<string, unknown>).background_overlay_color_b) || "transparent";
|
||||
const angle = sizeCss((settings as Record<string, unknown>).background_overlay_gradient_angle) || "180deg";
|
||||
overlay = (
|
||||
<div
|
||||
aria-hidden
|
||||
style={{ position: "absolute", inset: 0, background: `linear-gradient(${angle}, ${c1} 0%, ${c2} 100%)`, pointerEvents: "none" }}
|
||||
/>
|
||||
);
|
||||
} else if (ovType === "classic") {
|
||||
const c1 = s((settings as Record<string, unknown>).background_overlay_color);
|
||||
if (c1) overlay = <div aria-hidden style={{ position: "absolute", inset: 0, background: c1, pointerEvents: "none" }} />;
|
||||
}
|
||||
// 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 (
|
||||
<section style={style} data-el={el.id}>
|
||||
<div className="mx-auto flex flex-wrap" style={inner}>
|
||||
{overlay}
|
||||
{/* 行加 relative:与绝对定位遮罩同属定位层,DOM 靠后绘制在上,内容不被蒙灰 */}
|
||||
<div className="mx-auto flex flex-wrap" style={{ ...inner, position: "relative" }}>
|
||||
{(el.elements || []).map((c, i) => (
|
||||
<ElementNode key={c.id || i} el={c} gapPad={gapPad} />
|
||||
))}
|
||||
@@ -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<string, unknown>;
|
||||
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)
|
||||
<div className="el-col min-w-0" style={outer} data-el={el.id}>
|
||||
<div style={{ display: "flex", flexWrap: "wrap", alignContent: "flex-start", width: "100%", ...inner }}>
|
||||
{(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 <ImageWidget settings={settings} />;
|
||||
case "button":
|
||||
return <ButtonWidget settings={settings} />;
|
||||
return <ButtonWidget el={el} settings={settings} />;
|
||||
case "html":
|
||||
// 间距交由 kit-spacing 列规则(.el-col > div > [data-el]),内层不再加 mb;
|
||||
// 媒体地址经 localSiteUrl 改写为本地 /uploads(原站绝对地址 → 本地)
|
||||
@@ -763,24 +804,43 @@ function ContactFormWidget({ settings }: { settings: Record<string, unknown> })
|
||||
}
|
||||
|
||||
/** # 组件 button widget */
|
||||
function ButtonWidget({ settings }: { settings: Record<string, unknown> }) {
|
||||
/** # 组件 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)
|
||||
* 经节点级 <style> 注入(行内样式无法表达 :hover)。
|
||||
*/
|
||||
function ButtonWidget({ el, settings }: { el: ElElement; settings: Record<string, unknown> }) {
|
||||
const text = s(settings.text);
|
||||
const link = (settings.link as { url?: string; target?: string } | undefined)?.url;
|
||||
if (!link && !text) return null;
|
||||
const style: CSSProperties = {
|
||||
display: "inline-block",
|
||||
padding: "10px 24px",
|
||||
borderRadius: 4,
|
||||
background: s(settings.background_color) || "var(--brand)",
|
||||
color: s(settings.button_text_color) || s(settings.text_color) || "#fff",
|
||||
fontWeight: 600,
|
||||
lineHeight: 1,
|
||||
padding: paddingCss(settings.text_padding) || "12px 24px",
|
||||
borderRadius: paddingCss(settings.border_radius) || "3px",
|
||||
background: s(settings.background_color) || "#818a91",
|
||||
color: s(settings.button_text_color) || "#fff",
|
||||
textDecoration: "none",
|
||||
};
|
||||
if (s(settings.size) === "lg") style.padding = "14px 32px";
|
||||
if (s(settings.size) === "sm") style.padding = "7px 16px";
|
||||
if (!link && !text) return null;
|
||||
// border_width 是标量/盒值,collapseBoxValues 折叠四值简写("1px 1px 1px 1px" → "1px"),
|
||||
// 否则 `${bw} solid` 拼出非法 border 声明被浏览器丢弃
|
||||
const bw = paddingCss(settings.border_width);
|
||||
if (s(settings.border_border) === "solid") {
|
||||
style.border = `${bw ? collapseBoxValues(bw) : "1px"} solid ${s(settings.border_color) || "transparent"}`;
|
||||
} else if (bw) style.borderWidth = collapseBoxValues(bw);
|
||||
const fs = sizeCss(settings.typography_font_size);
|
||||
if (fs) style.fontSize = fs;
|
||||
const fwt = settings.typography_font_weight;
|
||||
if (fwt !== undefined && fwt !== null && fwt !== "") style.fontWeight = Number(fwt) || fwt as number;
|
||||
const hoverBg = s(settings.button_background_hover_color);
|
||||
const hoverColor = s(settings.hover_color);
|
||||
return (
|
||||
<div className="mb-[18px]" style={{ textAlign: (s(settings.align) as CSSProperties["textAlign"]) || "left" }}>
|
||||
<a href={link || "#"} style={style} target={(settings.link as { target?: string } | undefined)?.target === "_blank" ? "_blank" : undefined} rel="noreferrer">
|
||||
<div style={{ textAlign: (s(settings.align) as CSSProperties["textAlign"]) || "left" }}>
|
||||
{hoverBg || hoverColor ? (
|
||||
<style>{`[data-el="${el.id}"] .el-btn:hover{${hoverBg ? `background:${hoverBg};` : ""}${hoverColor ? `color:${hoverColor};` : ""}}`}</style>
|
||||
) : null}
|
||||
<a className="el-btn" href={link || "#"} style={style} target={(settings.link as { target?: string } | undefined)?.target === "_blank" ? "_blank" : undefined} rel="noreferrer">
|
||||
{text}
|
||||
</a>
|
||||
</div>
|
||||
@@ -819,11 +879,20 @@ function ImageBoxWidget({ settings }: { settings: Record<string, unknown> }) {
|
||||
? 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 (
|
||||
<div className={"el-imagebox" + posCls}>
|
||||
<div className="el-imagebox-wrapper">
|
||||
{url ? (
|
||||
<figure className="el-imagebox-img">
|
||||
<figure className="el-imagebox-img" style={figStyle}>
|
||||
{link ? (
|
||||
<a {...linkProps} tabIndex={-1}><img src={url} alt={alt} loading="lazy" /></a>
|
||||
) : (
|
||||
@@ -834,7 +903,7 @@ function ImageBoxWidget({ settings }: { settings: Record<string, unknown> }) {
|
||||
{title || desc ? (
|
||||
<div className="el-imagebox-content">
|
||||
{title ? (
|
||||
<Tag className="el-imagebox-title">
|
||||
<Tag className="el-imagebox-title" style={titleSpace ? { marginBottom: titleSpace } : undefined}>
|
||||
{link ? (
|
||||
<a {...linkProps} dangerouslySetInnerHTML={{ __html: title }} />
|
||||
) : (
|
||||
|
||||
@@ -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",
|
||||
|
||||
+15
-5
@@ -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 对应块)
|
||||
|
||||
Reference in New Issue
Block a user