9433cfb9创建于 2025年12月31日历史提交
<template>
	<view class="mask flex-center" v-if="shown">
		<view class="content botton-radius">
			<view class="content-top">
				<text class="content-top-text">{{ title }}</text>
				<image class="content-top" style="top: 0" width="100%" height="100%" src="/uni_modules/uni-upgrade-center-app/static/app/bg_top.png"></image>
			</view>
			<view class="content-header"></view>
			<view class="content-body">
				<view class="title">
					<text>{{ subTitle }}</text>
					<text class="content-body-version">{{ version }}</text>
				</view>
				<view class="body">
					<scroll-view class="box-des-scroll" scroll-y="true">
						<text class="box-des">
							{{ contents }}
						</text>
					</scroll-view>
				</view>
				<view class="footer flex-center">
					<template v-if="isApplicationStore">
						<button class="content-button" style="border: none; color: #fff" plain @click="jumpToApplicationStore">
							{{ downLoadBtnTextiOS }}
						</button>
					</template>
					<template v-else>
						<template v-if="!downloadSuccess">
							<view class="progress-box flex-column" v-if="downloading">
								<progress class="progress" :percent="downLoadPercent" activeColor="#3DA7FF" show-info stroke-width="10" />
								<view style="width: 100%; font-size: 28rpx; display: flex; justify-content: space-around">
									<text>{{ downLoadingText }}</text>
									<text>({{ downloadedSize }}/{{ packageFileSize }}M)</text>
								</view>
							</view>

							<button v-else class="content-button" style="border: none; color: #fff" plain @click="updateApp">
								{{ downLoadBtnText }}
							</button>
						</template>
						<button
							v-else-if="downloadSuccess && !installed"
							class="content-button"
							style="border: none; color: #fff"
							plain
							:loading="installing"
							:disabled="installing"
							@click="installPackage"
						>
							{{ installing ? '正在安装……' : '下载完成,立即安装' }}
						</button>
						<button
							v-else-if="installed && !isWGT"
							class="content-button"
							style="border: none; color: #fff"
							plain
							:loading="installing"
							:disabled="installing"
							@click="installPackage"
						>
							安装未完成,点击安装
						</button>

						<button v-else-if="installed && isWGT" class="content-button" style="border: none; color: #fff" plain @click="restart">安装完毕,点击重启</button>
					</template>
				</view>
			</view>

			<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>
		</view>
	</view>
</template>

<script>
// #ifdef APP-PLUS
import { createNotificationProgress, cancelNotificationProgress, finishNotificationProgress } from '@/uni_modules/uts-progressNotification';
// #endif
import { compare, platform_iOS, platform_Android, platform_Harmony } from '../utils/utils'
const localFilePathKey = 'UNI_ADMIN_UPGRADE_CENTER_LOCAL_FILE_PATH';

let downloadTask = null;
let openSchemePromise;

