Files
web_huangguo_web/app/static/js/pages/dialogLoader.js
T
2026-09-23 16:50:34 +07:00

196 lines
6.2 KiB
JavaScript

(function (global) {
var definitions = {
download: {
endpoint: "/ui/dialog/download",
elementId: "downloadAppDialog",
css: "/static/css/downloadAppDialog.css?v=2",
js: "/static/js/pages/downloadAppDialog.js?v=4",
open: "openDownloadAppDialog",
},
};
var pending = Object.create(null);
var stubOpeners = Object.create(null);
function hasRealOpener(name) {
var definition = definitions[name];
return (
!!definition &&
typeof global[definition.open] === "function" &&
global[definition.open] !== stubOpeners[name]
);
}
function loadStylesheet(href) {
var existing = document.querySelector('link[rel="stylesheet"][href="' + href + '"]');
if (existing) return Promise.resolve();
return new Promise(function (resolve, reject) {
var link = document.createElement("link");
var settled = false;
function finish(ok, err) {
if (settled) return;
settled = true;
if (ok) resolve();
else reject(err || new Error("dialog stylesheet failed"));
}
link.rel = "stylesheet";
link.href = href;
link.onload = function () {
finish(true);
};
link.onerror = function () {
finish(false, new Error("dialog stylesheet failed"));
};
document.head.appendChild(link);
// 部分浏览器对缓存 CSS 不触发 onload,避免一直卡住弹窗
requestAnimationFrame(function () {
try {
if (link.sheet) finish(true);
} catch (e) {}
});
setTimeout(function () {
finish(true);
}, 1500);
});
}
function loadScript(src) {
var existing = document.querySelector('script[src="' + src + '"]');
if (existing) {
if (existing.getAttribute("data-loaded") === "1") return Promise.resolve();
return new Promise(function (resolve, reject) {
existing.addEventListener("load", resolve, { once: true });
existing.addEventListener("error", function () {
reject(new Error("dialog script failed"));
}, { once: true });
});
}
return new Promise(function (resolve, reject) {
var script = document.createElement("script");
script.src = src;
script.async = true;
script.onload = function () {
script.setAttribute("data-loaded", "1");
resolve();
};
script.onerror = function () {
reject(new Error("dialog script failed"));
};
document.head.appendChild(script);
});
}
function fetchFragment(definition) {
if (document.getElementById(definition.elementId)) {
return Promise.resolve();
}
return fetch(definition.endpoint, {
credentials: "same-origin",
headers: { "X-Requested-With": "XMLHttpRequest" },
}).then(function (response) {
if (!response.ok) throw new Error("dialog fragment failed");
return response.text();
}).then(function (html) {
if (document.getElementById(definition.elementId)) return;
var host = document.createElement("div");
host.innerHTML = html;
var dialog = host.querySelector("#" + definition.elementId);
if (!dialog) throw new Error("dialog fragment is invalid");
document.body.appendChild(dialog);
});
}
function ensureDialog(name) {
var definition = definitions[name];
if (!definition) return Promise.reject(new Error("unknown dialog"));
if (document.getElementById(definition.elementId) && hasRealOpener(name)) {
return Promise.resolve();
}
if (pending[name]) return pending[name];
pending[name] = loadStylesheet(definition.css)
.then(function () {
return fetchFragment(definition);
})
.then(function () {
if (hasRealOpener(name)) return;
return loadScript(definition.js);
})
.then(function () {
if (!document.getElementById(definition.elementId) || !hasRealOpener(name)) {
throw new Error("dialog opener is unavailable");
}
})
.catch(function (error) {
delete pending[name];
throw error;
});
return pending[name];
}
function openDialog(name, value) {
return ensureDialog(name)
.then(function () {
var opener = global[definitions[name].open];
if (typeof opener !== "function" || opener === stubOpeners[name]) {
throw new Error("dialog opener is unavailable");
}
return opener(value);
})
.catch(function (error) {
throw error;
});
}
Object.keys(definitions).forEach(function (name) {
var apiName = definitions[name].open;
var stub = function (value) {
return openDialog(name, value);
};
stubOpeners[name] = stub;
if (typeof global[apiName] !== "function") global[apiName] = stub;
});
function triggerName(target) {
if (!target || !target.closest) return "";
if (target.closest("[data-download-app-open]")) return "download";
return "";
}
function isMobileViewport() {
return typeof global.matchMedia === "function"
? global.matchMedia("(max-width: 767px)").matches
: (global.innerWidth || 0) <= 767;
}
document.addEventListener("click", function (event) {
var name = triggerName(event.target);
if (!name) return;
// 移动端下载入口保留原生链接行为,直接访问 appAddr,不加载下载弹窗。
if (name === "download" && isMobileViewport()) return;
event.preventDefault();
event.stopPropagation();
if (event.stopImmediatePropagation) event.stopImmediatePropagation();
openDialog(name).catch(function () {});
}, true);
function appDownloadURL() {
var el = document.querySelector("[data-app-download-url]");
var url = el ? (el.getAttribute("data-app-download-url") || "").trim() : "";
return url || "/download-app/";
}
// 付费内容统一入口:移动端直接跳下载地址,PC 弹下载二维码。
function openDownloadApp() {
if (isMobileViewport()) {
global.location.href = appDownloadURL();
return Promise.resolve();
}
return openDialog("download").catch(function () {
global.location.href = appDownloadURL();
});
}
global.ensureDialog = ensureDialog;
global.openDownloadApp = openDownloadApp;
})(window);