upgrade-popup.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677
  1. <template>
  2. <view class="mask flex-center" v-if="shown">
  3. <view class="content botton-radius">
  4. <view class="content-top">
  5. <text class="content-top-text">{{ title }}</text>
  6. <image class="content-top" style="top: 0" width="100%" height="100%" src="/uni_modules/uni-upgrade-center-app/static/app/bg_top.png"></image>
  7. </view>
  8. <view class="content-header"></view>
  9. <view class="content-body">
  10. <view class="title">
  11. <text>{{ subTitle }}</text>
  12. <text class="content-body-version">{{ version }}</text>
  13. </view>
  14. <view class="body">
  15. <scroll-view class="box-des-scroll" scroll-y="true">
  16. <text class="box-des">
  17. {{ contents }}
  18. </text>
  19. </scroll-view>
  20. </view>
  21. <view class="footer flex-center">
  22. <template v-if="isApplicationStore">
  23. <button class="content-button" style="border: none; color: #fff" plain @click="jumpToApplicationStore">
  24. {{ downLoadBtnTextiOS }}
  25. </button>
  26. </template>
  27. <template v-else>
  28. <template v-if="!downloadSuccess">
  29. <view class="progress-box flex-column" v-if="downloading">
  30. <progress class="progress" :percent="downLoadPercent" activeColor="#3DA7FF" show-info stroke-width="10" />
  31. <view style="width: 100%; font-size: 28rpx; display: flex; justify-content: space-around">
  32. <text>{{ downLoadingText }}</text>
  33. <text>({{ downloadedSize }}/{{ packageFileSize }}M)</text>
  34. </view>
  35. </view>
  36. <button v-else class="content-button" style="border: none; color: #fff" plain @click="updateApp">
  37. {{ downLoadBtnText }}
  38. </button>
  39. </template>
  40. <button
  41. v-else-if="downloadSuccess && !installed"
  42. class="content-button"
  43. style="border: none; color: #fff"
  44. plain
  45. :loading="installing"
  46. :disabled="installing"
  47. @click="installPackage"
  48. >
  49. {{ installing ? '正在安装……' : '下载完成,立即安装' }}
  50. </button>
  51. <button
  52. v-else-if="installed && !isWGT"
  53. class="content-button"
  54. style="border: none; color: #fff"
  55. plain
  56. :loading="installing"
  57. :disabled="installing"
  58. @click="installPackage"
  59. >
  60. 安装未完成,点击安装
  61. </button>
  62. <button v-else-if="installed && isWGT" class="content-button" style="border: none; color: #fff" plain @click="restart">安装完毕,点击重启</button>
  63. </template>
  64. </view>
  65. </view>
  66. <image v-if="!is_mandatory" class="close-img" src="/uni_modules/uni-upgrade-center-app/static/app/app_update_close.png" @click.stop="closeUpdate"></image>
  67. </view>
  68. </view>
  69. </template>
  70. <script>
  71. // #ifdef APP-PLUS
  72. import { createNotificationProgress, cancelNotificationProgress, finishNotificationProgress } from '@/uni_modules/uts-progressNotification';
  73. // #endif
  74. import { compare, platform_iOS, platform_Android, platform_Harmony } from '../utils/utils'
  75. const localFilePathKey = 'UNI_ADMIN_UPGRADE_CENTER_LOCAL_FILE_PATH';
  76. let downloadTask = null;
  77. let openSchemePromise;
  78. export default {
  79. emits: ['close', 'show'],
  80. data() {
  81. return {
  82. // 从之前下载安装
  83. installForBeforeFilePath: '',
  84. // 安装
  85. installed: false,
  86. installing: false,
  87. // 下载
  88. downloadSuccess: false,
  89. downloading: false,
  90. downLoadPercent: 0,
  91. downloadedSize: 0,
  92. packageFileSize: 0,
  93. tempFilePath: '', // 要安装的本地包地址
  94. // 默认安装包信息
  95. title: '更新日志',
  96. contents: '',
  97. version: '',
  98. is_mandatory: false,
  99. url: '',
  100. platform: [],
  101. store_list: null,
  102. // 可自定义属性
  103. subTitle: '发现新版本',
  104. downLoadBtnTextiOS: '立即跳转更新',
  105. downLoadBtnText: '立即下载更新',
  106. downLoadingText: '安装包下载中,请稍后',
  107. // #ifdef APP-PLUS
  108. shown: true,
  109. // #endif
  110. // #ifdef APP-HARMONY
  111. shown: false,
  112. // #endif
  113. };
  114. },
  115. onLoad({ local_storage_key }) {
  116. if (!local_storage_key) {
  117. console.error('local_storage_key为空,请检查后重试');
  118. uni.navigateBack();
  119. return;
  120. }
  121. const localPackageInfo = uni.getStorageSync(local_storage_key);
  122. if (!localPackageInfo) {
  123. console.error('安装包信息为空,请检查后重试');
  124. uni.navigateBack();
  125. return;
  126. }
  127. this.setLocalPackageInfo(localPackageInfo)
  128. },
  129. onBackPress() {
  130. // 强制更新不允许返回
  131. if (this.is_mandatory) return true;
  132. if (!this.needNotificationProgress) downloadTask && downloadTask.abort();
  133. },
  134. onHide() {
  135. openSchemePromise = null;
  136. },
  137. computed: {
  138. isWGT() {
  139. return this.type === 'wgt';
  140. },
  141. isNativeApp() {
  142. return this.type === 'native_app';
  143. },
  144. isiOS() {
  145. return this.platform.indexOf(platform_iOS) !== -1;
  146. },
  147. isAndroid() {
  148. return this.platform.indexOf(platform_Android) !== -1;
  149. },
  150. isHarmony() {
  151. return this.platform.indexOf(platform_Harmony) !== -1;
  152. },
  153. isApplicationStore() {
  154. return !this.isWGT && this.isNativeApp && (
  155. this.isiOS ||
  156. this.isHarmony
  157. )
  158. // return this.isiOS || (!this.isiOS && !this.isWGT && this.url.indexOf('.apk') === -1);
  159. },
  160. needNotificationProgress() {
  161. return this.platform.indexOf(platform_iOS) === -1 && !this.is_mandatory && !this.isHarmony;
  162. }
  163. },
  164. methods: {
  165. show(shown, localPackageInfo) {
  166. // #ifdef APP-HARMONY
  167. this.$emit('show')
  168. if (localPackageInfo) {
  169. this.shown = shown
  170. this.setLocalPackageInfo(localPackageInfo)
  171. } else {
  172. console.error(`安装包信息为空,请检查后重试`);
  173. }
  174. // #endif
  175. },
  176. setLocalPackageInfo(localPackageInfo) {
  177. const requiredKey = ['version', 'url', 'type'];
  178. for (let key in localPackageInfo) {
  179. if (requiredKey.indexOf(key) !== -1 && !localPackageInfo[key]) {
  180. console.error(`参数 ${key} 必填,请检查后重试`);
  181. // #ifdef APP-PLUS
  182. uni.navigateBack();
  183. // #endif
  184. // #ifdef APP-HARMONY
  185. this.shown = false
  186. // #endif
  187. return;
  188. }
  189. }
  190. Object.assign(this, localPackageInfo);
  191. this.checkLocalStoragePackage();
  192. },
  193. checkLocalStoragePackage() {
  194. // 如果已经有下载好的包,则直接提示安装
  195. const localFilePathRecord = uni.getStorageSync(localFilePathKey);
  196. if (localFilePathRecord) {
  197. const { version, savedFilePath, installed } = localFilePathRecord;
  198. // 比对版本
  199. if (!installed && compare(version, this.version) === 0) {
  200. this.downloadSuccess = true;
  201. this.installForBeforeFilePath = savedFilePath;
  202. this.tempFilePath = savedFilePath;
  203. } else {
  204. // 如果保存的包版本小 或 已安装过,则直接删除
  205. this.deleteSavedFile(savedFilePath);
  206. }
  207. }
  208. },
  209. askAbortDownload() {
  210. uni.showModal({
  211. title: '是否取消下载?',
  212. cancelText: '否',
  213. confirmText: '是',
  214. success: (res) => {
  215. if (res.confirm) {
  216. downloadTask && downloadTask.abort();
  217. if (this.needNotificationProgress) {
  218. cancelNotificationProgress();
  219. }
  220. uni.navigateBack();
  221. }
  222. }
  223. });
  224. },
  225. async closeUpdate() {
  226. if (this.downloading) {
  227. if (this.is_mandatory) {
  228. return uni.showToast({
  229. title: '下载中,请稍后……',
  230. icon: 'none',
  231. duration: 500
  232. });
  233. }
  234. if (!this.needNotificationProgress) {
  235. this.askAbortDownload();
  236. return;
  237. }
  238. }
  239. if (!this.needNotificationProgress && this.downloadSuccess && this.tempFilePath) {
  240. // 包已经下载完毕,稍后安装,将包保存在本地
  241. await this.saveFile(this.tempFilePath, this.version);
  242. }
  243. // #ifdef APP-PLUS
  244. uni.navigateBack();
  245. // #endif
  246. // #ifdef APP-HARMONY
  247. this.shown = false
  248. this.$emit('close')
  249. // #endif
  250. },
  251. updateApp() {
  252. this.checkStoreScheme()
  253. .catch(() => {
  254. this.downloadPackage();
  255. })
  256. .finally(() => {
  257. openSchemePromise = null;
  258. });
  259. },
  260. // 跳转应用商店
  261. checkStoreScheme() {
  262. const storeList = (this.store_list || []).filter((item) => item.enable);
  263. if (storeList && storeList.length) {
  264. storeList
  265. .sort((cur, next) => next.priority - cur.priority)
  266. .map((item) => item.scheme)
  267. .reduce((promise, cur, curIndex) => {
  268. openSchemePromise = (promise || (promise = Promise.reject())).catch(() => {
  269. return new Promise((resolve, reject) => {
  270. plus.runtime.openURL(cur, (err) => {
  271. reject(err);
  272. });
  273. });
  274. });
  275. return openSchemePromise;
  276. }, openSchemePromise);
  277. return openSchemePromise;
  278. }
  279. return Promise.reject();
  280. },
  281. downloadPackage() {
  282. this.downloading = true;
  283. //下载包
  284. downloadTask = uni.downloadFile({
  285. url: this.url,
  286. success: (res) => {
  287. if (res.statusCode == 200) {
  288. // fix: wgt 文件下载完成后后缀不是 wgt
  289. if (this.isWGT && res.tempFilePath.split('.').slice(-1)[0] !== 'wgt') {
  290. const failCallback = (e) => {
  291. console.log('[FILE RENAME FAIL]:', JSON.stringify(e));
  292. };
  293. // #ifndef APP-HARMONY
  294. plus.io.resolveLocalFileSystemURL(
  295. res.tempFilePath,
  296. (entry) => {
  297. entry.getParent((parent) => {
  298. const newName = `new_wgt_${Date.now()}.wgt`;
  299. entry.copyTo(
  300. parent,
  301. newName,
  302. (res) => {
  303. this.tempFilePath = res.fullPath;
  304. this.downLoadComplete();
  305. },
  306. failCallback
  307. );
  308. }, failCallback);
  309. },
  310. failCallback
  311. );
  312. // #endif
  313. // #ifdef APP-HARMONY
  314. failCallback({code: -1, message: 'Download content error, is not wgt.'})
  315. // #endif
  316. } else {
  317. this.tempFilePath = res.tempFilePath;
  318. this.downLoadComplete();
  319. }
  320. } else {
  321. console.log('下载错误:' + JSON.stringify(res))
  322. this.downloadFail()
  323. }
  324. },
  325. fail: (err) => {
  326. console.log('下载错误:' + JSON.stringify(err))
  327. this.downloadFail()
  328. }
  329. });
  330. downloadTask.onProgressUpdate((res) => {
  331. this.downLoadPercent = res.progress;
  332. this.downloadedSize = (res.totalBytesWritten / Math.pow(1024, 2)).toFixed(2);
  333. this.packageFileSize = (res.totalBytesExpectedToWrite / Math.pow(1024, 2)).toFixed(2);
  334. if (this.needNotificationProgress && !this.downloadSuccess) {
  335. createNotificationProgress({
  336. title: '升级中心正在下载安装包……',
  337. content: `${this.downLoadPercent}%`,
  338. progress: this.downLoadPercent,
  339. onClick: () => {
  340. this.askAbortDownload();
  341. }
  342. });
  343. }
  344. });
  345. if (this.needNotificationProgress) {
  346. uni.navigateBack();
  347. }
  348. },
  349. downloadFail() {
  350. const errMsg = '下载失败,请点击重试'
  351. this.downloadSuccess = false;
  352. this.downloading = false;
  353. this.downLoadPercent = 0;
  354. this.downloadedSize = 0;
  355. this.packageFileSize = 0;
  356. this.downLoadBtnText = errMsg
  357. downloadTask = null;
  358. if (this.needNotificationProgress) {
  359. finishNotificationProgress({
  360. title: '升级包下载失败',
  361. content: '请重新检查更新'
  362. });
  363. }
  364. },
  365. downLoadComplete() {
  366. this.downloadSuccess = true;
  367. this.downloading = false;
  368. this.downLoadPercent = 0;
  369. this.downloadedSize = 0;
  370. this.packageFileSize = 0;
  371. downloadTask = null;
  372. if (this.needNotificationProgress) {
  373. finishNotificationProgress({
  374. title: '安装升级包',
  375. content: '下载完成'
  376. });
  377. this.installPackage();
  378. return;
  379. }
  380. // 强制更新,直接安装
  381. if (this.is_mandatory) {
  382. this.installPackage();
  383. }
  384. },
  385. installPackage() {
  386. // #ifdef APP-PLUS || APP-HARMONY
  387. // wgt资源包安装
  388. if (this.isWGT) {
  389. this.installing = true;
  390. }
  391. plus.runtime.install(
  392. this.tempFilePath,
  393. {
  394. force: false
  395. },
  396. async (res) => {
  397. this.installing = false;
  398. this.installed = true;
  399. // wgt包,安装后会提示 安装成功,是否重启
  400. if (this.isWGT) {
  401. // 强制更新安装完成重启
  402. if (this.is_mandatory) {
  403. // #ifdef APP-PLUS
  404. uni.showLoading({
  405. icon: 'none',
  406. title: '安装成功,正在重启……'
  407. });
  408. // #endif
  409. setTimeout(() => {
  410. // #ifdef APP-PLUS
  411. uni.hideLoading();
  412. // #endif
  413. this.restart();
  414. }, 1000);
  415. }
  416. } else {
  417. const localFilePathRecord = uni.getStorageSync(localFilePathKey);
  418. uni.setStorageSync(localFilePathKey, {
  419. ...localFilePathRecord,
  420. installed: true
  421. });
  422. }
  423. },
  424. async (err) => {
  425. // 如果是安装之前的包,安装失败后删除之前的包
  426. if (this.installForBeforeFilePath) {
  427. await this.deleteSavedFile(this.installForBeforeFilePath);
  428. this.installForBeforeFilePath = '';
  429. }
  430. // 安装失败需要重新下载安装包
  431. this.installing = false;
  432. this.installed = false;
  433. uni.showModal({
  434. title: '更新失败,请重新下载',
  435. content: err.message,
  436. showCancel: false
  437. });
  438. }
  439. );
  440. // 非wgt包,安装跳出覆盖安装,此处直接返回上一页
  441. if (!this.isWGT && !this.is_mandatory) {
  442. uni.navigateBack();
  443. }
  444. // #endif
  445. },
  446. restart() {
  447. this.installed = false;
  448. // #ifdef APP-HARMONY
  449. uni.showModal({
  450. title: '更新完毕',
  451. content: '请手动重启',
  452. showCancel: false,
  453. success(res) {
  454. plus.runtime.quit()
  455. }
  456. })
  457. // #endif
  458. // #ifdef APP-PLUS
  459. //更新完重启app
  460. plus.runtime.restart();
  461. // #endif
  462. },
  463. saveFile(tempFilePath, version) {
  464. return new Promise((resolve, reject) => {
  465. uni.saveFile({
  466. tempFilePath,
  467. success({ savedFilePath }) {
  468. uni.setStorageSync(localFilePathKey, {
  469. version,
  470. savedFilePath
  471. });
  472. },
  473. complete() {
  474. resolve();
  475. }
  476. });
  477. });
  478. },
  479. deleteSavedFile(filePath) {
  480. uni.removeStorageSync(localFilePathKey);
  481. return uni.removeSavedFile({
  482. filePath
  483. });
  484. },
  485. jumpToApplicationStore() {
  486. plus.runtime.openURL(this.url);
  487. }
  488. }
  489. };
  490. </script>
  491. <style>
  492. page {
  493. background: transparent;
  494. }
  495. .flex-center {
  496. /* #ifndef APP-NVUE */
  497. display: flex;
  498. /* #endif */
  499. justify-content: center;
  500. align-items: center;
  501. }
  502. .mask {
  503. position: fixed;
  504. left: 0;
  505. top: 0;
  506. right: 0;
  507. bottom: 0;
  508. background-color: rgba(0, 0, 0, 0.65);
  509. }
  510. .botton-radius {
  511. border-bottom-left-radius: 30rpx;
  512. border-bottom-right-radius: 30rpx;
  513. }
  514. .content {
  515. position: relative;
  516. top: 0;
  517. width: 600rpx;
  518. background-color: #fff;
  519. box-sizing: border-box;
  520. padding: 0 50rpx;
  521. font-family: Source Han Sans CN;
  522. }
  523. .text {
  524. /* #ifndef APP-NVUE */
  525. display: block;
  526. /* #endif */
  527. line-height: 200px;
  528. text-align: center;
  529. color: #ffffff;
  530. }
  531. .content-top {
  532. position: absolute;
  533. top: -195rpx;
  534. left: 0;
  535. width: 600rpx;
  536. height: 270rpx;
  537. }
  538. .content-top-text {
  539. font-size: 45rpx;
  540. font-weight: bold;
  541. color: #f8f8fa;
  542. position: absolute;
  543. top: 120rpx;
  544. left: 50rpx;
  545. z-index: 1;
  546. }
  547. .content-header {
  548. height: 70rpx;
  549. }
  550. .title {
  551. font-size: 33rpx;
  552. font-weight: bold;
  553. color: #3da7ff;
  554. line-height: 38px;
  555. }
  556. .content-body {
  557. width: 100%;
  558. }
  559. .content-body-version {
  560. padding-left: 20rpx;
  561. color: #fff;
  562. font-size: 20rpx;
  563. margin-left: 10rpx;
  564. padding: 4rpx 8rpx;
  565. border-radius: 20rpx;
  566. background: #50aefd;
  567. }
  568. .footer {
  569. height: 150rpx;
  570. display: flex;
  571. align-items: center;
  572. justify-content: space-around;
  573. }
  574. .box-des-scroll {
  575. box-sizing: border-box;
  576. padding: 0 40rpx;
  577. height: 200rpx;
  578. text-align: left;
  579. }
  580. .box-des {
  581. font-size: 26rpx;
  582. color: #000000;
  583. line-height: 50rpx;
  584. }
  585. .progress-box {
  586. width: 100%;
  587. }
  588. .progress {
  589. width: 90%;
  590. height: 40rpx;
  591. /* border-radius: 35px; */
  592. }
  593. .close-img {
  594. width: 70rpx;
  595. height: 70rpx;
  596. z-index: 1000;
  597. position: absolute;
  598. bottom: -120rpx;
  599. left: calc(50% - 70rpx / 2);
  600. }
  601. .content-button {
  602. text-align: center;
  603. flex: 1;
  604. font-size: 30rpx;
  605. font-weight: 400;
  606. color: #ffffff;
  607. border-radius: 40rpx;
  608. margin: 0 18rpx;
  609. height: 80rpx;
  610. line-height: 80rpx;
  611. background: linear-gradient(to right, #1785ff, #3da7ff);
  612. }
  613. .flex-column {
  614. display: flex;
  615. flex-direction: column;
  616. align-items: center;
  617. }
  618. </style>