Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
303 lines
8.4 KiB
JavaScript
303 lines
8.4 KiB
JavaScript
(function () {
|
|
var mask = document.getElementById("downloadAppDialog");
|
|
if (!mask) return;
|
|
|
|
var pcQRBox = mask.querySelector("[data-download-qr-box]");
|
|
var h5QRBox = null;
|
|
var h5URLEl = mask.querySelector("[data-download-url]");
|
|
var downloadCTA = mask.querySelector("[data-download-app-cta]");
|
|
var copyBtn = mask.querySelector("[data-download-copy]");
|
|
var platformBtns = mask.querySelectorAll("[data-download-platform]");
|
|
var copyTimer = null;
|
|
var qrReady = false;
|
|
var qrcodeLoading = null;
|
|
|
|
function loadQRCodeLib() {
|
|
if (typeof window.QRCode === "function") {
|
|
return Promise.resolve();
|
|
}
|
|
if (qrcodeLoading) return qrcodeLoading;
|
|
qrcodeLoading = new Promise(function (resolve, reject) {
|
|
var s = document.createElement("script");
|
|
s.src = "/static/common/qrcode.min.js";
|
|
s.async = true;
|
|
s.onload = function () { resolve(); };
|
|
s.onerror = function () { reject(new Error("qrcode load failed")); };
|
|
document.head.appendChild(s);
|
|
});
|
|
return qrcodeLoading;
|
|
}
|
|
|
|
function getAppConfig() {
|
|
return window.__APP_CONFIG__ || {};
|
|
}
|
|
|
|
/** 二维码、跳转和复制链接统一使用 config/info.newAddressInfo.appAddr。 */
|
|
function getAppURL() {
|
|
var cfg = getAppConfig();
|
|
var fromCfg = String((cfg && cfg.appAddr) || "").trim();
|
|
if (fromCfg) return fromCfg;
|
|
return String(mask.getAttribute("data-download-app-url") || "").trim();
|
|
}
|
|
|
|
function isHTTPURL(value) {
|
|
return /^https?:\/\//i.test(String(value || "").trim());
|
|
}
|
|
|
|
function isMobileViewport() {
|
|
return typeof window.matchMedia === "function"
|
|
? window.matchMedia("(max-width: 767px)").matches
|
|
: (window.innerWidth || 0) <= 767;
|
|
}
|
|
|
|
function qrLevels() {
|
|
var QR = window.QRCode;
|
|
if (QR && QR.CorrectLevel) {
|
|
return [QR.CorrectLevel.L, QR.CorrectLevel.M, QR.CorrectLevel.Q, QR.CorrectLevel.H];
|
|
}
|
|
return [1, 0, 3, 2];
|
|
}
|
|
|
|
function paintQRModel(model, size) {
|
|
if (!model || typeof model.getModuleCount !== "function") return null;
|
|
var n = model.getModuleCount();
|
|
if (!n) return null;
|
|
var canvas = document.createElement("canvas");
|
|
canvas.width = size;
|
|
canvas.height = size;
|
|
var ctx = canvas.getContext("2d");
|
|
if (!ctx) return null;
|
|
ctx.fillStyle = "#ffffff";
|
|
ctx.fillRect(0, 0, size, size);
|
|
var cell = size / n;
|
|
ctx.fillStyle = "#000000";
|
|
for (var row = 0; row < n; row++) {
|
|
for (var col = 0; col < n; col++) {
|
|
if (model.isDark(row, col)) {
|
|
ctx.fillRect(
|
|
Math.floor(col * cell),
|
|
Math.floor(row * cell),
|
|
Math.ceil(cell),
|
|
Math.ceil(cell)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
return canvas;
|
|
}
|
|
|
|
function encodeQRCanvas(text, size) {
|
|
var QR = window.QRCode;
|
|
if (typeof QR !== "function") return null;
|
|
var holder = document.createElement("div");
|
|
holder.style.cssText =
|
|
"position:fixed;left:0;top:0;width:" +
|
|
size +
|
|
"px;height:" +
|
|
size +
|
|
"px;opacity:0;pointer-events:none;z-index:-1;";
|
|
document.body.appendChild(holder);
|
|
var levels = qrLevels();
|
|
try {
|
|
for (var i = 0; i < levels.length; i++) {
|
|
holder.innerHTML = "";
|
|
try {
|
|
var inst = new QR(holder, {
|
|
text: text,
|
|
width: size,
|
|
height: size,
|
|
colorDark: "#000000",
|
|
colorLight: "#ffffff",
|
|
correctLevel: levels[i],
|
|
});
|
|
var painted = paintQRModel(inst && inst._oQRCode, size);
|
|
if (painted) return painted;
|
|
var srcCanvas = holder.querySelector("canvas");
|
|
if (srcCanvas) {
|
|
var copy = document.createElement("canvas");
|
|
copy.width = size;
|
|
copy.height = size;
|
|
copy.getContext("2d").drawImage(srcCanvas, 0, 0, size, size);
|
|
return copy;
|
|
}
|
|
} catch (e) {
|
|
/* try next level */
|
|
}
|
|
}
|
|
return null;
|
|
} finally {
|
|
if (holder.parentNode) holder.parentNode.removeChild(holder);
|
|
}
|
|
}
|
|
|
|
function renderQR(el, text, size) {
|
|
if (!el || !text) return false;
|
|
el.innerHTML = "";
|
|
var img = document.createElement("img");
|
|
img.alt = "下载二维码";
|
|
img.title = "黄果短剧 App 下载二维码";
|
|
img.width = size;
|
|
img.height = size;
|
|
var canvas = encodeQRCanvas(text, size);
|
|
if (canvas) {
|
|
try {
|
|
img.src = canvas.toDataURL("image/png");
|
|
el.appendChild(img);
|
|
return true;
|
|
} catch (e) {
|
|
el.appendChild(canvas);
|
|
return true;
|
|
}
|
|
}
|
|
img.src =
|
|
"https://api.qrserver.com/v1/create-qr-code/?size=" +
|
|
size +
|
|
"x" +
|
|
size +
|
|
"&margin=0&data=" +
|
|
encodeURIComponent(text);
|
|
el.appendChild(img);
|
|
return true;
|
|
}
|
|
|
|
function syncDownloadUI(force) {
|
|
var url = getAppURL();
|
|
if (!url) return;
|
|
|
|
if (force || !qrReady) {
|
|
var pcOk = renderQR(pcQRBox, url, 164);
|
|
var h5Ok = renderQR(h5QRBox, url, 164);
|
|
qrReady = !!(pcOk || h5Ok);
|
|
}
|
|
|
|
if (h5URLEl) {
|
|
h5URLEl.textContent = "官方网址 " + url;
|
|
if (isHTTPURL(url)) {
|
|
h5URLEl.href = url;
|
|
h5URLEl.removeAttribute("aria-disabled");
|
|
} else {
|
|
h5URLEl.href = "#";
|
|
h5URLEl.setAttribute("aria-disabled", "true");
|
|
}
|
|
}
|
|
if (downloadCTA) {
|
|
downloadCTA.href = isHTTPURL(url) ? url : "#";
|
|
downloadCTA.toggleAttribute("aria-disabled", !isHTTPURL(url));
|
|
}
|
|
}
|
|
|
|
function openDialog(event) {
|
|
if (event) event.preventDefault();
|
|
mask.classList.add("is-open");
|
|
document.body.style.overflow = "hidden";
|
|
loadQRCodeLib()
|
|
.catch(function () { /* fallback image QR still works */ })
|
|
.then(function () {
|
|
requestAnimationFrame(function () {
|
|
requestAnimationFrame(function () {
|
|
syncDownloadUI(true);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
function closeDialog() {
|
|
mask.classList.remove("is-open");
|
|
document.body.style.overflow = "";
|
|
}
|
|
|
|
function setPlatform(platform) {
|
|
if (platform !== "ios" && platform !== "android") return;
|
|
currentPlatform = platform;
|
|
platformBtns.forEach(function (btn) {
|
|
btn.classList.toggle(
|
|
"is-active",
|
|
btn.getAttribute("data-download-platform") === platform
|
|
);
|
|
});
|
|
if (copyBtn) {
|
|
copyBtn.textContent = "复制下载链接";
|
|
copyBtn.classList.remove("is-copied");
|
|
}
|
|
}
|
|
|
|
function copyLink() {
|
|
var url = getAppURL();
|
|
if (!url || !copyBtn) return;
|
|
|
|
function onSuccess() {
|
|
copyBtn.textContent = "已复制";
|
|
copyBtn.classList.add("is-copied");
|
|
if (copyTimer) clearTimeout(copyTimer);
|
|
copyTimer = setTimeout(function () {
|
|
copyBtn.textContent = "复制下载链接";
|
|
copyBtn.classList.remove("is-copied");
|
|
}, 1800);
|
|
}
|
|
|
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
navigator.clipboard.writeText(url).then(onSuccess).catch(function () {
|
|
fallbackCopy(url, onSuccess);
|
|
});
|
|
return;
|
|
}
|
|
fallbackCopy(url, onSuccess);
|
|
}
|
|
|
|
function fallbackCopy(text, onSuccess) {
|
|
var input = document.createElement("input");
|
|
input.value = text;
|
|
input.setAttribute("readonly", "");
|
|
input.style.position = "fixed";
|
|
input.style.left = "-9999px";
|
|
document.body.appendChild(input);
|
|
input.select();
|
|
try {
|
|
document.execCommand("copy");
|
|
onSuccess();
|
|
} catch (e) {
|
|
/* ignore */
|
|
}
|
|
document.body.removeChild(input);
|
|
}
|
|
|
|
document.addEventListener("click", function (event) {
|
|
var openTrigger = event.target.closest("[data-download-app-open]");
|
|
if (openTrigger) {
|
|
// 移动端由带 appAddr 的原生链接直接跳转,不展示二维码弹窗。
|
|
if (isMobileViewport()) return;
|
|
openDialog(event);
|
|
return;
|
|
}
|
|
|
|
if (event.target.closest("[data-download-app-close]")) {
|
|
closeDialog();
|
|
return;
|
|
}
|
|
|
|
if (event.target === mask) {
|
|
closeDialog();
|
|
return;
|
|
}
|
|
|
|
var platformBtn = event.target.closest("[data-download-platform]");
|
|
if (platformBtn && mask.contains(platformBtn)) {
|
|
setPlatform(platformBtn.getAttribute("data-download-platform"));
|
|
return;
|
|
}
|
|
|
|
if (event.target.closest("[data-download-copy]")) {
|
|
copyLink();
|
|
}
|
|
});
|
|
|
|
document.addEventListener("keydown", function (event) {
|
|
if (event.key === "Escape" && mask.classList.contains("is-open")) {
|
|
closeDialog();
|
|
}
|
|
});
|
|
|
|
window.openDownloadAppDialog = openDialog;
|
|
window.closeDownloadAppDialog = closeDialog;
|
|
})();
|