508 lines
12 KiB
Vue
508 lines
12 KiB
Vue
<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="../../static/images/app_update_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="isiOS">
|
||
<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"
|
||
border-radius="35"
|
||
:percent="downLoadPercent"
|
||
activeColor="#3DA7FF"
|
||
show-info
|
||
stroke-width="10"
|
||
/>
|
||
<view style="width: 100%; font-size: 14px; 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="downloadPackage">
|
||
{{ downLoadBtnText }}
|
||
</button>
|
||
</template>
|
||
<button
|
||
v-else-if="downloadSuccess"
|
||
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="../../static/images/app_update_close.png"
|
||
@click.stop="closeUpdate"
|
||
></image>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed } from 'vue'
|
||
import { onLoad, onBackPress } from '@dcloudio/uni-app'
|
||
|
||
const localFilePathKey = '__localFilePath__'
|
||
const platform_iOS = 'iOS'
|
||
let downloadTask = null
|
||
|
||
// 工具函数保持原样
|
||
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
|
||
}
|
||
|
||
// 响应式状态
|
||
const installForBeforeFilePath = ref('')
|
||
const installed = ref(false)
|
||
const installing = ref(false)
|
||
const downloadSuccess = ref(false)
|
||
const downloading = ref(false)
|
||
const downLoadPercent = ref(0)
|
||
const downloadedSize = ref(0)
|
||
const packageFileSize = ref(0)
|
||
const tempFilePath = ref('')
|
||
|
||
const title = ref('更新日志')
|
||
const contents = ref('')
|
||
const is_mandatory = ref(false)
|
||
const subTitle = ref('发现新版本')
|
||
const downLoadBtnTextiOS = ref('立即跳转更新')
|
||
const downLoadBtnText = ref('立即下载更新')
|
||
const downLoadingText = ref('安装包下载中,请稍后')
|
||
|
||
const version = ref('')
|
||
const url = ref('')
|
||
const type = ref('')
|
||
const platform = ref('')
|
||
|
||
// 计算属性
|
||
const isWGT = computed(() => type.value === 'wgt')
|
||
const isiOS = computed(() => !isWGT.value && platform.value.includes(platform_iOS))
|
||
|
||
// 方法
|
||
function checkLocalStoragePackage() {
|
||
const localFilePathRecord = uni.getStorageSync(localFilePathKey)
|
||
if (localFilePathRecord) {
|
||
const { version: recordVersion, savedFilePath, installed: recordInstalled } = localFilePathRecord
|
||
|
||
if (!recordInstalled && compare(recordVersion, version.value) === 0) {
|
||
downloadSuccess.value = true
|
||
installForBeforeFilePath.value = savedFilePath
|
||
tempFilePath.value = savedFilePath
|
||
} else {
|
||
deleteSavedFile(savedFilePath)
|
||
}
|
||
}
|
||
}
|
||
|
||
async function closeUpdate() {
|
||
if (downloading.value) {
|
||
if (is_mandatory.value) {
|
||
uni.showToast({ title: '下载中,请稍后……', icon: 'none', duration: 500 })
|
||
return
|
||
}
|
||
uni.showModal({
|
||
title: '是否取消下载?',
|
||
cancelText: '否',
|
||
confirmText: '是',
|
||
success: res => {
|
||
if (res.confirm) {
|
||
downloadTask?.abort()
|
||
uni.navigateBack()
|
||
}
|
||
}
|
||
})
|
||
return
|
||
}
|
||
|
||
if (downloadSuccess.value && tempFilePath.value) {
|
||
await saveFile(tempFilePath.value, version.value)
|
||
uni.navigateBack()
|
||
return
|
||
}
|
||
|
||
uni.navigateBack()
|
||
}
|
||
|
||
function downloadPackage() {
|
||
downloading.value = true
|
||
|
||
downloadTask = uni.downloadFile({
|
||
url: url.value,
|
||
success: res => {
|
||
if (res.statusCode === 200) {
|
||
downloadSuccess.value = true
|
||
tempFilePath.value = res.tempFilePath
|
||
// 强制更新
|
||
if (is_mandatory.value) {
|
||
installPackage()
|
||
}
|
||
}
|
||
},
|
||
complete: () => {
|
||
downloading.value = false
|
||
downLoadPercent.value = 0
|
||
downloadedSize.value = 0
|
||
packageFileSize.value = 0
|
||
downloadTask = null
|
||
}
|
||
})
|
||
|
||
downloadTask.onProgressUpdate(res => {
|
||
downLoadPercent.value = res.progress
|
||
downloadedSize.value = (res.totalBytesWritten / 1024 ** 2).toFixed(2)
|
||
packageFileSize.value = (res.totalBytesExpectedToWrite / 1024 ** 2).toFixed(2)
|
||
})
|
||
}
|
||
|
||
function installPackage() {
|
||
// #ifdef APP-PLUS
|
||
if (isWGT.value) {
|
||
installing.value = true
|
||
}
|
||
|
||
plus.runtime.install(
|
||
tempFilePath.value,
|
||
{
|
||
force: false
|
||
},
|
||
async () => {
|
||
installing.value = false
|
||
installed.value = true
|
||
|
||
if (isWGT.value) {
|
||
if (is_mandatory.value) {
|
||
uni.showLoading({ icon: 'none', title: '安装成功,正在重启……' })
|
||
setTimeout(() => {
|
||
uni.hideLoading()
|
||
restart()
|
||
}, 1000)
|
||
}
|
||
} else {
|
||
const localFilePathRecord = uni.getStorageSync(localFilePathKey)
|
||
uni.setStorageSync(localFilePathKey, {
|
||
...localFilePathRecord,
|
||
installed: true
|
||
})
|
||
}
|
||
},
|
||
async err => {
|
||
if (installForBeforeFilePath.value) {
|
||
await deleteSavedFile(installForBeforeFilePath.value)
|
||
installForBeforeFilePath.value = ''
|
||
}
|
||
|
||
installing.value = false
|
||
installed.value = false
|
||
|
||
uni.showModal({
|
||
title: `更新失败${isWGT.value ? '' : ',APK文件不存在'},请重新下载`,
|
||
content: err.message,
|
||
showCancel: false
|
||
})
|
||
}
|
||
)
|
||
|
||
if (!isWGT.value) {
|
||
uni.navigateBack()
|
||
}
|
||
// #endif
|
||
}
|
||
|
||
function restart() {
|
||
installed.value = false
|
||
// #ifdef APP-PLUS
|
||
plus.runtime.restart()
|
||
// #endif
|
||
}
|
||
|
||
async function saveFile(tempFilePath, version) {
|
||
try {
|
||
const res = await uni.saveFile({ tempFilePath })
|
||
uni.setStorageSync(localFilePathKey, {
|
||
version,
|
||
savedFilePath: res.savedFilePath
|
||
})
|
||
} catch (err) {
|
||
console.error('保存文件失败:', err)
|
||
}
|
||
}
|
||
|
||
async function deleteSavedFile(filePath) {
|
||
try {
|
||
await uni.removeSavedFile({ filePath })
|
||
uni.removeStorageSync(localFilePathKey)
|
||
} catch (err) {
|
||
console.error('删除文件失败:', err)
|
||
}
|
||
}
|
||
|
||
function jumpToAppStore() {
|
||
plus.runtime.openURL(url.value)
|
||
}
|
||
|
||
// 生命周期
|
||
onLoad(params => {
|
||
const { local_storage_key } = params
|
||
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 requiredKeys = ['version', 'url', 'type']
|
||
for (const key of requiredKeys) {
|
||
if (!localPackageInfo[key]) {
|
||
console.error(`参数 ${key} 必填`)
|
||
uni.navigateBack()
|
||
return
|
||
}
|
||
}
|
||
|
||
// 更新响应式状态
|
||
version.value = localPackageInfo.version
|
||
url.value = localPackageInfo.url
|
||
type.value = localPackageInfo.type
|
||
platform.value = localPackageInfo.platform || ''
|
||
title.value = localPackageInfo.title || '更新日志'
|
||
contents.value = localPackageInfo.contents || ''
|
||
is_mandatory.value = localPackageInfo.is_mandatory || false
|
||
subTitle.value = localPackageInfo.subTitle || '发现新版本'
|
||
downLoadBtnTextiOS.value = localPackageInfo.downLoadBtnTextiOS || '立即跳转更新'
|
||
downLoadBtnText.value = localPackageInfo.downLoadBtnText || '立即下载更新'
|
||
downLoadingText.value = localPackageInfo.downLoadingText || '安装包下载中,请稍后'
|
||
|
||
checkLocalStoragePackage()
|
||
})
|
||
|
||
onBackPress(() => {
|
||
if (is_mandatory.value) return true
|
||
if (downloadTask) downloadTask.abort()
|
||
return false
|
||
})
|
||
</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: 15px;
|
||
border-bottom-right-radius: 15px;
|
||
}
|
||
|
||
.content {
|
||
position: relative;
|
||
top: 0;
|
||
width: 400px;
|
||
background-color: #fff;
|
||
box-sizing: border-box;
|
||
padding: 0 30px;
|
||
font-family: Source Han Sans CN;
|
||
max-height: 80vh;
|
||
overflow: hidden;
|
||
border-radius: 20px;
|
||
}
|
||
|
||
.content-top {
|
||
position: absolute;
|
||
top: -55px;
|
||
left: 0;
|
||
width: 400px;
|
||
height: 180px;
|
||
}
|
||
|
||
.content-top-text {
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
color: #f8f8fa;
|
||
position: absolute;
|
||
top: 80px;
|
||
left: 30px;
|
||
z-index: 1;
|
||
}
|
||
|
||
.content-header {
|
||
padding-top: 80px;
|
||
height: 40px;
|
||
}
|
||
|
||
.content-body {
|
||
padding-bottom: 20px;
|
||
}
|
||
|
||
.title {
|
||
font-size: 20px;
|
||
font-weight: bold;
|
||
color: #3da7ff;
|
||
line-height: 1.5;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.footer {
|
||
height: 90px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-around;
|
||
margin-top: 15px;
|
||
}
|
||
|
||
.box-des-scroll {
|
||
box-sizing: border-box;
|
||
padding: 0 20px;
|
||
height: 50px;
|
||
text-align: left;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.box-des {
|
||
font-size: 16px;
|
||
color: #000000;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.progress-box {
|
||
width: 100%;
|
||
}
|
||
|
||
.progress {
|
||
width: 90%;
|
||
height: 20px;
|
||
border-radius: 10px;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.close-img {
|
||
width: 40px;
|
||
height: 40px;
|
||
z-index: 1000;
|
||
position: absolute;
|
||
bottom: -70px;
|
||
left: calc(50% - 20px);
|
||
}
|
||
|
||
.content-button {
|
||
text-align: center;
|
||
flex: 1;
|
||
font-size: 18px;
|
||
font-weight: 400;
|
||
color: #ffffff;
|
||
border-radius: 20px;
|
||
margin: 0 10px;
|
||
height: 50px;
|
||
line-height: 50px;
|
||
background: linear-gradient(to right, #1785ff, #3da7ff);
|
||
min-width: 160px;
|
||
}
|
||
|
||
.flex-column {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
}
|
||
</style>
|