388 lines
11 KiB
JavaScript
388 lines
11 KiB
JavaScript
/**
|
||
* 应用版本更新工具类
|
||
* 支持版本检查和 APK 下载下载
|
||
*/
|
||
|
||
import { checkAppVersion } from '@/api/update'
|
||
import manifest from '@/manifest.json'
|
||
|
||
/**
|
||
* 获取当前应用版本信息
|
||
* @returns {Object} {versionName}
|
||
*/
|
||
export const getCurrentVersion = () => {
|
||
// 统一从 manifest.json 读取版本号
|
||
let versionName = manifest?.versionName || ''
|
||
|
||
// #ifdef APP-PLUS
|
||
// APP 环境下,如果 manifest 读取失败,使用运行时版本作为后备
|
||
if (!versionName) {
|
||
versionName = plus.runtime.version || ''
|
||
}
|
||
// #endif
|
||
|
||
// 如果都读取不到,使用默认值
|
||
if (!versionName) {
|
||
versionName = '1.0.1'
|
||
}
|
||
|
||
return {
|
||
versionName: versionName
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 比较版本号
|
||
* @param {string} version1 - 版本号1(如 "1.0.1")
|
||
* @param {string} version2 - 版本号2(如 "1.0.2")
|
||
* @returns {number} 1: version1 > version2, -1: version1 < version2, 0: 相等
|
||
*/
|
||
export const compareVersion = (version1, version2) => {
|
||
const v1parts = version1.split('.').map(Number)
|
||
const v2parts = version2.split('.').map(Number)
|
||
const maxLength = Math.max(v1parts.length, v2parts.length)
|
||
|
||
for (let i = 0; i < maxLength; i++) {
|
||
const v1part = v1parts[i] || 0
|
||
const v2part = v2parts[i] || 0
|
||
|
||
if (v1part > v2part) return 1
|
||
if (v1part < v2part) return -1
|
||
}
|
||
|
||
return 0
|
||
}
|
||
|
||
/**
|
||
* 检查是否有新版本
|
||
* @param {boolean} silent - 是否静默检查(不显示提示)
|
||
* @returns {Promise<Object|null>} 返回新版本信息,无新版本返回 null
|
||
*/
|
||
export const checkForUpdate = async (silent = false) => {
|
||
try {
|
||
const currentVersion = getCurrentVersion()
|
||
const platform = uni.getSystemInfoSync().platform
|
||
|
||
// 只在 Android 平台检查更新
|
||
// #ifdef APP-PLUS
|
||
if (platform !== 'android') {
|
||
if (!silent) {
|
||
console.log('当前平台不支持自动更新:', platform)
|
||
}
|
||
return null
|
||
}
|
||
// #endif
|
||
|
||
// #ifndef APP-PLUS
|
||
// 非 APP 环境,跳过检查
|
||
return null
|
||
// #endif
|
||
|
||
const params = {
|
||
versionName: currentVersion.versionName,
|
||
platform: 'android'
|
||
}
|
||
|
||
const response = await checkAppVersion(params)
|
||
|
||
console.log('版本检查 API 返回:', response)
|
||
|
||
// API 返回的数据在 data 字段中,或者直接就是数据对象
|
||
const result = response.data || response
|
||
|
||
if (!result || !result.hasUpdate) {
|
||
if (!silent) {
|
||
uni.showToast({
|
||
title: '已是最新版本',
|
||
icon: 'success',
|
||
duration: 2000
|
||
})
|
||
}
|
||
return null
|
||
}
|
||
|
||
// 映射 API 返回的字段到内部使用的字段
|
||
// API 返回: packageUrl, versionNumber, log
|
||
// 内部使用: downloadUrl, versionName, updateLog
|
||
const updateInfo = {
|
||
hasUpdate: result.hasUpdate,
|
||
versionName: result.versionNumber || result.versionName || result.version || '',
|
||
downloadUrl: result.packageUrl || result.downloadUrl || result.download_url || '',
|
||
updateLog: result.log || result.updateLog || result.update_log || result.description || '',
|
||
forceUpdate: result.forceUpdate || result.force_update || false,
|
||
fileSize: result.fileSize || result.file_size || 0,
|
||
versionCode: result.versionCode || 0
|
||
}
|
||
|
||
console.log('处理后的更新信息:', updateInfo)
|
||
|
||
return updateInfo
|
||
} catch (error) {
|
||
console.error('检查版本更新失败:', error)
|
||
if (!silent) {
|
||
uni.showToast({
|
||
title: '检查更新失败',
|
||
icon: 'none',
|
||
duration: 2000
|
||
})
|
||
}
|
||
return null
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 显示版本更新提示
|
||
* @param {Object} updateInfo - 更新信息
|
||
* @param {string} updateInfo.versionName - 新版本号
|
||
* @param {string} updateInfo.downloadUrl - 下载地址
|
||
* @param {string} updateInfo.updateLog - 更新日志
|
||
* @param {boolean} updateInfo.forceUpdate - 是否强制更新
|
||
* @param {number} updateInfo.fileSize - 文件大小(字节)
|
||
*/
|
||
export const showUpdateDialog = (updateInfo) => {
|
||
const fileSizeMB = updateInfo.fileSize
|
||
? (updateInfo.fileSize / 1024 / 1024).toFixed(2) + 'MB'
|
||
: '未知大小'
|
||
|
||
const content = updateInfo.updateLog
|
||
? `版本:${updateInfo.versionName}\n大小:${fileSizeMB}\n\n${updateInfo.updateLog}`
|
||
: `发现新版本 ${updateInfo.versionName}\n大小:${fileSizeMB}`
|
||
|
||
uni.showModal({
|
||
title: '发现新版本',
|
||
content: content,
|
||
showCancel: !updateInfo.forceUpdate,
|
||
cancelText: '稍后更新',
|
||
confirmText: '立即更新',
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
downloadAndInstall(updateInfo.downloadUrl, updateInfo.versionName)
|
||
} else if (updateInfo.forceUpdate) {
|
||
// 强制更新时,取消也退出应用
|
||
// #ifdef APP-PLUS
|
||
plus.runtime.quit()
|
||
// #endif
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 下载并安装 APK
|
||
* @param {string} downloadUrl - APK 下载地址
|
||
* @param {string} versionName - 版本名称(用于文件命名)
|
||
*/
|
||
export const downloadAndInstall = (downloadUrl, versionName = '') => {
|
||
// #ifdef APP-PLUS
|
||
if (!downloadUrl) {
|
||
uni.showToast({
|
||
title: '下载地址无效',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
|
||
// 显示下载进度
|
||
let downloadTask = null
|
||
let progressModal = null
|
||
uni.showLoading({
|
||
title: '下载中',
|
||
mask: true
|
||
})
|
||
// 创建下载任务
|
||
downloadTask = uni.downloadFile({
|
||
url: downloadUrl,
|
||
|
||
//
|
||
// if (progress === 100) {
|
||
// uni.hideLoading()
|
||
// }
|
||
success: (res) => {
|
||
if (res.statusCode === 200) {
|
||
// 下载成功,安装 APK
|
||
uni.hideLoading()
|
||
installApk(res.tempFilePath)
|
||
|
||
} else {
|
||
uni.hideLoading()
|
||
uni.showToast({
|
||
title: '下载失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
},
|
||
fail: (err) => {
|
||
uni.hideLoading()
|
||
console.error('下载失败:', err)
|
||
uni.showToast({
|
||
title: '下载失败:' + (err.errMsg || '未知错误'),
|
||
icon: 'none',
|
||
duration: 3000
|
||
})
|
||
}
|
||
})
|
||
|
||
// // 监听下载进度
|
||
// downloadTask.onProgressUpdate((res) => {
|
||
// const progress = res.progress
|
||
// const received = (res.totalBytesWritten / 1024 / 1024).toFixed(2)
|
||
// const total = (res.totalBytesExpectedToWrite / 1024 / 1024).toFixed(2)
|
||
//
|
||
// // 显示下载进度(使用 uni.showLoading)
|
||
// uni.showLoading({
|
||
// title: '下载中',
|
||
// mask: true
|
||
// })
|
||
//
|
||
// if (progress === 100) {
|
||
// uni.hideLoading()
|
||
// }
|
||
// })
|
||
// #endif
|
||
|
||
// #ifndef APP-PLUS
|
||
// 非 APP 环境,提示用户手动下载
|
||
uni.showModal({
|
||
title: '提示',
|
||
content: `请访问以下地址下载新版本:\n${downloadUrl}`,
|
||
showCancel: false,
|
||
confirmText: '复制链接',
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
// #ifdef H5
|
||
// H5 环境可以尝试复制到剪贴板
|
||
if (navigator.clipboard) {
|
||
navigator.clipboard.writeText(downloadUrl).then(() => {
|
||
uni.showToast({
|
||
title: '链接已复制',
|
||
icon: 'success'
|
||
})
|
||
})
|
||
}
|
||
// #endif
|
||
}
|
||
}
|
||
})
|
||
// #endif
|
||
}
|
||
|
||
/**
|
||
* 安装 APK 文件
|
||
* @param {string} filePath - APK 文件路径
|
||
*/
|
||
const installApk = (filePath) => {
|
||
// #ifdef APP-PLUS
|
||
try {
|
||
// Android 8.0+ 需要请求安装权限
|
||
const systemInfo = uni.getSystemInfoSync()
|
||
const androidVersion = systemInfo.system.split(' ')[1] || '0'
|
||
const majorVersion = parseInt(androidVersion.split('.')[0]) || 0
|
||
|
||
// Android 8.0 (API 26) 及以上需要请求安装权限
|
||
if (majorVersion >= 8) {
|
||
// 检查是否有安装权限
|
||
const main = plus.android.runtimeMainActivity()
|
||
const pkName = main.getPackageName()
|
||
const Intent = plus.android.importClass('android.content.Intent')
|
||
const Settings = plus.android.importClass('android.provider.Settings')
|
||
const Uri = plus.android.importClass('android.net.Uri')
|
||
const Build = plus.android.importClass('android.os.Build')
|
||
|
||
// Android 8.0+ 使用 REQUEST_INSTALL_PACKAGES 权限
|
||
if (Build.VERSION.SDK_INT >= 26) {
|
||
const PackageManager = plus.android.importClass('android.content.pm.PackageManager')
|
||
const pm = main.getPackageManager()
|
||
const hasPermission = pm.canRequestPackageInstalls()
|
||
|
||
if (!hasPermission) {
|
||
// 请求安装权限
|
||
uni.showModal({
|
||
title: '需要安装权限',
|
||
content: '应用需要安装权限才能更新,是否前往设置开启?',
|
||
confirmText: '前往设置',
|
||
cancelText: '取消',
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
try {
|
||
const intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES)
|
||
const uri = Uri.parse('package:' + pkName)
|
||
intent.setData(uri)
|
||
main.startActivity(intent)
|
||
|
||
// 提示用户开启权限后重新安装
|
||
uni.showModal({
|
||
title: '提示',
|
||
content: '请在设置中开启"允许安装未知应用"权限,然后返回应用重新更新',
|
||
showCancel: false,
|
||
confirmText: '知道了'
|
||
})
|
||
} catch (e) {
|
||
console.error('打开设置失败:', e)
|
||
uni.showToast({
|
||
title: '无法打开设置',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
}
|
||
}
|
||
})
|
||
return
|
||
}
|
||
}
|
||
}
|
||
|
||
// 使用 plus.runtime.install 安装 APK
|
||
plus.runtime.install(
|
||
filePath,
|
||
{
|
||
force: false // 不强制安装
|
||
},
|
||
() => {
|
||
uni.showToast({
|
||
title: '下载成功',
|
||
icon: 'success'
|
||
})
|
||
// 安装成功后,延迟退出应用以便用户重启
|
||
setTimeout(() => {
|
||
plus.runtime.quit()
|
||
}, 1500)
|
||
},
|
||
(error) => {
|
||
console.error('下载失败:', error)
|
||
uni.showModal({
|
||
title: '下载失败',
|
||
content: '请检查是否允许下载未知来源应用,或手动下载下载的 APK 文件',
|
||
showCancel: false,
|
||
confirmText: '知道了'
|
||
})
|
||
}
|
||
)
|
||
} catch (error) {
|
||
console.error('下载 APK 异常:', error)
|
||
uni.showToast({
|
||
title: '下载失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
// #endif
|
||
}
|
||
|
||
/**
|
||
* 初始化版本检查(在 App 启动时调用)
|
||
* @param {Object} options - 配置选项
|
||
* @param {boolean} options.silent - 是否静默检查(默认 false)
|
||
* @param {number} options.delay - 延迟检查时间(毫秒,默认 2000)
|
||
*/
|
||
export const initVersionCheck = (options = {}) => {
|
||
const { silent = false, delay = 2000 } = options
|
||
|
||
setTimeout(() => {
|
||
checkForUpdate(silent).then(updateInfo => {
|
||
if (updateInfo) {
|
||
showUpdateDialog(updateInfo)
|
||
}
|
||
})
|
||
}, delay)
|
||
}
|
||
|