import config from '@/config/index.js' const CHECK_VERSION_URL = '/api/app/version/check' let checkingVersion = false let updatePopupVisible = false let latestUpdateInfo = null /** * 检查 APP 是否存在管理后台配置的新版本。 */ export function checkAppVersionUpdate() { // #ifndef APP-PLUS return // #endif if (checkingVersion) { return } checkingVersion = true resolveRuntimeVersionInfo() .then((runtimeInfo) => requestVersionCheck(runtimeInfo)) .then((payload) => { const updateInfo = payload && payload.code === 0 ? payload.data : null if (!updateInfo || updateInfo.hasUpdate !== true) { return } const downloadUrl = resolveUpdateUrl(updateInfo) if (!downloadUrl) { return } showUpdateModal(Object.assign({}, updateInfo, { downloadUrl })) }) .catch(() => { }) .finally(() => { checkingVersion = false }) } /** * 读取 APP 原生版本信息,优先使用 manifest 中的 versionCode。 */ function resolveRuntimeVersionInfo() { return new Promise((resolve) => { const fallbackInfo = { platform: resolvePlatform(), versionCode: 0, versionName: '' } if (typeof plus === 'undefined' || !plus.runtime || typeof plus.runtime.getProperty !== 'function') { resolve(fallbackInfo) return } plus.runtime.getProperty(plus.runtime.appid, (info) => { resolve({ platform: resolvePlatform(), versionCode: normalizeVersionCode(info && info.versionCode), versionName: normalizeText(info && info.version) }) }) }) } /** * 读取当前安装 APP 的展示版本号。 */ export function getRuntimeVersionName(fallbackVersionName = '') { return resolveRuntimeVersionInfo().then((runtimeInfo) => { return normalizeText(runtimeInfo.versionName) || normalizeText(fallbackVersionName) }) } /** * 将当前运行平台转换为后端版本检查平台值。 */ function resolvePlatform() { const platform = normalizeText(uni.getSystemInfoSync().platform).toLowerCase() return platform === 'ios' ? 'IOS' : 'ANDROID' } /** * 请求后端版本检查接口。 */ function requestVersionCheck(runtimeInfo) { return new Promise((resolve, reject) => { uni.request({ url: `${config.BASE_URL}${CHECK_VERSION_URL}`, method: 'GET', data: { platform: runtimeInfo.platform, currentVersionCode: runtimeInfo.versionCode, currentVersionNo: runtimeInfo.versionName }, header: { 'content-type': 'application/json' }, success: (res) => { resolve(res.data || {}) }, fail: reject }) }) } /** * 解析当前平台可用的更新地址,iOS 只允许跳转 App Store。 */ function resolveUpdateUrl(updateInfo) { if (isIosPlatform()) { return normalizeText(config.APP_STORE_URL) } return normalizeText(updateInfo.downloadUrl) } /** * 展示版本更新弹窗,强制更新时禁止跳过。 */ function showUpdateModal(updateInfo) { latestUpdateInfo = updateInfo if (updatePopupVisible) { return } updatePopupVisible = true const forceUpdate = updateInfo.forceUpdate === true let confirmed = false uni.showModal({ title: forceUpdate ? '发现新版本,请立即更新' : '发现新版本', content: buildUpdateContent(updateInfo), showCancel: !forceUpdate, cancelText: '暂不更新', confirmText: '立即更新', success: (modalResult) => { if (modalResult.confirm) { confirmed = true startAppUpdate(updateInfo.downloadUrl) return } }, complete: () => { updatePopupVisible = false if (forceUpdate && !confirmed) { setTimeout(() => { showUpdateModal(updateInfo) }, 100) } } }) } /** * 拼接更新提示内容,确保后台更新说明能直接展示给用户。 */ function buildUpdateContent(updateInfo) { const versionNo = normalizeText(updateInfo.latestVersionNo) const updateNotice = normalizeText(updateInfo.updateNotice) || '请更新到最新版本后继续使用。' return versionNo ? `最新版本:${versionNo}\n${updateNotice}` : updateNotice } /** * iOS 跳转 App Store;Android 下载完整 APK 后拉起系统安装界面。 */ function startAppUpdate(downloadUrl) { if (!downloadUrl) { return } if (isIosPlatform()) { plus.runtime.openURL(downloadUrl) return } const downloadTask = plus.downloader.createDownload(downloadUrl, {}, (download, status) => { if (status !== 200 || !download.filename) { handleUpdateFailure('安装包下载失败,请重试') return } const apkPath = plus.io.convertLocalFileSystemURL(download.filename) plus.runtime.install(apkPath, {}, undefined, () => { handleUpdateFailure('安装失败,请重试') }) }) downloadTask.start() } /** * 静默查询当前 APP 是否存在可用更新,供页面展示更新状态。 */ export function queryAppVersionUpdate() { // #ifndef APP-PLUS return Promise.resolve(null) // #endif return resolveRuntimeVersionInfo() .then((runtimeInfo) => requestVersionCheck(runtimeInfo)) .then((payload) => { const updateInfo = payload && payload.code === 0 ? payload.data : null if (!updateInfo || updateInfo.hasUpdate !== true) { return null } const downloadUrl = resolveUpdateUrl(updateInfo) return downloadUrl ? Object.assign({}, updateInfo, { downloadUrl }) : null }) } /** * 使用统一更新弹窗展示指定版本信息。 */ export function promptAppVersionUpdate(updateInfo) { if (updateInfo && updateInfo.hasUpdate === true) { showUpdateModal(updateInfo) } } /** * 下载或安装失败后重新开放更新入口。 */ function handleUpdateFailure(title) { uni.showToast({ title, icon: 'none' }) if (latestUpdateInfo) { setTimeout(() => { showUpdateModal(latestUpdateInfo) }, 1500) } } /** * APP 回到前台时,强制更新场景需要继续拦截用户使用旧版本。 */ export function resumeForceUpdatePrompt() { if (latestUpdateInfo && latestUpdateInfo.forceUpdate === true && !updatePopupVisible) { showUpdateModal(latestUpdateInfo) } } /** * 判断当前是否为 iOS APP。 */ function isIosPlatform() { return resolvePlatform() === 'IOS' } /** * 规范化可选字符串。 */ function normalizeText(value) { return String(value || '').trim() } /** * 规范化原生 versionCode,避免异常值传到后端。 */ function normalizeVersionCode(value) { const versionCode = Number(value || 0) return Number.isFinite(versionCode) && versionCode >= 0 ? versionCode : 0 }