export default {
    emits: ['close', 'show'],
	data() {
		return {
			// 从之前下载安装
			installForBeforeFilePath: '',

			// 安装
			installed: false,
			installing: false,

			// 下载
			downloadSuccess: false,
			downloading: false,

			downLoadPercent: 0,
			downloadedSize: 0,
			packageFileSize: 0,

			tempFilePath: '', // 要安装的本地包地址

			// 默认安装包信息
			title: '更新日志',
			contents: '',
			version: '',
			is_mandatory: false,
			url: '',
			platform: [],
			store_list: null,

			// 可自定义属性
			subTitle: '发现新版本',
			downLoadBtnTextiOS: '立即跳转更新',
			downLoadBtnText: '立即下载更新',
			downLoadingText: '安装包下载中,请稍后',

			// #ifdef APP-PLUS
			shown: true,
			// #endif
			// #ifdef APP-HARMONY
			shown: false,
			// #endif
		};
	},
	onLoad({ local_storage_key }) {
		if (!local_storage_key) {
			console.error('local_storage_key为空,请检查后重试');
			uni.navigateBack();
			return;
		}

		const localPackageInfo = uni.getStorageSync(local_storage_key);
		if (!localPackageInfo) {
			console.error('安装包信息为空,请检查后重试');
			uni.navigateBack();
			return;
		}

		this.setLocalPackageInfo(localPackageInfo)
	},
	onBackPress() {
		// 强制更新不允许返回
		if (this.is_mandatory) return true;
		if (!this.needNotificationProgress) downloadTask && downloadTask.abort();
	},
	onHide() {
		openSchemePromise = null;
	},
	computed: {
		isWGT() {
			return this.type === 'wgt';
		},
		isNativeApp() {
			return this.type === 'native_app';
		},
		isiOS() {
			return this.platform.indexOf(platform_iOS) !== -1;
		},
		isAndroid() {
			return this.platform.indexOf(platform_Android) !== -1;
		},
		isHarmony() {
			return this.platform.indexOf(platform_Harmony) !== -1;
		},
		isApplicationStore() {
			return !this.isWGT && this.isNativeApp && (
				this.isiOS ||
				this.isHarmony
			)
			// return this.isiOS || (!this.isiOS && !this.isWGT && this.url.indexOf('.apk') === -1);
		},
		needNotificationProgress() {
			return this.platform.indexOf(platform_iOS) === -1 && !this.is_mandatory && !this.isHarmony;
		}
	},
	methods: {
		show(shown, localPackageInfo) {
			// #ifdef APP-HARMONY
			this.$emit('show')
			if (localPackageInfo) {
				this.shown = shown
				this.setLocalPackageInfo(localPackageInfo)
			} else {
				console.error(`安装包信息为空,请检查后重试`);
			}
			// #endif
		},
		setLocalPackageInfo(localPackageInfo) {
			const requiredKey = ['version', 'url', 'type'];
			for (let key in localPackageInfo) {
				if (requiredKey.indexOf(key) !== -1 && !localPackageInfo[key]) {
					console.error(`参数 ${key} 必填,请检查后重试`);
					// #ifdef APP-PLUS
					uni.navigateBack();
					// #endif
					// #ifdef APP-HARMONY
					this.shown = false
					// #endif
					return;
				}
			}

			Object.assign(this, localPackageInfo);
			this.checkLocalStoragePackage();
		},
		checkLocalStoragePackage() {
			// 如果已经有下载好的包,则直接提示安装
			const localFilePathRecord = uni.getStorageSync(localFilePathKey);
			if (localFilePathRecord) {
				const { version, savedFilePath, installed } = localFilePathRecord;

				// 比对版本
				if (!installed && compare(version, this.version) === 0) {
					this.downloadSuccess = true;
					this.installForBeforeFilePath = savedFilePath;
					this.tempFilePath = savedFilePath;
				} else {
					// 如果保存的包版本小 或 已安装过,则直接删除
					this.deleteSavedFile(savedFilePath);
				}
			}
		},
		askAbortDownload() {
			uni.showModal({
				title: '是否取消下载?',
				cancelText: '否',
				confirmText: '是',
				success: (res) => {
					if (res.confirm) {
						downloadTask && downloadTask.abort();
            if (this.needNotificationProgress) {
              cancelNotificationProgress();
            }
						uni.navigateBack();
					}
				}
			});
		},
		async closeUpdate() {
			if (this.downloading) {
				if (this.is_mandatory) {
					return uni.showToast({
						title: '下载中,请稍后……',
						icon: 'none',
						duration: 500
					});
				}
				if (!this.needNotificationProgress) {
					this.askAbortDownload();
					return;
				}
			}

			if (!this.needNotificationProgress && this.downloadSuccess && this.tempFilePath) {
				// 包已经下载完毕,稍后安装,将包保存在本地
				await this.saveFile(this.tempFilePath, this.version);
			}

			// #ifdef APP-PLUS
			uni.navigateBack();
			// #endif
			// #ifdef APP-HARMONY
			this.shown = false
			this.$emit('close')
			// #endif
		},
		updateApp() {
			this.checkStoreScheme()
				.catch(() => {
					this.downloadPackage();
				})
				.finally(() => {
					openSchemePromise = null;
				});
		},
		// 跳转应用商店
		checkStoreScheme() {
			const storeList = (this.store_list || []).filter((item) => item.enable);
			if (storeList && storeList.length) {
				storeList
					.sort((cur, next) => next.priority - cur.priority)
					.map((item) => item.scheme)
					.reduce((promise, cur, curIndex) => {
						openSchemePromise = (promise || (promise = Promise.reject())).catch(() => {
							return new Promise((resolve, reject) => {
								plus.runtime.openURL(cur, (err) => {
									reject(err);
								});
							});
						});
						return openSchemePromise;
					}, openSchemePromise);
				return openSchemePromise;
			}

			return Promise.reject();
		},
		downloadPackage() {
			this.downloading = true;
			//下载包
			downloadTask = uni.downloadFile({
				url: this.url,
				success: (res) => {
					if (res.statusCode == 200) {
						// fix: wgt 文件下载完成后后缀不是 wgt
						if (this.isWGT && res.tempFilePath.split('.').slice(-1)[0] !== 'wgt') {
							const failCallback = (e) => {
								console.log('[FILE RENAME FAIL]:', JSON.stringify(e));
							};
							// #ifndef APP-HARMONY
							plus.io.resolveLocalFileSystemURL(
								res.tempFilePath,
								(entry) => {
									entry.getParent((parent) => {
										const newName = `new_wgt_${Date.now()}.wgt`;
										entry.copyTo(
											parent,
											newName,
											(res) => {
												this.tempFilePath = res.fullPath;
												this.downLoadComplete();
											},
											failCallback
										);
									}, failCallback);
								},
								failCallback
							);
							// #endif
							// #ifdef APP-HARMONY
							failCallback({code: -1, message: 'Download content error, is not wgt.'})
							// #endif
						} else {
							this.tempFilePath = res.tempFilePath;
							this.downLoadComplete();
						}
					} else {
						console.log('下载错误:' + JSON.stringify(res))
            this.downloadFail()
					}
				},
        fail: (err) => {
          console.log('下载错误:' + JSON.stringify(err))
          this.downloadFail()
        }
			});

			downloadTask.onProgressUpdate((res) => {
				this.downLoadPercent = res.progress;
				this.downloadedSize = (res.totalBytesWritten / Math.pow(1024, 2)).toFixed(2);
				this.packageFileSize = (res.totalBytesExpectedToWrite / Math.pow(1024, 2)).toFixed(2);

				if (this.needNotificationProgress && !this.downloadSuccess) {
					createNotificationProgress({
						title: '升级中心正在下载安装包……',
						content: `${this.downLoadPercent}%`,
						progress: this.downLoadPercent,
						onClick: () => {
							this.askAbortDownload();
						}
					});
				}
			});
			if (this.needNotificationProgress) {
				uni.navigateBack();
			}
		},
    downloadFail() {
      const errMsg = '下载失败,请点击重试'

      this.downloadSuccess = false;
      this.downloading = false;

      this.downLoadPercent = 0;
      this.downloadedSize = 0;
      this.packageFileSize = 0;

      this.downLoadBtnText = errMsg

      downloadTask = null;

      if (this.needNotificationProgress) {
        finishNotificationProgress({
          title: '升级包下载失败',
          content: '请重新检查更新'
        });
      }
    },
		downLoadComplete() {
			this.downloadSuccess = true;
			this.downloading = false;

			this.downLoadPercent = 0;
			this.downloadedSize = 0;
			this.packageFileSize = 0;

			downloadTask = null;

			if (this.needNotificationProgress) {
				finishNotificationProgress({
					title: '安装升级包',
					content: '下载完成'
				});

				this.installPackage();
				return;
			}

			// 强制更新,直接安装
			if (this.is_mandatory) {
				this.installPackage();
			}
		},
		installPackage() {
			// #ifdef APP-PLUS || APP-HARMONY
			// wgt资源包安装
			if (this.isWGT) {
				this.installing = true;
			}
			plus.runtime.install(
				this.tempFilePath,
				{
					force: false
				},
				async (res) => {
					this.installing = false;
					this.installed = true;

					// wgt包,安装后会提示 安装成功,是否重启
					if (this.isWGT) {
						// 强制更新安装完成重启
						if (this.is_mandatory) {
							// #ifdef APP-PLUS
							uni.showLoading({
								icon: 'none',
								title: '安装成功,正在重启……'
							});
							// #endif

							setTimeout(() => {
								// #ifdef APP-PLUS
								uni.hideLoading();
								// #endif
								this.restart();
							}, 1000);
						}
					} else {
						const localFilePathRecord = uni.getStorageSync(localFilePathKey);
						uni.setStorageSync(localFilePathKey, {
							...localFilePathRecord,
							installed: true
						});
					}
				},
				async (err) => {
					// 如果是安装之前的包,安装失败后删除之前的包
					if (this.installForBeforeFilePath) {
						await this.deleteSavedFile(this.installForBeforeFilePath);
						this.installForBeforeFilePath = '';
					}

					// 安装失败需要重新下载安装包
					this.installing = false;
					this.installed = false;

					uni.showModal({
						title: '更新失败,请重新下载',
						content: err.message,
						showCancel: false
					});
				}
			);

			// 非wgt包,安装跳出覆盖安装,此处直接返回上一页
			if (!this.isWGT && !this.is_mandatory) {
				uni.navigateBack();
			}
			// #endif
		},
		restart() {
			this.installed = false;
			// #ifdef APP-HARMONY
			uni.showModal({
				title: '更新完毕',
				content: '请手动重启',
				showCancel: false,
				success(res) {
					plus.runtime.quit()
				}
			})
			// #endif
			// #ifdef APP-PLUS
			//更新完重启app
			plus.runtime.restart();
			// #endif
		},
		saveFile(tempFilePath, version) {
			return new Promise((resolve, reject) => {
				uni.saveFile({
					tempFilePath,
					success({ savedFilePath }) {
						uni.setStorageSync(localFilePathKey, {
							version,
							savedFilePath
						});
					},
					complete() {
						resolve();
					}
				});
			});
		},
		deleteSavedFile(filePath) {
			uni.removeStorageSync(localFilePathKey);
			return uni.removeSavedFile({
				filePath
			});
		},
		jumpToApplicationStore() {
			plus.runtime.openURL(this.url);
		}
	}
};
</script>

