feat(升级中心): 实现备用更新方案并优化版本检查逻辑

This commit is contained in:
2025-12-26 17:11:58 +08:00
parent e76e6da008
commit e5415a8784
10 changed files with 245 additions and 53 deletions

View File

@@ -7,8 +7,8 @@ export const ENV = process.env.NODE_ENV || 'development';
*/ */
const BASE_URL_MAP = { const BASE_URL_MAP = {
development: { development: {
// MAIN: 'http://192.168.110.100:9300/pb/', // 张川川 MAIN: 'http://192.168.110.100:9300/pb/', // 张川川
MAIN: 'https://global.nuttyreading.com/', // 线上 // MAIN: 'https://global.nuttyreading.com/', // 线上
// PAYMENT: 'https://dev-pay.example.com', // 暂时用不到 // PAYMENT: 'https://dev-pay.example.com', // 暂时用不到
// CDN: 'https://cdn-dev.example.com', // 暂时用不到 // CDN: 'https://cdn-dev.example.com', // 暂时用不到
}, },
@@ -21,7 +21,6 @@ const BASE_URL_MAP = {
export const APP_INFO = { export const APP_INFO = {
TYPE: 'abroad', // APP 名称 TYPE: 'abroad', // APP 名称
VERSION_CODE: '1.0.0', // APP 版本号,可能升级的时候会用,这里需要再确定?
} }
export const REQUEST_TIMEOUT = 30000; export const REQUEST_TIMEOUT = 30000;

View File

@@ -1,6 +1,7 @@
import type { IRequestOptions } from '../types' import type { IRequestOptions } from '../types'
import { useUserStore } from '@/stores/user' import { useUserStore } from '@/stores/user'
import { APP_INFO } from '@/api/config' import { APP_INFO } from '@/api/config'
import { getCurrentVersion } from '@/uni_modules/uni-upgrade-center-app/utils/call-check-version'
export function requestInterceptor(options: IRequestOptions): IRequestOptions { export function requestInterceptor(options: IRequestOptions): IRequestOptions {
const headers = { ...(options.headers || {}) } const headers = { ...(options.headers || {}) }
@@ -21,9 +22,12 @@ export function requestInterceptor(options: IRequestOptions): IRequestOptions {
headers['Content-Type'] = 'application/json;charset=UTF-8' headers['Content-Type'] = 'application/json;charset=UTF-8'
} }
getCurrentVersion().then((version_code: string) => {
headers['version_code'] = version_code || ''
})
headers['appType'] = APP_INFO.TYPE headers['appType'] = APP_INFO.TYPE
headers['version_code'] = APP_INFO.VERSION_CODE || '1.0.0'
return { return {
...options, ...options,
header: headers, header: headers,

14
api/modules/sys.ts Normal file
View File

@@ -0,0 +1,14 @@
import { mainClient, skeletonClient } from '@/api/clients'
import type { IApiResponse } from '@/api/types'
/**
* 请求更新包
*/
export async function requestUpdatePackage(type: string, version: string) {
const res = await skeletonClient.request<IApiResponse>({
url: 'common/apkConfig/getUpdateUrl',
method: 'POST',
data: { type, version }
})
return res
}

View File

@@ -20,7 +20,7 @@
"coin": "Coin", "coin": "Coin",
"days": "Days", "days": "Days",
"and": "and", "and": "and",
"call": "Call" "queryFailed": "Query failed"
}, },
"tabar.course": "COURSE", "tabar.course": "COURSE",
"tabar.book": "EBOOK", "tabar.book": "EBOOK",

View File

@@ -20,7 +20,7 @@
"coin": "天医币", "coin": "天医币",
"days": "天", "days": "天",
"and": "和", "and": "和",
"call": "拨打电话" "queryFailed": "查询失败"
}, },
"tabar.course": "课程", "tabar.course": "课程",
"tabar.book": "图书", "tabar.book": "图书",

View File

@@ -66,7 +66,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed } from 'vue' import { ref, computed, onMounted } from 'vue'
import { useSysStore } from '@/stores/sys' import { useSysStore } from '@/stores/sys'
import { useUserStore } from '@/stores/user' import { useUserStore } from '@/stores/user'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
@@ -74,6 +74,7 @@ import { useMessage } from '@/uni_modules/wot-design-uni'
import { makePhoneCall, copyToClipboard } from '@/utils/index' import { makePhoneCall, copyToClipboard } from '@/utils/index'
// #ifdef APP-PLUS // #ifdef APP-PLUS
import update from "@/uni_modules/uni-upgrade-center-app/utils/check-update"; import update from "@/uni_modules/uni-upgrade-center-app/utils/check-update";
import { getCurrentVersion } from '@/uni_modules/uni-upgrade-center-app/utils/call-check-version'
// #endif // #endif
const { t, locale } = useI18n() const { t, locale } = useI18n()
@@ -89,6 +90,9 @@ const statusBarHeight = ref(0)
const showQrCode = ref(false) const showQrCode = ref(false)
const showLanguageSelect = ref(false) const showLanguageSelect = ref(false)
// 当前版本号
const currentVersion = ref('')
// 可选语言列表 // 可选语言列表
const availableLanguages = computed(() => [ const availableLanguages = computed(() => [
{ code: 'zh-Hans', name: t('locale.zh-hans') }, { code: 'zh-Hans', name: t('locale.zh-hans') },
@@ -131,7 +135,7 @@ const settingItems = computed(() => [
{ {
id: 4, id: 4,
label: t('user.checkVersion'), label: t('user.checkVersion'),
value: '', value: currentVersion.value,
type: 'version' type: 'version'
} }
]) ])
@@ -163,14 +167,14 @@ const handleSettingClick = (item: any) => {
* 拨打电话 * 拨打电话
*/ */
const handlePhoneCall = (phoneNumber: string, title: string) => { const handlePhoneCall = (phoneNumber: string, title: string) => {
makePhoneCall(phoneNumber, title, t) makePhoneCall(phoneNumber, title)
} }
/** /**
* 复制到剪贴板 * 复制到剪贴板
*/ */
const handleCopyEmail = (content: string, title: string) => { const handleCopyEmail = (content: string, title: string) => {
copyToClipboard(content, title, t) copyToClipboard(content, title)
} }
/** /**
@@ -209,14 +213,27 @@ const selectLanguage = (languageCode: string) => {
*/ */
const checkVersion = async () => { const checkVersion = async () => {
// #ifdef APP-PLUS // #ifdef APP-PLUS
var info = await update(); uni.showLoading()
console.log('版本检测信息', info) try {
if(info.result.code == 0){ const info = await update();
if(info.result.code == 0){
uni.showToast({
title:info.result.message,
icon:'none'
})
}
} catch (error: any) {
console.error('版本检测失败:', error)
const msg = error?.hasOwnProperty('message') && error.message
uni.showToast({ uni.showToast({
title:info.result.message, title: msg || t('user.checkVersionFailed'),
icon:'none' icon: 'none',
duration: 5000
}) })
} finally {
uni.hideLoading()
} }
// #endif // #endif
// #ifndef APP-PLUS // #ifndef APP-PLUS
@@ -272,6 +289,12 @@ const performLogout = () => {
url: '/pages/login/login' url: '/pages/login/login'
}) })
} }
onMounted(async () => {
// #ifdef APP-PLUS
currentVersion.value = await getCurrentVersion()
// #endif
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@@ -1,31 +1,124 @@
export default function() { import { requestUpdatePackage } from '@/api/modules/sys'
// #ifdef APP-PLUS import { getMaxVersion } from './tools'
return new Promise((resolve, reject) => { import { getFileExtension } from '@/utils'
plus.runtime.getProperty(plus.runtime.appid, function(widgetInfo) {
let data = { /**
action: 'checkVersion', * 统一检查更新入口
appid: plus.runtime.appid, */
appVersion: plus.runtime.version, export default async function checkUpdate() {
wgtVersion: widgetInfo.version // #ifdef APP-PLUS
} try {
uniCloud.callFunction({ const upgradeData = await getUpgradeCheckData()
name: 'uni-upgrade-center',
data, // 优先调用 uni-upgrade-center3 秒超时)
success: (e) => { const result = await withTimeout(
resolve(e) callUpgradeCenter(upgradeData),
}, 3000
fail: (error) => { )
reject(error)
} console.log('检查版本更新成功:', result)
}) return result
}) } catch (err) {
}) console.warn('uniCloud更新方案失败启用备用方案:', err?.message)
// #endif return await useBackupUpdate()
// #ifndef APP-PLUS }
return new Promise((resolve, reject) => { // #endif
reject({
message: '请在App中使用' // #ifndef APP-PLUS
}) throw { message: '请在 App 中使用' }
}) // #endif
// #endif }
}
/**
* 备用更新方案
*/
async function useBackupUpdate() {
const currentVersion = await getCurrentVersion()
if (!currentVersion) {
throw { message: '获取当前版本号失败' }
}
console.log('当前版本号:', currentVersion)
const res = await requestUpdatePackage('10', currentVersion)
if (!res || !res.updateUrl) {
throw { message: '没有匹配的更新包,当前版本无需升级,如您确定有更新,请卸载本版本后前往应用市场重新安装' }
}
// 将服务器返回的更新信息转换为 uni-upgrade-center 格式
return {
result: {
...res,
url: res.updateUrl,
platform: ['Android', 'Ios'],
type: getFileExtension(res.updateUrl),
is_mandatory: true,
is_backup_update: true,
title: "更新",
contents: "当前版本已经弃用,请立即更新",
}
}
}
/**
* uni-upgrade-center 调用 Promise 化
*/
function callUpgradeCenter(data) {
return new Promise((resolve, reject) => {
uniCloud.callFunction({
name: 'uni-upgrade-center',
data,
success: resolve,
fail: reject
})
})
}
/**
* 超时控制
*/
function withTimeout(promise, timeout) {
return Promise.race([
promise,
new Promise((_, reject) =>
setTimeout(() => reject(new Error('请求超时')), timeout)
)
])
}
/**
* 获取 upgrade-center 所需参数
*/
function getUpgradeCheckData() {
return new Promise((resolve, reject) => {
plus.runtime.getProperty(
plus.runtime.appid,
(widgetInfo) => {
resolve({
action: 'checkVersion',
appid: plus.runtime.appid,
appVersion: plus.runtime.version,
wgtVersion: widgetInfo.version
})
}
)
})
}
/**
* 获取当前客户端版本app / wgt 取最大)
*/
export async function getCurrentVersion() {
// #ifdef APP-PLUS
const widgetInfo = await new Promise(resolve => {
plus.runtime.getProperty(plus.runtime.appid, resolve)
})
return getMaxVersion(
plus.runtime.version,
widgetInfo.version
)
// #endif
}

View File

@@ -10,6 +10,7 @@ export default function() {
if (!e.result) return; if (!e.result) return;
const { const {
code, code,
is_backup_update, // 是否备用更新
message, message,
is_silently, // 是否静默更新 is_silently, // 是否静默更新
url, // 安装包下载地址 url, // 安装包下载地址
@@ -17,17 +18,20 @@ export default function() {
type // 安装包类型 type // 安装包类型
} = e.result; } = e.result;
// 此处逻辑仅为实例,可自行编写 const hasUpdate = code > 0 || is_backup_update
if (code > 0) {
// 腾讯云和阿里云下载链接不同,需要处理一下,阿里云会原样返回 // 如果不是备用更新,需要处理下载链接
if (!is_backup_update) {
const { const {
fileList fileList
} = await uniCloud.getTempFileURL({ } = await uniCloud.getTempFileURL({
fileList: [url] fileList: [url]
}); });
if (fileList[0].tempFileURL) e.result.url = fileList[0].tempFileURL;
e.result.url = fileList[0].tempFileURL; }
// 此处逻辑仅为实例,可自行编写
if (hasUpdate) {
resolve(e) resolve(e)
// 静默更新只有wgt有 // 静默更新只有wgt有

View File

@@ -0,0 +1,32 @@
/**
* 比较版本号
* @param {string} v1 - 版本号1
* @param {string} v2 - 版本号2
* @returns {number} - 1表示v1大于v2-1表示v1小于v20表示相等
*/
export function compareVersion(v1, v2) {
const arr1 = v1.split('.').map(Number)
const arr2 = v2.split('.').map(Number)
const maxLen = Math.max(arr1.length, arr2.length)
for (let i = 0; i < maxLen; i++) {
const n1 = arr1[i] ?? 0
const n2 = arr2[i] ?? 0
if (n1 > n2) return 1
if (n1 < n2) return -1
}
return 0
}
/**
* 获取较大版本号
* @param {string} v1 - 版本号1
* @param {string} v2 - 版本号2
* @returns {string} - 较大的版本号
*/
export function getMaxVersion(v1, v2) {
return compareVersion(v1, v2) >= 0 ? v1 : v2
}

View File

@@ -129,4 +129,27 @@ export function parseTime(time: any, cFormat: string) {
return value.toString().padStart(2, '0') return value.toString().padStart(2, '0')
}) })
return time_str return time_str
}
/**
* 获取文件扩展名
* @param {string} url - 文件的URL
* @returns {string} - 文件的扩展名(不包含点号),如果没有扩展名则返回空字符串
*/
export function getFileExtension(url: string) {
// 移除查询参数和hash
const cleanUrl = url.split(/[?#]/)[0];
// 获取文件名
const filename = cleanUrl.split('/').pop();
// 提取扩展名(支持多个点的情况)
const parts = filename?.split('.');
if (parts?.length && parts.length <= 1) {
return ''; // 没有扩展名
}
// 返回最后一个点之后的部分
return parts?.pop() || '';
} }