/**** 此文件说明请看注释 *****/ // 可以用自己项目的请求方法 // 请求配置说明:https://ext.dcloud.net.cn/plugin?id=822 import { getAppVersion } from '@/api/common'; /**** 结束 *****/ const platform = uni.getSystemInfoSync().platform; // 主颜色 const $mainColor = "FF5C03"; // 弹窗图标url const $iconUrl = "/uni_modules/uni-upgrade-center-app/static/app/bg_top.png"; const $upArrowUrl = "/uni_modules/uni-upgrade-center-app/static/app/ic_ar.png"; // 获取当前应用的版本号 export const getCurrentNo = function(callback) { // 获取本地应用资源版本号 plus.runtime.getProperty(plus.runtime.appid, function(inf) { callback && callback({ versionCode: inf.versionCode, versionName: inf.version }); }); } // 发起ajax请求获取服务端版本号 export const getServerNo = function(version, isPrompt = false, callback) { let isAndroid = platform == "android"; let type = isAndroid ? 1 : 2; getAppVersion(type).then(res => { if (res.code == 200) { let cVersion = version.versionCode; //用户当前版本 let appVersion = res.data.versionCode; //升级包版本 if (cVersion < appVersion) { callback && callback(res.data); } else if (isPrompt) { uni.showToast({ title: "当前已是最新版本", icon: "none" }); } } else if (isPrompt) { uni.showToast({ title: "检查更新失败", icon: "none" }); } }).catch(err => { if (isPrompt) { uni.showToast({ title: "检查更新失败", icon: "none" }); } }) } // 从服务器下载应用资源包(wgt文件) const getDownload = function(data) { let downloadUrl = data.url; if(data.isHotForce && !!data.wgtUrl){ downloadUrl = data.wgtUrl; } if(plus.runtime.channel == "baidu" && data.baiduUrl){ downloadUrl = data.baiduUrl; } console.log("qxj downloadUrl",downloadUrl); let popupData = Object.assign({}, data, { progress: true, buttonNum: 2 }); if(data.isForce || data.isHotForce){ popupData.buttonNum = 0; } // 判断是否为 WGT 文件,如果是则隐藏按钮 const isWGT = downloadUrl && String(downloadUrl).match(/\.wgt$/i); if (isWGT) { popupData.buttonNum = 0; } else if (popupData.buttonNum !== 0) { // 非强制且非WGT,默认显示关闭按钮 popupData.buttonNum = 1; } let dtask; let lastProgressValue = 0; let popupObj = downloadPopup(popupData); // 使用 plus.downloader 创建下载任务,参考官方逻辑,虽然官方使用的是 uni.downloadFile,但 plus.downloader 提供更细粒度的控制,且此处已有进度条 UI 适配 dtask = plus.downloader.createDownload(downloadUrl, { filename: "_doc/update/" }, function(download, status) { if (status == 200) { // 下载完成,如果是 WGT 热更新,立即提示正在安装 const isWGT = download.filename.match(/\.wgt$/i); if (isWGT) { popupObj.change({ contentText: "资源更新完成,正在安装...", buttonNum: 0, progress: false }); } // 安装应用资源包 // force: true 强制安装,忽略版本号校验,解决 -1205 错误(WGT安装包中manifest.json文件的version版本不匹配) // 因为前置逻辑已经校验过服务端版本号,此处强制安装以确保更新成功 plus.runtime.install(download.filename, { force: isWGT }, function() { // 安装成功 if (isWGT) { // WGT热更新:提示重启并自动重启 popupObj.change({ contentText: "安装成功,即将重启...", buttonNum: 0, // 不显示按钮 progress: false }); setTimeout(() => { plus.runtime.restart(); }, 500); // 缩短重启等待时间以提升体验 } else { // APK更新:系统安装界面已调起,直接关闭弹窗即可 // 用户在系统安装界面操作,不需要应用内重启 popupObj.cancel(); } }, function(e) { popupObj.cancel(); plus.nativeUI.alert("安装更新失败[" + e.code + "]:" + e.message); }); } else { popupObj.change({ contentText: "文件下载失败", buttonNum: 1, progress: false }); } }); dtask.start(); // 监听下载进度 dtask.addEventListener("statechanged", function(task, status) { switch (task.state) { case 1: // 开始 popupObj.change({ progressValue: 0, progressTip: "准备下载...", progress: true }); break; case 2: // 已连接到服务器 popupObj.change({ progressValue: 0, progressTip: "开始下载...", progress: true }); break; case 3: // 下载中 if (task.totalSize > 0) { const progress = parseInt(task.downloadedSize / task.totalSize * 100); if (progress - lastProgressValue >= 2 || progress === 100) { lastProgressValue = progress; popupObj.change({ progressValue: progress, progressTip: "已下载 " + progress + "%", progress: true }); } } break; case 4: // 下载完成 // 已经在 createDownload 回调中处理 break; } }); // 取消下载 popupObj.cancelDownload = function(){ dtask && dtask.abort(); uni.showToast({ title: "已取消下载", icon: "none" }); } // 关闭/重启 (虽然此处命名为 reboot,但在失败或完成时可能是关闭弹窗) popupObj.reboot = function(){ // 这里的逻辑取决于 popupObj.change 后的状态 // 如果是下载失败,点击关闭只是关闭弹窗 // 如果是安装成功,通常会自动重启,或者点击后重启 // 目前 buttonNum=1 时绑定的是 reboot popupObj.cancel(); } } // 文字换行 function drawtext(text, maxWidth) { if (!text) { return [{ type: "text", content: "" }]; } text = String(text); let textArr = text.split(""); let len = textArr.length; // 上个节点 let previousNode = 0; // 记录节点宽度 let nodeWidth = 0; // 文本换行数组 let rowText = []; // 如果是字母,侧保存长度 let letterWidth = 0; // 汉字宽度 let chineseWidth = 14; // otherFont宽度 let otherWidth = 7; for (let i = 0; i < len; i++) { if (/[\u4e00-\u9fa5]|[\uFE30-\uFFA0]/g.test(textArr[i])) { if(letterWidth > 0){ if(nodeWidth + chineseWidth + letterWidth * otherWidth > maxWidth){ rowText.push({ type: "text", content: text.substring(previousNode, i) }); previousNode = i; nodeWidth = chineseWidth; letterWidth = 0; } else { nodeWidth += chineseWidth + letterWidth * otherWidth; letterWidth = 0; } } else { if(nodeWidth + chineseWidth > maxWidth){ rowText.push({ type: "text", content: text.substring(previousNode, i) }); previousNode = i; nodeWidth = chineseWidth; }else{ nodeWidth += chineseWidth; } } } else { if(/\n/g.test(textArr[i])){ rowText.push({ type: "break", content: text.substring(previousNode, i) }); previousNode = i + 1; nodeWidth = 0; letterWidth = 0; }else if(textArr[i] == "\\" && textArr[i + 1] == "n"){ rowText.push({ type: "break", content: text.substring(previousNode, i) }); previousNode = i + 2; nodeWidth = 0; letterWidth = 0; }else if(/[a-zA-Z0-9]/g.test(textArr[i])){ letterWidth += 1; if(nodeWidth + letterWidth * otherWidth > maxWidth){ rowText.push({ type: "text", content: text.substring(previousNode, i + 1 - letterWidth) }); previousNode = i + 1 - letterWidth; nodeWidth = letterWidth * otherWidth; letterWidth = 0; } } else{ if(nodeWidth + otherWidth > maxWidth){ rowText.push({ type: "text", content: text.substring(previousNode, i) }); previousNode = i; nodeWidth = otherWidth; }else{ nodeWidth += otherWidth; } } } } if (previousNode < len) { rowText.push({ type: "text", content: text.substring(previousNode, len) }); } return rowText; } // 是否更新弹窗 function updatePopup(data, callback) { // 弹窗遮罩层 let maskLayer = new plus.nativeObj.View("maskLayer", { //先创建遮罩层 top: '0px', left: '0px', height: '100%', width: '100%', backgroundColor: 'rgba(0,0,0,0.5)' }); // 以下为计算菜单的nview绘制布局,为固定算法,使用者无关关心 const screenWidth = plus.screen.resolutionWidth; const screenHeight = plus.screen.resolutionHeight; // 弹窗容器宽度 const popupViewWidth = screenWidth * 0.75; // 弹窗容器的Padding const viewContentPadding = 20; // 弹窗容器的宽度 const viewContentWidth = parseInt(popupViewWidth - (viewContentPadding * 2)); // 描述的列表 const descriptionList = drawtext(data.note, viewContentWidth); // 弹窗容器高度 let popupViewHeight = 130 + 20 + 20 + 90 + 10; let popupViewContentList = [ { tag: 'rect', // 绘制白色背景 rectStyles: { color: "#FFFFFF", radius: "8px" }, position: { top: "40px", height: "100%", // Will be updated later if needed, but since we know the height we can use 100% or calc } }, { src: $iconUrl, id: "logo", tag: "img", position: { top: "0px", left: "0px", width: popupViewWidth + "px", height: "130px", } }, { src: $upArrowUrl, id: "upArrow", tag: "img", position: { top: "15px", left: (popupViewWidth / 2 - 35) + "px", width: "70px", height: "70px", } }, { tag: 'font', id: 'title', text: "发现新版本 ", textStyles: { size: '18px', color: "#333", weight: "bold", whiteSpace: "normal" }, position: { top: '140px', left: viewContentPadding + "px", width: "90px", height: "30px", } }, { tag: 'rect', // 版本号背景 rectStyles:{ color: "#FF5C03", radius: "10px" }, position: { top: '145px', left: viewContentPadding + 95 + "px", width: "40px", height: "20px", } }, { tag: 'font', id: 'version', text: data.versionName || "1.0.0", textStyles: { size: '12px', color: "#FFFFFF", }, position: { top: '145px', left: viewContentPadding + 95 + "px", width: "40px", height: "20px", } }]; const textHeight = 18; let contentTop = 180; descriptionList.forEach((item,index) => { if(index > 0){ popupViewHeight += textHeight; contentTop += textHeight; } popupViewContentList.push({ tag: 'font', id: 'content' + index + 1, text: item.content, textStyles: { size: '14px', color: "#666", lineSpacing: "50%", align: "left" }, position: { top: contentTop + "px", left: viewContentPadding + "px", width: viewContentWidth + "px", height: textHeight + "px", } }); if(item.type == "break"){ contentTop += 10; popupViewHeight += 10; } }); // 弹窗内容 let popupView = new plus.nativeObj.View("popupView", { //创建底部图标菜单 tag: "rect", top: (screenHeight - popupViewHeight) / 2 + "px", left: '12.5%', height: popupViewHeight + "px", width: "75%" }); // 绘制白色背景 popupViewContentList[0].position.height = popupViewHeight - 40 + "px"; // 判断是否强制更新,强制更新只显示一个按钮 const isForce = data.isForce || data.isHotForce; if (isForce) { // 绘制单个底边按钮 (强制更新) popupViewContentList.push({ tag: 'rect', rectStyles: { radius: "20px", color: $mainColor, }, position: { bottom: viewContentPadding + 'px', left: viewContentPadding + "px", width: viewContentWidth + "px", height: "40px", } }); popupViewContentList.push({ tag: 'font', id: 'confirmText', text: "立即下载更新", textStyles: { size: '16px', color: "#FFF", lineSpacing: "0%", whiteSpace: "normal" }, position: { bottom: viewContentPadding + 'px', left: viewContentPadding + "px", width: viewContentWidth + "px", height: "40px", } }); } else { // 绘制底边按钮 (非强制更新,显示暂不升级) popupViewContentList.push({ tag: 'rect', rectStyles: { radius: "20px", borderColor: "#f1f1f1", borderWidth: "1px", }, position: { bottom: viewContentPadding + 'px', left: viewContentPadding + "px", width: (viewContentWidth - viewContentPadding) / 2 + "px", height: "40px", } }); // 绘制底边按钮 (非强制更新,显示立即升级) popupViewContentList.push({ tag: 'rect', rectStyles: { radius: "20px", color: $mainColor, }, position: { bottom: viewContentPadding + 'px', left: ((viewContentWidth - viewContentPadding) / 2 + viewContentPadding * 2) + "px", width: (viewContentWidth - viewContentPadding) / 2 + "px", height: "40px", } }); popupViewContentList.push({ tag: 'font', id: 'cancelText', text: "取消", textStyles: { size: '16px', color: "#666", lineSpacing: "0%", whiteSpace: "normal" }, position: { bottom: viewContentPadding + 'px', left: viewContentPadding + "px", width: (viewContentWidth - viewContentPadding) / 2 + "px", height: "40px", } }); popupViewContentList.push({ tag: 'font', id: 'confirmText', text: "立即下载更新", textStyles: { size: '16px', color: "#FFF", lineSpacing: "0%", whiteSpace: "normal" }, position: { bottom: viewContentPadding + 'px', left: ((viewContentWidth - viewContentPadding) / 2 + viewContentPadding * 2) + "px", width: (viewContentWidth - viewContentPadding) / 2 + "px", height: "40px", } }); } popupView.draw(popupViewContentList); popupView.addEventListener("click", function(e) { let maxTop = popupViewHeight - viewContentPadding; let maxLeft = popupViewWidth - viewContentPadding; let buttonWidth = (viewContentWidth - viewContentPadding) / 2; if (e.clientY > maxTop - 40 && e.clientY < maxTop) { if (isForce) { // 强制更新只有立即升级 if (e.clientX > viewContentPadding && e.clientX < maxLeft) { maskLayer.hide(); popupView.hide(); callback && callback(); setTimeout(() => { maskLayer.close(); popupView.close(); }, 100); } } else { // 取消 if (e.clientX > viewContentPadding && e.clientX < maxLeft - buttonWidth - viewContentPadding) { maskLayer.hide(); popupView.hide(); setTimeout(() => { maskLayer.close(); popupView.close(); }, 100); } else if (e.clientX > maxLeft - buttonWidth && e.clientX < maxLeft) { // 立即升级 maskLayer.hide(); popupView.hide(); callback && callback(); setTimeout(() => { maskLayer.close(); popupView.close(); }, 100); } } } }); // 显示弹窗 maskLayer.show(); popupView.show(); } // 文件下载的弹窗 function downloadPopup(data) { // 弹窗遮罩层 let maskLayer = new plus.nativeObj.View("downloadMaskLayer", { //先创建遮罩层 top: '0px', left: '0px', height: '100%', width: '100%', backgroundColor: 'rgba(0,0,0,0.5)' }); let popupViewData = downloadPopupDrawing(data); // 弹窗内容 let popupView = new plus.nativeObj.View("downloadPopupView", { //创建底部图标菜单 tag: "rect", top: (popupViewData.screenHeight - popupViewData.popupViewHeight) / 2 + "px", left: '12.5%', height: popupViewData.popupViewHeight + "px", width: "75%", }); let progressValue = 0; let progressTop = popupViewData.progressTop; let progressTip = 0; let contentText = 0; let buttonNum = 2; if(data.buttonNum >= 0){ buttonNum = data.buttonNum; } popupView.draw(popupViewData.elementList); let callbackData = { change: function(res) { let progressElement = []; if(res.progressValue){ progressValue = res.progressValue; let progressWidth = res.progressValue / 100 * popupViewData.viewContentWidth; // 绘制进度条 progressElement.push({ tag: 'rect', //绘制进度条背景 id: 'progressValueBg', rectStyles:{ radius: "4px", color: $mainColor }, position:{ top: progressTop + 40 + 'px', left: popupViewData.viewContentPadding + "px", width: progressWidth + "px", height: "8px" } }); } if(res.progressTip){ progressTip = res.progressTip; progressElement.push({ tag: 'font', id: 'progressValue', text: res.progressTip, textStyles: { size: '14px', color: $mainColor, whiteSpace: "normal" }, position: { top: progressTop + 'px', height: "30px" } }); } if(res.contentText){ contentText = res.contentText; progressElement.push({ tag: 'font', id: 'content', text: res.contentText, textStyles: { size: '14px', color: $mainColor, whiteSpace: "normal" }, position: { top: progressTop + 'px', height: "30px", } }); } if(res.buttonNum >= 0 && buttonNum != res.buttonNum){ buttonNum = res.buttonNum; popupView.reset(); popupViewData = downloadPopupDrawing(Object.assign({ progressTip: res.contentText || "准备下载...", }, data, { buttonNum: buttonNum, })); progressTop = popupViewData.progressTop; let newElement = []; popupViewData.elementList.map((item, index) => { let have = false; progressElement.forEach((childItem, childIndex) => { if(item.id == childItem.id){ have = true; } }); if(!have){ newElement.push(item); } }); progressElement = newElement.concat(progressElement); popupView.setStyle({ tag: "rect", top: (popupViewData.screenHeight - popupViewData.popupViewHeight) / 2 + "px", left: '12.5%', height: popupViewData.popupViewHeight + "px", width: "75%", }); popupView.draw(progressElement); }else{ popupView.draw(progressElement); } }, cancel: function() { maskLayer.hide(); popupView.hide(); setTimeout(() => { maskLayer.close(); popupView.close(); }, 100); }, reboot: function() { maskLayer.hide(); popupView.hide(); setTimeout(() => { maskLayer.close(); popupView.close(); }, 100); }, cancelDownload: function() { maskLayer.hide(); popupView.hide(); setTimeout(() => { maskLayer.close(); popupView.close(); }, 100); } } popupView.addEventListener("click", function(e) { let maxTop = popupViewData.popupViewHeight - popupViewData.viewContentPadding; let maxLeft = popupViewData.popupViewWidth - popupViewData.viewContentPadding; if (e.clientY > maxTop - 40 && e.clientY < maxTop) { if (buttonNum == 1) { // 单按钮 if (e.clientX > popupViewData.viewContentPadding && e.clientX < maxLeft) { callbackData.reboot(); } } else if (buttonNum == 2) { // 双按钮 let buttonWidth = (popupViewData.viewContentWidth - popupViewData.viewContentPadding) / 2; if (e.clientX > popupViewData.viewContentPadding && e.clientX < maxLeft - buttonWidth - popupViewData.viewContentPadding) { callbackData.cancelDownload(); } else if (e.clientX > maxLeft - buttonWidth && e.clientX < maxLeft) { maskLayer.hide(); popupView.hide(); setTimeout(() => { maskLayer.close(); popupView.close(); }, 100); } } } }); // 显示弹窗 maskLayer.show(); popupView.show(); // 改变进度条 return callbackData; } // 文件下载的弹窗绘图 function downloadPopupDrawing(data){ // 以下为计算菜单的nview绘制布局,为固定算法,使用者无关关心 const screenWidth = plus.screen.resolutionWidth; const screenHeight = plus.screen.resolutionHeight; //弹窗容器宽度 const popupViewWidth = screenWidth * 0.75; // 弹窗容器的Padding const viewContentPadding = 20; // 弹窗容器的宽度 const viewContentWidth = popupViewWidth - (viewContentPadding * 2); // 描述的列表 const descriptionList = drawtext(data.note || "健康守护系统更新中~~请稍候", viewContentWidth); // 弹窗容器高度 let popupViewHeight = 130 + 20 + 20 + 90 + 10; let progressTip = data.progressTip || "准备下载..."; let contentText = data.contentText || "健康守护系统更新中~~请稍候"; let elementList = [ { src: $iconUrl, id: "logo", tag: "img", position: { top: "0px", left: "0px", width: popupViewWidth + "px", height: "130px", } }, { src: $upArrowUrl, id: "upArrow", tag: "img", position: { top: "15px", left: (popupViewWidth / 2 - 35) + "px", width: "70px", height: "70px", } }, { tag: 'rect', //背景色 color: '#FFFFFF', rectStyles:{ radius: "8px" }, position: { top: "40px", height: popupViewHeight - 40 + "px", } }, { tag: 'font', id: 'title', text: "发现新版本 ", textStyles: { size: '18px', color: "#333", weight: "bold", whiteSpace: "normal" }, position: { top: '140px', left: viewContentPadding + "px", width: "90px", height: "30px", } }, { tag: 'rect', // 版本号背景 rectStyles:{ color: "#FF5C03", radius: "10px" }, position: { top: '145px', left: viewContentPadding + 95 + "px", width: "40px", height: "20px", } }, { tag: 'font', id: 'version', text: data.versionName || "1.0.0", textStyles: { size: '12px', color: "#FFFFFF", }, position: { top: '145px', left: viewContentPadding + 95 + "px", width: "40px", height: "20px", } } ]; const textHeight = 18; let contentTop = 180; descriptionList.forEach((item,index) => { if(index > 0){ popupViewHeight += textHeight; contentTop += textHeight; } elementList.push({ tag: 'font', id: 'content' + index + 1, text: item.content, textStyles: { size: '14px', color: "#666", lineSpacing: "50%", align: "left" }, position: { top: contentTop + "px", left: viewContentPadding + "px", width: viewContentWidth + "px", height: textHeight + "px", } }); if(item.type == "break"){ contentTop += 10; popupViewHeight += 10; } }); let progressTop = 0; if (data.progress) { popupViewHeight += viewContentPadding * 2 + 40; progressTop = contentTop + 40; elementList = elementList.concat([ { tag: 'font', id: 'progressValue', text: progressTip, textStyles: { size: '14px', color: $mainColor, whiteSpace: "normal" }, position: { top: progressTop + 'px', height: "30px" } }, { tag: 'rect', //绘制进度条背景 id: 'progressBg', rectStyles:{ radius: "4px", borderColor: "#f1f1f1", borderWidth: "1px", }, position:{ top: progressTop + 40 + 'px', left: viewContentPadding + "px", width: viewContentWidth + "px", height: "8px" } }, ]); } if(data.buttonNum == 2){ popupViewHeight += viewContentPadding + 30; elementList = elementList.concat([ { tag: 'rect', //绘制底边按钮 rectStyles:{ radius: "20px", borderColor: "#f1f1f1", borderWidth: "1px", }, position:{ bottom: viewContentPadding + 'px', left: viewContentPadding + "px", width: (viewContentWidth - viewContentPadding) / 2 + "px", height: "40px" } }, { tag: 'rect', //绘制底边按钮 rectStyles:{ radius: "20px", color: $mainColor, }, position:{ bottom: viewContentPadding + 'px', left: ((viewContentWidth - viewContentPadding) / 2 + viewContentPadding * 2) + "px", width: (viewContentWidth - viewContentPadding) / 2 + "px", height: "40px" } }, { tag: 'font', id: 'cancelText', text: "取消", textStyles: { size: '16px', color: "#666", lineSpacing: "0%", whiteSpace: "normal" }, position: { bottom: viewContentPadding + 'px', left: viewContentPadding + "px", width: (viewContentWidth - viewContentPadding) / 2 + "px", height: "40px", } }, { tag: 'font', id: 'confirmText', text: "立即下载更新", textStyles: { size: '16px', color: "#FFF", lineSpacing: "0%", whiteSpace: "normal" }, position: { bottom: viewContentPadding + 'px', left: ((viewContentWidth - viewContentPadding) / 2 + viewContentPadding * 2) + "px", width: (viewContentWidth - viewContentPadding) / 2 + "px", height: "40px", } } ]); } if(data.buttonNum == 1){ popupViewHeight += viewContentPadding + 30; elementList = elementList.concat([ { tag: 'rect', //绘制底边按钮 rectStyles:{ radius: "20px", color: $mainColor, }, position:{ bottom: viewContentPadding + 'px', left: viewContentPadding + "px", width: viewContentWidth + "px", height: "40px" } }, { tag: 'font', id: 'confirmText', text: "立即下载更新", textStyles: { size: '16px', color: "#FFF", lineSpacing: "0%", whiteSpace: "normal" }, position: { bottom: viewContentPadding + 'px', left: viewContentPadding + "px", width: viewContentWidth + "px", height: "40px", } } ]); } return { popupView: popupView, popupViewHeight: popupViewHeight, popupViewWidth: popupViewWidth, screenHeight: screenHeight, viewContentWidth: viewContentWidth, viewContentPadding: viewContentPadding, progressTop: progressTop, elementList: elementList }; } export function openDownload(data) { updatePopup(data, function() { let isWgt = /\.wgt$/i.test(data.url) || (data.isHotForce && !!data.wgtUrl); if (isWgt) { getDownload(data); } else if(/\.html$/i.test(data.url)){ plus.runtime.openURL(data.url); } else { if (platform == "android") { getDownload(data); } else { plus.runtime.openURL(data.url); } } }); } export function appCheckUdate(isPrompt = false) { getCurrentNo(versionInfo => { getServerNo(versionInfo, isPrompt, res => { updatePopup(res, function() { let isWgt = /\.wgt$/i.test(res.url) || (res.isHotForce && !!res.wgtUrl); if (isWgt) { getDownload(res); } else if(/\.html$/i.test(res.url)){ plus.runtime.openURL(res.url); } else { if (platform == "android") { getDownload(res); } else { plus.runtime.openURL(res.url); } } }); }); }); }