HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

可视化编辑照片书

外包接单 外包

照片编辑可视化小程序,前端uniapp,后端PHP,一款综合全面的图片编辑软件,支持裁剪、旋转、改尺寸、压缩体积、文字、贴纸、边框等图片编辑功能。软件汇集了大量的模板和装饰素材,采用可视化的设计界面,用户只需导入图片编辑即可制作成书。软件支持快速排版,简单易用,适合制作个性杂志、个性台历、纪念册、个性笔记本等。照片书排版是一款功能强大的照片书制作工具。软件汇集了大量的模板和装饰素材,采用可视化的设计界面,用户只需导入图片编辑即可制作成书。软件支持快速排版,简单易用,适合制作个性杂志、个性台历、纪念册、个性笔记本等。可以打包app,目前已有现成小程序可以体验,欢迎有需要的人士前来询问,V:zpj2220

继续阅读 »

照片编辑可视化小程序,前端uniapp,后端PHP,一款综合全面的图片编辑软件,支持裁剪、旋转、改尺寸、压缩体积、文字、贴纸、边框等图片编辑功能。软件汇集了大量的模板和装饰素材,采用可视化的设计界面,用户只需导入图片编辑即可制作成书。软件支持快速排版,简单易用,适合制作个性杂志、个性台历、纪念册、个性笔记本等。照片书排版是一款功能强大的照片书制作工具。软件汇集了大量的模板和装饰素材,采用可视化的设计界面,用户只需导入图片编辑即可制作成书。软件支持快速排版,简单易用,适合制作个性杂志、个性台历、纪念册、个性笔记本等。可以打包app,目前已有现成小程序可以体验,欢迎有需要的人士前来询问,V:zpj2220

收起阅读 »

使用uniapp开发ZEBRA(斑马 TC21/TC26)PDA,开启/关闭激光红外,接收扫描数据

公司要为斑马PDA开发app,使用技术是uniapp,需求是当管理后台给PDA分配扫码权限后,PDA才能启动激光红外扫码功能,否则不能启动扫码,最开始的实现思路是当拥有扫码权限,就处理扫描出来的数据,没有扫码权限就不处理扫描出来的数据(注:当没有扫码权限时,按下物理的扫描按键也会出现激光),老板说不行,必须是没有权限不能启动激光红外线,也就是没有权限,按了物理的扫描按键,也不能出现激光。

带着需求在斑马官网找到了对应的API,因此记录一下自己开发过程,方便以后查找,也方便即将遇到同样问题的同学

ZEBRA(斑马) DataWedge API

1、拿出斑马PDA充电、并开机,手指在主屏幕上,向上滑动,找到应用“DataWedge”,如图

2、点击“DataWedge”,启动Profile0(default)文件,并配置Intent输出,禁用Launcher和DWDemo配置文件,如图


3、好了,斑马PDA已经设置好了,现在开始用uniapp做开发了,主要代码如下

    var scanObj = {};  
//是否开启激光红外线扫描,true开启,false关闭  
Vue.prototype.isOpenLaserScan = (isOpen) => {  
    scanObj.main = plus.android.runtimeMainActivity();//获取activity  
    var Intent = plus.android.importClass('android.content.Intent');  
    scanObj.intent = new Intent();  
    scanObj.intent.setAction("com.symbol.datawedge.api.ACTION");  
    scanObj.intent.putExtra("com.symbol.datawedge.api.ENABLE_DATAWEDGE", isOpen);  
    scanObj.main.sendBroadcast(scanObj.intent);  
}  

//初始化扫描设备  
Vue.prototype.initScan = (fun) => {  
    scanObj.main = plus.android.runtimeMainActivity();//获取activity    
    var IntentFilter = plus.android.importClass('android.content.IntentFilter');  
    scanObj.filter = new IntentFilter();  
    scanObj.filter.addAction("com.dwexample.ACTION"); // 换你的广播动作  com.service.scanner.data(海康威视的PDA)  
    scanObj.receiver = plus.android.implements('io.dcloud.feature.internal.reflect.BroadcastReceiver',{    
    onReceive: function(context, intent) {    
        plus.android.importClass(intent);  
        let code = intent.getStringExtra("com.motorolasolutions.emdk.datawedge.data_string");// 换你的广播标签  ScanCode(海康威视的PDA)  
        if(typeof(fun) === 'function'){  
            fun(code);  
        }  
    }});  
}  

//启动扫描设备  
Vue.prototype.startScan = () => {  
    try{  
        scanObj.main.registerReceiver(scanObj.receiver,scanObj.filter);  
    }catch(e){  
        //TODO handle the exception  
    }  
}  

//停止扫描设备  
Vue.prototype.stopScan = () => {    
    try{  
        scanObj.main.unregisterReceiver(scanObj.receiver);  
    }catch(e){  
        //TODO handle the exception  
    }  
}  

4、在生命周期onShow函数中调用扫码,页面调用如下
  onShow(){
        //监听页面显示。页面每次出现在屏幕上都触发,包括从下级页面点返回露出当前页面
        let _this = this;
        //是否开启激光红外线扫描,默认开启
        _this.isOpenLaserScan(true);
        //判断是否拥有当前页面的扫码权限,这里是自己业务的权限判断
        if (!_this.hasPerm('PutBaseFlour:scan')) {
                uni.showModal({
                        title: '提示',
                        content: '你没有扫码权限,无法开启激光扫码,请联系管理员!',
                        showCancel: false
                });
                setTimeout(() => {
                        //是否开启激光红外线扫描,没有相应的权限则关闭
                        _this.isOpenLaserScan(false);
                }, 500);
                return;
        }
        _this.initScan((code) => {
                //处理自己的业务逻辑
                //code为扫码取得的值
        });
        _this.startScan();
  }

5、使用完成后需要销毁,由于app、h5、ios、微信等所支持的生命周期有所不同,建议在以下生命周期中销毁防止没有销毁掉的情况,避免多次进入页面扫码后取得多个值
  onHide(){
        //监听页面隐藏
        if (this.hasPerm('PutBaseFlour:scan')) {
                this.stopScan();
        }
  },
  onUnload(){
        //监听页面卸载
        if (this.hasPerm('PutBaseFlour:scan')) {
                this.stopScan();
        }
  },
  onBackPress(){
        //监听页面返回
        if (this.hasPerm('PutBaseFlour:scan')) {
                this.stopScan();
        }
  },
  destroyed:function(){
        //页面退出时一定要卸载监听,否则下次进来时会重复,造成扫一次出2个以上的结果
        if (this.hasPerm('PutBaseFlour:scan')) {
                this.stopScan();
        }
  }

继续阅读 »

公司要为斑马PDA开发app,使用技术是uniapp,需求是当管理后台给PDA分配扫码权限后,PDA才能启动激光红外扫码功能,否则不能启动扫码,最开始的实现思路是当拥有扫码权限,就处理扫描出来的数据,没有扫码权限就不处理扫描出来的数据(注:当没有扫码权限时,按下物理的扫描按键也会出现激光),老板说不行,必须是没有权限不能启动激光红外线,也就是没有权限,按了物理的扫描按键,也不能出现激光。

带着需求在斑马官网找到了对应的API,因此记录一下自己开发过程,方便以后查找,也方便即将遇到同样问题的同学

ZEBRA(斑马) DataWedge API

1、拿出斑马PDA充电、并开机,手指在主屏幕上,向上滑动,找到应用“DataWedge”,如图

2、点击“DataWedge”,启动Profile0(default)文件,并配置Intent输出,禁用Launcher和DWDemo配置文件,如图


3、好了,斑马PDA已经设置好了,现在开始用uniapp做开发了,主要代码如下

    var scanObj = {};  
