congming_huose-apk/common/utils/appVersion.js

204 lines
6.3 KiB
JavaScript
Raw Normal View History

/**
* 应用版本 manifest 发版时尽量保持 versionName/versionCode 一致 App 端会回退到默认值
*
* 本地待更新信息写入 uni.setStorageSync(APP_PENDING_UPDATE_STORAGE_KEY, 对象或 JSON 字符串)
* 形状{ versionName, versionCode?, changelog?, downloadUrl?, packageSize? }
* packageSize 示例128 MB7.13 GB展示在标题下灰色副标题
* getCurrentAppVersionInfo() 比较若本地版本更新则展示可更新界面
*/
export const APP_PENDING_UPDATE_STORAGE_KEY = 'APP_PENDING_UPDATE'
export function readPendingUpdateFromStorage() {
try {
const raw = uni.getStorageSync(APP_PENDING_UPDATE_STORAGE_KEY)
if (raw == null || raw === '') return null
if (typeof raw === 'object' && raw !== null && !Array.isArray(raw)) return raw
if (typeof raw === 'string') {
const o = JSON.parse(raw)
return o && typeof o === 'object' && !Array.isArray(o) ? o : null
}
} catch (e) {}
return null
}
function parseVersionNameToComparable(name) {
if (!name || typeof name !== 'string') return 0
const m = name.trim().match(/^(\d+)\.(\d+)\.(\d+)/)
if (!m) return 0
const a = parseInt(m[1], 10) || 0
const b = parseInt(m[2], 10) || 0
const c = parseInt(m[3], 10) || 0
return a * 10000 + b * 100 + c
}
export function effectiveVersionComparable(info) {
if (!info) return 0
const n = Number(info.versionCode)
if (!Number.isNaN(n) && n > 0) return n
const fromName = parseVersionNameToComparable(info.versionName)
return fromName > 0 ? fromName : 0
}
/** 版本号字符串规范化后比较(与接口 `version`、本地存储一致) */
export function normalizeVersionString(s) {
if (s == null) return ''
return String(s).trim()
}
function stripHtmlToPlain(html) {
if (!html || typeof html !== 'string') return ''
return html
.replace(/<\/p>/gi, '\n')
.replace(/<br\s*\/?>/gi, '\n')
.replace(/<[^>]+>/g, '')
.replace(/\r\n/g, '\n')
.replace(/\n{3,}/g, '\n\n')
.trim()
}
/** 当前运行包版本App 读 runtime小程序读账号信息其余与 manifest 默认一致) */
export function getCurrentAppVersionInfo() {
let versionName = '1.0.2'
let versionCode = 102
// #ifdef APP-PLUS
try {
if (typeof plus !== 'undefined' && plus.runtime) {
versionName = plus.runtime.version || versionName
const vc = plus.runtime.versionCode
if (vc != null && vc !== '') {
const parsed = parseInt(String(vc), 10)
if (!Number.isNaN(parsed)) versionCode = parsed
}
}
} catch (e) {}
// #endif
// #ifdef MP-WEIXIN
try {
const acc = uni.getAccountInfoSync && uni.getAccountInfoSync()
if (acc && acc.miniProgram && acc.miniProgram.version) {
versionName = acc.miniProgram.version
const parsed = parseVersionNameToComparable(versionName)
if (parsed > 0) versionCode = parsed
}
} catch (e) {}
// #endif
return {
versionName: String(versionName),
versionCode: Number(versionCode) || 0
}
}
/** 与当前包比较pending 是否为更高版本(本地存储或任意来源的待更新描述对象) */
export function remoteHasNewerVersion(currentInfo, pending) {
if (!pending || typeof pending !== 'object') return false
const c = effectiveVersionComparable(currentInfo)
const r = effectiveVersionComparable({
versionName: pending.versionName,
versionCode: pending.versionCode
})
if (r <= 0) return false
return r > c
}
function isTrivialEmptyHtml(html) {
if (!html || typeof html !== 'string') return true
const t = html.replace(/\s/g, '').toLowerCase()
return (
t === '' ||
t === '<p></p>' ||
t === '<p><br></p>' ||
t === '<p><br/></p>' ||
t === '<br>' ||
t === '<br/>'
)
}
/** 富文本更新说明(接口 `updateContent`,原样供 rich-text 解析) */
export function pickUpdateContentHtml(remote) {
if (!remote || typeof remote !== 'object') return ''
const raw = remote.updateContent
if (typeof raw !== 'string') return ''
const trimmed = raw.trim()
if (!trimmed || isTrivialEmptyHtml(trimmed)) return ''
return trimmed
}
/** 纯文本更新说明(不含 updateContent避免与富文本重复 */
export function pickRemoteChangelog(remote) {
if (!remote || typeof remote !== 'object') return ''
const raw =
remote.changelog ||
remote.description ||
remote.releaseNotes ||
remote.intro ||
''
if (typeof raw !== 'string') return ''
return stripHtmlToPlain(raw)
}
export function pickDownloadUrl(remote) {
if (!remote || typeof remote !== 'object') return ''
const u =
remote.downloadUrl ||
remote.apkUrl ||
remote.apkDownloadUrl ||
remote.fileUrl ||
remote.url ||
remote.androidUrl ||
remote.iosUrl ||
''
return typeof u === 'string' ? u.trim() : ''
}
/** 从接口返回的单条记录里取版本号展示名 */
export function pickVersionNameFromRecord(row) {
if (!row || typeof row !== 'object') return ''
const v =
row.versionName ??
row.version ??
row.apkVersion ??
row.appVersion ??
row.newVersion ??
''
if (typeof v === 'string') return v.trim()
if (v != null && v !== '') return String(v).trim()
return ''
}
function pickVersionCodeFromRecord(row) {
if (!row || typeof row !== 'object') return 0
const raw = row.versionCode ?? row.build ?? row.versionNum
const n = Number(raw)
return Number.isNaN(n) ? 0 : n
}
/**
* 解析 GET /app/apkVersion/latest 等接口的响应体得到与本地 pending 相同形状的待更新对象
* 兼容data 为对象 / 数组首项 / rows 首项
*/
export function extractLatestApkRecord(httpResponse) {
if (!httpResponse || typeof httpResponse !== 'object') return null
let row = httpResponse.data
if (Array.isArray(row)) row = row[0]
else if (row && typeof row === 'object' && Array.isArray(row.rows)) row = row.rows[0]
if (!row || typeof row !== 'object' || Array.isArray(row)) return null
const versionName = pickVersionNameFromRecord(row)
const versionCode = pickVersionCodeFromRecord(row)
if (!versionName && versionCode <= 0) return null
return {
versionName,
versionCode,
changelog: pickRemoteChangelog(row),
updateContentHtml: pickUpdateContentHtml(row),
downloadUrl: pickDownloadUrl(row),
packageSize: pickPackageSizeLabel(row)
}
}
/** 可选展示用安装包大小如「128 MB」「7.13 GB」 */
export function pickPackageSizeLabel(remote) {
if (!remote || typeof remote !== 'object') return ''
const s = remote.packageSize || remote.size || remote.fileSize || ''
return typeof s === 'string' ? s.trim() : ''
}