(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);