//是否开启激光红外线扫描,true开启,false关闭  
Vue.prototype.isOpenLaserScan = (isOpen) => {  
    scanObj.main = plus.android.runtimeMainActivity();//获取activity  
    var Intent = plus.android.importClass('android.content.Intent');  
    scanObj.intent = new Intent();  
    scanObj.intent.setAction("com.symbol.datawedge.api.ACTION");  
    scanObj.intent.putExtra("com.symbol.datawedge.api.ENABLE_DATAWEDGE", isOpen);  
    scanObj.main.sendBroadcast(scanObj.intent);  
}  

//初始化扫描设备  
Vue.prototype.initScan = (fun) => {  
    scanObj.main = plus.android.runtimeMainActivity();//获取activity    
    var IntentFilter = plus.android.importClass('android.content.IntentFilter');  
    scanObj.filter = new IntentFilter();  
    scanObj.filter.addAction("com.dwexample.ACTION"); // 换你的广播动作  com.service.scanner.data(海康威视的PDA)  
    scanObj.receiver = plus.android.implements('io.dcloud.feature.internal.reflect.BroadcastReceiver',{    
    onReceive: function(context, intent) {    
        plus.android.importClass(intent);  
        let code = intent.getStringExtra("com.motorolasolutions.emdk.datawedge.data_string");// 换你的广播标签  ScanCode(海康威视的PDA)  
        if(typeof(fun) === 'function'){  
            fun(code);  
        }  
    }});  
}  

//启动扫描设备  
Vue.prototype.startScan = () => {  
    try{  
        scanObj.main.registerReceiver(scanObj.receiver,scanObj.filter);  
    }catch(e){  
        //TODO handle the exception  
    }  
}  

//停止扫描设备  
Vue.prototype.stopScan = () => {    
    try{  
        scanObj.main.unregisterReceiver(scanObj.receiver);  
    }catch(e){  
        //TODO handle the exception  
    }  
}  

4、在生命周期onShow函数中调用扫码,页面调用如下
  onShow(){
        //监听页面显示。页面每次出现在屏幕上都触发,包括从下级页面点返回露出当前页面
        let _this = this;
        //是否开启激光红外线扫描,默认开启
        _this.isOpenLaserScan(true);
        //判断是否拥有当前页面的扫码权限,这里是自己业务的权限判断
        if (!_this.hasPerm('PutBaseFlour:scan')) {
                uni.showModal({
                        title: '提示',
                        content: '你没有扫码权限,无法开启激光扫码,请联系管理员!',
                        showCancel: false
                });
                setTimeout(() => {
                        //是否开启激光红外线扫描,没有相应的权限则关闭
                        _this.isOpenLaserScan(false);
                }, 500);
                return;
        }
        _this.initScan((code) => {
                //处理自己的业务逻辑
                //code为扫码取得的值
        });
        _this.startScan();
  }

5、使用完成后需要销毁,由于app、h5、ios、微信等所支持的生命周期有所不同,建议在以下生命周期中销毁防止没有销毁掉的情况,避免多次进入页面扫码后取得多个值
  onHide(){
        //监听页面隐藏
        if (this.hasPerm('PutBaseFlour:scan')) {
                this.stopScan();
        }
  },
  onUnload(){
        //监听页面卸载
        if (this.hasPerm('PutBaseFlour:scan')) {
                this.stopScan();
        }
  },
  onBackPress(){
        //监听页面返回
        if (this.hasPerm('PutBaseFlour:scan')) {
                this.stopScan();
        }
  },
  destroyed:function(){
        //页面退出时一定要卸载监听,否则下次进来时会重复,造成扫一次出2个以上的结果
        if (this.hasPerm('PutBaseFlour:scan')) {
                this.stopScan();
        }
  }

收起阅读 »

更新最新版本后,uniapp安心打包报错

安心打包 HBuilderX
[Info] 正在制作apk安装包...  

