204 lines
6.3 KiB
JavaScript
204 lines
6.3 KiB
JavaScript
/**
|
||
* 应用版本:与 manifest 发版时尽量保持 versionName/versionCode 一致(非 App 端会回退到默认值)。
|
||
*
|
||
* 本地待更新信息:写入 uni.setStorageSync(APP_PENDING_UPDATE_STORAGE_KEY, 对象或 JSON 字符串)
|
||
* 形状:{ versionName, versionCode?, changelog?, downloadUrl?, packageSize? }
|
||
* packageSize 示例:「128 MB」「7.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() : ''
|
||
}
|