Files
pda-uniapp/utils/app-update.js
T

196 lines
5.5 KiB
JavaScript
Raw Normal View History

2026-08-26 20:50:58 +08:00
const updateEndpoint = String(import.meta.env.VITE_PDA_UPDATE_URL || '').trim()
let checking = false
function request(options) {
return new Promise((resolve, reject) => {
uni.request({
...options,
success: resolve,
fail: reject
})
})
}
function getRuntimeInfo() {
return new Promise((resolve, reject) => {
if (typeof plus === 'undefined' || !plus.runtime) {
reject(new Error('当前环境不支持 App 在线更新'))
return
}
plus.runtime.getProperty(plus.runtime.appid, (properties) => {
resolve({
appid: plus.runtime.appid,
appVersion: String(plus.runtime.version || ''),
appVersionCode: Number(plus.runtime.versionCode || 0),
wgtVersion: String(properties?.version || plus.runtime.version || '')
})
})
})
}
function normalizeUpdate(payload) {
const value = payload && typeof payload === 'object' ? payload : {}
if (value.update !== true) return null
const packageType = String(value.packageType || '').toLowerCase()
const downloadUrl = String(value.downloadUrl || '').trim()
const versionName = String(value.versionName || '').trim()
if (!['apk', 'wgt'].includes(packageType) || !downloadUrl || !/^\d+(\.\d+)+$/.test(versionName)) {
throw new Error('更新服务返回了无效的版本信息')
}
if (!/^https:\/\//i.test(downloadUrl)) throw new Error('更新包必须使用 HTTPS 地址')
return {
packageType,
downloadUrl,
versionName,
versionCode: Number(value.versionCode || 0),
mandatory: value.mandatory === true,
releaseNotes: String(value.releaseNotes || '').trim()
}
}
function compareVersions(left, right) {
const leftParts = String(left).split('.').map(Number)
const rightParts = String(right).split('.').map(Number)
const length = Math.max(leftParts.length, rightParts.length)
for (let index = 0; index < length; index += 1) {
const difference = (leftParts[index] || 0) - (rightParts[index] || 0)
if (difference !== 0) return difference
}
return 0
}
function isNewerUpdate(update, runtime) {
if (update.packageType === 'apk') {
return Number.isInteger(update.versionCode) && update.versionCode > runtime.appVersionCode
}
return compareVersions(update.versionName, runtime.wgtVersion) > 0
}
function confirmUpdate(update) {
const notes = update.releaseNotes || '修复已知问题并提升作业稳定性。'
return new Promise((resolve) => {
uni.showModal({
title: `${update.mandatory ? '必须' : '发现'}更新 ${update.versionName}`,
content: notes,
confirmText: '立即更新',
cancelText: '稍后再说',
showCancel: !update.mandatory,
success: ({ confirm }) => resolve(Boolean(confirm)),
fail: () => resolve(false)
})
})
}
function downloadUpdate(update) {
return new Promise((resolve, reject) => {
uni.showLoading({ title: '正在下载 0%', mask: true })
const task = uni.downloadFile({
url: update.downloadUrl,
success: (result) => {
if (result.statusCode === 200 && result.tempFilePath) {
resolve(result.tempFilePath)
return
}
reject(new Error(`更新包下载失败(${result.statusCode || '未知状态'})`))
},
fail: () => reject(new Error('更新包下载失败,请检查网络后重试')),
complete: () => uni.hideLoading()
})
task.onProgressUpdate?.(({ progress }) => {
uni.showLoading({ title: `正在下载 ${progress}%`, mask: true })
})
})
}
function installUpdate(filePath, update) {
return new Promise((resolve, reject) => {
plus.runtime.install(
filePath,
{ force: false },
resolve,
() => reject(new Error('更新包安装失败,请重新下载'))
)
})
}
function showFailure(error, mandatory) {
const message = error?.message || '检查更新失败,请稍后重试'
if (!mandatory) {
uni.showToast({ title: message, icon: 'none', duration: 3000 })
return Promise.resolve(false)
}
return new Promise((resolve) => {
uni.showModal({
title: '更新失败',
content: message,
confirmText: '重新尝试',
showCancel: false,
complete: () => resolve(true)
})
})
}
async function runUpdate(update) {
const accepted = await confirmUpdate(update)
if (!accepted) return
try {
const filePath = await downloadUpdate(update)
await installUpdate(filePath, update)
if (update.packageType === 'wgt') {
uni.showModal({
title: '更新完成',
content: '应用将立即重启以启用新版本。',
showCancel: false,
complete: () => plus.runtime.restart()
})
}
} catch (error) {
const retry = await showFailure(error, update.mandatory)
if (retry) await runUpdate(update)
}
}
export async function checkForAppUpdate({ manual = false } = {}) {
if (checking) return
if (!updateEndpoint) {
if (manual) uni.showToast({ title: '未配置更新服务', icon: 'none' })
return
}
checking = true
try {
if (!/^https:\/\//i.test(updateEndpoint)) throw new Error('更新服务必须使用 HTTPS 地址')
const runtime = await getRuntimeInfo()
const response = await request({
url: updateEndpoint,
method: 'GET',
data: {
appid: runtime.appid,
platform: 'android',
appVersion: runtime.appVersion,
appVersionCode: runtime.appVersionCode,
wgtVersion: runtime.wgtVersion
}
})
if (response.statusCode !== 200) throw new Error('更新服务暂时不可用')
const update = normalizeUpdate(response.data?.data ?? response.data)
if (update && isNewerUpdate(update, runtime)) {
await runUpdate(update)
} else if (manual) {
uni.showToast({ title: '当前已是最新版本', icon: 'none' })
}
} catch (error) {
if (manual) await showFailure(error, false)
} finally {
checking = false
}
}