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, runtime) { const value = payload && typeof payload === 'object' ? payload : {} if (value.update !== true) return null if (value.appid !== runtime.appid) throw new Error('更新包与当前应用不匹配') 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 地址') const minAppVersionCode = Number(value.minAppVersionCode) if (packageType === 'wgt' && (!Number.isInteger(minAppVersionCode) || minAppVersionCode < 1 || minAppVersionCode > runtime.appVersionCode)) { throw new Error('当前安装包不支持此热更新,请安装新版 APK') } return { packageType, downloadUrl, versionName, versionCode: Number(value.versionCode || 0), minAppVersionCode, 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, runtime) 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 } }