热更新

This commit is contained in:
yangzhq68909 2025-04-30 09:16:10 +08:00
parent cfe8db7730
commit f0060f0a02
25 changed files with 341 additions and 153 deletions

BIN
certificate/book.keystore Normal file

Binary file not shown.

View File

@ -0,0 +1,9 @@
证书指纹:
MD5: 9A:AD:4D:78:17:EE:17:D3:62:B2:6E:B2:56:0B:18:C3
SHA1: B1:1E:CE:4B:70:C7:88:B3:6E:8B:B9:05:98:4B:FD:67:81:C1:3A:8F
SHA256: A9:78:BD:BF:BB:DE:35:14:AA:5D:45:E9:6A:D0:E5:17:6D:BE:58:86:7A:1D:66:A2:F3:62:2F:E7:9C:9E:59:F6
别名: __uni__9f097f0
证书密码为uZ6Stufj
包名com.tianranqi.app

View File

@ -2,13 +2,15 @@
import { onLaunch, onShow, onHide, onLoad, onReady } from '@dcloudio/uni-app'
import 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only'
import { beforEach } from '@/router/index'
import { jurisdictionApi } from '@/api/system/login';
import { useAppStore } from '@/store'
import { jurisdictionApi } from '@/api/system';
import { useAppStore, useUpdateApp } from '@/store'
export default {
onLaunch: function (options) {
console.log('App Launch')
console.log('应用启动路径:', options.path)
//
useUpdateApp().checkAppUpdate()
},
onShow: function (options) {
console.log('App Show')

View File

@ -21,6 +21,18 @@ export function loginApi(config : LoginParams) {
});
}
/**
* 热更新
* @param
* @returns
*/
export function upDateAppApi() {
return http({
url: '/sys/common/upDateApp',
method: 'GET'
})
}
/**
* 获取是否灰化
* @param id 登录参数
@ -33,5 +45,6 @@ export function jurisdictionApi(id : string) { // 是否灰化
data: {
id
}
})
}
});
}

0
src/api/user/index.ts Normal file
View File

View File

@ -69,7 +69,7 @@
import { useRouter } from '@/plugin/uni-mini-router'
// import { useParamsStore } from '@/store/page-params'
import Base64 from 'base-64';
import { loginApi } from '@/api/system/login';
import { loginApi } from '@/api/system';
defineOptions({
name: 'login',

View File

@ -1,6 +1,7 @@
import { createPinia, defineStore } from 'pinia'
import { createPersistedState } from 'pinia-plugin-persistedstate' //
import { upDateAppApi } from '@/api/system';
import { hasNewVersion, onClickUpdate } from '@/utils/index';
const store = createPinia()
store.use(
createPersistedState({
@ -22,6 +23,66 @@ export const useAppStore = defineStore('app', {
persist: true, //
})
export const useUpdateApp = defineStore('updateApp', () => {
const updateOptions = reactive({
force: false,
hasNew: false,
content: '',
url: '',
apkUrl: '',
wgtUrl: ''
})
const systemInfo = uni.getSystemInfoSync()
/**
* 当前处于APP_PLUS时 检查是否有新版本
*
* @param to 是否跳转新页面显示更新 默认值false
*/
function checkAppUpdate(to = false) {
try {
upDateAppApi().then(async (res : any) => {
let {
result
} = res
result.apkUrl = import.meta.env.VITE_SERVER_BASEURL + '/sys/common/static/' + result.apkUrl;
result.wgtUrl = import.meta.env.VITE_SERVER_BASEURL + '/sys/common/static/' + result.wgtUrl
updateOptions.wgtUrl = result.wgtUrl
if (systemInfo.osName === 'android') {
// Android
updateOptions.apkUrl = result.apkUrl
// #ifdef APP_PLUS
updateOptions.hasNew = await hasNewVersion(result.versionCode, result.update == 'wgt') as boolean
// #endif
} else {
// IOS
updateOptions.url = `itms-apps://itunes.apple.com/cn/app/id${123456}?mt=8`
}
updateOptions.hasNew &&
uni.showModal({
title: '更新',
content: '发现新版本,请更新',
success(res) {
if (res.confirm) {
onClickUpdate(result.update, result)
} else {
plus.runtime.quit()
}
}
})
})
} catch (error) {
updateOptions.hasNew = false
}
}
return {
checkAppUpdate,
...toRefs(updateOptions),
systemInfo
}
})
export default store
//

View File