[Info] I: Using Apktool 2.4.1 on __UNI__2DB9CA0_cm.apk  
[Info] I: Loading resource table...  
[Info] I: Decoding AndroidManifest.xml with resources...  
[Info] I: Loading resource table from file: C:\Users\FGHF\AppData\Local\HBuilder X\AndroidPackWork\apktool\1.apk  
[Info] I: Regular manifest package...  
[Info] I: Decoding file-resources...  
[Info] I: Decoding values */* XMLs...  
[Info] I: Copying raw classes.dex file...  
[Info] I: Copying raw classes2.dex file...  
[Info] I: Copying raw classes3.dex file...  
[Info] I: Copying raw assets/39285EFA.dex file...  
[Info] I: Copying assets and libs...  
[Info] I: Copying unknown files...  
[Info] I: Copying original files...  
[Info] begin replace files to apk...  
[Info] begin copy file[E:/work_wkz/yg_h5_v2/unpackage/cache/wgt/__UNI__2DB9CA0/.manifest/icon-android-hdpi.png] to [C:/Users/FGHF/AppData/Local/HBuilder X/AndroidPackWork/cache/__UNI__2DB9CA0/packge_cache/__NONE__/__UNI__2DB9CA0_cm/res/drawable-hdpi/icon.png]...  
[Info] copy file[E:/work_wkz/yg_h5_v2/unpackage/cache/wgt/__UNI__2DB9CA0/.manifest/icon-android-hdpi.png] to [C:/Users/FGHF/AppData/Local/HBuilder X/AndroidPackWork/cache/__UNI__2DB9CA0/packge_cache/__NONE__/__UNI__2DB9CA0_cm/res/drawable-hdpi/icon.png] success.  
[Info] begin copy file[E:/work_wkz/yg_h5_v2/unpackage/cache/wgt/__UNI__2DB9CA0/.manifest/icon-android-xhdpi.png] to [C:/Users/FGHF/AppData/Local/HBuilder X/AndroidPackWork/cache/__UNI__2DB9CA0/packge_cache/__NONE__/__UNI__2DB9CA0_cm/res/drawable-xhdpi/icon.png]...  
[Info] copy file[E:/work_wkz/yg_h5_v2/unpackage/cache/wgt/__UNI__2DB9CA0/.manifest/icon-android-xhdpi.png] to [C:/Users/FGHF/AppData/Local/HBuilder X/AndroidPackWork/cache/__UNI__2DB9CA0/packge_cache/__NONE__/__UNI__2DB9CA0_cm/res/drawable-xhdpi/icon.png] success.  
[Info] begin copy file[E:/work_wkz/yg_h5_v2/unpackage/cache/wgt/__UNI__2DB9CA0/.manifest/icon-android-xxhdpi.png] to [C:/Users/FGHF/AppData/Local/HBuilder X/AndroidPackWork/cache/__UNI__2DB9CA0/packge_cache/__NONE__/__UNI__2DB9CA0_cm/res/drawable-xxhdpi/icon.png]...  
[Info] copy file[E:/work_wkz/yg_h5_v2/unpackage/cache/wgt/__UNI__2DB9CA0/.manifest/icon-android-xxhdpi.png] to [C:/Users/FGHF/AppData/Local/HBuilder X/AndroidPackWork/cache/__UNI__2DB9CA0/packge_cache/__NONE__/__UNI__2DB9CA0_cm/res/drawable-xxhdpi/icon.png] success.  
[Info] begin copy file[E:/work_wkz/yg_h5_v2/unpackage/cache/wgt/__UNI__2DB9CA0/.manifest/icon-android-xxxhdpi.png] to [C:/Users/FGHF/AppData/Local/HBuilder X/AndroidPackWork/cache/__UNI__2DB9CA0/packge_cache/__NONE__/__UNI__2DB9CA0_cm/res/drawable-xxxhdpi/icon.png]...  
[Info] copy file[E:/work_wkz/yg_h5_v2/unpackage/cache/wgt/__UNI__2DB9CA0/.manifest/icon-android-xxxhdpi.png] to [C:/Users/FGHF/AppData/Local/HBuilder X/AndroidPackWork/cache/__UNI__2DB9CA0/packge_cache/__NONE__/__UNI__2DB9CA0_cm/res/drawable-xxxhdpi/icon.png] success.  
[Info] begin update files to apk...  
[Error] try compile package:0  
[Info] I: Using Apktool 2.4.1  
[Info] I: Copying C:\Users\FGHF\AppData\Local\HBuilder X\AndroidPackWork\cache\__UNI__2DB9CA0\packge_cache\__NONE__\__UNI__2DB9CA0_cm classes.dex file...  
[Info] I: Copying C:\Users\FGHF\AppData\Local\HBuilder X\AndroidPackWork\cache\__UNI__2DB9CA0\packge_cache\__NONE__\__UNI__2DB9CA0_cm classes2.dex file...  
[Info] I: Copying C:\Users\FGHF\AppData\Local\HBuilder X\AndroidPackWork\cache\__UNI__2DB9CA0\packge_cache\__NONE__\__UNI__2DB9CA0_cm classes3.dex file...  
[Info] I: Checking whether resources has changed...  
[Info] I: Building resources...  
[Error] W: C:\Users\FGHF\AppData\Local\HBuilder X\AndroidPackWork\cache\__UNI__2DB9CA0\packge_cache\__NONE__\__UNI__2DB9CA0_cm\AndroidManifest.xml:48: Tag <action> attribute name has invalid character '*'.  
[Error] brut.androlib.AndrolibException: brut.common.BrutException: could not exec (exit code = 1): [C:\Users\FGHF\AppData\Local\Temp\brut_util_Jar_13841587595912769514.tmp, p, --forced-package-id, 127, --min-sdk-version, 19, --target-sdk-version, 28, --version-code, 32, --version-name, 3.1.0, --no-version-vectors, -F, C:\Users\FGHF\AppData\Local\Temp\APKTOOL640673907049858768.tmp, -e, C:\Users\FGHF\AppData\Local\Temp\APKTOOL11117073117559654397.tmp, -0, arsc, -I, C:\Users\FGHF\AppData\Local\HBuilder X\AndroidPackWork\apktool\1.apk, -S, C:\Users\FGHF\AppData\Local\HBuilder X\AndroidPackWork\cache\__UNI__2DB9CA0\packge_cache\__NONE__\__UNI__2DB9CA0_cm\res, -M, C:\Users\FGHF\AppData\Local\HBuilder X\AndroidPackWork\cache\__UNI__2DB9CA0\packge_cache\__NONE__\__UNI__2DB9CA0_cm\AndroidManifest.xml]  
[Error] try compile package:1  
[Info] I: Using Apktool 2.4.1  
[Info] I: Checking whether resources has changed...  
[Info] I: Building resources...  
[Error] W: C:\Users\FGHF\AppData\Local\HBuilder X\AndroidPackWork\cache\__UNI__2DB9CA0\packge_cache\__NONE__\__UNI__2DB9CA0_cm\AndroidManifest.xml:48: Tag <action> attribute name has invalid character '*'.  
[Error] brut.androlib.AndrolibException: brut.common.BrutException: could not exec (exit code = 1): [C:\Users\FGHF\AppData\Local\Temp\brut_util_Jar_1364484411461162108.tmp, p, --forced-package-id, 127, --min-sdk-version, 19, --target-sdk-version, 28, --version-code, 32, --version-name, 3.1.0, --no-version-vectors, -F, C:\Users\FGHF\AppData\Local\Temp\APKTOOL9195127418260236703.tmp, -e, C:\Users\FGHF\AppData\Local\Temp\APKTOOL13100643559112781843.tmp, -0, arsc, -I, C:\Users\FGHF\AppData\Local\HBuilder X\AndroidPackWork\apktool\1.apk, -S, C:\Users\FGHF\AppData\Local\HBuilder X\AndroidPackWork\cache\__UNI__2DB9CA0\packge_cache\__NONE__\__UNI__2DB9CA0_cm\res, -M, C:\Users\FGHF\AppData\Local\HBuilder X\AndroidPackWork\cache\__UNI__2DB9CA0\packge_cache\__NONE__\__UNI__2DB9CA0_cm\AndroidManifest.xml]  
[Error] try compile package:2  
[Info] I: Using Apktool 2.4.1  
[Info] I: Checking whether resources has changed...  
[Info] I: Building resources...  
[Error] W: C:\Users\FGHF\AppData\Local\HBuilder X\AndroidPackWork\cache\__UNI__2DB9CA0\packge_cache\__NONE__\__UNI__2DB9CA0_cm\AndroidManifest.xml:48: Tag <action> attribute name has invalid character '*'.  
[Error] brut.androlib.AndrolibException: brut.common.BrutException: could not exec (exit code = 1): [C:\Users\FGHF\AppData\Local\Temp\brut_util_Jar_8421301333268526767.tmp, p, --forced-package-id, 127, --min-sdk-version, 19, --target-sdk-version, 28, --version-code, 32, --version-name, 3.1.0, --no-version-vectors, -F, C:\Users\FGHF\AppData\Local\Temp\APKTOOL15143234046447346603.tmp, -e, C:\Users\FGHF\AppData\Local\Temp\APKTOOL5501540500666506840.tmp, -0, arsc, -I, C:\Users\FGHF\AppData\Local\HBuilder X\AndroidPackWork\apktool\1.apk, -S, C:\Users\FGHF\AppData\Local\HBuilder X\AndroidPackWork\cache\__UNI__2DB9CA0\packge_cache\__NONE__\__UNI__2DB9CA0_cm\res, -M, C:\Users\FGHF\AppData\Local\HBuilder X\AndroidPackWork\cache\__UNI__2DB9CA0\packge_cache\__NONE__\__UNI__2DB9CA0_cm\AndroidManifest.xml]  
[Error] Apk tool compile package to apk failed  
[Error] 制作结果:Failed.    Reason:   
继续阅读 »
[Info] 正在制作apk安装包...  

[Info] I: Using Apktool 2.4.1 on __UNI__2DB9CA0_cm.apk  
[Info] I: Loading resource table...  
[Info] I: Decoding AndroidManifest.xml with resources...  
[Info] I: Loading resource table from file: C:\Users\FGHF\AppData\Local\HBuilder X\AndroidPackWork\apktool\1.apk  
[Info] I: Regular manifest package...  
[Info] I: Decoding file-resources...  
[Info] I: Decoding values */* XMLs...  
[Info] I: Copying raw classes.dex file...  
[Info] I: Copying raw classes2.dex file...  
[Info] I: Copying raw classes3.dex file...  
[Info] I: Copying raw assets/39285EFA.dex file...  
[Info] I: Copying assets and libs...  
[Info] I: Copying unknown files...  
[Info] I: Copying original files...  
[Info] begin replace files to apk...  
[Info] begin copy file[E:/work_wkz/yg_h5_v2/unpackage/cache/wgt/__UNI__2DB9CA0/.manifest/icon-android-hdpi.png] to [C:/Users/FGHF/AppData/Local/HBuilder X/AndroidPackWork/cache/__UNI__2DB9CA0/packge_cache/__NONE__/__UNI__2DB9CA0_cm/res/drawable-hdpi/icon.png]...  
[Info] copy file[E:/work_wkz/yg_h5_v2/unpackage/cache/wgt/__UNI__2DB9CA0/.manifest/icon-android-hdpi.png] to [C:/Users/FGHF/AppData/Local/HBuilder X/AndroidPackWork/cache/__UNI__2DB9CA0/packge_cache/__NONE__/__UNI__2DB9CA0_cm/res/drawable-hdpi/icon.png] success.  
[Info] begin copy file[E:/work_wkz/yg_h5_v2/unpackage/cache/wgt/__UNI__2DB9CA0/.manifest/icon-android-xhdpi.png] to [C:/Users/FGHF/AppData/Local/HBuilder X/AndroidPackWork/cache/__UNI__2DB9CA0/packge_cache/__NONE__/__UNI__2DB9CA0_cm/res/drawable-xhdpi/icon.png]...  
[Info] copy file[E:/work_wkz/yg_h5_v2/unpackage/cache/wgt/__UNI__2DB9CA0/.manifest/icon-android-xhdpi.png] to [C:/Users/FGHF/AppData/Local/HBuilder X/AndroidPackWork/cache/__UNI__2DB9CA0/packge_cache/__NONE__/__UNI__2DB9CA0_cm/res/drawable-xhdpi/icon.png] success.  
[Info] begin copy file[E:/work_wkz/yg_h5_v2/unpackage/cache/wgt/__UNI__2DB9CA0/.manifest/icon-android-xxhdpi.png] to [C:/Users/FGHF/AppData/Local/HBuilder X/AndroidPackWork/cache/__UNI__2DB9CA0/packge_cache/__NONE__/__UNI__2DB9CA0_cm/res/drawable-xxhdpi/icon.png]...  
[Info] copy file[E:/work_wkz/yg_h5_v2/unpackage/cache/wgt/__UNI__2DB9CA0/.manifest/icon-android-xxhdpi.png] to [C:/Users/FGHF/AppData/Local/HBuilder X/AndroidPackWork/cache/__UNI__2DB9CA0/packge_cache/__NONE__/__UNI__2DB9CA0_cm/res/drawable-xxhdpi/icon.png] success.  
[Info] begin copy file[E:/work_wkz/yg_h5_v2/unpackage/cache/wgt/__UNI__2DB9CA0/.manifest/icon-android-xxxhdpi.png] to [C:/Users/FGHF/AppData/Local/HBuilder X/AndroidPackWork/cache/__UNI__2DB9CA0/packge_cache/__NONE__/__UNI__2DB9CA0_cm/res/drawable-xxxhdpi/icon.png]...  
[Info] copy file[E:/work_wkz/yg_h5_v2/unpackage/cache/wgt/__UNI__2DB9CA0/.manifest/icon-android-xxxhdpi.png] to [C:/Users/FGHF/AppData/Local/HBuilder X/AndroidPackWork/cache/__UNI__2DB9CA0/packge_cache/__NONE__/__UNI__2DB9CA0_cm/res/drawable-xxxhdpi/icon.png] success.  
[Info] begin update files to apk...  
[Error] try compile package:0  
[Info] I: Using Apktool 2.4.1  
[Info] I: Copying C:\Users\FGHF\AppData\Local\HBuilder X\AndroidPackWork\cache\__UNI__2DB9CA0\packge_cache\__NONE__\__UNI__2DB9CA0_cm classes.dex file...  
[Info] I: Copying C:\Users\FGHF\AppData\Local\HBuilder X\AndroidPackWork\cache\__UNI__2DB9CA0\packge_cache\__NONE__\__UNI__2DB9CA0_cm classes2.dex file...  
[Info] I: Copying C:\Users\FGHF\AppData\Local\HBuilder X\AndroidPackWork\cache\__UNI__2DB9CA0\packge_cache\__NONE__\__UNI__2DB9CA0_cm classes3.dex file...  
[Info] I: Checking whether resources has changed...  
[Info] I: Building resources...  
[Error] W: C:\Users\FGHF\AppData\Local\HBuilder X\AndroidPackWork\cache\__UNI__2DB9CA0\packge_cache\__NONE__\__UNI__2DB9CA0_cm\AndroidManifest.xml:48: Tag <action> attribute name has invalid character '*'.  
[Error] brut.androlib.AndrolibException: brut.common.BrutException: could not exec (exit code = 1): [C:\Users\FGHF\AppData\Local\Temp\brut_util_Jar_13841587595912769514.tmp, p, --forced-package-id, 127, --min-sdk-version, 19, --target-sdk-version, 28, --version-code, 32, --version-name, 3.1.0, --no-version-vectors, -F, C:\Users\FGHF\AppData\Local\Temp\APKTOOL640673907049858768.tmp, -e, C:\Users\FGHF\AppData\Local\Temp\APKTOOL11117073117559654397.tmp, -0, arsc, -I, C:\Users\FGHF\AppData\Local\HBuilder X\AndroidPackWork\apktool\1.apk, -S, C:\Users\FGHF\AppData\Local\HBuilder X\AndroidPackWork\cache\__UNI__2DB9CA0\packge_cache\__NONE__\__UNI__2DB9CA0_cm\res, -M, C:\Users\FGHF\AppData\Local\HBuilder X\AndroidPackWork\cache\__UNI__2DB9CA0\packge_cache\__NONE__\__UNI__2DB9CA0_cm\AndroidManifest.xml]  
[Error] try compile package:1  
[Info] I: Using Apktool 2.4.1  
[Info] I: Checking whether resources has changed...  
[Info] I: Building resources...  
[Error] W: C:\Users\FGHF\AppData\Local\HBuilder X\AndroidPackWork\cache\__UNI__2DB9CA0\packge_cache\__NONE__\__UNI__2DB9CA0_cm\AndroidManifest.xml:48: Tag <action> attribute name has invalid character '*'.  
[Error] brut.androlib.AndrolibException: brut.common.BrutException: could not exec (exit code = 1): [C:\Users\FGHF\AppData\Local\Temp\brut_util_Jar_1364484411461162108.tmp, p, --forced-package-id, 127, --min-sdk-version, 19, --target-sdk-version, 28, --version-code, 32, --version-name, 3.1.0, --no-version-vectors, -F, C:\Users\FGHF\AppData\Local\Temp\APKTOOL9195127418260236703.tmp, -e, C:\Users\FGHF\AppData\Local\Temp\APKTOOL13100643559112781843.tmp, -0, arsc, -I, C:\Users\FGHF\AppData\Local\HBuilder X\AndroidPackWork\apktool\1.apk, -S, C:\Users\FGHF\AppData\Local\HBuilder X\AndroidPackWork\cache\__UNI__2DB9CA0\packge_cache\__NONE__\__UNI__2DB9CA0_cm\res, -M, C:\Users\FGHF\AppData\Local\HBuilder X\AndroidPackWork\cache\__UNI__2DB9CA0\packge_cache\__NONE__\__UNI__2DB9CA0_cm\AndroidManifest.xml]  
[Error] try compile package:2  
[Info] I: Using Apktool 2.4.1  
[Info] I: Checking whether resources has changed...  
[Info] I: Building resources...  
[Error] W: C:\Users\FGHF\AppData\Local\HBuilder X\AndroidPackWork\cache\__UNI__2DB9CA0\packge_cache\__NONE__\__UNI__2DB9CA0_cm\AndroidManifest.xml:48: Tag <action> attribute name has invalid character '*'.  
[Error] brut.androlib.AndrolibException: brut.common.BrutException: could not exec (exit code = 1): [C:\Users\FGHF\AppData\Local\Temp\brut_util_Jar_8421301333268526767.tmp, p, --forced-package-id, 127, --min-sdk-version, 19, --target-sdk-version, 28, --version-code, 32, --version-name, 3.1.0, --no-version-vectors, -F, C:\Users\FGHF\AppData\Local\Temp\APKTOOL15143234046447346603.tmp, -e, C:\Users\FGHF\AppData\Local\Temp\APKTOOL5501540500666506840.tmp, -0, arsc, -I, C:\Users\FGHF\AppData\Local\HBuilder X\AndroidPackWork\apktool\1.apk, -S, C:\Users\FGHF\AppData\Local\HBuilder X\AndroidPackWork\cache\__UNI__2DB9CA0\packge_cache\__NONE__\__UNI__2DB9CA0_cm\res, -M, C:\Users\FGHF\AppData\Local\HBuilder X\AndroidPackWork\cache\__UNI__2DB9CA0\packge_cache\__NONE__\__UNI__2DB9CA0_cm\AndroidManifest.xml]  
[Error] Apk tool compile package to apk failed  
[Error] 制作结果:Failed.    Reason:   
收起阅读 »

