feat:推广页点击联系我们展示企业微信二维码

This commit is contained in:
gwokwong 2024-09-24 17:12:26 +08:00
parent 8c8c5b04d5
commit 977cf61b50
4 changed files with 312 additions and 59 deletions

117
public/site/css/ad.css vendored
View File

@ -8,6 +8,7 @@
background-position: center; background-position: center;
background-repeat: no-repeat; background-repeat: no-repeat;
} }
header .ad { header .ad {
width: 100vw; width: 100vw;
height: 0; height: 0;
@ -54,6 +55,7 @@ header .ad .ad-content .ad-close {
background: rgba(0, 0, 0, 0.2); background: rgba(0, 0, 0, 0.2);
transition: all 0.3s; transition: all 0.3s;
} }
header .ad .ad-content .ad-close:hover { header .ad .ad-content .ad-close:hover {
background: rgba(0, 0, 0, 0.15); background: rgba(0, 0, 0, 0.15);
} }
@ -438,3 +440,118 @@ footer.ad-footer .footer-layout {
grid-template-columns: repeat(1, 1fr); grid-template-columns: repeat(1, 1fr);
} }
} }
.ad-dialog {
position: fixed;
z-index: 10000;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: none;
}
.ad-dialog.show {
display: flex;
justify-content: center;
align-items: center;
}
.ad-dialog .ad-dialog-backdrop {
position: absolute;
z-index: 10010;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #0000001f;
}
.ad-dialog .ad-dialog-wrapper {
z-index: 10020;
min-height: 200px;
min-width: 200px;
max-width: 320px;
max-height: 480px;
background-color: #fff;
border-radius: 16px;
padding: 24px;
box-shadow: 0px 4px 16px 8px #00000014;
display: flex;
flex-direction: column;
}
.ad-dialog .ad-dialog-wrapper .ad-dialog-header {
display: flex;
justify-content: center;
align-items: center;
flex-grow: 0;
flex-shrink: 0;
padding: 8px 12px;
}
.ad-dialog .ad-dialog-wrapper .ad-dialog-header .ad-dialog-header-img {
height: 240px;
width: 240px;
background-image: url(../img/side_nav_wechat.png);
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
.ad-dialog .ad-dialog-wrapper .ad-dialog-content {
display: flex;
justify-content: center;
align-items: center;
flex-grow: 1;
flex-shrink: 0;
padding: 8px 12px;
font-size: 14px;
color: #727570;
}
.ad-dialog .ad-dialog-wrapper .ad-dialog-footer {
display: flex;
justify-content: center;
align-items: center;
flex-grow: 0;
flex-shrink: 0;
padding: 8px 12px;
}
.ad-dialog .ad-dialog-wrapper .ad-dialog-footer .ad-dialog-footer-btn {
width: 100%;
height: 40px;
padding: 10px 16px 10px 16px;
border-radius: 8px;
background: linear-gradient(
104.43deg,
#ff7155 1.18%,
#ef3e56 46.98%,
#ee61d4 93.24%
);
border: none;
font-size: 16px;
font-weight: 500;
line-height: 20px;
text-align: center;
color: #fff;
cursor: pointer;
}
.ad-dialog .ad-dialog-wrapper .ad-dialog-footer .ad-dialog-footer-btn:hover {
background: linear-gradient(
-104.43deg,
#ff7155 1.18%,
#ef3e56 46.98%,
#ee61d4 93.24%
);
}

View File

@ -27,6 +27,22 @@
</head> </head>
<body> <body>
<div class="ad-dialog">
<div class="ad-dialog-backdrop"></div>
<div class="ad-dialog-wrapper">
<div id="ad-dialog-header" class="ad-dialog-header">
<div id="ad-dialog-header-img" class="ad-dialog-header-img"></div>
</div>
<div id="ad-dialog-content" class="ad-dialog-content">
Please scan the QR code to add our WeChat customer service representative for purchase
</div>
<div class="ad-dialog-footer">
<button id="ad-dialog-footer-btn" class="ad-dialog-footer-btn">Got it</button>
</div>
</div>
</div>
<div id="layout" class="ad-wrapper"> <div id="layout" class="ad-wrapper">
<!-- 同意cookie弹框 --> <!-- 同意cookie弹框 -->
<div id="cookieConsent"> <div id="cookieConsent">

214
public/site/js/ad.js vendored
View File

@ -28,6 +28,8 @@ document.addEventListener("DOMContentLoaded", function () {
fetchAdIntro(language); fetchAdIntro(language);
manageAnimate(); manageAnimate();
handleDialog();
} else { } else {
// 如果不是广告页面,插入广告样式表并获取广告栏 // 如果不是广告页面,插入广告样式表并获取广告栏
insertAdStylesheet(); insertAdStylesheet();
@ -370,8 +372,7 @@ async function handleAdPlanPlans(plans) {
const planItemEl = document.createElement("div"); const planItemEl = document.createElement("div");
planItemEl.className = `plan-item ${plan.activated ? "active" : "" planItemEl.className = `plan-item ${plan.activated ? "active" : ""
}`; }`;
planItemEl.innerHTML = planItemEl.innerHTML = `
`
<div class="plan-item-title"> <div class="plan-item-title">
<span>${plan.title}</span> <span>${plan.title}</span>
</div> </div>
@ -382,12 +383,14 @@ async function handleAdPlanPlans(plans) {
${plan.price.payment ?? ""} ${plan.price.payment ?? ""}
</span> </span>
</span> </span>
<span class="plan-item-price-original ${plan.price.isPrice ? "price" : ""}"> <span class="plan-item-price-original ${plan.price.isPrice ? "price" : ""
}">
${plan.price.original ?? ""} ${plan.price.original ?? ""}
</span> </span>
</div> </div>
<div class="plan-item-button"> <div class="plan-item-button">
<a href="${plan.button.href}" ${plan.button.target === "_blank" ? 'target="_blank"' : ""}> <a href="${plan.button.href}" ${plan.button.target === "_blank" ? 'target="_blank"' : ""
}>
<button class="btn-primary"> <button class="btn-primary">
${plan.button.label} ${plan.button.label}
</button> </button>
@ -395,7 +398,8 @@ async function handleAdPlanPlans(plans) {
</div> </div>
<div class="plan-item-description"> <div class="plan-item-description">
<ul class="plan-item-description-list"> <ul class="plan-item-description-list">
${plan.features.map((feature) => { ${plan.features
.map((feature) => {
const iconUrl = feature.icon.data const iconUrl = feature.icon.data
? getMediaUrl(feature.icon) ? getMediaUrl(feature.icon)
: "../img/ad/checked.svg"; : "../img/ad/checked.svg";
@ -404,12 +408,14 @@ async function handleAdPlanPlans(plans) {
<i class="plan-item-description-item-icon"> <i class="plan-item-description-item-icon">
<img src="${iconUrl}" alt="${feature.title}" /> <img src="${iconUrl}" alt="${feature.title}" />
</i> </i>
<span class="plan-item-description-item-content ${feature.activated ? "" : "disabled"}"> <span class="plan-item-description-item-content ${feature.activated ? "" : "disabled"
}">
${feature.text} ${feature.text}
</span> </span>
</li> </li>
`; `;
}).join("")} })
.join("")}
</ul> </ul>
</div> </div>
`; `;
@ -420,16 +426,26 @@ async function handleAdPlanPlans(plans) {
"animate__faster", "animate__faster",
"animate__delay-1s" "animate__delay-1s"
); );
couldAdPlanElAnimate[`${plan.id}`] = false couldAdPlanElAnimate[`${plan.id}`] = false;
planItemEl.addEventListener("animationend", () => { planItemEl.addEventListener(
planItemEl.classList.remove("animate__backInUp", "animate__faster", "animate__delay-1s") "animationend",
couldAdPlanElAnimate[`${plan.id}`] = true () => {
}, { once: true }) planItemEl.classList.remove(
"animate__backInUp",
"animate__faster",
"animate__delay-1s"
);
couldAdPlanElAnimate[`${plan.id}`] = true;
},
{ once: true }
);
await new Promise((resolve) => { await new Promise((resolve) => {
setTimeout(resolve, 150); setTimeout(resolve, 150);
}); });
} }
overridePlanButton();
} }
} }
@ -493,8 +509,7 @@ async function handleAdIntroIntros(intros) {
? getMediaUrl(intro.cover) ? getMediaUrl(intro.cover)
: `../img/ad/intro-card-img${intro.priority + 1}.svg`; : `../img/ad/intro-card-img${intro.priority + 1}.svg`;
introItemEl.className = "ad-intro-item"; introItemEl.className = "ad-intro-item";
introItemEl.innerHTML = introItemEl.innerHTML = `
`
<div class="ad-intro-item-header"> <div class="ad-intro-item-header">
<img src="${barUrl}" alt="intro-bar" /> <img src="${barUrl}" alt="intro-bar" />
</div> </div>
@ -514,13 +529,19 @@ async function handleAdIntroIntros(intros) {
"animate__zoomIn", "animate__zoomIn",
"animate__delay-1s" "animate__delay-1s"
); );
couldAdIntroElAnimate[`${intro.id}`] = false couldAdIntroElAnimate[`${intro.id}`] = false;
introItemEl.addEventListener(
introItemEl.addEventListener("animationend", () => { "animationend",
introItemEl.classList.remove("animate__zoomIn", "animate__delay-1s") () => {
couldAdIntroElAnimate[`${intro.id}`] = true introItemEl.classList.remove(
}, { once: true }) "animate__zoomIn",
"animate__delay-1s"
);
couldAdIntroElAnimate[`${intro.id}`] = true;
},
{ once: true }
);
await new Promise((resolve) => { await new Promise((resolve) => {
setTimeout(resolve, 150); setTimeout(resolve, 150);
}); });
@ -535,8 +556,7 @@ function handleError(error) {
// 插入广告栏元素 // 插入广告栏元素
function insertAdBarElement() { function insertAdBarElement() {
const adBarHTML = const adBarHTML = `
`
<div id="ad" class="ad"> <div id="ad" class="ad">
<div class="ad-content"> <div class="ad-content">
<div class="ad-content-left"> <div class="ad-content-left">
@ -620,63 +640,73 @@ function manageAnimate() {
window.addEventListener("scroll", () => { window.addEventListener("scroll", () => {
throttle(() => { throttle(() => {
console.log("scroll") detectAdPlanEl();
detectAdPlanEl() detectAdIntroEl();
detectAdIntroEl() }, 200);
}, 200)
}); });
} }
const couldAdPlanElAnimate = {} const couldAdPlanElAnimate = {};
const couldAdIntroElAnimate = {} const couldAdIntroElAnimate = {};
function detectAdPlanEl() { function detectAdPlanEl() {
const adPlanEl = document.querySelector(".ad-plan") const adPlanEl = document.querySelector(".ad-plan");
if (isElementOutOfViewport(adPlanEl)) { if (isElementOutOfViewport(adPlanEl)) {
const els = document.querySelectorAll(".plan-item") const els = document.querySelectorAll(".plan-item");
for (const el of els) { for (const el of els) {
couldAdPlanElAnimate[`${el.id}`] = true couldAdPlanElAnimate[`${el.id}`] = true;
} }
return return;
} }
const _couldAdPlanElAnimate = Object.values(couldAdPlanElAnimate).every(Boolean) const _couldAdPlanElAnimate =
Object.values(couldAdPlanElAnimate).every(Boolean);
if (!_couldAdPlanElAnimate) return; if (!_couldAdPlanElAnimate) return;
if (isElementPartiallyInViewport(adPlanEl)) { if (isElementPartiallyInViewport(adPlanEl)) {
const els = document.querySelectorAll(".plan-item") const els = document.querySelectorAll(".plan-item");
for (const el of els) { for (const el of els) {
el.classList.add("animate__flipInX") el.classList.add("animate__flipInX");
couldAdPlanElAnimate[`${el.id}`] = false couldAdPlanElAnimate[`${el.id}`] = false;
el.addEventListener("animationend", () => { el.addEventListener(
el.classList.remove("animate__flipInX") "animationend",
}, { once: true }) () => {
el.classList.remove("animate__flipInX");
},
{ once: true }
);
} }
return return;
} }
} }
function detectAdIntroEl() { function detectAdIntroEl() {
const adIntroEl = document.querySelector(".ad-intro") const adIntroEl = document.querySelector(".ad-intro");
if (isElementOutOfViewport(adIntroEl)) { if (isElementOutOfViewport(adIntroEl)) {
const els = document.querySelectorAll(".ad-intro-item") const els = document.querySelectorAll(".ad-intro-item");
for (const el of els) { for (const el of els) {
couldAdIntroElAnimate[`${el.id}`] = true couldAdIntroElAnimate[`${el.id}`] = true;
} }
return return;
} }
const _couldAdIntroElAnimate = Object.values(couldAdIntroElAnimate).every(Boolean) const _couldAdIntroElAnimate = Object.values(couldAdIntroElAnimate).every(
Boolean
);
if (!_couldAdIntroElAnimate) return; if (!_couldAdIntroElAnimate) return;
if (isElementPartiallyInViewport(adIntroEl)) { if (isElementPartiallyInViewport(adIntroEl)) {
const els = document.querySelectorAll(".ad-intro-item") const els = document.querySelectorAll(".ad-intro-item");
for (const el of els) { for (const el of els) {
el.classList.add("animate__zoomIn") el.classList.add("animate__zoomIn");
couldAdIntroElAnimate[`${el.id}`] = false couldAdIntroElAnimate[`${el.id}`] = false;
el.addEventListener("animationend", () => { el.addEventListener(
el.classList.remove("animate__zoomIn") "animationend",
}, { once: true }) () => {
el.classList.remove("animate__zoomIn");
},
{ once: true }
);
} }
return return;
} }
} }
@ -684,9 +714,11 @@ function isElementPartiallyInViewport(el) {
if (!el) return false; if (!el) return false;
const rect = el.getBoundingClientRect(); const rect = el.getBoundingClientRect();
return ( return (
rect.top < (window.innerHeight || document.documentElement.clientHeight) && rect.top <
(window.innerHeight || document.documentElement.clientHeight) &&
rect.bottom > 0 && rect.bottom > 0 &&
rect.left < (window.innerWidth || document.documentElement.clientWidth) && rect.left <
(window.innerWidth || document.documentElement.clientWidth) &&
rect.right > 0 rect.right > 0
); );
} }
@ -696,8 +728,80 @@ function isElementOutOfViewport(el) {
const rect = el.getBoundingClientRect(); const rect = el.getBoundingClientRect();
return ( return (
rect.bottom < 0 || rect.bottom < 0 ||
rect.top > (window.innerHeight || document.documentElement.clientHeight) || rect.top >
(window.innerHeight || document.documentElement.clientHeight) ||
rect.right < 0 || rect.right < 0 ||
rect.left > (window.innerWidth || document.documentElement.clientWidth) rect.left > (window.innerWidth || document.documentElement.clientWidth)
); );
} }
function handleDialog() {
const dialogEl = document.querySelector(".ad-dialog");
if (!dialogEl) return;
lockBodyScroll(dialogEl.classList.contains("show"));
overridePlanButton();
const dialogBackdropEl = dialogEl.querySelector(".ad-dialog-backdrop");
if (dialogBackdropEl) {
dialogBackdropEl.addEventListener("click", () => {
dialogEl.classList.remove("show");
lockBodyScroll(false);
handleDialogAnimate(false)
});
}
const dialogFooterBtnEl = dialogEl.querySelector(".ad-dialog-footer-btn");
if (dialogFooterBtnEl) {
dialogFooterBtnEl.addEventListener("click", () => {
dialogEl.classList.remove("show");
lockBodyScroll(false);
handleDialogAnimate(false)
});
}
}
function lockBodyScroll(bool) {
document.body.style.overflowY = bool ? "hidden" : "auto";
}
function overridePlanButton() {
function showDialog(e) {
e.preventDefault();
e.stopPropagation();
const dialogEl = document.querySelector(".ad-dialog");
if (!dialogEl) return;
dialogEl.classList.add("show");
lockBodyScroll(true);
handleDialogAnimate(true)
}
const planButtonEl = document.querySelectorAll(".plan-item-button");
planButtonEl.forEach((el) => {
el.removeEventListener("click", showDialog);
const aEl = el.querySelector("a");
if (!aEl.href || aEl.href.includes("#")) {
aEl.removeAttribute("href");
el.addEventListener("click", showDialog);
}
});
}
function handleDialogAnimate(bool) {
const dialogEl = document.querySelector(".ad-dialog");
if (!dialogEl) return;
const dialogWrapperEl = dialogEl.querySelector(".ad-dialog-wrapper");
if (!dialogWrapperEl) return;
if (bool) {
dialogWrapperEl.classList.add("animate__animated", "animate__bounceIn", "animate__faster");
dialogWrapperEl.addEventListener("animationend", () => {
dialogWrapperEl.classList.remove("animate__animated", "animate__bounceIn", "animate__faster")
})
} else {
dialogWrapperEl.classList.remove("animate__animated", "animate__bounceIn", "animate__faster")
}
}

View File

@ -25,6 +25,22 @@
</head> </head>
<body> <body>
<div class="ad-dialog">
<div class="ad-dialog-backdrop"></div>
<div class="ad-dialog-wrapper">
<div id="ad-dialog-header" class="ad-dialog-header">
<div id="ad-dialog-header-img" class="ad-dialog-header-img"></div>
</div>
<div id="ad-dialog-content" class="ad-dialog-content">
如需购买请扫码添加微信客服专员
</div>
<div class="ad-dialog-footer">
<button id="ad-dialog-footer-btn" class="ad-dialog-footer-btn">确定</button>
</div>
</div>
</div>
<div id="layout" class="ad-wrapper"> <div id="layout" class="ad-wrapper">
<!-- 同意cookie弹框 --> <!-- 同意cookie弹框 -->
<div id="cookieConsent"> <div id="cookieConsent">