This commit is contained in:
weipengfei 2023-08-26 18:41:06 +08:00
parent d52d5be7c8
commit d243cd46de
17 changed files with 594 additions and 40 deletions

13
App.vue
View File

@ -1,10 +1,12 @@
<script>
//#ifdef APP-PLUS
const jpushModule = uni.requireNativePlugin("JG-JPush");
import Updater from '@/uni_modules/guyue-updater/index';
// #endif
export default {
onLaunch: async function() {
console.log('App Launch')
this.$store.dispatch('initConfig');
try {
if (!this.$store.state.app.token) uni.redirectTo({
url: '/pages/oaLogin/oaLogin'
@ -34,9 +36,16 @@
}
});
}
})
//
// Updater.update({
// title: '',
// content: '1. UI\n2. UI\n3. UI\n4. UI\n',
// versionName: '1.3.6',
// downUrl: 'https://lihai001.oss-cn-chengdu.aliyuncs.com/__UNI__B5B1EDD__20230816174515.apk',
// force: false, //
// quiet: true //
// })
// #endif
console.log('App Show')
},

6
api/config.js Normal file
View File

@ -0,0 +1,6 @@
import oahttp from "@/utils/oahttp.js";
/**
* 获取配置信息
*/
export const getConfig = (data) => oahttp.get('/index/config', data, { noAuth:true }, true)

View File