解决cli项目下配合uniCloud开发时云函数上传下载过于麻烦的问题

uniCloud

一、要解决的问题
uniapp cli vue3 开发小程序时,大多采用的编辑器时vscode 如果此时同时使用unicloud,那么需要两个IDE来回切换,因此制作了一个小工具,原理上是对hbuilder cli 进行封装,从而可以更便捷的上传下载云函数
二、图片介绍

三、使用方法

务必将Hbuilder路径设在在环境变量内,否则会提示版本冲突

下载Releases内最符合您操作系统的版本,将uc.exe 文件放置在您的运行目录下即可使用如下方法进行操作

1. 列举资源信息

uc.exe -l [resource]uc.exe -l cf 表示:列举当前项目中所有云端函数

2. 资源上传[默认覆盖非跳过]

uc.exe -u [resource] [name]uc.exe -u cf test 表示:上传名称为test的云函数,如果云端存在同名函数则覆盖

3. 资源下载[默认覆盖非跳过]

uc.exe -d [resource] [name]uc.exe -d cf test 表示:下载名称为test的云函数,如果本地存在同名函数则覆盖

4. resource的取值如下

resource 含义
cf 或 cloudfunction 云函数
cm 或 common 云函数的公共模块
db 数据集合Schema
vf 数据库校验函数
ac 或 action 数据库触发条件
sp 或 space 云空间