<style>
page {
	background: transparent;
}

.flex-center {
	/* #ifndef APP-NVUE */
	display: flex;
	/* #endif */
	justify-content: center;
	align-items: center;
}

.mask {
	position: fixed;
	left: 0;
	top: 0;
	right: 0;
	bottom: 0;
	background-color: rgba(0, 0, 0, 0.65);
}

.botton-radius {
	border-bottom-left-radius: 30rpx;
	border-bottom-right-radius: 30rpx;
}

.content {
	position: relative;
	top: 0;
	width: 600rpx;
	background-color: #fff;
	box-sizing: border-box;
	padding: 0 50rpx;
	font-family: Source Han Sans CN;
}

.text {
	/* #ifndef APP-NVUE */
	display: block;
	/* #endif */
	line-height: 200px;
	text-align: center;
	color: #ffffff;
}

.content-top {
	position: absolute;
	top: -195rpx;
	left: 0;
	width: 600rpx;
	height: 270rpx;
}

.content-top-text {
	font-size: 45rpx;
	font-weight: bold;
	color: #f8f8fa;
	position: absolute;
	top: 120rpx;
	left: 50rpx;
	z-index: 1;
}

.content-header {
	height: 70rpx;
}

.title {
	font-size: 33rpx;
	font-weight: bold;
	color: #3da7ff;
	line-height: 38px;
}