@ -140,7 +140,25 @@
}
}
],
,{
"path": "uni_modules/guyue-updater/pages/updater",
"style": {
"app-plus": {
"animationDuration": 200,
"animationType": "fade-in",
"background": "transparent",
"backgroundColorTop": "transparent",
"bounce": "none",
"popGesture": "none",
"scrollIndicator": false,
"titleNView": false
},
"backgroundColor": "transparent",
"disableScroll": true,
"navigationStyle": "custom"
}
}
],
"subPackages": [{
"root": "pages/views",
"name": "views",

View File

@ -109,6 +109,7 @@
})
let res = await loginAccount(that.formData);
encrypt.encode('ACT', this.formData);
this.$store.dispatch('initConfig');
this.$store.commit('SET_USERINFO', {
user: data,
token: res.data.token

View File

@ -1,12 +1,10 @@
import Cache from '@/utils/cache';
import { getConfig } from "@/api/config.js";
const state = {
eyeType: Cache.get('eyeType') || true, // 小眼睛
request: Cache.get('request') || true, // 网络请求
timer: [{
id: -1,
time: 60,
timer: null
}], //
config: {}
};
const mutations = {
@ -18,38 +16,15 @@ const mutations = {
state.request = data;
Cache.set('request', state.request);
},
clearTimer(state, id){
let time = state.timer.find(item=>item.id==id);
clearInterval(time.timer);
time.timer = null;
time.time = 0;
SET_CONFIG(state, data){
state.config = data;
}
};
const actions = {
startTimer({state, commit}, data){
let time = state.timer.find(item=>item.id==data.id);
if(time){
time.timer = setInterval(() => {
time.time--;
if (time.time <= 0) {
commit('clearTimer', data.id);
}
})
}else {
state.timer.push({
id: data.id,
time: data.time,
timer: null
})
let new_time = state.timer.find(item=>item.id==data.id);
new_time.timer = setInterval(() => {
new_time.time--;
if (new_time.time <= 0) {
commit('clearTimer', data.id);
}
}, 1000)
}
async initConfig({ state, commit }) {
let res = await getConfig();
commit('SET_CONFIG', res.data);
}
};

View File

@ -5,13 +5,13 @@
<view>当日完成金额()</view>
<!-- <view class="price" v-if="company.deposit">{{cCount(company.deposit)}}</view>
<view class="price" v-else>0.00</view> -->
<view class="price">{{card.total_price.toFixed(2)}}</view>
<view class="price">{{c_money(card.total_price)}}</view>
</view>
<view class="item">
<view>目标完成金额()</view>
<!-- <view class="price" v-if="company.company_money">{{cCount(company.company_money)}}</view>
<view class="price" v-else>0.00</view> -->
<view class="price">{{card.day_money.toFixed(2)}}</view>
<view class="price">{{c_money(card.day_money)}}</view>
</view>
</view>
@ -119,8 +119,8 @@
id: this.task_id,
// company_id: this.$store.state.app.userInfo.company_id
})
this.loadConfig.status = "loadmore"
if (res.data?.list.length < this.loadConfig.limit) {
this.loadConfig.status = "loadmore";
if (res.data?.list?.length == 0 || res.data?.list?.length < 25) {
this.loadConfig.status = "nomore"
} else {
this.loadConfig.page++;
@ -134,6 +134,16 @@
this.loadConfig.status = "nomore"
}
},
c_money(str){
try{
if(!isNaN(str)){
typeof str != 'string' ? str = str.toFixed(2) : str = parseFloat(str).toFixed(2);
}else str = '0.00'
}catch(e){
str = '0.00'
}
return str;
}
},
onPullDownRefresh() {
this.initLoad().then(()=>{

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,6 @@
## 1.0.22023-06-02
修复部分bug新增条件编译仅限app平台调用
## 1.0.12023-06-02
去除ts使用js进行开发兼容性更好
## 1.0.02023-05-04
完成初始功能支持apk安装以及wgt升级支持显示弹窗升级、静默更新以及强制更新支持进度显示支持覆盖原生tabbar、原生导航栏

View File

@ -0,0 +1,24 @@
import { download, install } from "./updater";
export default class Updater {
static update(options ) {
// #ifdef APP-PLUS
// 静默更新
if (options.quiet) {
download({
url: options.downUrl,
onSuccess(filePath) {
install(filePath, false);
},
});
} else if (options.downUrl) {
uni.navigateTo({
url: `/uni_modules/guyue-updater/pages/updater?data=${encodeURIComponent(JSON.stringify(options))}`,
animationType: "fade-in",
animationDuration: 200,
});
}
// #endif
}
}

View File

@ -0,0 +1,12 @@
export type UpdateParams = {
content: string; // 必填,更新内容,内容中使用 \n 进行换行
downUrl: string; // 必填wgt热更新请给出 .wgt 的文件地址APK整包更新请设置下载apk地址ios请设置苹果商店的连接地址;
title?: string; // 用于显示弹窗标题,默认 发现新版本
versionName?: string; // 版本名,用于显示更新版本,如 1.0.0
quiet?: boolean; // 是否是静默更新开启后不会有弹窗会在后台下载更新文件在下次启动APP时使用更新
force?: boolean; // 是否是强制更新,开启后,弹窗无法被关闭,必须更新
updateBtnText?: string; // 升级按钮文字,默认 立即升级
downMsgTip?: string; // 仅android默认 下载中,请稍后
downSucTip?: string; // 仅android默认 下载完成,安装中
downErrorTip?: string; // 仅android默认 下载失败,请重试
}

View File

@ -0,0 +1,82 @@
{
"id": "guyue-updater",
"displayName": "App版本升级弹框和进度提示",
"version": "1.0.2",
"description": "app热更新模块支持apk安装以及wgt升级支持显示弹窗升级、静默更新以及强制更新支持进度显示支持覆盖原生tabar原生导航栏",
"keywords": [
"热更新",
"进度提示",
"版本升级",
"app自动升级",
"wgt自动升级"
],
"repository": "",
"engines": {
"HBuilderX": "^3.4.9"
},
"dcloudext": {
"type": "sdk-js",
"sale": {
"regular": {
"price": "9.98"
},
"sourcecode": {
"price": "16.80"
}
},
"contact": {
"qq": "2292550932"
},
"declaration": {
"ads": "无",
"data": "插件不采集任何数据",
"permissions": "无"
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"Vue": {
"vue2": "y",
"vue3": "y"
},
"App": {},
"H5-mobile": {
"Safari": "n",
"Android Browser": "n",
"微信浏览器(Android)": "n",
"QQ浏览器(Android)": "n"
},
"H5-pc": {
"Chrome": "n",
"IE": "n",
"Edge": "n",
"Firefox": "n",
"Safari": "n"
},
"小程序": {
"微信": "n",
"阿里": "n",
"百度": "n",
"字节跳动": "n",
"QQ": "n",
"钉钉": "n",
"快手": "n",
"飞书": "n",
"京东": "n"
},
"快应用": {
"华为": "n",
"联盟": "n"
}
}
}
}
}

View File

@ -0,0 +1,315 @@
<template>
<view class="container">
<view class="main" @click.stop="">
<view class="header">
<image src="../assets/bg1.png" class="bg1" />
<image src="../assets/bg2.png" class="bg2" />
<view class="version-title">{{ updateParams.title }}</view>
<view class="version-name" v-if="updateParams.versionName">V{{ updateParams.versionName }}</view>
</view>
<view class="title">更新内容</view>
<view class="content" >
<rich-text :nodes="content" />
</view>
<view class="progress" v-if="downloading">
<view class="slider">
<view class="active-slider" :style="{ width: `${progress}%` }">
<view class="bar" />
<view class="dot">
<view class="text">{{ progress }}%</view>
<view class="circle" />
</view>
</view>
</view>
</view>
<view class="button" :class="{'active': !downloading || downloadError}" @click="handleButton">
{{ downloadText }}
</view>
</view>
<view class="bottom" v-if="!updateParams.force" @click="back">
<view class="line"/>
<image src="../assets/close.png" class="close" />
</view>
</view>
</template>
<script>
import { download, install } from "../updater";
export default {
data() {
const data = {
updateParams: {},
progress: 0,
downloading: false,
downloadSucc: false,
downloadError: false,
};
return data;
},
computed: {
content() {
return (this.updateParams.content || '').replace(/[\r\n]/gim, '<br/>');
},
downloadText () {
if (this.downloadSucc) {
return this.updateParams.downSucTip;
}
if (this.downloadError) {
return this.updateParams.downErrorTip;
}
if (this.downloading) {
return this.updateParams.downMsgTip;
}
return this.updateParams.updateBtnText;
},
},
onLoad(params) {
const data = {
title: '发现新版本',
updateBtnText: '立即升级',
downMsgTip: '下载中,请稍后',
downSucTip: '下载完成,安装中',
downErrorTip: '下载失败,请重试',
quiet: false,
force: false,
...(JSON.parse(decodeURIComponent(params.data)))
};
this.updateParams = data;
},
onBackPress() {
return this.updateParams.force;
},
methods: {
back() {
if (!this.updateParams.force) {
uni.navigateBack();
}
},
//
start() {
if (!this.updateParams.downUrl) {
return;
}
// ios appstore.apk.wgt
const isResource = ['.apk', '.wgt'].some(ext => this.updateParams.downUrl.toLocaleLowerCase().includes(ext));
if (plus.os.name !== "Android" || !isResource) {
plus.runtime.openURL(this.updateParams.downUrl);
return;
}
this.downloading = true;
const self = this;
download({
url: self.updateParams.downUrl,
onProgress(progress) {
self.progress = progress;
},
onSuccess(filePath) {
self.downloadSucc = true;
self.downloadError = false;
install(filePath, true);
},
onFail() {
self.downloading = false;
self.downloadSucc = false;
self.downloadError = true;
},
});
},
handleButton() {
if (!this.downloading) {
return this.start();
}
if (this.downloadError) {
this.progress = 0;
this.downloading = false;
this.downloadSucc = false;
this.downloadError = true;
return this.start();
}
},
},
};
</script>
<style lang="less">
page {
width: 100%;
height: 100%;
background: transparent;
}
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.3);
}
.main {
width: 75%;
background-color: #fff;
border-radius: 8rpx;
padding-bottom: 10rpx;
}
.header {
position: relative;
.bg1 {
width: 100%;
height: calc(100vw * 0.375);
border-top-left-radius: 8rpx;
border-top-right-radius: 8rpx;
}
.bg2 {
position: absolute;
top: -40%;
right: 13%;
width: 35.9%;
height: calc(100vw * 0.5441);
}
.version-title {
position: absolute;
top: 13%;
left: 8%;
color: #961c00;
font-size: 40rpx;
}
.version-name {
position: absolute;
top: 36%;
left: 24%;
background-color: #e54139;
color: #fff;
font-size: 26rpx;
line-height: 26rpx;
padding: 8rpx 20rpx;
border-radius: 20rpx;
}
}
.title {
background-color: #ff6d42;
color: #fff;
display: inline-block;
margin-top: 12rpx;
margin-left: 30rpx;
font-size: 26rpx;
line-height: 26rpx;
padding: 8rpx 20rpx;
border-radius: 8rpx;
}
.content {
margin-top: 12rpx;
margin-left: 30rpx;
font-size: 28rpx;
line-height: 2;
max-height: 240rpx;
overflow-y: auto;
}
.button {
margin: 20rpx 30rpx;
background-color: #ffaa00;
color: #fff;
font-size: 30rpx;
text-align: center;
padding: 20rpx 0;
border-radius: 14rpx;
opacity: 0.5;
pointer-events: none;
&:active {
opacity: 0.6;
}
&.active {
opacity: 1;
pointer-events: initial;
}
}
.progress {
padding: 50rpx 50rpx 18rpx;
.slider {
position: relative;
width: 100%;
height: 10rpx;
border-radius: 5rpx;
background-color: #e2e2e2;
.active-slider {
display: flex;
flex-direction: row;
align-items: center;
position: absolute;
left: 0;
top: 0;
width: 0%;
height: 10rpx;
border-radius: 5rpx;
.bar {
flex: 1;
height: 100%;
background-color: #e84116;
border-top-left-radius: 5rpx;
border-bottom-left-radius: 5rpx;
}
.dot {
position: relative;
margin-left: -12rpx;
.text {
position: absolute;
top: -34rpx;
left: -50%;
color: #e84116;
font-size: 24rpx;
font-weight: 500;
}
.circle {
width: 12rpx;
height: 12rpx;
border: 6rpx solid #e84116;
border-radius: 50%;
background-color: #fff;
}
}
}
}
}
.bottom {
display: flex;
flex-direction: column;
align-items: center;
.line {
width: 3rpx;
height: 50rpx;
background-color: #fff;
}
.close {
width: 64rpx;
height: 64rpx;
margin-top: -4rpx;
}
}
</style>

View File

@ -0,0 +1,65 @@
# App热更新
App热更新模块支持apk安装以及wgt升级支持显示弹窗升级、静默更新以及强制更新支持进度显示支持覆盖原生tabbar、原生导航栏。
可用于自建热更新渠道,不依赖于云服务,无云服务费用支出,也可以适配官方更新中心。
## 使用说明
### 1.将此项目导入自己的项目工程
### 2.在page.json中注册页面如下
```javascript
{
"path": "uni_modules/guyue-updater/pages/updater",
"style": {
"navigationStyle": "custom",
"backgroundColor": "transparent",
"disableScroll": true,
"app-plus": {
"backgroundColorTop": "transparent",
"background": "transparent",
"scrollIndicator": false,
"titleNView": false,
"popGesture": "none",
"bounce": "none",
"animationType": "fade-in",
"animationDuration": 200
}
}
}
```
### 3.将版本检测函数导入需要使用的页面
一般在App.vue中的onLaunch导入或者首页导入需要自行完成热更新检查一般在APP启动时发起一个请求获取热更新数据数据获取后可以调用该组件完成更新。
```javascript
import Updater from '@/uni_modules/guyue-updater/index';
// 仅在app平台有效其他平台调用无效
Updater.update({
title: '发现新版本',
content: '1. 我们更新了新的UI设计\n2. 我们更新了新的UI设计\n3. 我们更新了新的UI设计\n4. 我们更新了新的UI设计\n',
versionName: '1.3.6',
downUrl: 'https://cdn.xxx.cn/mp/__UNI__1F29D65.wgt',
force: false,
})
```
## 参数说明
```javascript
export type UpdateParams = {
content: string; // 必填,更新内容,内容中使用 \n 进行换行
downUrl: string; // 必填wgt热更新请给出 .wgt 的文件地址APK整包更新请设置下载apk地址ios请设置苹果商店的连接地址;
title?: string; // 用于显示弹窗标题,默认 发现新版本
versionName?: string; // 版本名,用于显示更新版本,如 1.0.0
quiet?: boolean; // 是否是静默更新开启后不会有弹窗会在后台下载更新文件在下次启动APP时使用更新
force?: boolean; // 是否是强制更新,开启后,弹窗无法被关闭,必须更新
updateBtnText?: string; // 升级按钮文字,默认 立即升级
downMsgTip?: string; // 仅android默认 下载中,请稍后
downSucTip?: string; // 仅android默认 下载完成,安装中
downErrorTip?: string; // 仅android默认 下载失败,请重试
}
```
## Android如何跳转到应用市场更新
downUrl 设置为应用市场的地址即可,如: market://details?id={这里写你的应用包名}
## iOS 如何跳转到AppStore
downUrl 设置为AppStore的地址即可 itms-apps://itunes.apple.com/cn/app/hello-uni-app/id1417078253

View File

@ -0,0 +1,30 @@
export const download = ({ url, onProgress, onSuccess, onFail }) => {
const task = uni.downloadFile({
url,
success(res) {
if (res.statusCode === 200) {
onSuccess && onSuccess(res.tempFilePath);
}
},
fail() {
onFail && onFail();
}
});
task.onProgressUpdate(res => {
onProgress && onProgress(res.progress);
});
};
export const install = (filePath, restart = false) => {
plus.runtime.install(filePath, {
force: true
}, () => {
console.log('install success...');
if (restart) {
plus.runtime.restart();
}
}, (e) => {
console.error('install fail...', e);
});
};

View File

@ -38,6 +38,7 @@ function baseRequestTwo(url, method, data, {
// if (store.state.app.token) header[TOKENNAME] = 'Bearer ' + store.state.app.token;
if (store.state.app.token) header[TOKENNAME] = store.state.app.token;
// header[TOKENNAME] = 'Bearer sdjflidshjgfkbdasgjmasbgvhauuiavhkesvndkaesbvkjsdbv';
return new Promise((reslove, reject) => {
// uni.showLoading({