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:
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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 />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user