|
|
@@ -4,9 +4,208 @@ 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'
|
|
|
+// ================== 类型声明 ==================
|
|
|
+
|
|
|
+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<T = any> {
|
|
|
+ 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<string> {
|
|
|
+ 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<T = any>(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()
|
|
|
@@ -16,54 +215,81 @@ export function useAppUpdate() {
|
|
|
const checking = ref(false)
|
|
|
const updating = ref(false)
|
|
|
|
|
|
- let downloadTask: any = null
|
|
|
- let networkListener: any = null
|
|
|
+ let downloadTask: PlusDownloaderDownload | null = null
|
|
|
+ let networkListener: ((res: any) => void) | null = null
|
|
|
let downloadUrl = ''
|
|
|
- let savedFilePath = '_downloads/app_update.wgt'
|
|
|
- let currentNetworkType = ''
|
|
|
+ let retryCount = 0
|
|
|
+ let stateChangeHandler: ((d: any) => void) | null = null
|
|
|
|
|
|
- /* ================== 对外入口 ================== */
|
|
|
+ // ================== 对外入口 ==================
|
|
|
|
|
|
- async function checkUpdate() {
|
|
|
+ /**
|
|
|
+ * 检查应用更新
|
|
|
+ */
|
|
|
+ async function checkUpdate(): Promise<void> {
|
|
|
// #ifdef APP-PLUS
|
|
|
- if (checking.value) return
|
|
|
+ if (checking.value) {
|
|
|
+ console.warn('更新检查已在进行中')
|
|
|
+ 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 equipmentType = getEquipmentType()
|
|
|
+ const res: ApiResponse<UpdateInfo> = await ucardApi.getAppVersionDetail({ equipmentType })
|
|
|
+
|
|
|
+ if (res.code !== 200 || !res.data) {
|
|
|
+ const errorMsg = res.message || '获取版本信息失败'
|
|
|
+ console.warn(errorMsg)
|
|
|
+ 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
|
|
|
+ if (!needUpdate) {
|
|
|
+ console.log('当前已是最新版本')
|
|
|
+ return
|
|
|
+ }
|
|
|
|
|
|
- const skip = uni.getStorageSync(SKIP_VERSION_KEY)
|
|
|
- if (!update.forceUpdate && skip === update.version) return
|
|
|
+ // 检查是否已跳过此版本
|
|
|
+ // const skipVersion = getStorageSync<string>(STORAGE_KEYS.SKIP_VERSION)
|
|
|
+ // if (!update.forceUpdate && skipVersion === update.version) {
|
|
|
+ // console.log('用户已跳过此版本更新')
|
|
|
+ // return
|
|
|
+ // }
|
|
|
|
|
|
+ // 显示更新提示
|
|
|
if (update.forceUpdate) {
|
|
|
showForceUpdate(update)
|
|
|
} else {
|
|
|
showOptionalUpdate(update)
|
|
|
}
|
|
|
+ } catch (error) {
|
|
|
+ console.error('检查更新失败:', error)
|
|
|
+ const errorMsg = error instanceof Error ? error.message : String(error)
|
|
|
+ showToast(t('mine.p28') || `检查更新失败: ${errorMsg}`)
|
|
|
} finally {
|
|
|
checking.value = false
|
|
|
}
|
|
|
// #endif
|
|
|
}
|
|
|
|
|
|
- /* ================== 更新流程 ================== */
|
|
|
+ // ================== 更新流程 ==================
|
|
|
|
|
|
- function showForceUpdate(update: any) {
|
|
|
+ /**
|
|
|
+ * 显示强制更新弹窗
|
|
|
+ */
|
|
|
+ function showForceUpdate(update: UpdateInfo): void {
|
|
|
uni.showModal({
|
|
|
title: t('mine.p22'),
|
|
|
content: t('mine.p23'),
|
|
|
@@ -73,185 +299,401 @@ export function useAppUpdate() {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
- function showOptionalUpdate(update: any) {
|
|
|
+ /**
|
|
|
+ * 显示可选更新弹窗
|
|
|
+ */
|
|
|
+ function showOptionalUpdate(update: UpdateInfo): void {
|
|
|
uni.showModal({
|
|
|
title: t('mine.p24'),
|
|
|
- content: t('mine.p25', { version: 'v' + update.version }),
|
|
|
+ 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)
|
|
|
+ success: (res) => {
|
|
|
+ if (res.confirm) {
|
|
|
+ doUpdate(update)
|
|
|
+ } else {
|
|
|
+ // 记录跳过的版本
|
|
|
+ setStorageSync(STORAGE_KEYS.SKIP_VERSION, update.version)
|
|
|
+ }
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
|
|
|
- function doUpdate(update: any) {
|
|
|
- const platform = uni.getSystemInfoSync().platform
|
|
|
+ /**
|
|
|
+ * 执行更新
|
|
|
+ */
|
|
|
+ function doUpdate(update: UpdateInfo): void {
|
|
|
+ const platform = getPlatform()
|
|
|
+
|
|
|
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 {
|
|
|
+ if (!update.wgtUrl) {
|
|
|
+ showToast(t('mine.p28') || '更新包链接不存在')
|
|
|
return
|
|
|
}
|
|
|
+
|
|
|
downloadWgt(update.wgtUrl)
|
|
|
}
|
|
|
|
|
|
+ // ================== 下载核心 ==================
|
|
|
|
|
|
- /* ================== 下载核心 ================== */
|
|
|
-
|
|
|
- function downloadWgt(url: string) {
|
|
|
+ /**
|
|
|
+ * 下载 wgt 更新包
|
|
|
+ */
|
|
|
+ function downloadWgt(url: string): void {
|
|
|
// #ifdef APP-PLUS
|
|
|
- if (downloadTask) return
|
|
|
+ if (downloadTask) {
|
|
|
+ console.warn('下载任务已存在')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!url || !isValidUrl(url)) {
|
|
|
+ handleDownloadFail(t('mine.p28') || '下载链接无效')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
downloadUrl = url
|
|
|
+ retryCount = 0
|
|
|
updating.value = true
|
|
|
progress.show(t('mine.p29'))
|
|
|
- // 获取当前网络类型
|
|
|
- uni.getNetworkType({
|
|
|
- success: (res) => {
|
|
|
- currentNetworkType = res.networkType
|
|
|
- }
|
|
|
- })
|
|
|
+
|
|
|
+ // 初始化网络监听
|
|
|
+ initNetworkType()
|
|
|
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'))
|
|
|
- }
|
|
|
+
|
|
|
+ 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('创建下载任务失败')
|
|
|
}
|
|
|
- )
|
|
|
- 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)
|
|
|
+
|
|
|
+ // 创建状态变化处理器
|
|
|
+ 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)
|
|
|
}
|
|
|
})
|
|
|
- downloadTask.start()
|
|
|
- // #endif
|
|
|
}
|
|
|
- /* ================== 网络处理 ================== */
|
|
|
|
|
|
- function startNetworkMonitor() {
|
|
|
+ // ================== 网络处理 ==================
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 启动网络监听
|
|
|
+ */
|
|
|
+ function startNetworkMonitor(): void {
|
|
|
// #ifdef APP-PLUS
|
|
|
- networkListener = uni.onNetworkStatusChange(res => {
|
|
|
- if (!downloadTask || !updating.value) return
|
|
|
- // 只要有网络变化,直接尝试恢复下载
|
|
|
+ if (networkListener) {
|
|
|
+ console.warn('网络监听已存在')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ networkListener = (res: any) => {
|
|
|
+ if (!downloadTask || !updating.value) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
if (!res.isConnected) {
|
|
|
- currentNetworkType = 'none'
|
|
|
- try {
|
|
|
- downloadTask.pause && downloadTask.pause()
|
|
|
- } catch (e) {
|
|
|
- console.error('暂停下载失败:', e)
|
|
|
- }
|
|
|
+ // 网络断开,暂停下载
|
|
|
+ pauseDownload()
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- // 网络恢复或切换,直接 resume,不依赖 state
|
|
|
- currentNetworkType = res.networkType
|
|
|
+ // 网络恢复,继续下载
|
|
|
+ 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.resume) {
|
|
|
- downloadTask.resume()
|
|
|
- } else {
|
|
|
- downloadTask.start()
|
|
|
- }
|
|
|
- } catch (e) {
|
|
|
- // 失败时尝试用 start 重试
|
|
|
- try {
|
|
|
+ if (downloadTask.start) {
|
|
|
downloadTask.start()
|
|
|
- } catch (e2) {
|
|
|
- console.error('start() 也失败:', e2)
|
|
|
}
|
|
|
+ } catch (e2) {
|
|
|
+ console.error('重新开始下载也失败:', e2)
|
|
|
}
|
|
|
- })
|
|
|
- // #endif
|
|
|
+ }
|
|
|
}
|
|
|
- function stopNetworkMonitor() {
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 停止网络监听
|
|
|
+ */
|
|
|
+ function stopNetworkMonitor(): void {
|
|
|
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'))
|
|
|
- )
|
|
|
+ // ================== 安装 ==================
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 安装 wgt 更新包
|
|
|
+ */
|
|
|
+ function installWgt(path: string): void {
|
|
|
+ // #ifdef APP-PLUS
|
|
|
+ try {
|
|
|
+ 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()
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+ (error) => {
|
|
|
+ // 安装失败
|
|
|
+ console.error('安装失败:', error)
|
|
|
+ handleDownloadFail(t('mine.p33'))
|
|
|
+ }
|
|
|
+ )
|
|
|
+ } catch (error) {
|
|
|
+ console.error('安装异常:', error)
|
|
|
+ handleDownloadFail(t('mine.p33'))
|
|
|
+ }
|
|
|
+ // #endif
|
|
|
}
|
|
|
|
|
|
- /* ================== 缓存(仅 UI) ================== */
|
|
|
+ // ================== 缓存管理 ==================
|
|
|
|
|
|
- function saveCache(progress: number) {
|
|
|
- uni.setStorageSync(DOWNLOAD_CACHE_KEY, {
|
|
|
+ /**
|
|
|
+ * 保存下载缓存
|
|
|
+ */
|
|
|
+ function saveCache(progressPercent: number): void {
|
|
|
+ const cache: DownloadCache = {
|
|
|
url: downloadUrl,
|
|
|
- progress,
|
|
|
+ progress: progressPercent,
|
|
|
time: Date.now()
|
|
|
- })
|
|
|
+ }
|
|
|
+ setStorageSync(STORAGE_KEYS.DOWNLOAD_CACHE, cache)
|
|
|
}
|
|
|
|
|
|
- function clearCache() {
|
|
|
- uni.removeStorageSync(DOWNLOAD_CACHE_KEY)
|
|
|
+ /**
|
|
|
+ * 清除下载缓存
|
|
|
+ */
|
|
|
+ function clearCache(): void {
|
|
|
+ removeStorageSync(STORAGE_KEYS.DOWNLOAD_CACHE)
|
|
|
}
|
|
|
|
|
|
- /* ================== 兜底失败 ================== */
|
|
|
+ // ================== 错误处理 ==================
|
|
|
|
|
|
- function fail(msg: string) {
|
|
|
+ /**
|
|
|
+ * 处理下载失败
|
|
|
+ */
|
|
|
+ function handleDownloadFail(msg: string): void {
|
|
|
progress.hide()
|
|
|
updating.value = false
|
|
|
+ retryCount = 0
|
|
|
stopNetworkMonitor()
|
|
|
- if (downloadTask) {
|
|
|
- try { downloadTask.abort() } catch { }
|
|
|
+ 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
|
|
|
}
|
|
|
- uni.showToast({ title: msg, icon: 'none' })
|
|
|
}
|
|
|
|
|
|
- onUnmounted(() => {
|
|
|
- stopNetworkMonitor()
|
|
|
- })
|
|
|
+ /**
|
|
|
+ * 取消更新
|
|
|
+ */
|
|
|
+ function cancelUpdate(): void {
|
|
|
+ if (!updating.value) {
|
|
|
+ console.warn('当前没有正在进行的更新')
|
|
|
+ return
|
|
|
+ }
|
|
|
|
|
|
- return { checkUpdate }
|
|
|
-}
|
|
|
+ console.log('用户取消更新')
|
|
|
+ handleDownloadFail(t('mine.p39') || '已取消更新')
|
|
|
+ }
|
|
|
|
|
|
-/* ================== 工具 ================== */
|
|
|
+ // ================== 生命周期 ==================
|
|
|
|
|
|
-function getCurrentVersion(): Promise<string> {
|
|
|
- return new Promise(resolve => {
|
|
|
- plus.runtime.getProperty(plus.runtime.appid, info => {
|
|
|
- resolve(info.version)
|
|
|
- })
|
|
|
+ onUnmounted(() => {
|
|
|
+ console.log('useAppUpdate 组件卸载,清理资源')
|
|
|
+ stopNetworkMonitor()
|
|
|
+ cleanupDownloadTask()
|
|
|
+ retryCount = 0
|
|
|
})
|
|
|
-}
|
|
|
|
|
|
-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 {
|
|
|
+ checkUpdate,
|
|
|
+ cancelUpdate,
|
|
|
+ checking,
|
|
|
+ updating
|
|
|
}
|
|
|
- return 0
|
|
|
}
|