upgrade.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. const compareVersion = (a = "", b = "") => {
  2. const aList = String(a)
  3. .split(".")
  4. .map((item) => Number(item || 0));
  5. const bList = String(b)
  6. .split(".")
  7. .map((item) => Number(item || 0));
  8. const length = Math.max(aList.length, bList.length);
  9. for (let i = 0; i < length; i += 1) {
  10. const aNum = aList[i] || 0;
  11. const bNum = bList[i] || 0;
  12. if (aNum > bNum) return 1;
  13. if (aNum < bNum) return -1;
  14. }
  15. return 0;
  16. };
  17. let appUpgradePromise = null;
  18. /**
  19. * 全局执行 APK 整包更新。
  20. *
  21. * App.vue / 页面全局 onShow 都可能触发更新检查,因此这里不能依赖某个页面
  22. * 是否挂载了 upgrade 组件。使用 uni 的全局提示能力后,用户停留在任意页面
  23. * 都能收到更新提示;模块内 Promise 同时避免重复弹窗和重复下载。
  24. */
  25. export const runAppUpgrade = ({
  26. appVersion: name,
  27. note: content = "",
  28. url,
  29. forceUpdate = true,
  30. }) => {
  31. if (appUpgradePromise) return appUpgradePromise;
  32. appUpgradePromise = new Promise((resolve, reject) => {
  33. uni.showModal({
  34. title: `发现新版本 V${name}`,
  35. content: content || "检测到新的应用版本,是否立即更新?",
  36. showCancel: !forceUpdate,
  37. confirmText: "立即更新",
  38. cancelText: "稍后再说",
  39. success: async ({ confirm }) => {
  40. if (!confirm) {
  41. resolve({ status: "cancelled" });
  42. return;
  43. }
  44. try {
  45. uni.showLoading({
  46. title: "下载更新中",
  47. mask: true,
  48. });
  49. const fileName = await downloadApp(url, (percent) => {
  50. uni.showLoading({
  51. title: `下载中 ${percent}%`,
  52. mask: true,
  53. });
  54. });
  55. uni.hideLoading();
  56. installApp(fileName);
  57. resolve({ status: "install-triggered" });
  58. } catch (error) {
  59. uni.hideLoading();
  60. reject(error);
  61. }
  62. },
  63. fail: reject,
  64. });
  65. }).finally(() => {
  66. appUpgradePromise = null;
  67. });
  68. return appUpgradePromise;
  69. };
  70. /**
  71. * @description 检查app版本是否需要升级
  72. * @param name:最新版本名称
  73. * @param code:最新版本号
  74. * @param content:更新内容
  75. * @param url:下载链接
  76. * @param forceUpdate:是否强制升级
  77. */
  78. export const checkVersion = async ({
  79. name, //最新版本名称
  80. code, //最新版本号
  81. content, //更新内容
  82. url, //下载链接
  83. forceUpdate, //是否强制升级
  84. }) => {
  85. const currentAppVersion = plus.runtime.version;
  86. if (compareVersion(code, currentAppVersion) > 0) {
  87. let platform = uni.getSystemInfoSync().platform; //手机平台
  88. if (platform === "android") {
  89. uni.$emit("upgrade-app", {
  90. name,
  91. content,
  92. url,
  93. forceUpdate,
  94. });
  95. return true;
  96. } else {
  97. uni.showModal({
  98. title: "发现新版本 V" + name,
  99. content: "请到App store进行升级",
  100. showCancel: false,
  101. });
  102. }
  103. }
  104. return false;
  105. };
  106. //获取当前页面url
  107. const getCurrentPageRoute = () => {
  108. let currentRoute;
  109. let pages = getCurrentPages(); // 获取栈实例
  110. if (pages && pages.length) {
  111. currentRoute = pages[pages.length - 1].route;
  112. }
  113. return currentRoute;
  114. };
  115. /**
  116. * @description H5+下载App
  117. * @param downloadUrl:App下载链接
  118. * @param progressCallBack:下载进度回调
  119. */
  120. export const downloadApp = (downloadUrl, progressCallBack = () => {}) => {
  121. console.log("🚀 ~ downloadApp ~ downloadUrl:", downloadUrl);
  122. // 将模拟进度变量移到事件监听外部,确保状态能累积
  123. let mockProgress = 0;
  124. // 进度更新节流控制变量
  125. let lastProgressTime = 0;
  126. const PROGRESS_INTERVAL = 300; // 限制300ms内最多更新一次进度
  127. return new Promise((resolve, reject) => {
  128. // 创建下载任务
  129. const downloadTask = plus.downloader.createDownload(
  130. downloadUrl,
  131. { method: "GET" },
  132. (task, status) => {
  133. console.log("🚀 ~ returnnewPromise ~ task, status:", task, status);
  134. if (status == 200) {
  135. resolve(task.filename);
  136. } else {
  137. reject("fail");
  138. uni.showToast({
  139. title: "下载失败",
  140. duration: 1500,
  141. icon: "none",
  142. });
  143. }
  144. }
  145. );
  146. downloadTask.addEventListener("statechanged", (task, status) => {
  147. // 获取当前时间戳用于节流控制
  148. const now = Date.now();
  149. switch (task.state) {
  150. case 1: // 开始
  151. mockProgress = 0; // 仅在开始时重置
  152. progressCallBack(0, 0, task.totalSize || 0);
  153. break;
  154. case 2: // 已连接到服务器
  155. break;
  156. case 3: // 已接收到数据
  157. const hasProgress = task.totalSize && task.totalSize > 0;
  158. if (hasProgress) {
  159. mockProgress = 0;
  160. const current = parseInt(
  161. (100 * task.downloadedSize) / task.totalSize
  162. );
  163. progressCallBack(current, task.downloadedSize, task.totalSize);
  164. } else {
  165. // 模拟进度:添加节流控制和平滑增长
  166. if (
  167. mockProgress < 95 &&
  168. now - lastProgressTime > PROGRESS_INTERVAL
  169. ) {
  170. // 根据时间间隔动态调整增长速度,避免停滞感
  171. const baseIncrement = 1;
  172. const maxIncrement =
  173. mockProgress < 30 ? 3 : mockProgress < 60 ? 2 : 1;
  174. const increment =
  175. Math.floor(Math.random() * maxIncrement) + baseIncrement;
  176. mockProgress = Math.min(mockProgress + increment, 95);
  177. lastProgressTime = now; // 更新最后更新时间
  178. progressCallBack(mockProgress, task.downloadedSize, 0);
  179. }
  180. }
  181. break;
  182. case 4: // 下载完成
  183. progressCallBack(100, task.downloadedSize, task.totalSize || 0);
  184. break;
  185. }
  186. });
  187. downloadTask.start();
  188. });
  189. };
  190. /**
  191. * @description H5+安装APP
  192. * @param fileName:app文件名
  193. * @param callBack:安装成功回调
  194. */
  195. export const installApp = (fileName, callBack = () => {}) => {
  196. console.log("🚀 ~ installApp ~ fileName:", fileName);
  197. //注册广播监听app安装情况
  198. onInstallListening(callBack);
  199. //开始安装
  200. plus.runtime.install(
  201. plus.io.convertLocalFileSystemURL(fileName),
  202. {},
  203. () => {
  204. //成功跳转到安装界面
  205. },
  206. function (error) {
  207. console.log("🚀 ~ plus.runtime.install ~ error:", error);
  208. uni.showToast({
  209. title: "安装失败",
  210. duration: 1500,
  211. icon: "none",
  212. });
  213. }
  214. );
  215. };
  216. /**
  217. * @description 注册广播监听APP是否成功
  218. * @param callBack:安装成功回调函数
  219. */
  220. const onInstallListening = (callBack = () => {}) => {
  221. let mainActivity = plus.android.runtimeMainActivity(); //获取activity
  222. //生成广播接收器
  223. let receiver = plus.android.implements(
  224. "io.dcloud.android.content.BroadcastReceiver",
  225. {
  226. onReceive: (context, intent) => {
  227. //接收广播回调
  228. plus.android.importClass(intent);
  229. mainActivity.unregisterReceiver(receiver); //取消监听
  230. callBack();
  231. },
  232. }
  233. );
  234. let IntentFilter = plus.android.importClass("android.content.IntentFilter");
  235. let Intent = plus.android.importClass("android.content.Intent");
  236. let filter = new IntentFilter();
  237. filter.addAction(Intent.ACTION_PACKAGE_ADDED); //监听apk安装
  238. filter.addDataScheme("package");
  239. mainActivity.registerReceiver(receiver, filter); //注册广播
  240. };