四、链接下载地址
可以点击链接下载,或者下载附件内的压缩文件使用
github UC

继续阅读 »

一、要解决的问题
uniapp cli vue3 开发小程序时,大多采用的编辑器时vscode 如果此时同时使用unicloud,那么需要两个IDE来回切换,因此制作了一个小工具,原理上是对hbuilder cli 进行封装,从而可以更便捷的上传下载云函数
二、图片介绍

三、使用方法

务必将Hbuilder路径设在在环境变量内,否则会提示版本冲突

下载Releases内最符合您操作系统的版本,将uc.exe 文件放置在您的运行目录下即可使用如下方法进行操作

1. 列举资源信息

uc.exe -l [resource]uc.exe -l cf 表示:列举当前项目中所有云端函数

2. 资源上传[默认覆盖非跳过]

uc.exe -u [resource] [name]uc.exe -u cf test 表示:上传名称为test的云函数,如果云端存在同名函数则覆盖

3. 资源下载[默认覆盖非跳过]

uc.exe -d [resource] [name]uc.exe -d cf test 表示:下载名称为test的云函数,如果本地存在同名函数则覆盖

4. resource的取值如下

resource 含义
cf 或 cloudfunction 云函数
cm 或 common 云函数的公共模块
db 数据集合Schema
vf 数据库校验函数
ac 或 action 数据库触发条件
sp 或 space 云空间

四、链接下载地址
可以点击链接下载,或者下载附件内的压缩文件使用
github UC

收起阅读 »

关于动态取消checkbox选中状态代码实现~~~~~~

选择器 checkbox

记住文档的一句话:checkbox的checked只做默认选中状态功能
业务逻辑是,在列表中选择学生,但是选中人数有限定,当选中事件触发后,判断当前选中人数是否超过限定人数,如果有,则将新选中的学生取消选中
核心逻辑是:
记录前几次选中的ID,找出最新选中的下标,然后将列表list清空,并根据前几次选中情况重新赋值新变量存数组,并将前几次选中的学生赋值为选中状态,在页面渲染完成后,在赋值给list
相当于对list的指针做了修改,达到页面同步更新的目的

<template>  
    <view class="cflex content">  
        <view class="w100 p25 cflex aifs w100 info-box jcsb">  
            <view>{{info.title}}</view>  
            <view class="rflex jcfs info-status">  
                <view>状态:{{info.statusName}}</view>  
                <view class="split">|</view>  
                <view>可投票:{{info.voteNum}}人</view>  
            </view>  
        </view>  

        <view class="line"></view>  

        <view class="p30 w100">  
            <checkbox-group @change="checkStudent">  
                <template v-for="item in list">  
                    <label>  
                        <view class="w100 score-item p30 cflex jcsa aifs" :key="item.studentId">  
                            <view class="list-title rflex jcsb">  
                                <text>{{item.studentName}}</text>  
                                <checkbox :value="item.studentId.toString()" color="#fff" :checked="item.checked"/>  
                            </view>  
                            <view class="rflex aic w100">  
                                <view>姓名:{{item.studentName}}</view>  
                                <view class="split">|</view>  
                                <view>性别:{{item.sexName}}</view>  
                            </view>  
                        </view>       
                    </label>  
                </template>  
            </checkbox-group>  
        </view>  

        <view class="w100 cflex">  
            <view class="submit-btn cflex">  
                <text>继续学习</text>  
            </view>  
        </view>  
    </view>  
</template>  

<script>  
    import $cache from '@/common/js/cache.js';  
    import $studentTheory from '@/common/js/import/studentTheory.js';  
    export default {  
        onShow() {  
            let courseInfo = $cache.getCourseCache();  
            if (courseInfo) {  
                this.courseInfo = courseInfo;  
                let evaluationCache = $cache.getEvaluationCache();  
                if (evaluationCache) {  
                    this.info = evaluationCache;  
                    this.getDetail();  
                } else {  
                    this.$common.backTips('未找到互评信息');  
                }  
            } else {  
                this.$common.backTips('未找到课程信息');  
            }  
        },  
        data() {  
            return {  
                info: {},  
                courseInfo: {},  
                list: [],  
                submitData: {  
                    checkList: []  
                }  
            }  
        },  
        methods: {  
            getDetail() {  
                let statusConfig = {  
                    1: '未开始',  
                    2: '互评中',  
                    3: '已结束'  
                };  
                this.$common.loading();  
                $studentTheory.evaluationDetail({courseEvaluationId: this.info.courseEvaluationId}, (res) => {  
                    if (res.code == 200) {  
                        res.data.statusName = statusConfig[res.data.status] != null ? statusConfig[res.data.status] : '';  
                        this.info = Object.assign({}, this.info, res.data);  
                        this.$common.getDict('sex', (dict) => {  
                            $studentTheory.evaluationClassStudent({}, (res) => {  
                                this.$common.loading(false);  
                                if (res.code == 200) {  
                                    this.$each(res.data, (item) => {  
                                        item.checked = false;  
                                        item.sexName = dict[item.sex] ? dict[item.sex].dictValue : '';  
                                    });  
                                    this.list = res.data;  
                                } else {  
                                    this.$common.backTips(res.msg);  
                                }  
                            });  
                        });  
                    } else {  
                        this.$common.backTips(res.msg);  
                    }  
                });  
            },  
            checkStudent(val) {  
                let valArr = val.detail.value;  
                //取出新增项下标  
                let addIndex = -1;  
                if (valArr.length > this.submitData.checkList.length) {  
                    this.$each(valArr, (id, index) => {  
                        if (!this.submitData.checkList.includes(id)) {  
                            addIndex = index;  
                            return false;  
                        }  
                    });  
                }  

                let num = valArr.length;  
                if (num > this.info.voteNum) {  
                    let lastIndex = valArr.length - 1;  
                    valArr.splice(addIndex, 1); //删除新增项  
                    this.$common.tips('最多只能选择 ' + this.info.voteNum + ' 人');  
                    let tmpArr = Object.assign([], this.list);  
                    this.$each(tmpArr, (item, index) => {  
                        item.checked = false;  
                        if (valArr.includes(item.studentId.toString())) {  
                            item.checked = true;  
                        }  
                    });  
                    this.list = [];  
                    this.$nextTick(() => {  
                        this.list = tmpArr;  
                    });  
                }  
                this.submitData.checkList = valArr;  
            }  
        }  
    }  
</script>
继续阅读 »