.content-body {
  width: 100%;
}

.content-body-version {
	padding-left: 20rpx;
	color: #fff;
	font-size: 20rpx;
	margin-left: 10rpx;
	padding: 4rpx 8rpx;
	border-radius: 20rpx;
	background: #50aefd;
}

.footer {
	height: 150rpx;
	display: flex;
	align-items: center;
	justify-content: space-around;
}

.box-des-scroll {
	box-sizing: border-box;
	padding: 0 40rpx;
	height: 200rpx;
	text-align: left;
}

.box-des {
	font-size: 26rpx;
	color: #000000;
	line-height: 50rpx;
}

.progress-box {
	width: 100%;
}

.progress {
	width: 90%;
	height: 40rpx;
	/* border-radius: 35px; */
}

.close-img {
	width: 70rpx;
	height: 70rpx;
	z-index: 1000;
	position: absolute;
	bottom: -120rpx;
	left: calc(50% - 70rpx / 2);
}

.content-button {
	text-align: center;
	flex: 1;
	font-size: 30rpx;
	font-weight: 400;
	color: #ffffff;
	border-radius: 40rpx;
	margin: 0 18rpx;

	height: 80rpx;
	line-height: 80rpx;

	background: linear-gradient(to right, #1785ff, #3da7ff);
}

.flex-column {
	display: flex;
	flex-direction: column;
	align-items: center;
}
</style>