This commit is contained in:
houjunxiang
2025-10-09 18:19:55 +08:00
parent f2ffc65094
commit 386f1e7466
1553 changed files with 284685 additions and 32820 deletions

View File

@@ -0,0 +1,507 @@
<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>