记住文档的一句话:checkbox的checked只做默认选中状态功能
业务逻辑是,在列表中选择学生,但是选中人数有限定,当选中事件触发后,判断当前选中人数是否超过限定人数,如果有,则将新选中的学生取消选中
核心逻辑是:
记录前几次选中的ID,找出最新选中的下标,然后将列表list清空,并根据前几次选中情况重新赋值新变量存数组,并将前几次选中的学生赋值为选中状态,在页面渲染完成后,在赋值给list
相当于对list的指针做了修改,达到页面同步更新的目的

<template>  
    <view class="cflex content">  
        <view class="w100 p25 cflex aifs w100 info-box jcsb">  
            <view>{{info.title}}</view>  
            <view class="rflex jcfs info-status">  
                <view>状态:{{info.statusName}}</view>  
                <view class="split">|</view>  
                <view>可投票:{{info.voteNum}}人</view>  
            </view>  
        </view>  

        <view class="line"></view>  

        <view class="p30 w100">  
            <checkbox-group @change="checkStudent">  
                <template v-for="item in list">  
                    <label>  
                        <view class="w100 score-item p30 cflex jcsa aifs" :key="item.studentId">  
                            <view class="list-title rflex jcsb">  
                                <text>{{item.studentName}}</text>  
                                <checkbox :value="item.studentId.toString()" color="#fff" :checked="item.checked"/>  
                            </view>  
                            <view class="rflex aic w100">  
                                <view>姓名:{{item.studentName}}</view>  
                                <view class="split">|</view>  
                                <view>性别:{{item.sexName}}</view>  
                            </view>  
                        </view>       
                    </label>  
                </template>  
            </checkbox-group>  
        </view>  

        <view class="w100 cflex">  
            <view class="submit-btn cflex">  
                <text>继续学习</text>  
            </view>  
        </view>  
    </view>  
</template>  

<script>  
    import $cache from '@/common/js/cache.js';  
    import $studentTheory from '@/common/js/import/studentTheory.js';  
    export default {  
        onShow() {  
            let courseInfo = $cache.getCourseCache();  
            if (courseInfo) {  
                this.courseInfo = courseInfo;  
                let evaluationCache = $cache.getEvaluationCache();  
                if (evaluationCache) {  
                    this.info = evaluationCache;  
                    this.getDetail();  
                } else {  
                    this.$common.backTips('未找到互评信息');  
                }  
            } else {  
                this.$common.backTips('未找到课程信息');  
            }  
        },  
        data() {  
            return {  
                info: {},  
                courseInfo: {},  
                list: [],  
                submitData: {  
                    checkList: []  
                }  
            }  
        },  
        methods: {  
            getDetail() {  
                let statusConfig = {  
                    1: '未开始',  
                    2: '互评中',  
                    3: '已结束'  
                };  
                this.$common.loading();  
                $studentTheory.evaluationDetail({courseEvaluationId: this.info.courseEvaluationId}, (res) => {  
                    if (res.code == 200) {  
                        res.data.statusName = statusConfig[res.data.status] != null ? statusConfig[res.data.status] : '';  
                        this.info = Object.assign({}, this.info, res.data);  
                        this.$common.getDict('sex', (dict) => {  
                            $studentTheory.evaluationClassStudent({}, (res) => {  
                                this.$common.loading(false);  
                                if (res.code == 200) {  
                                    this.$each(res.data, (item) => {  
                                        item.checked = false;  
                                        item.sexName = dict[item.sex] ? dict[item.sex].dictValue : '';  
                                    });  
                                    this.list = res.data;  
                                } else {  
                                    this.$common.backTips(res.msg);  
                                }  
                            });  
                        });  
                    } else {  
                        this.$common.backTips(res.msg);  
                    }  
                });  
            },  
            checkStudent(val) {  
                let valArr = val.detail.value;  
                //取出新增项下标  
                let addIndex = -1;  
                if (valArr.length > this.submitData.checkList.length) {  
                    this.$each(valArr, (id, index) => {  
                        if (!this.submitData.checkList.includes(id)) {  
                            addIndex = index;  
                            return false;  
                        }  
                    });  
                }  

                let num = valArr.length;  
                if (num > this.info.voteNum) {  
                    let lastIndex = valArr.length - 1;  
                    valArr.splice(addIndex, 1); //删除新增项  
                    this.$common.tips('最多只能选择 ' + this.info.voteNum + ' 人');  
                    let tmpArr = Object.assign([], this.list);  
                    this.$each(tmpArr, (item, index) => {  
                        item.checked = false;  
                        if (valArr.includes(item.studentId.toString())) {  
                            item.checked = true;  
                        }  
                    });  
                    this.list = [];  
                    this.$nextTick(() => {  
                        this.list = tmpArr;  
                    });  
                }  
                this.submitData.checkList = valArr;  
            }  
        }  
    }  
</script>
收起阅读 »

有关uniapp的苹果应用内支付调起代码编写经验分享

IAP 苹果内购

官方文档在这里:苹果应用内支付
官方文档有一部分说的比较模糊不太详细,报错也是如此。因此这里说一下方便大家排雷避坑

  1. 申请流程

    • 要有苹果开发者账号这个就不说了
    • 要在appstoreconnect的应用的功能下面管理你的 App 内购项目,亲测添加后把信息填写完整并保存,只要状态为准备提交即可!
    • 把你添加的商品的产品 ID记下来一会要用到(下文称pid)
    • 你需要到苹果开发者中心证书管理生成一个iOS App Development类型的证书并生成p12文件和Profile页面生成mobileprovision文件,具体生成教程很多就不一一列举了。生成后打包个自定义基座就可以写代码了。

      注意!上述过程一个都不能漏

  2. 代码编写流程
    代码编写文档放在文章开头了,这里说一下调起顺序:

    • 使用uni.getProvider获取id为appleiap的channel,下文称该channel为支付通道。例:
      async getIAPChannel() {  
      return await new Promise((recv, recj) => {  
      uni.getProvider({  
          service: 'payment',  
          success: res => {  
              const iapChannel = res.providers.find(channel => {  
                  return channel.id === 'appleiap';  
              });  
              if (iapChannel) recv(iapChannel);  
              recj(new Error('当前环境不支持使用IAP支付!'));  
          }  
      });  
      });  
      }
    • 使用通道的requestProduct方法获取pid对应的商品信息。例:
      let orderDetail = await new Promise((recv, recj) => {  
      channel.requestProduct([pid], recv, recj);  
      });

      注意!!!这一步不能省略,否则会无法调起支付!!!

    • 使用uni.requestPayment调起支付。例:
      let transaction = await new Promise((recv, recj) => {  
      uni.requestPayment({  
      provider: 'appleiap',  
      orderInfo: {  
          productid: pid,  
          username: 'ORDERID' + orderid,  
          quantity: 1,  
          manualFinishTransaction: true  
      },  
      success: t => {  
          recv(t);  
      },  
      fail: e => {  
          recj(new Error('支付调起失败:Errcode ' + e.errCode + ',' + e.errMsg));  
      }  
      });  
      });
  3. 掉单处理
    官方文档中说明部分与实际demo代码不一致。这里说一下流程:

    • 使用uni.getProvider获取id为appleiap的channel,下文称该channel为支付通道。例同代码编写流程
    • 使用支付通道的restoreCompletedTransactions方法获取可能掉单的订单列表(支付时一定要设置manualFinishTransaction为true否则这里就找不到了)。例:
      let customOrderList = await new Promise((recv, recj) => {  
      channel.restoreCompletedTransactions({}, recv, e => { //第一个参数必传,否则会导致app闪退!  
      recj(new Error('获取未完成订单列表失败:Errcode ' + e.errCode + ',' + e.errMsg));  
      });  
      });
    • 处理掉单,掉单分几种情况,官方文档有介绍:
      const IapTransactionState = {  
      purchasing: "0", // 【应用商店正在处理的交易】A transaction that is being processed by the App Store.  
      purchased: "1", // 【一个成功处理的交易】A successfully processed transaction.  
      failed: "2", // 【一个失败的交易】A failed transaction.  
      restored: "3", // 【已经购买过该商品】A transaction that restores content previously purchased by the user.  
      deferred: "4" // 【在队列中,但其最终状态为等待外部操作(如“请求购买”)的交易】A transaction that is in the queue, but its final status is pending external action such as Ask to Buy.  
      };

      我只需要处理failed和purchased两种状态的交易即可,具体根据业务需求决定。例:

      for (let i = 0; i < customOrderList.length; i++) {  
      if (customOrderList[i].transactionState == 2) {  
      //关闭失败订单  
      for (let b = 0; b < 5; b++)  
          try {  
              await new Promise((recv, recj) => {  
                  channel.finishTransaction(transaction, recv, recj);  
              });  
              break;  
          } catch (e) {  
              console.log(e);  
          }  
      } else  
          await this.verifyTransactionReceipt(customOrderList[i]); //上报已经成功但服务器没有处理的订单(把transactionReceipt属性值发给服务端就行了),接收到服务器成功响应后再关闭订单。  
      }