@ -2,25 +2,25 @@ import { pages, subPackages, tabBar } from '@/pages.json'
import { isMpWeixin } from './platform'
const getLastPage = () => {
// getCurrentPages() 1
// const lastPage = getCurrentPages().at(-1)
// src/interceptions/prototype.ts
const pages = getCurrentPages()
return pages[pages.length - 1]
// getCurrentPages() 1
// const lastPage = getCurrentPages().at(-1)
// src/interceptions/prototype.ts
const pages = getCurrentPages()
return pages[pages.length - 1]
}
/** 判断当前页面是否是tabbar页 */
export const getIsTabbar = () => {
if (!tabBar) {
return false
}
if (!tabBar.list.length) {
// tabBarlist2
return false
}
const lastPage = getLastPage()
const currPath = lastPage.route
return !!tabBar.list.find((e) => e.pagePath === currPath)
if (!tabBar) {
return false
}
if (!tabBar.list.length) {
// tabBarlist2
return false
}
const lastPage = getLastPage()
const currPath = lastPage.route
return !!tabBar.list.find((e) => e.pagePath === currPath)
}
/**
@ -29,48 +29,48 @@ export const getIsTabbar = () => {
* redirectPath /pages/demo/base/route-interceptor
*/
export const currRoute = () => {
const lastPage = getLastPage()
const currRoute = (lastPage as any).$page
// console.log('lastPage.$page:', currRoute)
// console.log('lastPage.$page.fullpath:', currRoute.fullPath)
// console.log('lastPage.$page.options:', currRoute.options)
// console.log('lastPage.options:', (lastPage as any).options)
// fullPath
const { fullPath } = currRoute as { fullPath: string }
// console.log(fullPath)
// eg: /pages/login/index?redirect=%2Fpages%2Fdemo%2Fbase%2Froute-interceptor ()
// eg: /pages/login/index?redirect=%2Fpages%2Froute-interceptor%2Findex%3Fname%3Dfeige%26age%3D30(h5)
return getUrlObj(fullPath)
const lastPage = getLastPage()
const currRoute = (lastPage as any).$page
// console.log('lastPage.$page:', currRoute)
// console.log('lastPage.$page.fullpath:', currRoute.fullPath)
// console.log('lastPage.$page.options:', currRoute.options)
// console.log('lastPage.options:', (lastPage as any).options)
// fullPath
const { fullPath } = currRoute as { fullPath : string }
// console.log(fullPath)
// eg: /pages/login/index?redirect=%2Fpages%2Fdemo%2Fbase%2Froute-interceptor ()
// eg: /pages/login/index?redirect=%2Fpages%2Froute-interceptor%2Findex%3Fname%3Dfeige%26age%3D30(h5)
return getUrlObj(fullPath)
}
const ensureDecodeURIComponent = (url: string) => {
if (url.startsWith('%')) {
return ensureDecodeURIComponent(decodeURIComponent(url))
}
return url
const ensureDecodeURIComponent = (url : string) => {
if (url.startsWith('%')) {
return ensureDecodeURIComponent(decodeURIComponent(url))
}
return url
}
/**
* 解析 url 得到 path query
* 比如输入url: /pages/login/index?redirect=%2Fpages%2Fdemo%2Fbase%2Froute-interceptor
* 输出: {path: /pages/login/index, query: {redirect: /pages/demo/base/route-interceptor}}
*/
export const getUrlObj = (url: string) => {
const [path, queryStr] = url.split('?')
// console.log(path, queryStr)
export const getUrlObj = (url : string) => {
const [path, queryStr] = url.split('?')
// console.log(path, queryStr)
if (!queryStr) {
return {
path,
query: {},
}
}
const query: Record<string, string> = {}
queryStr.split('&').forEach((item) => {
const [key, value] = item.split('=')
// console.log(key, value)
query[key] = ensureDecodeURIComponent(value) // decodeURIComponent h5y
})
return { path, query }
if (!queryStr) {
return {
path,
query: {},
}
}
const query : Record<string, string> = {}
queryStr.split('&').forEach((item) => {
const [key, value] = item.split('=')
// console.log(key, value)
query[key] = ensureDecodeURIComponent(value) // decodeURIComponent h5y
})
return { path, query }
}
/**
* 得到所有的需要登录的pages包括主包和分包的
@ -78,103 +78,103 @@ export const getUrlObj = (url: string) => {
* 如果没有传 key则表示所有的pages如果传递了 key, 则表示通过 key 过滤
*/
export const getAllPages = (key = 'needLogin') => {
//
const mainPages = [
...pages
.filter((page) => !key || page[key])
.map((page) => ({
...page,
path: `/${page.path}`,
})),
]
//
const subPages: any[] = []
subPackages.forEach((subPageObj) => {
// console.log(subPageObj)
const { root } = subPageObj
//
const mainPages = [
...pages
.filter((page) => !key || page[key])
.map((page) => ({
...page,
path: `/${page.path}`,
})),
]
//
const subPages : any[] = []
subPackages.forEach((subPageObj) => {
// console.log(subPageObj)
const { root } = subPageObj
subPageObj.pages
.filter((page) => !key || page[key])
.forEach((page: { path: string } & Record<string, any>) => {
subPages.push({
...page,
path: `/${root}/${page.path}`,
})
})
})
const result = [...mainPages, ...subPages]
// console.log(`getAllPages by ${key} result: `, result)
return result
subPageObj.pages
.filter((page) => !key || page[key])
.forEach((page : { path : string } & Record<string, any>) => {
subPages.push({
...page,
path: `/${root}/${page.path}`,
})
})
})
const result = [...mainPages, ...subPages]
// console.log(`getAllPages by ${key} result: `, result)
return result
}
/**
* 得到所有的需要登录的pages包括主包和分包的
* 只得到 path 数组
*/
export const getNeedLoginPages = (): string[] => getAllPages('needLogin').map((page) => page.path)
export const getNeedLoginPages = () : string[] => getAllPages('needLogin').map((page) => page.path)
/**
* 得到所有的需要登录的pages包括主包和分包的
* 只得到 path 数组
*/
export const needLoginPages: string[] = getAllPages('needLogin').map((page) => page.path)
export const needLoginPages : string[] = getAllPages('needLogin').map((page) => page.path)
/**
* 根据微信小程序当前环境判断应该获取的BaseUrl
*/
export const getEnvBaseUrl = () => {
//
let baseUrl = import.meta.env.VITE_SERVER_BASEURL
//
let baseUrl = import.meta.env.VITE_SERVER_BASEURL
//
if (isMpWeixin) {
const {
miniProgram: { envVersion },
} = uni.getAccountInfoSync()
//
if (isMpWeixin) {
const {
miniProgram: { envVersion },
} = uni.getAccountInfoSync()
switch (envVersion) {
case 'develop':
baseUrl = import.meta.env.VITE_SERVER_BASEURL__WEIXIN_DEVELOP || baseUrl
break
case 'trial':
baseUrl = import.meta.env.VITE_SERVER_BASEURL__WEIXIN_TRIAL || baseUrl
break
case 'release':
baseUrl = import.meta.env.VITE_SERVER_BASEURL__WEIXIN_RELEASE || baseUrl
break
}
}
switch (envVersion) {
case 'develop':
baseUrl = import.meta.env.VITE_SERVER_BASEURL__WEIXIN_DEVELOP || baseUrl
break
case 'trial':
baseUrl = import.meta.env.VITE_SERVER_BASEURL__WEIXIN_TRIAL || baseUrl
break
case 'release':
baseUrl = import.meta.env.VITE_SERVER_BASEURL__WEIXIN_RELEASE || baseUrl
break
}
}
return baseUrl
return baseUrl
}
/**
* 根据微信小程序当前环境判断应该获取的UPLOAD_BASEURL
*/
export const getEnvBaseUploadUrl = () => {
//
let baseUploadUrl = import.meta.env.VITE_UPLOAD_BASEURL
//
let baseUploadUrl = import.meta.env.VITE_UPLOAD_BASEURL
//
if (isMpWeixin) {
const {
miniProgram: { envVersion },
} = uni.getAccountInfoSync()
//
if (isMpWeixin) {
const {
miniProgram: { envVersion },
} = uni.getAccountInfoSync()
switch (envVersion) {
case 'develop':
baseUploadUrl = import.meta.env.VITE_UPLOAD_BASEURL__WEIXIN_DEVELOP || baseUploadUrl
break
case 'trial':
baseUploadUrl = import.meta.env.VITE_UPLOAD_BASEURL__WEIXIN_TRIAL || baseUploadUrl
break
case 'release':
baseUploadUrl = import.meta.env.VITE_UPLOAD_BASEURL__WEIXIN_RELEASE || baseUploadUrl
break
}
}
switch (envVersion) {
case 'develop':
baseUploadUrl = import.meta.env.VITE_UPLOAD_BASEURL__WEIXIN_DEVELOP || baseUploadUrl
break
case 'trial':
baseUploadUrl = import.meta.env.VITE_UPLOAD_BASEURL__WEIXIN_TRIAL || baseUploadUrl
break
case 'release':
baseUploadUrl = import.meta.env.VITE_UPLOAD_BASEURL__WEIXIN_RELEASE || baseUploadUrl
break
}
}
return baseUploadUrl
return baseUploadUrl
}
/**
* 时间格式化
@ -183,35 +183,138 @@ export const getEnvBaseUploadUrl = () => {
* @returns {*}
*/
export function formatDate(value, fmt) {
var regPos = /^\d+(\.\d+)?$/;
if(regPos.test(value)){
//
let getDate = new Date(value);
let o = {
'M+': getDate.getMonth() + 1,
'd+': getDate.getDate(),
'h+': getDate.getHours(),
'H+': getDate.getHours(),
'm+': getDate.getMinutes(),
's+': getDate.getSeconds(),
'q+': Math.floor((getDate.getMonth() + 3) / 3),
'S': getDate.getMilliseconds()
};
if (/(y+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, (getDate.getFullYear() + '').substr(4 - RegExp.$1.length))
}
for (let k in o) {
if (new RegExp('(' + k + ')').test(fmt)) {
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length)))
}
}
return fmt;
}else{
//TODO
if(value && value.length>0){
value = value.trim();
return value.substr(0,fmt.length);
}
return value
}
var regPos = /^\d+(\.\d+)?$/;
if (regPos.test(value)) {
//
let getDate = new Date(value);
let o = {
'M+': getDate.getMonth() + 1,
'd+': getDate.getDate(),
'h+': getDate.getHours(),
'H+': getDate.getHours(),
'm+': getDate.getMinutes(),
's+': getDate.getSeconds(),
'q+': Math.floor((getDate.getMonth() + 3) / 3),
'S': getDate.getMilliseconds()
};
if (/(y+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, (getDate.getFullYear() + '').substr(4 - RegExp.$1.length))
}
for (let k in o) {
if (new RegExp('(' + k + ')').test(fmt)) {
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length)))
}
}
return fmt;
} else {
//TODO
if (value && value.length > 0) {
value = value.trim();
return value.substr(0, fmt.length);
}
return value
}
}
/**
* 判断是否有新版本
* @param version 接口返回的版本号
* @param isWgt 是否是热资源更新 默认不是
*/
export const hasNewVersion = (version, isWgt = false) => {
// #ifdef APP_PLUS
return new Promise((resolve) => {
const transfer = (str) => str.replace(/\./g, '')
if (isWgt) {
plus.runtime.getProperty(plus.runtime.appid, (widgetInfo) => {
const currentVersion = widgetInfo.version
resolve(+transfer(version) > +transfer(currentVersion))
})
} else {
const currentVersion = plus.runtime.version
resolve(+transfer(version) > +transfer(currentVersion))
}
})
// #endif
}
export function onClickUpdate(updateType : string, url) {
// #ifdef APP-PLUS
if (updateType != 'wgt') plus.runtime.openURL(url.apkUrl)
else downloadApp(url.wgtUrl)
// #endif
}
function downloadApp(url : string) {
console.log('url', url);
var dtask = plus.downloader.createDownload(url, {
filename: `_downloads/wgt-${Date.now()}.wgt` //
}, function (d, status) {
//d
if (status == 200) {
//,d.filename使API
var fileSaveUrl = plus.io.convertLocalFileSystemURL(d.filename);
console.log('fileSaveUrl', fileSaveUrl);
installApp(fileSaveUrl)
} else {
//
plus.downloader.clear(); //
uni.showToast({
title: 'App下载失败',
icon: 'error'
})
}
})
let prg = 0
let showLoading = plus.nativeUI.showWaiting('正在下載')
dtask.start()
dtask.addEventListener('statechanged', (task) => {
//
switch (task.state) {
case 1:
showLoading.setTitle("正在下载")
break
case 2:
showLoading.setTitle("已连接到服务器")
break
case 3: {
const downloaded = +task.downloadedSize; //
const total = +task.totalSize; //
prg = total > 0 ? Math.floor((downloaded / total) * 100) : 0;
showLoading.setTitle(`正在下载 ${prg}%`);
break;
}
case 4:
plus.nativeUI.closeWaiting()
//
break
}
})
}
function installApp(tempFilePath : string) {
// #ifdef APP-PLUS
plus.runtime.install(
tempFilePath, {
force: true
},
() => {
uni.showModal({
title: '更新',
content: '更新成功,请点击确认后重启',
showCancel: false,
success(res) {
if (res.confirm) {
plus.runtime.restart()
}
}
})
},
() =>
uni.showToast({
title: '安装失败!',
icon: 'error'
})
)
// #endif
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 527 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 689 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 971 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB