wu-app-update.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  1. <template>
  2. <view class="wu-app-update-box">
  3. <uni-popup ref="popup" type="center" :isMaskClick="false" @touchmove.stop.prevent>
  4. <view class="content_popup" :style="{backgroundColor: bgColor}">
  5. <!-- 关闭app -->
  6. <wu-icon v-if="!isForceUpdata" class="close" name="close" :color="closeIconColor" :size="closeIconSize"
  7. @click="closeUpdate"></wu-icon>
  8. <!-- 版本提示 -->
  9. <view class="version" :style="{color: versionColor}">v{{version}}</view>
  10. <!-- 背景 -->
  11. <image class="backgroundImg" width="100%" height="100%" src="../../static/appUploadAlertBoxBg.png">
  12. </image>
  13. <!-- 更新详细信息 -->
  14. <view class="info center">
  15. <text class="title" :style="{color: titleColor}">{{title}}</text>
  16. <!-- 更新内容 -->
  17. <scroll-view class="info_desc_scroll" :style="{color: contentColor}" scroll-y="true">
  18. <rich-text :nodes="content"></rich-text>
  19. </scroll-view>
  20. </view>
  21. <view class="footer" v-if="platform">
  22. <button v-if="downloadSuccess && !wgtInstalled" class="btn" :style="btnStyle"
  23. @click="installPackage" :loading="wgtInstalling" :disabled="wgtInstalling">
  24. {{wgtInstalling ? wgtInstallingText : downloadSuccessText}}
  25. </button>
  26. <button v-else-if="wgtInstalled && isWGT" class="btn" :style="btnStyle" @click="restart">
  27. {{wgtInstalledText}}
  28. </button>
  29. <!-- 更新进度 -->
  30. <view class="progress-box flex f-c f-y-c" :style="{color: progressTextColor}"
  31. v-else-if="downloading">
  32. <progress class="progress" :percent="downLoadPercent" :activeColor="progressColor || themeColor"
  33. show-info stroke-width="10" />
  34. <view style="width:100%;font-size: 28rpx;display: flex;justify-content: space-around;">
  35. <text>{{downLoadingText}}</text>
  36. <text>({{downloadedSize}}M/{{packageFileSize || 0}}M)</text>
  37. </view>
  38. </view>
  39. <!-- 选项 -->
  40. <view v-else class="btns flex f-x-b">
  41. <!-- IOS -->
  42. <view v-if="platform == 'ios'" class="btn confirm" :style="btnStyle" @click="jumpToAppStore">
  43. {{downloadBtnTextIOS}}
  44. </view>
  45. <!-- android -->
  46. <view v-else class="btn confirm" :style="btnStyle" @click="updataApp">
  47. {{downloadBtnTextAndroid}}
  48. </view>
  49. </view>
  50. <!-- 短期内不在提醒 -->
  51. <view v-if="!isForceUpdata" class="notRemind" @click="userNotRemind = !userNotRemind"
  52. :class="{active: userNotRemind}">
  53. <uni-icons :type="userNotRemind ? 'checkbox' : 'circle'" :size="notRemindIconSize"
  54. :color="userNotRemind ? (notRemindIconActColor || themeColor) : notRemindIconNotActColor"></uni-icons>
  55. <view class="remind-text"
  56. :style="{color: userNotRemind ? notRemindTextActColor : notRemindTextNotActColor}">
  57. {{intervalAlertUserUpdateDay}}日内不在提醒
  58. </view>
  59. </view>
  60. </view>
  61. </view>
  62. </uni-popup>
  63. </view>
  64. </template>
  65. <script>
  66. /**
  67. * wu-app-update app更新提示框
  68. * @description app更新提示框,支持热更新,强制更新,普通更新,暂不更新,后台下载,更新内容展示,进度条显示,ios跳转appstore等功能。
  69. * @property {String} titleColor 更新标题文字颜色(默认: #5e5e5e)。
  70. * @property {String} contentColor 更新内容文字颜色(默认: #878787)。
  71. * @property {String} themeColor 主题颜色。
  72. * @property {String} bgColor 背景色(默认: #fff)。
  73. * @property {String} versionColor 版本号字体颜色(默认: #fff)。
  74. * @property {String} closeIconColor 关闭图标颜色(默认: #fff)。
  75. * @property {String} closeIconSize 关闭图标大小(默认: 26)。
  76. * @property {String} notRemindIconSize 短期内不更新图标大小(默认: 22)。
  77. * @property {String} notRemindIconActColor 短期内不更新选中图标颜色(默认: '')。
  78. * @property {String} notRemindIconNotActColor 短期内不更新未选中图标颜色(默认: #9d9d9d)。
  79. * @property {String} notRemindTextActColor 短期内不更新选中文字颜色(默认: #6b6b6b)。
  80. * @property {String} notRemindTextNotActColor 短期内不更新未选中文字颜色(默认: #9d9d9d)。
  81. * @property {String} downloadBtnTextIOS 下载按钮ios文字(默认: 立即跳转更新)
  82. * @property {String} downloadBtnTextAndroid 下载按钮Android文字(默认: 立即升级)。
  83. * @property {String} downLoadingText 下载中文字提示(默认: 安装包下载中,请稍后)。
  84. * @property {String} downloadSuccessText 下载完成文字提示(默认: 下载完成,立即安装)。
  85. * @property {String} wgtInstallingText wgt安装中显示文字(默认: 正在安装....)。
  86. * @property {Number} wgtInstalledText wgt安装完成重启显示文字(默认: 安装完毕,点击重启)。
  87. * @property {Number} btnBgColor 按钮背景色(默认: '')。
  88. * @property {Number} btnColor 按钮文字颜色(默认: '#fff')。
  89. * @property {Number} progressColor 进度条颜色(默认: '')。
  90. * @property {Number} progressTextColor 进度条文字样式(默认: #4c4c4c)。
  91. * @property {Number} intervalAlertUserUpdateDay 提示用户更新的间隔时间 单位day(默认: 7)。
  92. * @example
  93. */
  94. import config from '../../common/update_config.js';
  95. import checkVersion from '../../common/checkVersion.js';
  96. export default {
  97. name: 'wuAppUpdata',
  98. props: config.props,
  99. data() {
  100. return {
  101. // 更新的版本号
  102. version: '',
  103. // 系统环境
  104. platform: '',
  105. // 下载链接
  106. downloadUrl: '',
  107. // 跳转的应用市场列表
  108. storeList: [],
  109. // 是否wgt资源包
  110. isWGT: false,
  111. // 是否强制更新
  112. isForceUpdata: false,
  113. // 更新的标题
  114. title: '',
  115. // 更新的内容
  116. content: ``,
  117. // 下载下载状态
  118. downloading: '',
  119. // 是否下载完成
  120. downloadSuccess: false,
  121. // 下载进度
  122. downLoadPercent: 0,
  123. // 目前app已下载大小
  124. downloadedSize: 0,
  125. // app总大小
  126. packageFileSize: 0,
  127. // wgt是否安装中
  128. wgtInstalling: false,
  129. // wgt是否安装完成
  130. wgtInstalled: false,
  131. // 要安装的本地包地址
  132. tempFilePath: false,
  133. // 之前的安装的本地包地址
  134. installForBeforeFilePath: null,
  135. // 创建的下载任务
  136. downloadTask: null,
  137. // 用户上次拒绝的时间
  138. userLastRefuseTime: uni.getStorageSync('userLastRefuseTime'),
  139. // 用户是否短期内不更新
  140. userNotRemind: false,
  141. }
  142. },
  143. mounted() {
  144. this.init();
  145. let that = this;
  146. uni.$on('check_update', function() {
  147. that.init();
  148. });
  149. },
  150. onShow() {},
  151. methods: {
  152. // 版本对比
  153. compare(v1 = '0', v2 = '0') {
  154. v1 = String(v1).split('.')
  155. v2 = String(v2).split('.')
  156. const minVersionLens = Math.min(v1.length, v2.length);
  157. let result = 0;
  158. for (let i = 0; i < minVersionLens; i++) {
  159. const curV1 = Number(v1[i])
  160. const curV2 = Number(v2[i])
  161. if (curV1 > curV2) {
  162. result = 1
  163. break;
  164. } else if (curV1 < curV2) {
  165. result = -1
  166. break;
  167. }
  168. }
  169. if (result === 0 && (v1.length !== v2.length)) {
  170. const v1BiggerThenv2 = v1.length > v2.length;
  171. const maxLensVersion = v1BiggerThenv2 ? v1 : v2;
  172. for (let i = minVersionLens; i < maxLensVersion.length; i++) {
  173. const curVersion = Number(maxLensVersion[i])
  174. if (curVersion > 0) {
  175. v1BiggerThenv2 ? result = 1 : result = -1
  176. break;
  177. }
  178. }
  179. }
  180. return result;
  181. },
  182. // 获取更新内容片段
  183. getContentHTML(content) {
  184. let contentArr = content.split('\n');
  185. return contentArr.map(item => `<p>${item}</p>`).join('\n')
  186. },
  187. // 跳转应用市场
  188. checkStoreScheme() {
  189. /**
  190. * 跳转应用市场逻辑
  191. * 如果本次更新设置了需要跳转的应用市场则从整个列表中筛选出来启用的应用市场
  192. * 按照设置的优先级(priority)从大到小排序
  193. * 并尝试跳转到所有应用市场
  194. * 如果都跳转失败的话则会显示失败
  195. */
  196. // 可以跳转的应用市场
  197. const canStoreList = (this.storeList || []).filter(item => item.enable)
  198. let openSchemePromise;
  199. if (canStoreList && canStoreList.length) {
  200. canStoreList
  201. .sort((cur, next) => next.priority - cur.priority)
  202. .map(item => item.scheme)
  203. .reduce((promise, cur, curIndex) => {
  204. openSchemePromise = (promise || (promise = Promise.reject())).catch(() => {
  205. return new Promise((resolve, reject) => {
  206. plus.runtime.openURL(cur, (err) => {
  207. reject(err)
  208. })
  209. })
  210. })
  211. return openSchemePromise
  212. }, openSchemePromise)
  213. return openSchemePromise
  214. }
  215. return Promise.reject()
  216. },
  217. // 初始化
  218. init() {
  219. console.log('xxxx');
  220. // 如果在用户上次拒绝的时间存在
  221. if (this.userLastRefuseTime) {
  222. // 目标时间戳
  223. let targetTime = this.userLastRefuseTime + this.intervalAlertUserUpdateDay * 24 * 60 * 60 * 1000;
  224. // 现在时间戳
  225. let nowTime = (new Date).getTime();
  226. // 如果目标时间戳大于现在时间戳
  227. if (targetTime > nowTime) {
  228. // 并阻止执行
  229. return;
  230. } else {
  231. // 清除拒绝时间
  232. uni.removeStorageSync('userLastRefuseTime');
  233. }
  234. }
  235. // 检查版本 需要更新时才会触发回调
  236. checkVersion().then((res) => {
  237. // 非静默更新时触发
  238. if (!res.is_silently) {
  239. // 读取下载好的包的缓存
  240. const appDownLoadTempFilePath = uni.getStorageSync('appDownLoadTempFilePath');
  241. // 更新的版本号
  242. this.version = res.version;
  243. // 系统环境
  244. this.platform = res.appPlatform;
  245. // 网络下载地址
  246. this.downloadUrl = res.url;
  247. // 跳转的应用市场列表
  248. this.storeList = res.store_list || [];
  249. // 更新内容
  250. this.content = this.getContentHTML(res.contents);
  251. // 更新标题
  252. this.title = res.title || '发现新版本';
  253. // 是否强制更新
  254. this.isForceUpdata = res.is_mandatory;
  255. // 是否wgt资源包
  256. this.isWGT = res.type == 'wgt';
  257. // 如果已经有下载好的包
  258. if (appDownLoadTempFilePath && this.compare(this.version, uni.getStorageSync(
  259. 'appDownLoadTempFilePathVersion')) == 0) {
  260. this.tempFilePath = appDownLoadTempFilePath;
  261. this.downloadSuccess = true;
  262. this.installForBeforeFilePath = appDownLoadTempFilePath;
  263. } else {
  264. uni.removeStorageSync('appDownLoadTempFilePath');
  265. uni.removeStorageSync('appDownLoadTempFilePathVersion');
  266. }
  267. // 打开更新提示
  268. this.$refs.popup.open();
  269. }
  270. })
  271. },
  272. // 下载app
  273. downloadPackage() {
  274. if (!this.downloadTask) {
  275. this.downloading = true;
  276. //下载包
  277. this.downloadTask = plus.downloader.createDownload(this.downloadUrl, {}, (download, status) => {
  278. if (status == 200) {
  279. this.downloadSuccess = true;
  280. this.tempFilePath = download.filename;
  281. uni.setStorageSync('appDownLoadTempFilePathVersion', this.version)
  282. uni.setStorageSync('appDownLoadTempFilePath', this.tempFilePath);
  283. }
  284. // 清空下载进度
  285. this.downLoadPercent = 0;
  286. this.downloadedSize = 0;
  287. this.packageFileSize = 0;
  288. this.downloadTask = null;
  289. });
  290. this.downloadTask.start();
  291. this.downloadTask.addEventListener("statechanged", (task, status) => {
  292. switch (task.state) {
  293. case 3:
  294. // 更新下载进度
  295. this.downLoadPercent = parseInt(task.downloadedSize / task.totalSize * 100);
  296. this.downloadedSize = (task.downloadedSize / Math.pow(1024, 2)).toFixed(2);
  297. this.packageFileSize = (task.totalSize / Math.pow(1024, 2)).toFixed(2);
  298. break;
  299. }
  300. });
  301. }
  302. },
  303. // 安装app
  304. installPackage() {
  305. // wgt资源包安装
  306. if (this.isWGT) {
  307. this.wgtInstalling = true;
  308. }
  309. plus.runtime.install(this.tempFilePath, {
  310. force: true
  311. }, async res => {
  312. this.wgtInstalling = false;
  313. this.wgtInstalled = true;
  314. }, async err => {
  315. this.downloadSuccess = false;
  316. // 如果是安装之前的包,安装失败后删除之前的包
  317. if (this.installForBeforeFilePath) {
  318. await this.deleteSavedFile(this.installForBeforeFilePath)
  319. this.installForBeforeFilePath = '';
  320. }
  321. uni.showLoading({
  322. icon: 'none',
  323. title: '更新失败,请重新下载',
  324. mask: true
  325. })
  326. })
  327. },
  328. // 删除保存的文件
  329. deleteSavedFile(tempFilePath) {
  330. uni.removeStorageSync('appDownLoadTempFilePath')
  331. uni.removeSavedFile({
  332. tempFilePath
  333. })
  334. },
  335. // 保存文件
  336. saveFile(tempFilePath) {
  337. return new Promise((resolve, reject) => {
  338. uni.saveFile({
  339. tempFilePath,
  340. success({
  341. savedFilePath
  342. }) {
  343. uni.setStorageSync('appDownLoadTempFilePath', tempFilePath)
  344. },
  345. complete() {
  346. resolve()
  347. }
  348. })
  349. })
  350. },
  351. // 重启应用
  352. restart() {
  353. this.wgtInstalled = false;
  354. //更新完重启app
  355. plus.runtime.restart();
  356. },
  357. // 跳转appstore
  358. jumpToAppStore() {
  359. // 请填入appid
  360. plus.runtime.openURL(this.downloadUrl);
  361. },
  362. // 更新用户拒绝时间
  363. updataUserRefuseTime() {
  364. // 存储用户暂不升级的时间戳
  365. this.userLastRefuseTime = (new Date).getTime();
  366. uni.setStorageSync('userLastRefuseTime', this.userLastRefuseTime);
  367. },
  368. // 关闭更新框
  369. closeUpdate() {
  370. if (this.downloading) {
  371. uni.showModal({
  372. title: '是否取消下载?',
  373. cancelText: '否',
  374. confirmText: '是',
  375. success: res => {
  376. if (res.confirm) {
  377. this.downloadTask && this.downloadTask.abort();
  378. this.$refs.popup.close();
  379. }
  380. }
  381. });
  382. } else {
  383. this.$refs.popup.close();
  384. // 如果用户短期内不更新
  385. if (this.userNotRemind) {
  386. this.updataUserRefuseTime();
  387. }
  388. }
  389. if (this.downloadSuccess) {
  390. // 包已经下载完毕,稍后安装,将包保存在本地
  391. this.saveFile(this.tempFilePath)
  392. }
  393. },
  394. // 应用更新
  395. updataApp() {
  396. // 检查可跳转的应用市场 如果失败则走应用内更新
  397. this.checkStoreScheme().catch(() => {
  398. this.downloadPackage()
  399. })
  400. },
  401. },
  402. computed: {
  403. btnStyle() {
  404. return {
  405. color: this.btnColor,
  406. backgroundColor: this.btnBgColor || this.themeColor
  407. }
  408. }
  409. }
  410. }
  411. </script>
  412. <style lang="scss" scoped>
  413. .wu-app-update-box {
  414. :deep(.uni-popup) {
  415. z-index: 1000;
  416. }
  417. .bg {
  418. width: 100%;
  419. }
  420. .content_popup {
  421. width: 590rpx;
  422. border-radius: 10rpx;
  423. background-size: cover;
  424. background-repeat: no-repeat;
  425. background-position: center;
  426. box-sizing: border-box;
  427. background-color: #FFF;
  428. padding-bottom: 40rpx;
  429. position: relative;
  430. .close {
  431. position: absolute;
  432. right: 15rpx;
  433. top: 15rpx;
  434. z-index: 3;
  435. }
  436. .version {
  437. position: absolute;
  438. left: 40rpx;
  439. top: 45rpx;
  440. z-index: 3;
  441. font-size: 75rpx;
  442. }
  443. .backgroundImg {
  444. width: 100.1%;
  445. height: 270rpx;
  446. position: absolute;
  447. top: -48rpx;
  448. }
  449. .info {
  450. position: relative;
  451. padding: 240rpx 40rpx 0;
  452. z-index: 2;
  453. .title {
  454. font-size: 42rpx;
  455. }
  456. .info_desc_scroll {
  457. margin-top: 20rpx;
  458. font-size: 33rpx;
  459. min-height: 200rpx;
  460. max-height: 400rpx;
  461. box-sizing: border-box;
  462. line-height: 1.3;
  463. p:not(:last-child) {
  464. margin-bottom: 12rpx;
  465. }
  466. }
  467. }
  468. .footer {
  469. padding: 0 30rpx;
  470. .progress-box {
  471. width: 100%;
  472. margin-top: 25rpx;
  473. }
  474. :deep(.progress) {
  475. width: 90%;
  476. height: 40rpx;
  477. margin-bottom: 5rpx;
  478. .uni-progress-bar {
  479. border-radius: 35rpx;
  480. .uni-progress-inner-bar {
  481. border-radius: 35rpx;
  482. }
  483. }
  484. }
  485. .btn {
  486. margin-top: 35rpx;
  487. height: 75rpx;
  488. line-height: 75rpx;
  489. border-radius: 14rpx;
  490. font-size: 34rpx;
  491. font-weight: 400;
  492. text-align: center;
  493. width: 100%;
  494. }
  495. .notRemind {
  496. display: flex;
  497. justify-content: center;
  498. align-items: center;
  499. margin-top: 15rpx;
  500. .remind-text {
  501. color: #9d9d9d;
  502. font-size: 30rpx;
  503. margin-left: 3rpx;
  504. transition: color 80ms linear;
  505. }
  506. }
  507. }
  508. }
  509. .close-img {
  510. width: 70rpx;
  511. height: 70rpx;
  512. z-index: 1000;
  513. position: absolute;
  514. bottom: -120rpx;
  515. left: calc(50% - 70rpx / 2);
  516. }
  517. }
  518. </style>