继续阅读 »

官方文档在这里:苹果应用内支付
官方文档有一部分说的比较模糊不太详细,报错也是如此。因此这里说一下方便大家排雷避坑

  1. 申请流程

    • 要有苹果开发者账号这个就不说了
    • 要在appstoreconnect的应用的功能下面管理你的 App 内购项目,亲测添加后把信息填写完整并保存,只要状态为准备提交即可!
    • 把你添加的商品的产品 ID记下来一会要用到(下文称pid)
    • 你需要到苹果开发者中心证书管理生成一个iOS App Development类型的证书并生成p12文件和Profile页面生成mobileprovision文件,具体生成教程很多就不一一列举了。生成后打包个自定义基座就可以写代码了。

      注意!上述过程一个都不能漏

  2. 代码编写流程
    代码编写文档放在文章开头了,这里说一下调起顺序:

    • 使用uni.getProvider获取id为appleiap的channel,下文称该channel为支付通道。例:
      async getIAPChannel() {  
      return await new Promise((recv, recj) => {  
      uni.getProvider({  
          service: 'payment',  
          success: res => {  
              const iapChannel = res.providers.find(channel => {  
                  return channel.id === 'appleiap';  
              });  
              if (iapChannel) recv(iapChannel);  
              recj(new Error('当前环境不支持使用IAP支付!'));  
          }  
      });  
      });  
      }
    • 使用通道的requestProduct方法获取pid对应的商品信息。例:
      let orderDetail = await new Promise((recv, recj) => {  
      channel.requestProduct([pid], recv, recj);  
      });

      注意!!!这一步不能省略,否则会无法调起支付!!!

    • 使用uni.requestPayment调起支付。例:
      let transaction = await new Promise((recv, recj) => {  
      uni.requestPayment({  
      provider: 'appleiap',  
      orderInfo: {  
          productid: pid,  
          username: 'ORDERID' + orderid,  
          quantity: 1,  
          manualFinishTransaction: true  
      },  
      success: t => {  
          recv(t);  
      },  
      fail: e => {  
          recj(new Error('支付调起失败:Errcode ' + e.errCode + ',' + e.errMsg));  
      }  
      });  
      });
  3. 掉单处理
    官方文档中说明部分与实际demo代码不一致。这里说一下流程:

    • 使用uni.getProvider获取id为appleiap的channel,下文称该channel为支付通道。例同代码编写流程
    • 使用支付通道的restoreCompletedTransactions方法获取可能掉单的订单列表(支付时一定要设置manualFinishTransaction为true否则这里就找不到了)。例:
      let customOrderList = await new Promise((recv, recj) => {  
      channel.restoreCompletedTransactions({}, recv, e => { //第一个参数必传,否则会导致app闪退!  
      recj(new Error('获取未完成订单列表失败:Errcode ' + e.errCode + ',' + e.errMsg));  
      });  
      });
    • 处理掉单,掉单分几种情况,官方文档有介绍:
      const IapTransactionState = {  
      purchasing: "0", // 【应用商店正在处理的交易】A transaction that is being processed by the App Store.  
      purchased: "1", // 【一个成功处理的交易】A successfully processed transaction.  
      failed: "2", // 【一个失败的交易】A failed transaction.  
      restored: "3", // 【已经购买过该商品】A transaction that restores content previously purchased by the user.  
      deferred: "4" // 【在队列中,但其最终状态为等待外部操作(如“请求购买”)的交易】A transaction that is in the queue, but its final status is pending external action such as Ask to Buy.  
      };

      我只需要处理failed和purchased两种状态的交易即可,具体根据业务需求决定。例:

      for (let i = 0; i < customOrderList.length; i++) {  
      if (customOrderList[i].transactionState == 2) {  
      //关闭失败订单  
      for (let b = 0; b < 5; b++)  
          try {  
              await new Promise((recv, recj) => {  
                  channel.finishTransaction(transaction, recv, recj);  
              });  
              break;  
          } catch (e) {  
              console.log(e);  
          }  
      } else  
          await this.verifyTransactionReceipt(customOrderList[i]); //上报已经成功但服务器没有处理的订单(把transactionReceipt属性值发给服务端就行了),接收到服务器成功响应后再关闭订单。  
      }
收起阅读 »

希望能把打包功能和编辑器功能分开 或者专门出一个打包的工具

希望能把打包功能和编辑器功能分开 或者专门出一个打包的工具 不要每次更新 hbuilderx 就会要更新整包

希望能把打包功能和编辑器功能分开 或者专门出一个打包的工具 不要每次更新 hbuilderx 就会要更新整包

解决 HBuildeX 卡顿 和 打开慢 问题

定期清理标签页,标签页面越少越流畅

定期清理标签页,标签页面越少越流畅

关于iOS真机无法运行标准基座的公告

iOS

HBuilderX中自带的标准真机运行基座,使用DCloud向苹果申请的企业开发者证书签名,根据苹果开发者企业计划许可协议要求,使用企业开发者证书签名的App只允许企业员工内部使用,不允许企业外部人员安装使用。

因收到苹果公司警告,目前开发者已无法在iOS真机设备使用标准运行基座。(Mac电脑中的iOS模拟器中还可以继续使用标准基座,它不限制企业证书使用)

在HBuilderX 3.6.1及更低版本:

  • iOS真机运行时会提示以下错误:

  • 已经安装基座的iOS真机设备运行时会提示以下错误:

HBuilder3.6.2起,错误提示已经改善。

解决方案
方案一
如果要在真机设备使用,开发者需要自己向苹果申请Development证书,重签标准基座(需HBuilderX 3.7+),或使用自己的证书打包自定义基座,参考:https://uniapp.dcloud.net.cn/tutorial/run/run-app.html

如何申请开发(Development)证书和描述文件,请参考:https://ask.dcloud.net.cn/article/152

方案二
在Mac电脑中安装XCode,使用iOS模拟器真机运行,参考:iOS设备选择-使用iOS模拟器

此限制不影响其他范围,不影响使用开发者自己的证书打包发布应用。

继续阅读 »

HBuilderX中自带的标准真机运行基座,使用DCloud向苹果申请的企业开发者证书签名,根据苹果开发者企业计划许可协议要求,使用企业开发者证书签名的App只允许企业员工内部使用,不允许企业外部人员安装使用。

因收到苹果公司警告,目前开发者已无法在iOS真机设备使用标准运行基座。(Mac电脑中的iOS模拟器中还可以继续使用标准基座,它不限制企业证书使用)

在HBuilderX 3.6.1及更低版本:

  • iOS真机运行时会提示以下错误:

  • 已经安装基座的iOS真机设备运行时会提示以下错误:

HBuilder3.6.2起,错误提示已经改善。

解决方案
方案一
如果要在真机设备使用,开发者需要自己向苹果申请Development证书,重签标准基座(需HBuilderX 3.7+),或使用自己的证书打包自定义基座,参考:https://uniapp.dcloud.net.cn/tutorial/run/run-app.html

