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' import { userToken } from '@/composables/config' import config from '@/config' import { trim } from 'lodash' // ================== 类型声明 ================== declare const plus: any declare const uni: any interface PlusDownloaderDownload { filename: string pause?: () => void resume?: () => void start: () => void abort: () => void addEventListener: (event: string, callback: (data: any) => void) => void removeEventListener?: (event: string, callback: (data: any) => void) => void } // ================== 常量定义 ================== /** 存储键名 */ const STORAGE_KEYS = { LAST_CHECK: 'last_update_check_time', SKIP_VERSION: 'skip_update_version', DOWNLOAD_CACHE: 'app_download_cache', } as const /** 下载状态码 */ enum DownloadState { DOWNLOADING = 3, // 下载中 COMPLETED = 4 // 下载完成 } /** HTTP 成功状态码 */ enum HttpStatus { OK = 200, // 成功 PARTIAL_CONTENT = 206 // 部分内容(断点续传) } /** 下载配置 */ const DOWNLOAD_CONFIG = { FILE_PATH: '_downloads/app_update.wgt', MAX_RETRY: 3, // 最大重试次数 RETRY_DELAY: 2000, // 重试延迟(毫秒) TOAST_DURATION: 2000, // Toast 显示时长 } as const // ================== 类型定义 ================== /** 更新信息 */ interface UpdateInfo { version: string forceUpdate: boolean wgtUrl?: string iosStoreUrl?: string } /** 下载缓存 */ interface DownloadCache { url: string progress: number time: number } /** API 响应 */ interface ApiResponse { code: number data?: T message?: string } /** 下载状态变化事件 */ interface DownloadStateEvent { state: number downloadedSize: number totalSize: number } /** 平台类型 */ type PlatformType = 'ios' | 'android' /** 设备类型 */ type EquipmentType = 'ios' | 'Android' // ================== 工具函数 ================== /** * 获取当前应用版本 */ function getCurrentVersion(): Promise { return new Promise((resolve, reject) => { // #ifdef APP-PLUS try { plus.runtime.getProperty(plus.runtime.appid, (info) => { resolve(info.version) }, (error) => { reject(error) }) } catch (error) { reject(error) } // #endif // #ifndef APP-PLUS reject(new Error('Not in APP-PLUS environment')) // #endif }) } /** * 比较版本号 * @returns 1: v1 > v2, -1: v1 < v2, 0: v1 === v2 */ function compareVersion(v1: string, v2: string): number { const normalizeVersion = (v: string): number[] => { return v.split('.').map(segment => parseInt(segment || '0', 10)) } const parts1 = normalizeVersion(v1) const parts2 = normalizeVersion(v2) const maxLength = Math.max(parts1.length, parts2.length) for (let i = 0; i < maxLength; i++) { const num1 = parts1[i] || 0 const num2 = parts2[i] || 0 if (num1 > num2) return 1 if (num1 < num2) return -1 } return 0 } /** * 获取平台类型 */ function getPlatform(): PlatformType { // #ifdef APP-PLUS try { const platform = uni.getSystemInfoSync().platform return platform === 'ios' ? 'ios' : 'android' } catch (error) { // console.error('获取平台类型失败:', error) return 'android' } // #endif // #ifndef APP-PLUS return 'android' // #endif } /** * 获取设备类型(API 参数格式) */ function getEquipmentType(): EquipmentType { return getPlatform() === 'ios' ? 'ios' : 'Android' } /** * 显示 Toast 提示 */ function showToast(message: string, duration = DOWNLOAD_CONFIG.TOAST_DURATION): void { uni.showToast({ title: message, icon: 'none', duration, }) } /** * 安全地获取存储值 */ function getStorageSync(key: string, defaultValue: T | null = null): T | null { try { return uni.getStorageSync(key) || defaultValue } catch (error) { // console.warn(`获取存储失败: ${key}`, error) return defaultValue } } /** * 安全地设置存储值 */ function setStorageSync(key: string, value: any): boolean { try { uni.setStorageSync(key, value) return true } catch (error) { // console.warn(`设置存储失败: ${key}`, error) return false } } /** * 安全地移除存储值 */ function removeStorageSync(key: string): boolean { try { uni.removeStorageSync(key) return true } catch (error) { // console.warn(`移除存储失败: ${key}`, error) return false } } // ================== 主函数 ================== export function useAppUpdate() { const { t } = useI18n() const { Host80 } = config // 测试环境地址 // const Host80 = 'https://secure.44a5c8109e4.com/' const userStore = useUserStore() const progress = useProgress() const checking = ref(false) const updating = ref(false) const lastVersion = ref('') let downloadTask: PlusDownloaderDownload | null = null let networkListener: ((res: any) => void) | null = null let downloadUrl = '' let retryCount = 0 let stateChangeHandler: ((d: any) => void) | null = null // ================== 对外入口 ================== /** * 检查应用更新 */ async function checkUpdate(): Promise { // #ifdef APP-PLUS if (checking.value) { // console.warn('更新检查已在进行中') return } checking.value = true try { const res = await uni.request({ url: `${Host80}/wgt/list.json?_t=${Date.now()}`, method: 'GET', timeout: 5000, }) console.log('up:filedata', res) const currentVersion = await getCurrentVersion() console.log(currentVersion, 'currentVersion') const files = res.data?.files || [] if (!files.length) return const latestFile = files[files.length - 1] const latestVersion = latestFile console.log('last', latestFile, latestVersion) if (!latestFile) return lastVersion.value = latestVersion const lastInstalled = uni.getStorageSync('lastWgtVersion') console.log(lastInstalled, 'lastInstalled') if (latestVersion === lastInstalled) return console.log('版本对比', compareVersion(latestVersion, currentVersion)) const needUpdate = compareVersion(latestVersion, currentVersion) > 0 // 保存版本信息 userStore.saveAppVersion({ currentVersion, version: latestVersion, isUpdate: !needUpdate, }) if (!needUpdate) { // console.log('当前已是最新版本') return } // 检查是否已跳过此版本 // const skipVersion = getStorageSync(STORAGE_KEYS.SKIP_VERSION) // if (!update.forceUpdate && skipVersion === update.version) { // // console.log('用户已跳过此版本更新') // return // } // 显示更新提示 const url = `${Host80}/wgt/CwgApp_${latestVersion}.wgt` console.log('url',url) showForceUpdate({ version: latestVersion, forceUpdate: true, wgtUrl: url }) } catch (error) { // console.error('检查更新失败:', error) const errorMsg = error instanceof Error ? error.msg : String(error) showToast(t('mine.p28') || `检查更新失败: ${errorMsg}`) } finally { checking.value = false } // #endif } // ================== 更新流程 ================== /** * 显示强制更新弹窗 */ function showForceUpdate(update: UpdateInfo): void { uni.showModal({ title: t('mine.p22'), content: t('mine.p23'), showCancel: false, confirmText: t('mine.p35'), success: () => doUpdate(update), }) } /** * 显示可选更新弹窗 */ function showOptionalUpdate(update: UpdateInfo): void { 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 { // 记录跳过的版本 setStorageSync(STORAGE_KEYS.SKIP_VERSION, update.version) } }, }) } /** * 执行更新 */ function doUpdate(update: UpdateInfo): void { console.log(update) const platform = getPlatform() // ios不走这里面 if (platform === 'ios') { // handleIosUpdate(update) } else { handleAndroidUpdate(update) } } /** * 处理 iOS 更新 */ function handleIosUpdate(update: UpdateInfo): void { if (!update.iosStoreUrl) { showToast(t('mine.p28') || 'iOS 更新链接不存在') return } try { plus.runtime.openURL(update.iosStoreUrl) } catch (error) { // console.error('打开 App Store 失败:', error) showToast(t('mine.p28') || '打开 App Store 失败') } } /** * 处理 Android 更新 */ function handleAndroidUpdate(update: UpdateInfo): void { console.log('up2',update) if (!update.wgtUrl) { showToast(t('mine.p28') || '更新包链接不存在') return } downloadWgt(update.wgtUrl) } // ================== 下载核心 ================== /** * 下载 wgt 更新包 */ function downloadWgt(url: string): void { // #ifdef APP-PLUS if (downloadTask) { console.log('下载任务已存在') return } if (!url || !isValidUrl(url)) { handleDownloadFail(t('mine.p28') || '下载链接无效') return } downloadUrl = url retryCount = 0 updating.value = true progress.show(t('mine.p29')) // 初始化网络监听 initNetworkType() startNetworkMonitor() createDownloadTask(url) // #endif } /** * 创建下载任务 */ function createDownloadTask(url: string): void { try { downloadTask = plus.downloader.createDownload( url, { filename: DOWNLOAD_CONFIG.FILE_PATH }, handleDownloadComplete, ) if (!downloadTask) { throw new Error('创建下载任务失败') } // 创建状态变化处理器 stateChangeHandler = handleDownloadStateChanged downloadTask.addEventListener('statechanged', stateChangeHandler) downloadTask.start() } catch (error) { // console.error('创建下载任务失败:', error) downloadTask = null handleDownloadFail(t('mine.p28') || '创建下载任务失败') } } /** * 验证 URL 格式 */ function isValidUrl(url: string): boolean { try { return /^https?:\/\/.+/.test(url) } catch { return false } } /** * 处理下载完成回调 */ function handleDownloadComplete(download: PlusDownloaderDownload, status: number): void { const isSuccess = status === HttpStatus.OK || status === HttpStatus.PARTIAL_CONTENT if (isSuccess) { // console.log('下载成功,准备安装') clearCache() stopNetworkMonitor() installWgt(download.filename) } else { // console.error('下载失败,状态码:', status) retryDownload() } } /** * 处理下载状态变化 */ function handleDownloadStateChanged(event: DownloadStateEvent): void { if (event.state === DownloadState.DOWNLOADING && event.totalSize > 0) { const percent = Math.min(100, Math.floor((event.downloadedSize / event.totalSize) * 100)) const progressText = `${t('mine.p29')} ${percent}%` progress.update(percent, progressText) saveCache(percent) } } /** * 重试下载 */ function retryDownload(): void { if (retryCount >= DOWNLOAD_CONFIG.MAX_RETRY) { // console.error(`下载失败,已重试 ${retryCount} 次`) handleDownloadFail(t('mine.p28') || '下载失败') return } retryCount++ // console.log(`下载失败,${DOWNLOAD_CONFIG.RETRY_DELAY / 1000}秒后重试 (${retryCount}/${DOWNLOAD_CONFIG.MAX_RETRY})`) cleanupDownloadTask() setTimeout(() => { if (downloadUrl) { createDownloadTask(downloadUrl) } }, DOWNLOAD_CONFIG.RETRY_DELAY) } /** * 初始化网络类型 */ function initNetworkType(): void { uni.getNetworkType({ success: (res) => { // console.log('当前网络类型:', res.networkType) }, fail: (error) => { // console.warn('获取网络类型失败:', error) }, }) } // ================== 网络处理 ================== /** * 启动网络监听 */ function startNetworkMonitor(): void { // #ifdef APP-PLUS if (networkListener) { // console.warn('网络监听已存在') return } networkListener = (res: any) => { if (!downloadTask || !updating.value) { return } if (!res.isConnected) { // 网络断开,暂停下载 pauseDownload() return } // 网络恢复,继续下载 resumeDownload() } uni.onNetworkStatusChange(networkListener) // #endif } /** * 暂停下载 */ function pauseDownload(): void { if (!downloadTask) return try { if (typeof downloadTask.pause === 'function') { downloadTask.pause() } } catch (error) { // console.error('暂停下载失败:', error) } } /** * 恢复下载 */ function resumeDownload(): void { if (!downloadTask) return try { if (typeof downloadTask.resume === 'function') { downloadTask.resume() } else if (typeof downloadTask.start === 'function') { downloadTask.start() } } catch (error) { // console.error('恢复下载失败,尝试重新开始:', error) // 失败时尝试重新开始 try { if (downloadTask.start) { downloadTask.start() } } catch (e2) { // console.error('重新开始下载也失败:', e2) } } } /** * 停止网络监听 */ function stopNetworkMonitor(): void { if (networkListener) { uni.offNetworkStatusChange(networkListener) networkListener = null } } // ================== 安装 ================== /** * 安装 wgt 更新包 */ function installWgt(path: string): void { // #ifdef APP-PLUS try { plus.runtime.install( path, { force: true }, () => { // 安装成功 progress.hide() updating.value = false uni.showModal({ title: t('mine.p37'), content: t('mine.p38'), showCancel: false, success: () => { uni.setStorageSync('lastWgtVersion', lastVersion.value) console.log('[wgt] install success:', lastVersion.value) uni.setStorageSync('wgtNeedRestart', true) plus.runtime.restart() }, }) }, (error) => { // 安装失败 // console.error('安装失败:', error) handleDownloadFail(t('mine.p33')) }, ) } catch (error) { // console.error('安装异常:', error) handleDownloadFail(t('mine.p33')) } // #endif } // ================== 缓存管理 ================== /** * 保存下载缓存 */ function saveCache(progressPercent: number): void { const cache: DownloadCache = { url: downloadUrl, progress: progressPercent, time: Date.now(), } setStorageSync(STORAGE_KEYS.DOWNLOAD_CACHE, cache) } /** * 清除下载缓存 */ function clearCache(): void { removeStorageSync(STORAGE_KEYS.DOWNLOAD_CACHE) } // ================== 错误处理 ================== /** * 处理下载失败 */ function handleDownloadFail(msg: string): void { progress.hide() updating.value = false retryCount = 0 stopNetworkMonitor() cleanupDownloadTask() showToast(msg) } /** * 清理下载任务 */ function cleanupDownloadTask(): void { if (!downloadTask) return try { // 移除事件监听 if (stateChangeHandler && downloadTask.removeEventListener) { downloadTask.removeEventListener('statechanged', stateChangeHandler) } // 中止下载 downloadTask.abort() } catch (error) { // console.warn('清理下载任务失败:', error) } finally { downloadTask = null stateChangeHandler = null } } /** * 取消更新 */ function cancelUpdate(): void { if (!updating.value) { // console.warn('当前没有正在进行的更新') return } // console.log('用户取消更新') handleDownloadFail(t('mine.p39') || '已取消更新') } // ================== 生命周期 ================== onUnmounted(() => { // console.log('useAppUpdate 组件卸载,清理资源') stopNetworkMonitor() cleanupDownloadTask() retryCount = 0 }) return { checkUpdate, cancelUpdate, checking, updating, } }