v0.10.20: G3 询价弹窗/表单(原站 Elementor popup 1129 + CF7 604)

- 新增 InquiryPopup:全局委托拦截 #elementor-action:popup:open 链接(全站 24 处,id 1129),
  按 Elementor 弹窗 DOM 渲染(遮罩 rgba(0,0,0,.8)、700px 白卡、关闭按钮 20/20、Esc/遮罩关闭、
  打开时锁定页面滚动)
- 表单按 CF7 表单 604 重建:Name/Company/Business email/Phone/Product/Interest/Annual Demand/
  Message + consent 勾选 + reCAPTCHA 声明;样式取 popup 生成 CSS(padding 2em 3em、
  48%/31.3% 两列三列、#C9CEE8 边框、textarea 134px、按钮 #ED6C00→#FFA11B 圆角 10)
- 提交走 submitInquiry(form_key contact-form-1),字段映射 + interest/annualdemand/consent
  extra;成功显示 CF7 原文提示;弹窗内 5 个社交图标(20px/间距 14/品牌色)
- app.css 补弹窗样式与 FA tiktok/twitter 码位;Layout 全局挂载
- 实测 1436:700×737 居中、两列 289.9/三列 189、消息 604×153;390:全宽、移动端三列退化两列;
  首页与 landing 页触发器均生效,提交入库验证后清理测试数据;bun test 11 pass
- 版本 0.10.20
This commit is contained in:
bigcode
2026-11-02 11:37:28 +08:00
parent a7614e5485
commit 5ff5f9d477
5 changed files with 425 additions and 2 deletions
+301
View File
@@ -0,0 +1,301 @@
/**
* # 组件 询价弹窗 InquiryPopup(原站 Elementor popup 1129 + CF7 表单 604)
* 原站首页/方案页等 24 处 “Make an Inquiry” / 按钮均指向
* `#elementor-action%3Aaction%3Dpopup%3Aopen%26settings%3D...` 的 popup 1129,
* 由 Elementor Pro 弹窗 DOM(.elementor-popup-modal + .dialog-*)承载 EAEL Contact Form 7。
* 本站改为预渲染弹窗(首屏不加载,点击时才挂载),表单直接提交 submitInquiry。
* 表单字段/文案/样式取自:
* - popup 1129 的 _elementor_data(标题 CONTACT US、内边距 2em 3em、textarea 134px 等)
* - CF7 表单 604 的 post_content(Name/Company/Email/Phone/Product/Interest/AnnualDemand/Message
* + acceptance 勾选 + reCAPTCHA 声明 + Submit)
* - popup 1129 生成 CSS(字段 48%/31.3% 两列三列布局、边框 #C9CEE8、按钮 #ED6C00→#FFA11B)
*/
import { useEffect, useRef, useState, type FormEvent } from "react";
import { submitInquiry } from "~/server/inquiries";
/** # 常量 展开的弹窗 id(原站 popup 1129) **/
const POPUP_ID = "1129";
/** # 常量 弹窗社交图标(popup 内 social-icons widget:20px/间距 14/官方品牌色) **/
const POPUP_SOCIALS = [
{ icon: "fa-linkedin", label: "LinkedIn", href: "https://www.linkedin.com/company/dusun-electron-ltd/", color: "#0077B5" },
{ icon: "fa-youtube", label: "YouTube", href: "https://www.youtube.com/channel/UCyb4PpqVgvKgC9KpkByZaaQ", color: "#CD201F" },
{ icon: "fa-tiktok", label: "TikTok", href: "https://www.tiktok.com/@dusun_iot", color: "#000000" },
{ icon: "fa-facebook", label: "Facebook", href: "https://www.facebook.com/DUSUN-IoT-101398069457701", color: "#3B5998" },
{ icon: "fa-twitter", label: "Twitter", href: "https://twitter.com/Dusunelectron", color: "#1DA1F2" },
];
/** # 常量 产品线下拉(CF7 表单 604 原文) **/
const PRODUCT_OPTIONS = ["IoT Gateway", "System on Module", "Development Board", "Remote Control", "Voice Recognition Module", "Control Board", "Beacon & Sensor", "Other"];
/** # 常量 合作意向下拉 **/
const INTEREST_OPTIONS = ["Sales", "Distribution", "ODM", "OEM", "Technical support", "Other"];
/** # 常量 年需求量下拉 **/
const DEMAND_OPTIONS = ["1-50", "51-200", "201-1000", "1001-5000", "5000+"];
/** # 组件 询价弹窗(全局挂载于 Layout,监听 Elementor popup 链接) */
export function InquiryPopup() {
const [open, setOpen] = useState(false);
const [status, setStatus] = useState<"idle" | "submitting" | "success" | "error">("idle");
const [errorMsg, setErrorMsg] = useState("");
const formRef = useRef<HTMLFormElement>(null);
// 全局委托:拦截任意指向 popup 1129 的 Elementor 动作链接
useEffect(() => {
const onClick = (e: MouseEvent) => {
const a = (e.target as HTMLElement | null)?.closest?.("a[href*='elementor-action']") as HTMLAnchorElement | null;
if (!a) return;
const href = a.getAttribute("href") || "";
if (!href.includes("popup%3Aopen")) return;
const m = /settings%3D([A-Za-z0-9%_=-]+)/.exec(href);
let id = "";
if (m) {
try {
id = String(JSON.parse(atob(decodeURIComponent(m[1]!))).id || "");
} catch {
id = "";
}
}
if (id !== POPUP_ID) return;
e.preventDefault();
setStatus("idle");
setErrorMsg("");
setOpen(true);
};
document.addEventListener("click", onClick);
return () => document.removeEventListener("click", onClick);
}, []);
// 打开时锁定页面滚动 + Esc 关闭(原站 dialog-prevent-scroll 行为)
useEffect(() => {
if (!open) return;
const onKey = (e: KeyboardEvent) => {
if (e.key === "Escape") setOpen(false);
};
document.addEventListener("keydown", onKey);
document.body.classList.add("dialog-prevent-scroll");
return () => {
document.removeEventListener("keydown", onKey);
document.body.classList.remove("dialog-prevent-scroll");
};
}, [open]);
if (!open) return null;
/** # 函数 表单提交:映射到 submitInquiry(form_key=contact-form-1,legacy 604) */
async function handleSubmit(e: FormEvent<HTMLFormElement>) {
e.preventDefault();
const fd = new FormData(e.currentTarget);
setStatus("submitting");
setErrorMsg("");
try {
const res = await submitInquiry({
data: {
formKey: "contact-form-1",
name: String(fd.get("your-name") || ""),
email: String(fd.get("your-email") || ""),
phone: String(fd.get("Phone") || ""),
company: String(fd.get("Company") || ""),
country: "",
message: String(fd.get("your-message") || ""),
productInterest: String(fd.get("Product") || ""),
_gotcha: String(fd.get("_gotcha") || ""),
extra: {
interest: String(fd.get("Interest") || ""),
annualdemand: String(fd.get("AnnualDemand") || ""),
consent: fd.get("acceptance-918") ? "1" : "",
},
},
});
if (res.ok) {
setStatus("success");
formRef.current?.reset();
} else {
setStatus("error");
setErrorMsg(res.error || "提交失败,请稍后重试");
}
} catch (err) {
setStatus("error");
setErrorMsg(err instanceof Error ? err.message : "网络异常,请稍后重试");
}
}
return (
<div
className="ds-inquiry-popup elementor-popup-modal dialog-type-lightbox"
id="elementor-popup-modal-1129"
role="dialog"
aria-modal="true"
aria-label="Contact us"
>
<div className="dialog-widget-overlay" onClick={() => setOpen(false)} aria-hidden="true" />
<div className="dialog-widget dialog-lightbox-widget dialog-type-popup">
<div className="dialog-widget-content dialog-lightbox-widget-content">
<div className="dialog-message dialog-lightbox-message">
<div className="elementor elementor-1129" data-elementor-type="popup">
<section className="elementor-section elementor-top-section elementor-element elementor-element-110a215 elementor-section-boxed">
<div className="elementor-container elementor-column-gap-default">
<div className="elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-4133dbe">
<div className="elementor-widget-wrap elementor-element-populated">
<div className="elementor-element elementor-element-2a6b73c elementor-widget elementor-widget-eael-contact-form-7 ele-new-form4-dif">
<div className="elementor-widget-container">
<div className="eael-contact-form-7-wrapper">
<div className="eael-contact-form eael-contact-form-7">
<div className="eael-contact-form-7-heading">
<h3 className="eael-contact-form-title">CONTACT US</h3>
</div>
<div className="wpcf7">
<form ref={formRef} className="wpcf7-form init" onSubmit={handleSubmit}>
<p>
<label>
Name*
<br />
<span className="wpcf7-form-control-wrap" data-name="your-name">
<input type="text" name="your-name" required className="wpcf7-form-control wpcf7-text wpcf7-validates-as-required" maxLength={400} />
</span>
</label>
</p>
<p>
<label>
Company*
<br />
<span className="wpcf7-form-control-wrap" data-name="Company">
<input type="text" name="Company" required className="wpcf7-form-control wpcf7-text wpcf7-validates-as-required" maxLength={400} />
</span>
</label>
</p>
<p>
<label>
Business email*
<br />
<span className="wpcf7-form-control-wrap" data-name="your-email">
<input type="email" name="your-email" required className="wpcf7-form-control wpcf7-text wpcf7-email wpcf7-validates-as-required" maxLength={400} />
</span>
</label>
</p>
<p>
<label>
Phone number
<br />
<span className="wpcf7-form-control-wrap" data-name="Phone">
<input type="tel" name="Phone" className="wpcf7-form-control wpcf7-text wpcf7-tel" maxLength={400} />
</span>
</label>
</p>
<p>
<label>
Product*
<br />
<span className="wpcf7-form-control-wrap" data-name="Product">
<select name="Product" required className="wpcf7-form-control wpcf7-select wpcf7-validates-as-required">
<option value="">—Please choose an option—</option>
{PRODUCT_OPTIONS.map((o) => (
<option key={o} value={o}>{o}</option>
))}
</select>
</span>
</label>
</p>
<p>
<label>
Interest*
<br />
<span className="wpcf7-form-control-wrap" data-name="Interest">
<select name="Interest" required className="wpcf7-form-control wpcf7-select wpcf7-validates-as-required">
<option value="">—Please choose an option—</option>
{INTEREST_OPTIONS.map((o) => (
<option key={o} value={o}>{o}</option>
))}
</select>
</span>
</label>
</p>
<p>
<label>
Annual Demand*
<br />
<span className="wpcf7-form-control-wrap" data-name="AnnualDemand">
<select name="AnnualDemand" required className="wpcf7-form-control wpcf7-select wpcf7-validates-as-required">
<option value="">—Please choose an option—</option>
{DEMAND_OPTIONS.map((o) => (
<option key={o} value={o}>{o}</option>
))}
</select>
</span>
</label>
</p>
<p>
<label>
Message*
<br />
<span className="wpcf7-form-control-wrap" data-name="your-message">
<textarea name="your-message" required className="wpcf7-form-control wpcf7-textarea" rows={6} maxLength={2000} placeholder="Leave us a message about your project or product needs" />
</span>
</label>
</p>
<p>
<span className="wpcf7-form-control-wrap" data-name="acceptance-918">
<span className="wpcf7-list-item">
<label>
<input type="checkbox" name="acceptance-918" value="1" defaultChecked />
<span className="wpcf7-list-item-label">
By checking this box, you consent to receive commercial messages such as newsletters, event invitations, promotional and educational content,
product updates, transaction-related emails, and customer service emails, from DusunIoT, in accordance with our{" "}
<a href="/privacy-policy/">Privacy Policy</a>. You may withdraw your consent at any time.
</span>
</label>
</span>
</span>
</p>
<p className="ds-recaptcha-note">
This site is protected by reCAPTCHA and the Google <a href="https://policies.google.com/privacy" target="_blank" rel="noopener noreferrer">Privacy Policy</a> and{" "}
<a href="https://policies.google.com/terms" target="_blank" rel="noopener noreferrer">Terms of Service</a> apply.
</p>
<p>
<input className="wpcf7-form-control wpcf7-submit" type="submit" value={status === "submitting" ? "Submitting…" : "Submit"} disabled={status === "submitting"} />
</p>
<div className={"wpcf7-response-output" + (status === "success" ? " ds-ok" : status === "error" ? " ds-err" : "")} aria-hidden={status === "idle"}>
{status === "success" ? "Thank you for your message. It has been sent." : status === "error" ? errorMsg : ""}
</div>
</form>
</div>
</div>
</div>
{/* 社交图标行(popup 内 social-icons widget 2a08dd3) */}
<div className="elementor-element elementor-element-2a08dd3 elementor-widget elementor-widget-social-icons">
<div className="elementor-widget-container">
<div className="elementor-social-icons-wrapper elementor-grid">
{POPUP_SOCIALS.map((s) => (
<a
key={s.icon}
className={`elementor-social-icon elementor-social-icon-${s.icon.replace(/^fa-/, "")} elementor-animation-shrink`}
href={s.href}
target="_blank"
rel="noopener noreferrer"
aria-label={s.label}
style={{ backgroundColor: s.color }}
>
<span className="elementor-screen-only">{s.label}</span>
<i className={`fab ${s.icon}`} aria-hidden="true" />
</a>
))}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</div>
</div>
<button type="button" className="dialog-close-button dialog-lightbox-close-button" aria-label="Close" onClick={() => setOpen(false)}>
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" />
</svg>
</button>
</div>
</div>
</div>
);
}
+3
View File
@@ -6,6 +6,7 @@
import "../../styles/app.css";
import { Header } from "./Header";
import { Footer, type FooterCategory } from "./Footer";
import { InquiryPopup } from "./InquiryPopup";
import { createServerFn } from "@tanstack/react-start";
import { getFooterTemplate, getMenuTree, getProductCategoryTree, type MenuNode } from "~/server/data";
import type { ElElement } from "~/components/elementor/Renderer";
@@ -45,6 +46,8 @@ export function Layout(props: { layout: LayoutData; children: React.ReactNode })
<Header menu={props.layout.menu} />
<main>{props.children}</main>
<Footer categories={props.layout.categories} menu={props.layout.footerMenu} doc={props.layout.footerDoc} />
{/* 全局询价弹窗:接管页面内所有 Elementor popup 1129 链接(Make an Inquiry / 按钮) */}
<InquiryPopup />
</>
);
}