diff --git a/common/js/diy.js b/common/js/diy.js index 0f5f93e..db1e222 100644 --- a/common/js/diy.js +++ b/common/js/diy.js @@ -311,56 +311,72 @@ export default { // 否则广告弹窗会被隐私弹窗(z-index:5000)盖住, 真机上表现为"点了隐私协议后广告不弹" await this._waitPrivacyResolved(); if (!this.$refs.uniPopupWindow) return; + + // 先判定本次是否需要弹出, 避免无谓的图片预加载 + let shouldOpen = false; + let isFirstType = false; if (this.adv.advtype == 1) { // 首次弹出: 从未展示过才弹 var popwindow_count = uni.getStorageSync(this.id + this.name + '_popwindow_count'); if (popwindow_count == '' || popwindow_count == 0) { - this.$refs.uniPopupWindow.open(); - uni.setStorageSync(this.id + this.name + '_popwindow_count', 1); + shouldOpen = true; + isFirstType = true; } } else { // 每次弹出: 冷启动首次 或 真正"重新进入小程序"(App 前后台切换)时弹出, // 由 App.onShow 在重新进入时置位的信号驱动, 切 Tab 不会触发 App.onShow 故不弹 if (consumeNeedStartupAd() || isColdLaunchPending()) { - this.$refs.uniPopupWindow.open(); + shouldOpen = true; } } + if (!shouldOpen) return; + + // 关键: 广告图片是网络图, 而 uni-popup 用 v-if 在 open() 时才渲染 slot, + // 图片要等弹窗打开后才开始请求/解码, 真机网络慢时就会"一截一截"逐步显示。 + // 先把所有广告图片预加载进本地缓存, 全部就绪后再 open, 使弹窗一次性完整出现。 + await this._preloadAdvImages(this.adv.list); + if (!this.$refs.uniPopupWindow) return; + + this.$refs.uniPopupWindow.open(); + if (isFirstType) { + uni.setStorageSync(this.id + this.name + '_popwindow_count', 1); + } }, 500); - } - }, - /** - * 等待隐私协议流程结束后再弹启动广告 - * - 鸿蒙元服务: 隐私由系统托管, 应用层不弹自建弹窗, 直接放行 - * - 已同意过(storage privacyAgreed): 无需等待, 直接放行 - * - 未同意: 监听 PrivacyManager 广播的 privacy:agreed / privacy:disagreed, - * 用户点击隐私协议(同意/拒绝)关闭弹窗后放行 - * - 兜底超时: 隐私协议因规则未弹等情况永不 emit 时也要弹广告, 避免卡死 - * @param {number} [maxWait=15000] - * @returns {Promise} - */ - _waitPrivacyResolved(maxWait = 15000) { - // 鸿蒙元服务: 隐私协议由系统托管, 应用层不弹自建隐私弹窗, 无需等待 - // #ifdef MP-HARMONY - return Promise.resolve(); - // #endif - // 已经同意过 → 隐私弹窗不会再出现, 直接放行 - if (uni.getStorageSync('privacyAgreed')) return Promise.resolve(); - return new Promise((resolve) => { - let done = false; - const finish = () => { - if (done) return; - done = true; - uni.$off('privacy:agreed', finish); - uni.$off('privacy:disagreed', finish); - clearTimeout(timer); - resolve(); - }; - uni.$on('privacy:agreed', finish); - uni.$on('privacy:disagreed', finish); - // 兜底: 隐私协议因规则未弹 / 一直未处理时, 超时后也要弹广告 - const timer = setTimeout(finish, maxWait); - }); - }, + } + }, + /** + * 等待隐私协议流程结束后再弹启动广告 + * - 鸿蒙元服务: 隐私由系统托管, 应用层不弹自建弹窗, 直接放行 + * - 已同意过(storage privacyAgreed): 无需等待, 直接放行 + * - 未同意: 监听 PrivacyManager 广播的 privacy:agreed / privacy:disagreed, + * 用户点击隐私协议(同意/拒绝)关闭弹窗后放行 + * - 兜底超时: 隐私协议因规则未弹等情况永不 emit 时也要弹广告, 避免卡死 + * @param {number} [maxWait=15000] + * @returns {Promise} + */ + _waitPrivacyResolved(maxWait = 15000) { + // 鸿蒙元服务: 隐私协议由系统托管, 应用层不弹自建隐私弹窗, 无需等待 + // #ifdef MP-HARMONY + return Promise.resolve(); + // #endif + // 已经同意过 → 隐私弹窗不会再出现, 直接放行 + if (uni.getStorageSync('privacyAgreed')) return Promise.resolve(); + return new Promise((resolve) => { + let done = false; + const finish = () => { + if (done) return; + done = true; + uni.$off('privacy:agreed', finish); + uni.$off('privacy:disagreed', finish); + clearTimeout(timer); + resolve(); + }; + uni.$on('privacy:agreed', finish); + uni.$on('privacy:disagreed', finish); + // 兜底: 隐私协议因规则未弹 / 一直未处理时, 超时后也要弹广告 + const timer = setTimeout(finish, maxWait); + }); + }, // 无法从网络获取 diy 数据时:在页面内渲染错误横幅(各端可靠、一定可见) // 同时通过 uni.$emit 通知 splash-state 关闭启动屏 (否则 strict 模式永远不关) // @param {Object|string} [err] 网络异常对象 {errMsg, statusCode} / 服务端消息字符串 @@ -586,6 +602,44 @@ export default { console.warn('[DIY][getDiyInfo] no value in res.data', JSON.stringify(diyDataValue).slice(0, 200)); } }, + /** + * 预加载启动广告图片 + * 广告弹窗由 uni-popup 在 open() 时才用 v-if 渲染内容, 图片是网络图, + * 若等到弹窗打开后才开始加载, 真机网络慢时会出现"一截一截"逐步显示。 + * 这里提前把图片下载进本地缓存, 等全部加载完成(或超时)后再弹出, + * 使弹窗一次性完整呈现。 + * @param {Array} list - adv.list, 元素形如 { imageUrl, link } + * @param {number} [timeout=4000] - 单张兜底超时, 避免个别图片卡住导致广告永不弹出 + * @returns {Promise} + */ + _preloadAdvImages(list, timeout = 4000) { + if (!list || !list.length) return Promise.resolve(); + const tasks = list.map((item) => { + return new Promise((resolve) => { + const url = (item && item.imageUrl) ? this.$util.img(item.imageUrl) : ''; + if (!url) return resolve(); + let done = false; + const finish = () => { + if (done) return; + done = true; + clearTimeout(timer); + resolve(); + }; + const timer = setTimeout(finish, timeout); + try { + // getImageInfo 会下载图片并写入本地缓存, 之后 image 组件可直接命中缓存 + uni.getImageInfo({ + src: url, + success: finish, + fail: finish + }); + } catch (e) { + finish(); + } + }); + }); + return Promise.all(tasks).then(() => {}); + }, closePopupWindow() { this.$refs.uniPopupWindow.close(); },