| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234 |
- import appSettings from '@/core/config/app-settings'
- import { buildRestPath } from '@/core/config/api-paths'
- import { get } from '@/core/http/http-client'
- import { STORAGE } from '@/core/constants/storage-keys'
- /**
- * 解析更新包 / App Store 等跳转地址。
- * 绝对 URI(https://、itms-apps:// 等)不再拼 CDN;相对路径才拼 cdnurl。
- */
- function resolveUpdateAssetUrl(path) {
- let p = String(path || '').trim().replace(/^\uFEFF/, '')
- if (!p) return ''
- p = p.replace(/[\u200B-\u200D\uFEFF\u2060]/g, '')
- p = p.replace(/(https?)\uFF1A(\/\/)/gi, '$1:$2')
- p = p.replace(/(https?)(\/\/)/gi, (m, scheme, slashes, offset, str) => {
- const prev = offset > 0 ? str[offset - 1] : ''
- return prev === ':' ? m : `${scheme}:${slashes}`
- })
- p = p.replace(/^(https?):\/([^/])/i, '$1://$2')
- const absIdx = p.search(/https?:\/\//i)
- if (absIdx > 0) {
- p = p.slice(absIdx)
- }
- if (/^\/\//.test(p)) {
- p = `https:${p}`
- }
- if (/^[a-z][a-z0-9+.-]*:/i.test(p)) {
- return p
- }
- const base = String(appSettings.cdnurl || '').replace(/\/+$/, '')
- const rel = p.replace(/^\/+/, '')
- return base ? `${base}/${rel}` : p
- }
- function normalizeUpdateContent(data) {
- const copy = { ...data }
- copy.content = String(copy.content || '').replace(/(\r\n)|(\n)/g, '<br>')
- return copy
- }
- function buildDownloadLink(data) {
- const os = plus.os.name.toLowerCase()
- const linkKey = os === 'ios' ? 'iosLink' : 'androidLink'
- const raw = data[linkKey]
- if (!raw) return null
- const url = resolveUpdateAssetUrl(raw)
- if (!url) return null
- return {
- url,
- type: /\.wgt/i.test(url) ? 'wgt' : 'url'
- }
- }
- async function fetchRemoteUpdate() {
- const { data } = await get(buildRestPath('common/update'))
- if (!data || typeof data !== 'object') return null
- const remoteCode = Number(data.versionCode)
- const localCode = Number(appSettings.versionCode)
- if (!Number.isFinite(remoteCode) || remoteCode <= localCode) return null
- return data
- }
- function presentUpdate({ commit, dispatch }, data) {
- const meta = normalizeUpdateContent(data)
- commit('assign', { key: 'meta', value: meta })
- // #ifdef APP-PLUS
- const link = buildDownloadLink(meta)
- if (!link) {
- commit('assign', { key: 'visible', value: false })
- return { hasUpdate: true, hasLink: false, data: meta }
- }
- commit('assign', { key: 'link', value: link })
- if (Number(meta.enforce) === 1) {
- dispatch('startDownload')
- return { hasUpdate: true, hasLink: true, data: meta, enforced: true }
- }
- commit('assign', { key: 'visible', value: true })
- return { hasUpdate: true, hasLink: true, data: meta }
- // #endif
- // #ifndef APP-PLUS
- commit('assign', { key: 'visible', value: true })
- return { hasUpdate: true, hasLink: true, data: meta }
- // #endif
- }
- export default {
- namespaced: true,
- state: {
- visible: false,
- meta: {},
- link: {},
- download: {
- path: null,
- start: false,
- install: false,
- progress: 0,
- totalBytesWritten: 0,
- totalBytesExpectedToWrite: 0
- },
- task: null
- },
- mutations: {
- assign(state, { key, value }) {
- state[key] = value
- }
- },
- actions: {
- check({ dispatch }) {
- // #ifdef MP
- const mgr = uni.getUpdateManager()
- mgr.onUpdateReady(() => {
- uni.showModal({
- title: '更新提示',
- content: '新版本已就绪,是否重启应用?',
- success: (res) => res.confirm && mgr.applyUpdate()
- })
- })
- // #endif
- // #ifdef APP-PLUS
- const cache = uni.getStorageSync(STORAGE.update)
- if (cache?.ignore) return Promise.resolve({ hasUpdate: false, ignored: true })
- return dispatch('checkManual', { silent: true })
- // #endif
- // #ifndef APP-PLUS
- return Promise.resolve({ hasUpdate: false })
- // #endif
- },
- /**
- * 手动检查更新(关于我们页):忽略「不再提醒」,无新版本时由调用方 toast。
- */
- async checkManual({ commit, dispatch }, { silent = false } = {}) {
- // #ifdef MP
- try {
- const mgr = uni.getUpdateManager()
- mgr.onCheckForUpdate((res) => {
- if (!res.hasUpdate && !silent) {
- uni.showToast({ title: '已是最新版本', icon: 'none' })
- }
- })
- mgr.onUpdateReady(() => {
- uni.showModal({
- title: '更新提示',
- content: '新版本已就绪,是否重启应用?',
- success: (r) => r.confirm && mgr.applyUpdate()
- })
- })
- } catch (_) {}
- return { hasUpdate: false }
- // #endif
- // #ifdef APP-PLUS
- try {
- const data = await fetchRemoteUpdate()
- if (!data) {
- return { hasUpdate: false }
- }
- return presentUpdate({ commit, dispatch }, data)
- } catch (e) {
- if (!silent) throw e
- return { hasUpdate: false, error: e }
- }
- // #endif
- // #ifndef APP-PLUS
- return { hasUpdate: false }
- // #endif
- },
- startDownload({ state, commit, dispatch }) {
- // #ifdef APP-PLUS
- const link = state.link
- if (!link || !link.url) return
- if (link.type === 'url') {
- plus.runtime.openURL(link.url)
- commit('assign', { key: 'visible', value: false })
- return
- }
- commit('assign', { key: 'visible', value: true })
- state.download.start = true
- state.task = uni.downloadFile({
- url: link.url,
- success: (res) => {
- if (res.statusCode !== 200) return
- uni.saveFile({
- tempFilePath: res.tempFilePath,
- success: (file) => {
- uni.setStorageSync(STORAGE.update, { path: file.savedFilePath, ignore: false })
- state.download.path = file.savedFilePath
- state.download.start = false
- state.task?.abort?.()
- state.task = null
- dispatch('installPackage')
- }
- })
- },
- fail: () => {
- state.download.start = false
- uni.showToast({ title: '下载失败', icon: 'none' })
- }
- })
- state.task.onProgressUpdate((p) => {
- state.download.progress = p.progress
- state.download.totalBytesWritten = p.totalBytesWritten
- state.download.totalBytesExpectedToWrite = p.totalBytesExpectedToWrite
- })
- // #endif
- },
- installPackage({ state, commit }) {
- // #ifdef APP-PLUS
- state.download.install = true
- plus.runtime.install(state.download.path, state.visible ? {} : { force: true }, () => {
- commit('assign', { key: 'visible', value: false })
- state.download.install = false
- uni.showModal({
- title: '更新完成',
- content: '应用将重启以完成更新',
- showCancel: false,
- complete: () => plus.runtime.restart()
- })
- }, (e) => {
- state.download.install = false
- uni.showToast({ title: `更新失败:${e.message}`, icon: 'none' })
- })
- // #endif
- },
- ignore({ commit }) {
- uni.setStorageSync(STORAGE.update, { path: null, ignore: true })
- commit('assign', { key: 'visible', value: false })
- }
- }
- }
|