提交 ad080cde 编写于 作者: D DCloud_LXH

feat: 新增 升级中心

上级 d7bb3e1c
...@@ -39,6 +39,8 @@ ...@@ -39,6 +39,8 @@
</uni-collapse-item> </uni-collapse-item>
</template> </template>
</uni-collapse> </uni-collapse>
<upgradePopup ref="upgradePopup" @close="upgradePopupClose" />
</view> </view>
<!-- #ifdef APP --> <!-- #ifdef APP -->
</scroll-view> </scroll-view>
...@@ -46,6 +48,9 @@ ...@@ -46,6 +48,9 @@
</template> </template>
<script lang="uts"> <script lang="uts">
import checkUpdate from '@/uni_modules/uni-upgrade-center-app/utils/check-update'
import upgradePopup from '@/uni_modules/uni-upgrade-center-app/pages/upgrade-popup-uts.uvue'
type Page = { type Page = {
name : string name : string
enable ?: boolean enable ?: boolean
...@@ -267,6 +272,9 @@ export default { ...@@ -267,6 +272,9 @@ export default {
arrowRightIcon: '/static/icons/arrow-right.png', arrowRightIcon: '/static/icons/arrow-right.png',
} }
}, },
components: {
upgradePopup: upgradePopup
},
methods: { methods: {
goDetailPage(e : Page) { goDetailPage(e : Page) {
if (e.enable == false) { if (e.enable == false) {
...@@ -282,7 +290,16 @@ export default { ...@@ -282,7 +290,16 @@ export default {
url, url,
}) })
}, },
upgradePopupClose() {
console.log('upgradePopup close');
}
},
onReady() {
checkUpdate(this.$refs['upgradePopup'] as ComponentPublicInstance)
}, },
beforeUnmount() {
uni.showTabBar()?.catch(_ => {})
}
} }
</script> </script>
......
## 0.6.5(2023-10-27)
- 修复 安装 wgt 报错 manifest.json 文件不存在的Bug
## 0.6.4(2023-09-01)
chore: 优化代码结构
## 0.6.3(2023-08-30)
- 修复 下载 wgt 时如果后缀名不正确,重命名后安装
## 0.6.2(2022-11-21)
- 处理 cloudfunctions 目录
## 0.6.1(2022-08-17)
- 修复 后台添加应用市场,但都没有启用的情况下报错的Bug (需要 uni-admin 1.9.3+)
## 0.6.0(2022-07-19)
- 新增 支持多应用商店配置(需要 uni-admin 1.9.3+)
## 0.4.1(2022-05-27)
- 修复 上版引出的报错问题
## 0.4.0(2022-05-27)
- 新增 Android 支持跳转手机自带商店,填写升级包地址时请填写跳转商店链接
- 新增 改为云对象调用方式,使用更直观
## 0.3.3(2022-04-14)
- 修复 调用 check-update,当 code 为 0 时没有回调
## 0.3.2(2022-01-12)
- 优化显示逻辑
## 0.3.1(2021-11-24)
- 修复 vue3 上图片不显示的Bug
## 0.3.0(2021-11-18)
- 移除 wgt 安装成功后提示,防止重启过快弹框不消失
## 0.2.2(2021-08-25)
- 兼容vue3.0
## 0.2.1(2021-07-26)
- 修复 使用腾讯云并手动填写地址时,导致下载链接失效的bug
## 0.2.0(2021-07-13)
- 更新文档 关于报错local_storage_key 为空,请不要将页面路径设置为pages.json中第一项
## 0.1.9(2021-06-28)
- 更新文档
- 修复 wgt安装失败时,按钮状态不对
## 0.1.8(2021-06-16)
- 修复 跳转安装时,导致上次下载的apk还没安装就被删掉的bug
## 0.1.7(2021-06-03)
- 修改 移除static中的图片
## 0.1.6(2021-06-03)
- 修改 下载更新按钮使用CSS渐变色
## 0.1.5(2021-04-22)
- 更新check-update函数。现在返回一个Promise,有更新时成功回调,其他情况错误回调
## 0.1.4(2021-04-13)
- 更新文档。明确云函数调用结果
## 0.1.3(2021-04-13)
- 解耦云函数与弹框处理。utils中新增 call-check-version.js,可用于单独检测是否有更新
## 0.1.2(2021-04-07)
- 更新版本对比函数 compare
## 0.1.1(2021-04-07)
- 修复 腾讯云空间下载链接不能下载问题
## 0.1.0(2021-04-07)
- 新增使用uni.showModal提示升级示例
- 修改iOS升级提示方式
## 0.0.7(2021-04-02)
- 修复在iOS上打开弹框报错
## 0.0.6(2021-04-01)
- 兼容旧版本安卓
## 0.0.5(2021-04-01)
- 修复低版本安卓上进度条错位
## 0.0.4(2021-04-01)
- 更新readme
- 修复check-update语法错误
## 0.0.3(2021-04-01)
- 新增前台更新弹框,详见readme
- 更新前台检查更新方法
## 0.0.2(2021-03-29)
- 更新文档
- 移除 dependencies
## 0.0.1(2021-03-25)
- 升级中心前台检查更新
{
"id": "uni-upgrade-center-app",
"displayName": "升级中心 uni-upgrade-center - App",
"version": "0.6.5",
"description": "uni升级中心 - 客户端检查更新",
"keywords": [
"uniCloud",
"update",
"升级",
"wgt"
],
"repository": "https://gitee.com/dcloud/uni-upgrade-center/tree/master/uni_modules/uni-upgrade-center-app",
"engines": {
"HBuilderX": "^3.2.14"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "插件不采集任何数据",
"permissions": "无"
},
"npmurl": "",
"type": "unicloud-template-page"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "u"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "u",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}
<template>
<view v-show="shown" class="mask flex-center">
<view class="content">
<view class="content-top">
<text class="content-top-text">{{title}}</text>
<image class="content-top-image" mode="widthFix"
src="/uni_modules/uni-upgrade-center-app/static/bg_top.png"></image>
</view>
<view class="content-body">
<view class="content-body-title">
<text class="text title">{{subTitle}}</text>
<!-- <text style="padding-left:20rpx;font-size: 0.5em;color: #666;">v.{{version}}</text> -->
</view>
<view class="body">
<scroll-view class="box-des-scroll" scroll-y="true">
<text class="text box-des">
{{contents}}
</text>
</scroll-view>
</view>
<view class="footer flex-center">
<template v-if="isiOS">
uni-app x 暂不支持 iOS
<!-- <button class="content-button" style="border: none;color: #fff;" plain @click="jumpToAppStore">
{{downLoadBtnTextiOS}}
</button> -->
</template>
<template v-else>
<template v-if="!downloadSuccess">
<view class="progress-box flex-column" v-if="downloading">
<progress class="progress" style="width: 90%;height: 40rpx;border-radius: 35px;"
:percent="downLoadPercent" activeColor="#3DA7FF" :show-info="true"
:stroke-width="10" />
<view
style="width:100%;display: flex;justify-content: space-around;flex-direction: row;">
<text class="text" style="font-size: 28rpx;">{{downLoadingText}}</text>
<text class="text"
style="font-size: 28rpx;">({{downloadedSize}}/{{packageFileSize}}M)</text>
</view>
</view>
<button v-else class="content-button"
style="border: none; color: #fff;width: 100%;height: 80rpx;line-height: 80rpx;font-size: 30rpx;font-weight: 400;border-radius: 40rpx;text-align: center;background: #1785ff"
:plain="true" @click="updateApp">
{{downLoadBtnText}}
</button>
</template>
<button v-else-if="downloadSuccess && !installed" class="content-button"
style="border: none; color: #fff;width: 100%;height: 80rpx;line-height: 80rpx;font-size: 30rpx;font-weight: 400;border-radius: 40rpx;text-align: center;background: #1785ff"
:plain="true" :loading="installing" :disabled="installing" @click="installPackage">
{{installing ? '正在安装……' : '下载完成,立即安装'}}
</button>
<button v-else-if="installed" class="content-button"
style="border: none; color: #ccc;width: 100%;height: 80rpx;line-height: 80rpx;font-size: 30rpx;font-weight: 400;border-radius: 40rpx;text-align: center;background: #1785ff"
:plain="true" :disabled="true">
安装完成后,请重启应用
</button>
</template>
</view>
</view>
<view class="content-bottom">
<image v-if="!is_mandatory" class="close-img" mode="widthFix"
src="/uni_modules/uni-upgrade-center-app/static/app_update_close.png" @click="closeUpdate">
</image>
</view>
</view>
</view>
</template>
<script>
import { UniUpgradeCenterResult } from '../utils/call-check-version'
const localFilePathKey = 'UNI_ADMIN_UPGRADE_CENTER_LOCAL_FILE_PATH'
const platform_iOS = 'iOS';
const requiredKey = ['version', 'url', 'type']
let downloadTask : DownloadTask | null = null;
/**
* 对比版本号,如需要,请自行修改判断规则
* 支持比对 ("3.0.0.0.0.1.0.1", "3.0.0.0.0.1") ("3.0.0.1", "3.0") ("3.1.1", "3.1.1.1") 之类的
* @param {Object} v1
* @param {Object} v2
* v1 > v2 return 1
* v1 < v2 return -1
* v1 == v2 return 0
*/
function compare(v1 : string = '0', v2 : string = '0') : number {
const v1_array = v1.split('.')
const v2_array = v2.split('.')
const minVersionLens = Math.min(v1.length, v2.length);
let result = 0;
for (let i = 0; i < minVersionLens; i++) {
const curV1 = parseInt(v1_array[i])
const curV2 = parseInt(v2_array[i])
if (curV1 > curV2) {
result = 1
break;
} else if (curV1 < curV2) {
result = -1
break;
}
}
if (result === 0 && (v1_array.length !== v2_array.length)) {
const v1BiggerThenv2 = v1_array.length > v2_array.length;
const maxLensVersion = v1BiggerThenv2 ? v1_array : v2_array;
for (let i = minVersionLens; i < maxLensVersion.length; i++) {
const curVersion = parseInt(maxLensVersion[i])
if (curVersion > 0) {
v1BiggerThenv2 ? result = 1 : result = -1
break;
}
}
}
return result;
}
export default {
data() {
return {
shown: false,
showTabbar: false,
// 从之前下载安装
installForBeforeFilePath: '',
// 安装
installed: false,
installing: false,
// 下载
downloadSuccess: false,
downloading: false,
downLoadPercent: 0,
downloadedSize: 0,
packageFileSize: 0,
tempFilePath: '', // 要安装的本地包地址
// 默认安装包信息
title: '更新日志',
contents: '',
is_mandatory: false,
// 可自定义属性
subTitle: '发现新版本',
downLoadBtnTextiOS: '立即跳转更新',
downLoadBtnText: '立即下载更新',
downLoadingText: '安装包下载中,请稍后',
url: "",
platform: [] as string[]
}
},
computed: {
isiOS() : boolean {
return this.platform.includes(platform_iOS);
}
},
methods: {
show(shown : boolean, localPackageInfo : UniUpgradeCenterResult | null) {
if (localPackageInfo === null) return;
uni.hideTabBar()?.then(_ => { this.showTabbar = true })?.catch(_ => { })
for (let key in localPackageInfo) {
if (requiredKey.indexOf(key) != -1 && localPackageInfo[key] === null) {
console.error(`参数 ${key} 必填,请检查后重试`)
uni.navigateBack()
return;
}
}
this.title = localPackageInfo.title
this.url = localPackageInfo.url
this.contents = localPackageInfo.contents
this.is_mandatory = localPackageInfo.is_mandatory
this.platform = localPackageInfo.platform
this.shown = shown
},
closeUpdate() {
if (this.downloading) {
if (this.is_mandatory) {
return uni.showToast({
title: '下载中,请稍后……',
icon: 'none',
duration: 500
})
}
uni.showModal({
title: '是否取消下载?',
cancelText: '否',
confirmText: '是',
success: res => {
if (res.confirm) {
if (downloadTask !== null) downloadTask!.abort()
this.closePopup()
}
}
});
return;
}
this.closePopup()
},
closePopup() {
this.shown = false
if (this.showTabbar) {
uni.showTabBar()?.then(_ => { })?.catch(_ => { })
}
this.downloadSuccess = false
this.downloading = false
this.downLoadPercent = 0
this.downloadedSize = 0
this.packageFileSize = 0
this.tempFilePath = ''
this.installing = false;
this.installed = false
this.showTabbar = false
this.$emit('close')
},
updateApp() {
this.downloadPackage()
},
downloadPackage() {
//下载包
downloadTask = uni.downloadFile({
url: this.url,
success: res => {
console.log('downloadFile res: ',res);
if (res.statusCode == 200) {
this.tempFilePath = res.tempFilePath
this.downLoadComplete()
}
},
fail: err => {
console.log('downloadFile err: ', err);
}
});
if (downloadTask !== null) {
this.downloading = true;
downloadTask!.onProgressUpdate(res => {
this.downLoadPercent = parseFloat(res.progress.toFixed(0));
this.downloadedSize = parseFloat((res.totalBytesWritten / Math.pow(1024, 2)).toFixed(2));
this.packageFileSize = parseFloat((res.totalBytesExpectedToWrite / Math.pow(1024, 2)).toFixed(2));
});
}
},
downLoadComplete() {
this.downloadSuccess = true;
this.downloading = false;
this.downLoadPercent = 0
this.downloadedSize = 0
this.packageFileSize = 0
downloadTask = null;
// 强制更新,直接安装
if (this.is_mandatory) {
this.installPackage();
}
},
installPackage() {
this.installing = true;
// #ifdef APP-PLUS
uni.installApk({
filePath: this.tempFilePath,
success: _ => {
console.log('installApk success');
this.installing = false;
this.installed = true;
},
fail: err => {
console.log('installApk fail');
// 安装失败需要重新下载安装包
this.installing = false;
this.installed = false;
uni.showModal({
title: '更新失败,请重新下载',
content: `uni.installApk 错误码 ${err.errCode}`,
showCancel: false
});
}
});
// 非wgt包,安装跳出覆盖安装,此处直接返回上一页
if (!this.is_mandatory) {
uni.navigateBack()
}
// #endif
}
}
}
</script>
<style>
.flex-center {
/* #ifndef APP-NVUE | UNI-APP-X */
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, .65);
}
.content {
position: relative;
top: 0;
width: 600rpx;
background-color: transparent;
}
.text {
font-family: Source Han Sans CN;
}
.content-top {
width: 100%;
border-bottom-color: #fff;
border-bottom-width: 30rpx;
border-bottom-style: solid;
}
.content-top-image {
width: 100%;
position: relative;
bottom: -35rpx;
}
.content-top-text {
font-size: 45rpx;
font-weight: bold;
color: #F8F8FA;
position: absolute;
width: 65%;
top: 155rpx;
left: 50rpx;
z-index: 1;
}
.content-body {
box-sizing: border-box;
padding: 0 50rpx;
width: 100%;
background-color: #fff;
border-bottom-left-radius: 30rpx;
border-bottom-right-radius: 30rpx;
}
.title {
font-size: 32rpx;
font-weight: bold;
color: #3DA7FF;
line-height: 38px;
}
.footer {
height: 150rpx;
display: flex;
align-items: center;
justify-content: space-around;
}
.box-des-scroll {
box-sizing: border-box;
padding: 0 40rpx;
height: 200rpx;
}
.box-des {
font-size: 26rpx;
color: #000000;
line-height: 50rpx;
}
.progress-box {
width: 100%;
}
.progress {
width: 90%;
height: 40rpx;
}
.content-bottom {
height: 150rpx;
}
.close-img {
width: 70rpx;
height: 70rpx;
z-index: 1000;
position: relative;
bottom: -50rpx;
left: 265rpx;
}
.content-button {
width: 100%;
height: 80rpx;
line-height: 80rpx;
font-size: 30rpx;
font-weight: 400;
border-radius: 40rpx;
text-align: center;
background-color: #1785ff;
}
.flex-column {
display: flex;
flex-direction: column;
align-items: center;
}
</style>
\ No newline at end of file
<template>
<view class="mask flex-center">
<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="../images/bg_top.png">
</image>
</view>
<view class="content-header"></view>
<view class="content-body">
<view class="title">
<text>{{subTitle}}</text>
<!-- <text style="padding-left:20rpx;font-size: 0.5em;color: #666;">v.{{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="isAppStore">
<button class="content-button" style="border: none;color: #fff;" plain @click="jumpToAppStore">
{{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-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="../images/app_update_close.png" @click.stop="closeUpdate">
</image>
</view>
</view>
</template>
<script>
const localFilePathKey = 'UNI_ADMIN_UPGRADE_CENTER_LOCAL_FILE_PATH'
const platform_iOS = 'iOS';
let downloadTask = null;
let openSchemePromise
/**
* 对比版本号,如需要,请自行修改判断规则
* 支持比对 ("3.0.0.0.0.1.0.1", "3.0.0.0.0.1") ("3.0.0.1", "3.0") ("3.1.1", "3.1.1.1") 之类的
* @param {Object} v1
* @param {Object} v2
* v1 > v2 return 1
* v1 < v2 return -1
* v1 == v2 return 0
*/
function compare(v1 = '0', v2 = '0') {
v1 = String(v1).split('.')
v2 = String(v2).split('.')
const minVersionLens = Math.min(v1.length, v2.length);
let result = 0;
for (let i = 0; i < minVersionLens; i++) {
const curV1 = Number(v1[i])
const curV2 = Number(v2[i])
if (curV1 > curV2) {
result = 1
break;
} else if (curV1 < curV2) {
result = -1
break;
}
}
if (result === 0 && (v1.length !== v2.length)) {
const v1BiggerThenv2 = v1.length > v2.length;
const maxLensVersion = v1BiggerThenv2 ? v1 : v2;
for (let i = minVersionLens; i < maxLensVersion.length; i++) {
const curVersion = Number(maxLensVersion[i])
if (curVersion > 0) {
v1BiggerThenv2 ? result = 1 : result = -1
break;
}
}
}
return result;
}
export default {
data() {
return {
// 从之前下载安装
installForBeforeFilePath: '',
// 安装
installed: false,
installing: false,
// 下载
downloadSuccess: false,
downloading: false,
downLoadPercent: 0,
downloadedSize: 0,
packageFileSize: 0,
tempFilePath: '', // 要安装的本地包地址
// 默认安装包信息
title: '更新日志',
contents: '',
is_mandatory: false,
// 可自定义属性
subTitle: '发现新版本',
downLoadBtnTextiOS: '立即跳转更新',
downLoadBtnText: '立即下载更新',
downLoadingText: '安装包下载中,请稍后'
}
},
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;
};
const requiredKey = ['version', 'url', 'type']
for (let key in localPackageInfo) {
if (requiredKey.indexOf(key) !== -1 && !localPackageInfo[key]) {
console.error(`参数 ${key} 必填,请检查后重试`)
uni.navigateBack()
return;
}
}
Object.assign(this, localPackageInfo)
this.checkLocalStoragePackage()
},
onBackPress() {
// 强制更新不允许返回
if (this.is_mandatory) {
return true
}
downloadTask && downloadTask.abort()
},
onHide() {
openSchemePromise = null
},
computed: {
isWGT() {
return this.type === 'wgt'
},
isiOS() {
return !this.isWGT ? this.platform.includes(platform_iOS) : false;
},
isAppStore() {
return this.isiOS || (!this.isiOS && !this.isWGT && this.url.indexOf('.apk') === -1)
}
},
methods: {
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)
}
}
},
async closeUpdate() {
if (this.downloading) {
if (this.is_mandatory) {
return uni.showToast({
title: '下载中,请稍后……',
icon: 'none',
duration: 500
})
}
uni.showModal({
title: '是否取消下载?',
cancelText: '',
confirmText: '',
success: res => {
if (res.confirm) {
downloadTask && downloadTask.abort()
uni.navigateBack()
}
}
});
return;
}
if (this.downloadSuccess && this.tempFilePath) {
// 包已经下载完毕,稍后安装,将包保存在本地
await this.saveFile(this.tempFilePath, this.version)
uni.navigateBack()
return;
}
uni.navigateBack()
},
updateApp() {
this.checkStoreScheme().catch(() => {
this.downloadPackage()
})
},
// 跳转应用商店
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) {
this.downloadSuccess = true;
// fix: wgt 文件下载完成后后缀不是 wgt
if (this.isWGT && res.tempFilePath.split('.').slice(-1)[0] !== 'wgt') {
const failCallback = (e) => {
console.log('[FILE RENAME FAIL]:', JSON.stringify(e));
}
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);
} else {
this.tempFilePath = res.tempFilePath
this.downLoadComplete()
}
}
}
});
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);
});
},
downLoadComplete() {
this.downloading = false;
this.downLoadPercent = 0
this.downloadedSize = 0
this.packageFileSize = 0
downloadTask = null;
// 强制更新,直接安装
if (this.is_mandatory) {
this.installPackage();
}
},
installPackage() {
// #ifdef APP-PLUS
// 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) {
uni.showLoading({
icon: 'none',
title: '安装成功,正在重启……'
})
setTimeout(() => {
uni.hideLoading()
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-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
})
},
jumpToAppStore() {
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, .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;
}
.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>
\ No newline at end of file
## 升级中心 - app插件与 `uni-admin` 版本关系
### `uni-admin >= 1.9.3`:云函数 `checkVersion` 废弃,使用 uni-admin 自带的 `uni-upgrade-center` 云函数。
# uni-upgrade-center - App
### 概述
> 统一管理App及App在`Android`、`iOS`平台上`App安装包`和`wgt资源包`的发布升级
> uni升级中心分为业务插件和后台管理插件。本插件为业务插件,包括uni升级中心客户端检查更新的前后端逻辑。后台管理系统另见 [uni-upgrade-center - Admin](https://ext.dcloud.net.cn/plugin?id=4470)
### uni升级中心 - 客户端检查更新插件
- 一键式检查更新,同时支持整包升级与wgt资源包更新
- 好看、实用、可自定义的客户端提示框
## 安装指引
1. 依赖数据库`opendb-app-versions`,如果没有此库,请在云服务空间中创建。
2. 使用`HBuilderX 3.1.0+`,因为要使用到`uni_modules`
3. 在插件市场打开本插件页面,在右侧点击`使用 HBuilderX 导入插件`,选择要导入的项目点击确定
4. 绑定一个服务空间。自 `0.6.0` 起,依赖 `uni-admin 1.9.3+``uni-upgrade-center 云函数`,请和 uni-admin 项目关联同一个服务空间
5. 找到`/uni_modules/uni-upgrade-center-app/uniCloud/cloudfunctions/check-version`,右键上传部署。自 `0.6.0` 起,依赖 `uni-admin 1.9.3+``uni-upgrade-center 云函数`,插件不再单独提供云函数,这样可以省下一个云函数名额。
6.`pages.json`中添加页面路径。**注:请不要设置为pages.json中第一项**
```json
"pages": [
// ……其他页面配置
{
"path": "uni_modules/uni-upgrade-center-app/pages/upgrade-popup",
"style": {
"disableScroll": true,
"app-plus": {
"backgroundColorTop": "transparent",
"background": "transparent",
"titleNView": false,
"scrollIndicator": false,
"popGesture": "none",
"animationType": "fade-in",
"animationDuration": 200
}
}
}
]
```
7.`@/uni_modules/uni-upgrade-center-app/utils/check-update`import到需要用到的地方,调用一下即可
1. 默认使用当前绑定的服务空间,如果要请求其他服务空间,可以使用其他服务空间的 `callFunction`[详情](https://uniapp.dcloud.io/uniCloud/cf-functions.html#call-by-function-cross-space)
8. 升级弹框可自行编写,也可以使用`uni.showModal`,或使用现有的升级弹框样式,如果不满足UI需求请自行替换资源文件。在`utils/check-update.js`中都有实例。
9. wgt更新时,打包前请务必将manifest.json中的版本修改为更高版本。
### 更新下载安装`check-update.js`
*该函数在utils目录下*
1. 如果是静默更新,则不会打开更新弹框,会在后台下载后安装,下次启动应用生效
2. 如果是 iOS,则会直接打开AppStore的链接
3. 其他情况,会将`check-version`返回的结果保存在localStorage中,并跳转进入`upgrade-popup.vue`打开更新弹框
### 检查更新函数`check-version`
*该函数在uniCloud/cloudfunctions目录下*
1. 使用检查更新需要传递三个参数 `appid``appVersion``wgtVersion`
2. `appid` 使用 plus.runtime.appid 获取,*注:真机运行时为固定值HBuilder,在调试的时候请使用本地调试云函数*
3. `appVersion` 使用 plus.runtime.version 获取
4. `wgtVersion` 使用 plus.runtime.getProperty(plus.runtime.appid,(wgtInfo) => { wgtInfo.version }) 获取
5. `check-version`云函数内部会自动获取 App 平台
**Tips**
1. `check-version`云函数内部有版本对比函数(compare)。
- 使用多段式版本格式(如:"3.0.0.0.0.1.0.1", "3.0.0.0.0.1")。如果不满足对比规则,请自行修改。
- 如果修改,请将*pages/upgrade-popup.vue**compare*函数一并修改
## 项目代码说明
### 更新弹框
- `upgrade-popup.vue` - 更新应用:
- 如果云函数`check-version`返回的参数表明需要更新,则将参数保存在localStorage中,带着键值跳转该页面
- 进入时会先从localStorage中尝试取出之前存的安装包路径(此包不会是强制安装类型的包)
- 如果有已经保存的包,则和传进来的 `version` 进行比较,如果相等则安装。大于和小于都不进行安装,因为admin端可能会调整包的版本。不符合更新会将此包删除
- 如果本地没有包或者包不符合安装条件,则进行下载安装包
- 点击下载会有进度条、已下载大小和下载包的大小
- 下载完成会提示安装:
- 如果是 wgt 包,安装时则会提示 正在安装…… 和 安装完成。安装完成会提示是否重启
- 如果是 原生安装包,则直接跳出去覆盖安装
- 下载过程中,如果退出会提示是否取消下载。如果是强制更新,则只会提示正在下载请稍后,此时不可退出
- 如果是下载完成了没有安装就退出,则会将下载完成的包保存在本地。将包的本地路径和包version保存在localStorage中
### 工具类 utils
- `call-check-version`
- 请求云函数`check-version`拿取版本检测结果
- `check-update`
- 调用`call-check-version`并根据结果判断是否显示更新弹框
### 云函数
- `check-version` - 检查应用更新:
- 根据传参,先检测传参是否完整,appid appVersion wgtVersion 必传
- 先从数据库取出所有该平台(会从上下文读取平台信息)的所有线上发行更新
- 再从所有线上发行更新中取出版本最大的一版。如果可以,尽量先检测wgt的线上发行版更新
- 使用上一步取出的版本包的版本号 和传参 appVersion、wgtVersion 来检测是否有更新。必须同时大于这两项,因为上一次可能是wgt热更新,否则返回暂无更新
- 如果库中 wgt包 版本大于传参 appVersion,但是不满足 min_uni_version < appVersion,则不会使用wgt更新,会接着判断库中 app包version 是否大于 appVersion
- 返回结果:
|code|message|
|:-:|:-:|
|0|当前版本已经是最新的,不需要更新|
|101|wgt更新|
|102|整包更新|
|-101|暂无更新或检查appid是否填写正确|
|-102|请检查传参是否填写正确|
\ No newline at end of file
export type StoreListItem = {
enable : boolean
id : string
name : string
scheme : string
priority : number // 优先级
}
export type UniUpgradeCenterResult = {
_id : string
appid : string
name : string
title : string
contents : string
url : string // 安装包下载地址
platform : Array<string> // Array<'Android' | 'iOS'>
version : string // 版本号 1.0.0
uni_platform : string // "android" | "ios" // 版本号 1.0.0
stable_publish : boolean // 是否是稳定版
is_mandatory : boolean // 是否强制更新
is_silently : boolean // 是否静默更新
create_env : string // "upgrade-center"
create_date : number
message : string
code : number
type : string // "native_app" | "wgt"
store_list : StoreListItem[] | null
min_uni_version : string | null // 升级 wgt 的最低 uni-app 版本
}
export default function (): Promise<UniUpgradeCenterResult> {
// #ifdef APP-PLUS
return new Promise<UniUpgradeCenterResult>((resolve, reject) => {
const systemInfo = uni.getSystemInfoSync()
const appId = systemInfo.appId
const appVersion = systemInfo.appVersion
// #ifndef UNI-APP-X
if (plus.runtime.appid && plus.runtime.version) {
plus.runtime.getProperty(plus.runtime.appid, function (widgetInfo) {
if (widgetInfo.version) {
let data = {
action: 'checkVersion',
appid: plus.runtime.appid,
appVersion: plus.runtime.version,
wgtVersion: widgetInfo.version
}
uniCloud.callFunction({
name: 'uni-upgrade-center',
data: data,
success: (e) => {
resolve(e.result as UniUpgradeCenterResult)
},
fail: (error) => {
reject(error)
}
})
} else {
reject('widgetInfo.version is EMPTY')
}
})
} else {
reject('plus.runtime.appid is EMPTY')
}
// #endif
// // #ifdef UNI-APP-X
if (typeof appId === 'string' && typeof appVersion === 'string' && appId.length > 0 && appVersion.length > 0) {
let data = {
action: 'checkVersion',
appid: appId,
appVersion: appVersion,
wgtVersion: '0.0.0.0.0.1'
}
try {
uniCloud.callFunction({
name: 'uni-upgrade-center',
data: data
}).then(res => {
const result = JSON.parse<UniUpgradeCenterResult>(JSON.stringify(res.result))
if (result === null ){
reject({
code: res.result['code'],
message: res.result['message']
})
} else {
resolve(result)
}
}).catch<void>((err : any | null) => {
console.log('err: ', err);
const error = err as UniCloudError
reject(error.errMsg)
})
} catch (e) {
reject(e.message)
}
} else {
reject('plus.runtime.appid is EMPTY')
}
// #endif
})
// #endif
// #ifndef APP-PLUS
/* return new Promise((resolve, reject) => {
reject({
message: '请在App中使用'
})
}) */
// #endif
}
import callCheckVersion, { UniUpgradeCenterResult } from "./call-check-version"
// #ifdef UNI-APP-X
import { ComponentPublicInstance } from 'vue'
// #endif
// 推荐再App.vue中使用
const PACKAGE_INFO_KEY = '__package_info__'
// #ifdef UNI-APP-X
export default function (component : ComponentPublicInstance | null = null) : Promise<UniUpgradeCenterResult> {
// #endif
// #ifndef UNI-APP-X
export default function () : Promise<UniUpgradeCenterResult> {
// #endif
// #ifdef APP-PLUS
return new Promise<UniUpgradeCenterResult>((resolve, reject) => {
callCheckVersion().then(async (uniUpgradeCenterResult) => {
// NOTE uni-app x 3.96 解构有问题
const code = uniUpgradeCenterResult.code
const message = uniUpgradeCenterResult.message
const url = uniUpgradeCenterResult.url // 安装包下载地址
// const is_silently = uniUpgradeCenterResult.is_silently // 是否静默更新
// const platform = uniUpgradeCenterResult.platform // 安装包平台
// const type = uniUpgradeCenterResult.type // 安装包类型
// 此处逻辑仅为实例,可自行编写
if (code > 0) {
// 腾讯云和阿里云下载链接不同,需要处理一下,阿里云会原样返回
const tcbRes = await uniCloud.getTempFileURL({ fileList: [url] });
if (typeof tcbRes.fileList[0].tempFileURL !== 'undefined') uniUpgradeCenterResult.url = tcbRes.fileList[0].tempFileURL;
/**
* 提示升级一
* 使用 uni.showModal
*/
// return updateUseModal(uniUpgradeCenterResult)
/**
* 提示升级二
* 官方适配的升级弹窗,可自行替换资源适配UI风格
*/
// #ifndef UNI-APP-X
uni.setStorageSync(PACKAGE_INFO_KEY, uniUpgradeCenterResult)
uni.navigateTo({
url: `/uni_modules/uni-upgrade-center-app/pages/upgrade-popup?local_storage_key=${PACKAGE_INFO_KEY}`,
fail: (err) => {
console.error('更新弹框跳转失败', err)
uni.removeStorageSync(PACKAGE_INFO_KEY)
}
})
// #endif
// #ifdef UNI-APP-X
component?.$callMethod('show', true, uniUpgradeCenterResult)
// #endif
return resolve(uniUpgradeCenterResult)
} else if (code < 0) {
// TODO 云函数报错处理
console.error(message)
return reject(uniUpgradeCenterResult)
}
return resolve(uniUpgradeCenterResult)
}).catch((err) => {
// TODO 云函数报错处理
console.error(err)
reject(err)
})
});
// #endif
}
// #ifdef UNI-APP-X
/**
* 使用 uni.showModal 升级
*/
function updateUseModal(packageInfo : UniUpgradeCenterResult) : void {
const {
title, // 标题
contents, // 升级内容
is_mandatory, // 是否强制更新
url, // 安装包下载地址
} = packageInfo;
let confirmText = '立即下载更新'
return uni.showModal({
title,
content: contents,
showCancel: !is_mandatory,
confirmText,
success: res => {
if (res.cancel) return;
uni.showToast({
title: '后台下载中……',
duration: 1000
});
// wgt 和 安卓下载更新
uni.downloadFile({
url,
success: res => {
if (res.statusCode !== 200) {
console.error('下载安装包失败');
return;
}
// 下载好直接安装,下次启动生效
uni.installApk({
filePath: res.tempFilePath,
success: () => {
uni.showModal({
title: '安装成功请手动重启'
});
},
fail: err => {
uni.showModal({
title: '更新失败',
content: err
.message,
showCancel: false
});
}
});
}
});
}
});
}
// #endif
// #ifndef UNI-APP-X
/**
* 使用 uni.showModal 升级
*/
function updateUseModal(packageInfo : UniUpgradeCenterResult) : void {
const {
title, // 标题
contents, // 升级内容
is_mandatory, // 是否强制更新
url, // 安装包下载地址
platform, // 安装包平台
type // 安装包类型
} = packageInfo;
let isWGT = type === 'wgt'
let isiOS = !isWGT ? platform.includes('iOS') : false;
let confirmText = isiOS ? '立即跳转更新' : '立即下载更新'
return uni.showModal({
title,
content: contents,
showCancel: !is_mandatory,
confirmText,
success: res => {
if (res.cancel) return;
// 安装包下载
if (isiOS) {
plus.runtime.openURL(url);
return;
}
uni.showToast({
title: '后台下载中……',
duration: 1000
});
// wgt 和 安卓下载更新
uni.downloadFile({
url,
success: res => {
if (res.statusCode !== 200) {
console.error('下载安装包失败');
return;
}
// 下载好直接安装,下次启动生效
plus.runtime.install(res.tempFilePath, {
force: false
}, () => {
if (is_mandatory) {
//更新完重启app
plus.runtime.restart();
return;
}
uni.showModal({
title: '安装成功是否重启?',
success: res => {
if (res.confirm) {
//更新完重启app
plus.runtime.restart();
}
}
});
}, err => {
uni.showModal({
title: '更新失败',
content: err
.message,
showCancel: false
});
});
}
});
}
});
}
// #endif
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册