import { ref, onUnmounted } from 'vue' import { useI18n } from 'vue-i18n' import { useProgress } from './useProgress' import useUserStore from '@/stores/use-user-store' import { ucardApi } from '@/api/ucard' const LAST_CHECK_KEY = 'last_update_check_time' const SKIP_VERSION_KEY = 'skip_update_version' const DOWNLOAD_CACHE_KEY = 'app_download_cache' export function useAppUpdate() { const { t } = useI18n() const userStore = useUserStore() const progress = useProgress() const checking = ref(false) const updating = ref(false) let downloadTask: any = null let networkListener: any = null let downloadUrl = '' let savedFilePath = '_downloads/app_update.wgt' let currentNetworkType = '' /* ================== 对外入口 ================== */ async function checkUpdate() { // #ifdef APP-PLUS if (checking.value) return checking.value = true try { const platform = uni.getSystemInfoSync().platform const equipmentType = platform === 'ios' ? 'ios' : 'Android' const res = await ucardApi.getAppVersionDetail({ equipmentType }) if (res.code !== 200 || !res.data) return const update = res.data const currentVersion = await getCurrentVersion() const needUpdate = compareVersion(update.version, currentVersion) > 0 userStore.saveAppVersion({ currentVersion, version: update.version, isUpdate: !needUpdate }) if (!needUpdate) return const skip = uni.getStorageSync(SKIP_VERSION_KEY) if (!update.forceUpdate && skip === update.version) return if (update.forceUpdate) { showForceUpdate(update) } else { showOptionalUpdate(update) } } finally { checking.value = false } // #endif } /* ================== 更新流程 ================== */ function showForceUpdate(update: any) { uni.showModal({ title: t('mine.p22'), content: t('mine.p23'), showCancel: false, confirmText: t('mine.p35'), success: () => doUpdate(update) }) } function showOptionalUpdate(update: any) { uni.showModal({ title: t('mine.p24'), content: t('mine.p25', { version: 'v' + update.version }), confirmText: t('mine.p35'), cancelText: t('mine.p36'), success: res => { if (res.confirm) doUpdate(update) else uni.setStorageSync(SKIP_VERSION_KEY, update.version) } }) } function doUpdate(update: any) { const platform = uni.getSystemInfoSync().platform if (platform === 'ios') { plus.runtime.openURL(update.iosStoreUrl) return } downloadWgt(update.wgtUrl) } /* ================== 下载核心 ================== */ function downloadWgt(url: string) { // #ifdef APP-PLUS if (downloadTask) return downloadUrl = url updating.value = true progress.show(t('mine.p29')) // 获取当前网络类型 uni.getNetworkType({ success: (res) => { currentNetworkType = res.networkType } }) startNetworkMonitor() downloadTask = plus.downloader.createDownload( url, { filename: savedFilePath }, (download, status) => { if (status === 200 || status === 206) { clearCache() stopNetworkMonitor() installWgt(download.filename) } else { fail(t('mine.p28')) } } ) downloadTask.addEventListener('statechanged', (d: any) => { const state = d.state if (state === 3 && d.totalSize) { // 下载中 const p = Math.floor((d.downloadedSize / d.totalSize) * 100) progress.update(p, `${t('mine.p29')} ${p}%`) saveCache(p) } }) downloadTask.start() // #endif } /* ================== 网络处理 ================== */ function startNetworkMonitor() { // #ifdef APP-PLUS networkListener = uni.onNetworkStatusChange(res => { if (!downloadTask || !updating.value) return // 只要有网络变化,直接尝试恢复下载 if (!res.isConnected) { currentNetworkType = 'none' try { downloadTask.pause && downloadTask.pause() } catch (e) { console.error('暂停下载失败:', e) } return } // 网络恢复或切换,直接 resume,不依赖 state currentNetworkType = res.networkType try { if (downloadTask.resume) { downloadTask.resume() } else { downloadTask.start() } } catch (e) { // 失败时尝试用 start 重试 try { downloadTask.start() } catch (e2) { console.error('start() 也失败:', e2) } } }) // #endif } function stopNetworkMonitor() { if (networkListener) { uni.offNetworkStatusChange(networkListener) networkListener = null } currentNetworkType = '' } /* ================== 安装 ================== */ function installWgt(path: string) { plus.runtime.install( path, { force: false }, () => { progress.hide() updating.value = false uni.showModal({ title: t('mine.p37'), content: t('mine.p38'), showCancel: false, success: () => plus.runtime.restart() }) }, () => fail(t('mine.p33')) ) } /* ================== 缓存(仅 UI) ================== */ function saveCache(progress: number) { uni.setStorageSync(DOWNLOAD_CACHE_KEY, { url: downloadUrl, progress, time: Date.now() }) } function clearCache() { uni.removeStorageSync(DOWNLOAD_CACHE_KEY) } /* ================== 兜底失败 ================== */ function fail(msg: string) { progress.hide() updating.value = false stopNetworkMonitor() if (downloadTask) { try { downloadTask.abort() } catch { } downloadTask = null } uni.showToast({ title: msg, icon: 'none' }) } onUnmounted(() => { stopNetworkMonitor() }) return { checkUpdate } } /* ================== 工具 ================== */ function getCurrentVersion(): Promise { return new Promise(resolve => { plus.runtime.getProperty(plus.runtime.appid, info => { resolve(info.version) }) }) } function compareVersion(v1: string, v2: string) { const a1 = v1.split('.') const a2 = v2.split('.') const len = Math.max(a1.length, a2.length) for (let i = 0; i < len; i++) { const n1 = parseInt(a1[i] || '0') const n2 = parseInt(a2[i] || '0') if (n1 > n2) return 1 if (n1 < n2) return -1 } return 0 }