如何申请开发(Development)证书和描述文件,请参考:https://ask.dcloud.net.cn/article/152

方案二
在Mac电脑中安装XCode,使用iOS模拟器真机运行,参考:iOS设备选择-使用iOS模拟器

此限制不影响其他范围,不影响使用开发者自己的证书打包发布应用。

收起阅读 »

uniapp 与webview传参 uni.webview.1.5.4.js

Webview uniapp

版本
uni.webview.1.5.4.js 版本 1.5.4
https://uniapp.dcloud.net.cn/component/web-view.html#
https://gitee.com/dcloud/uni-app/raw/dev/dist/uni.webview.1.5.4.js

传参:
vue中。监听。
可以写在app.vue中。需要用的地方监听。

// #ifdef APP-PLUS  
            //  app 用于监听 验证码webview 事件  
            plus.globalEvent.addEventListener('plusMessage', (msg) => {  
                const result = msg.data.args.data;  
                if(result.name == 'postMessage'){  
                    console.log('postMessage', msg)  
                    uni.$emit('webviewCode', msg);  
                }  
            })

webview中。

// 待触发 `UniAppJSBridgeReady` 事件后,即可调用 uni 的 API。  
document.addEventListener('UniAppJSBridgeReady', function() {  
uni.webView.getEnv(function(res) {  
console.log('当前环境:' + JSON.stringify(res));  
if (res.h5) {  
//$('body').css('background-color', 'transparent');  
//  h5  
h5Ready();  
} else if (res.plus) {  
// 通知到APP  
//  此处理是关键, 传参内容入在 对象的data属性中。  
uni.postMessage({data: {status: 1}});  
//if(window.plus){  
//     plusReady();  
//}else{  
//    document.addEventListener('plusready', plusReady, false);  
// }  
}  
});  
});

sdk原码

继续阅读 »

版本
uni.webview.1.5.4.js 版本 1.5.4
https://uniapp.dcloud.net.cn/component/web-view.html#
https://gitee.com/dcloud/uni-app/raw/dev/dist/uni.webview.1.5.4.js

传参:
vue中。监听。
可以写在app.vue中。需要用的地方监听。

// #ifdef APP-PLUS  
            //  app 用于监听 验证码webview 事件  
            plus.globalEvent.addEventListener('plusMessage', (msg) => {  
                const result = msg.data.args.data;  
                if(result.name == 'postMessage'){  
                    console.log('postMessage', msg)  
                    uni.$emit('webviewCode', msg);  
                }  
            })

webview中。

// 待触发 `UniAppJSBridgeReady` 事件后,即可调用 uni 的 API。  
document.addEventListener('UniAppJSBridgeReady', function() {  
uni.webView.getEnv(function(res) {  
console.log('当前环境:' + JSON.stringify(res));  
if (res.h5) {  
//$('body').css('background-color', 'transparent');  
//  h5  
h5Ready();  
} else if (res.plus) {  
// 通知到APP  
//  此处理是关键, 传参内容入在 对象的data属性中。  
uni.postMessage({data: {status: 1}});  
//if(window.plus){  
//     plusReady();  
//}else{  
//    document.addEventListener('plusready', plusReady, false);  
// }  
}  
});  
});

sdk原码

收起阅读 »

app-vue + html2canvas + renderjs 页面保存图片模糊问题分享

不废话,上代码

<view id="inviter_hb" class="bg-img">  
        <img class="bg" :src="bgImg" mode="scaleToFill"></img>  
        <view class="qr-box">  
          <img class="qr-img" :src="qrImg" mode="scaleToFill"></img>  
        </view>  
        <text class="inviter-code">邀请码:<text>{{inviteCode}}</text></text>  
</view>

以下是renderjs 代码,可以在此操作dom

<script module="html2canvas" lang="renderjs">  
  // import html2canvas from '@/common/html2canvas.min.js'  
  export default {  
    mounted() {  
      if (!window.html2canvas) {  
        // 动态引入较大类库避免影响页面展示  
        const script = document.createElement('script')  
        // view 层的页面运行在 www 根目录,其相对路径相对于 www 计算  
        script.src = 'static/js/html2canvas.min.js'  
        document.head.appendChild(script)  
      }  
    },  
    methods: {  
      createInviter(event, ownerInstance) {  
        ownerInstance.callMethod('showLoading')  
        const inviterDom = document.getElementById("inviter_hb")  
        html2canvas(inviterDom, {  
          width: inviterDom.scrollWidth,  
          height: inviterDom.scrollHeight,  
          allowTaint: true,  
          useCORS: true,  
          scale: 3,  
          dpi: 300  
        }).then(canvas => {  
          ownerInstance.callMethod('renderFinish', canvas.toDataURL('image/jpeg', 1.0))  
        }).catch(error => {  
          console.log(error)  
          ownerInstance.callMethod('hideLoading')  
        })  
      }  
    }  
  }  
</script>

重要:页面中图片不能用background-image设置,也不能用image标签设置,需要用img,否则生成的图片还是模糊

继续阅读 »

不废话,上代码

<view id="inviter_hb" class="bg-img">  
        <img class="bg" :src="bgImg" mode="scaleToFill"></img>  
        <view class="qr-box">  
          <img class="qr-img" :src="qrImg" mode="scaleToFill"></img>  
        </view>  
        <text class="inviter-code">邀请码:<text>{{inviteCode}}</text></text>  
</view>

以下是renderjs 代码,可以在此操作dom

<script module="html2canvas" lang="renderjs">  
  // import html2canvas from '@/common/html2canvas.min.js'  
  export default {  
    mounted() {  
      if (!window.html2canvas) {  
        // 动态引入较大类库避免影响页面展示  
        const script = document.createElement('script')  
        // view 层的页面运行在 www 根目录,其相对路径相对于 www 计算  
        script.src = 'static/js/html2canvas.min.js'  
        document.head.appendChild(script)  
      }  
    },  
    methods: {  
      createInviter(event, ownerInstance) {  
        ownerInstance.callMethod('showLoading')  
        const inviterDom = document.getElementById("inviter_hb")  
        html2canvas(inviterDom, {  
          width: inviterDom.scrollWidth,  
          height: inviterDom.scrollHeight,  
          allowTaint: true,  
          useCORS: true,  
          scale: 3,  
          dpi: 300  
        }).then(canvas => {  
          ownerInstance.callMethod('renderFinish', canvas.toDataURL('image/jpeg', 1.0))  
        }).catch(error => {  
          console.log(error)  
          ownerInstance.callMethod('hideLoading')  
        })  
      }  
    }  
  }  
</script>

重要:页面中图片不能用background-image设置,也不能用image标签设置,需要用img,否则生成的图片还是模糊

收起阅读 »

做了一个uni-app版的vconsole,可以查看network。并且支持在每个页面自动插入自定义组件

调试

微信小程序在开发环境是自带vconsole的,但是不支持network面板,要看网络请求还需要抓包,所以我自己做了一个调试工具,支持network面板。

另外,小程序是没有开放根节点的,因此在开发的过程中解决了在各个页面自动插入组件的问题,发现有些人也是有这个场景的,因此把这个功能也暴露了出来。

直接使用可以直接看npm包的说明:uni-devtool

原理说明可以看这里:定制一个小程序版的vconsole

插件效果预览

继续阅读 »

微信小程序在开发环境是自带vconsole的,但是不支持network面板,要看网络请求还需要抓包,所以我自己做了一个调试工具,支持network面板。

另外,小程序是没有开放根节点的,因此在开发的过程中解决了在各个页面自动插入组件的问题,发现有些人也是有这个场景的,因此把这个功能也暴露了出来。

直接使用可以直接看npm包的说明:uni-devtool

原理说明可以看这里:定制一个小程序版的vconsole

插件效果预览

收起阅读 »