HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

基于mui+vue框架的跨平台客户端app---购物商城

Vue

涉及到知识点:

  • vue.js
  • 首页底部Tab
  • banner广告
  • 沉浸式状态栏
  • 分类顶部切换的slider
  • 图片预览并保存到相册
  • 拍照、扫码、选择本地文件
  • 侧滑栏
  • 搜索列表页两层侧滑栏筛选
  • 上拉刷新下拉加载
  • 登录、退出逻辑处理

主要功能点:
首页商品展示,商品分类,搜索列表,商品详情,购物车,下订单,个人中心,我的订单/退单,收货地址管理

示例图片

常见问题

  • 首页底部的Tab
    html页面
<nav class="mui-bar mui-bar-tab footerBar">  
    <a id="defaultTab" class="mui-tab-item ft-tab-item mui-active" href="home.html">  
        <span class=" mui-icon iconfont icon-m-ao"></span>  
        <span class="mui-tab-label">首页</span>  
    </a>  
    <a id="tab1" class="mui-tab-item ft-tab-item" href="classify.html">  
        <span class="mui-icon iconfont icon-m-am"></span>  
        <span class="mui-tab-label">分类</span>  
    </a>  
    <a id="tab2" class="mui-tab-item ft-tab-item" href="cart.html">  
        <span class="mui-icon iconfont icon-m-y"></span>  
        <span class="mui-tab-label">购物车</span>  
    </a>  
    <a id="tab3" class="mui-tab-item ft-tab-item" href="my.html">  
        <span class="mui-icon iconfont icon-m-aq"></span>  
        <span class="mui-tab-label">我的</span>  
    </a>  
</nav>

js代码

//底部选项卡切换跳转  
(function jumpPage() {  
    //跳转页面  
    var subpages = ['view/home/home.html', 'view/home/classify.html', 'view/home/cart.html', 'view/home/my.html'];  
    var ids = ['home.html', 'classify.html', 'cart.html', 'my.html'];  
    var aniShow = {};  
    //创建子页面,首个选项卡页面显示,其它均隐藏;  
    mui.plusReady(function() {  
        plus.screen.lockOrientation("portrait-primary");   
        var subpage_style = {  
            top: '0px',  
            bottom: '51px'  
        };  
        //设置bottom绝对位置  
        //iphoneX中出现遮挡底部tab现象,采用js判断屏幕大小方式改变bottom值  
        //isIPhoneX() 要在plusReady后调用  
        if (isIPhoneX()) {  
            subpage_style = {  
                top: '0px',  
                bottom: '88px', //34px  
                styles: {  
                    "render": "always",  
                }  
            };  
        }  
        var self = plus.webview.currentWebview();  
        for (var i = 0; i < 4; i++) {  
            var temp = {};  
            var sub = plus.webview.create(subpages[i], ids[i], subpage_style);  
            if (i > 0) {  
                sub.hide();  
            } else {  
                temp[ids[i]] = "true";  
                mui.extend(aniShow, temp);  
            }  
            self.append(sub);  
        }  
    });  
    //当前激活选项  
    var activeTab = ids[0];  

    //选项卡点击事件  
    mui('.mui-bar-tab').on('tap', 'a', function(e) {  
        e.preventDefault();  
        var targetTab = this.getAttribute('href');  
        if (targetTab == activeTab) {  
            return;  
        }  
        //显示目标选项卡  
        //若为iOS平台或非首次显示,则直接显示  
        if (mui.os.ios || aniShow[targetTab]) {  
            plus.webview.show(targetTab);  
        } else {  
            //否则,使用fade-in动画,且保存变量  
            var temp = {};  
            temp[targetTab] = "true";  
            mui.extend(aniShow, temp);  
            plus.webview.show(targetTab, "fade-in", 300);  
        }  
        //隐藏当前;  
        plus.webview.hide(activeTab);  
        //更改当前活跃的选项卡  
        activeTab = targetTab;  
    });  
})()  

注意iphoneX中出现遮挡底部tab现象,采用js判断屏幕大小方式改变bottom值,isIPhoneX(),isIPhoneX() 要在plusReady后调用。

  • 沉浸式状态栏

1.在manifest.json文件,切换到代码视图,在plus -> statusbar 下添加immersed节点并设置值为true

"statusbar" : {  
    "immersed" : true  
},

2.设置了沉浸式状态栏后,状态栏的高度变为0,如图所示
https://github.com/gs-wenbing/mui-mall/blob/master/img/show/status1.jpg
输入框把状态挡住了,这时候需要重写mui.css或者mui.min.css样式表,在样式表底部添加如下一段样式

*解决沉寖式状态栏导致导航栏高度少20px的问题*/  
.mui-bar-nav {  
    height: 64px;  
    padding-top: 22px;  
}  

.mui-bar-nav ~ .mui-content  
{  
    padding-top: 64px;  
}  

.mui-bar-nav ~ .mui-content .mui-pull-top-pocket  
{  
    top: 64px;  
}  

.mui-bar-nav ~ .mui-content.mui-scroll-wrapper .mui-scrollbar-vertical  
{  
    top: 64px;  
}  

.mui-bar-nav ~ .mui-content .mui-slider.mui-fullscreen  
{  
    top: 64px;  
}

显示效果如图
https://github.com/gs-wenbing/mui-mall/blob/master/img/show/status2.jpg
注意:以上操作后Android沉浸式状态就完成了,但是IOS还需在distribute节点下的apple节点下添加

"UIReserveStatusbarOffset" : false

本以为沉浸式状态栏就完成了,结果老板iPhoneX手机显示有问题,于是又单独适配iPhoneX,具体操作:
在mui.js或者mui.min.js中底部添加如下一段代码

/**  
 * 适配iPhone X 系列手机的导航栏(包括状态栏)  
 */  
mui.plusReady(function(){  
    if(plus.navigator.isImmersedStatusbar() && isIPhoneX()){  
        //.mui-bar-nav  
        var nav = document.querySelector(".mui-bar-nav");  
        if(nav){  
            nav.style.cssText="height:88px; padding-top: 44px;";  
        } else {  
            return;  
        }  
        //.mui-bar-nav ~ .mui-content  
        var content = document.querySelector(".mui-content");  
        if (content) {  
            content.style.paddingTop = "88px";  
        } else {  
            return;  
        }  
        //.mui-bar-nav ~ .mui-content .mui-pull-top-pocket  
        var pullTopPocket_Arr = content.querySelectorAll(".mui-pull-top-pocket");  
        if (pullTopPocket_Arr) {  
            pullTopPocket_Arr.forEach(function(value){  
                value.style.top = "88px";  
            });  
        }  
        //.mui-bar-nav ~ .mui-content.mui-scroll-wrapper .mui-scrollbar-vertical  
        var scrollbarVertical = document.querySelector(".mui-content.mui-scroll-wrapper .mui-scrollbar-vertical");  
        if (scrollbarVertical) {  
            scrollbarVertical.style.top = "88px";  
        }  
        //.mui-bar-nav ~ .mui-content .mui-slider.mui-fullscreen  
        var slider_fullscreen_Arr = content.querySelectorAll(".mui-content .mui-slider.mui-fullscreen");  
        if (slider_fullscreen_Arr) {  
            slider_fullscreen_Arr.forEach(function(value){  
                value.style.top = "88px";  
            });  
        }  
    }  
});  

/**  
 * 判断是否为iPhone X 系列机型  
 */  
function isIPhoneX(){  
    if(plus.device.model.indexOf('iPhone') > -1 && screen.height >= 812){  
        return true;  
    }else{  
        return false;  
    }  
}
  • 标题栏在IOS上存在的问题
    原生标题栏
var styles = { // 窗口参数 参考5+规范中的WebviewStyle,也就是说WebviewStyle下的参数都可以在此设置  
    titleNView: { // 窗口的标题栏控件  
        titleText: title, // 标题栏文字,当不设置此属性时,默认加载当前页面的标题,并自动更新页面的标题  
        titleColor: "#FFFFFF", // 字体颜色,颜色值格式为"#RRGGBB",默认值为"#000000"  
        backgroundColor: "#E60012", // 控件背景颜色,颜色值格式为"#RRGGBB",默认值为"#F7F7F7"  
        autoBackButton: true  
    }  
}  
plus.webview.create(url, id, styles, extras);

1.mui原生标题栏,假如titleColor的值为小写(#ffffff)的话,在IOS上不显示标题,必须要大写(#FFFFFF)才显示,亲测<br>
2.非原生标题栏,假如页面中有输入框的话,软键盘弹出,IOS上会把标题栏顶上去,因为ios弹出软键盘的时候,webview的高度没有变化导致超出屏幕范围,
而plus这时候又会自动把header的 position:fixed 属性设置为 position:relative,header就跟着滚动了。在mui社区找到一个的解决方案:

http://ask.dcloud.net.cn/question/10629

plus.webview.currentWebview().setStyle({    
    softinputMode: "adjustResize"  // 弹出软键盘时自动改变webview的高度    
});   

html, body {    
    height: 100%;    
    margin: 0px;    
    padding: 0px;    
    overflow: hidden;    
    -webkit-touch-callout: none;    
    -webkit-user-select: none;    
}    

.mui-content {    
    height: 100%;    
    overflow: auto;     
}   

这样会解决IOS标题栏顶上去的问题,但是这样处理后,页面打开标题栏会有抖动,虽然很短暂,但是看着不爽,有更好的方案可以联系我(QQ742330561)

具体的demo地址: https://github.com/gs-wenbing/mui-mall
如果对您有帮助,您可以"Star" 支持一下 谢谢! ^^

继续阅读 »

涉及到知识点:

  • vue.js
  • 首页底部Tab
  • banner广告
  • 沉浸式状态栏
  • 分类顶部切换的slider
  • 图片预览并保存到相册
  • 拍照、扫码、选择本地文件
  • 侧滑栏
  • 搜索列表页两层侧滑栏筛选
  • 上拉刷新下拉加载
  • 登录、退出逻辑处理

主要功能点:
首页商品展示,商品分类,搜索列表,商品详情,购物车,下订单,个人中心,我的订单/退单,收货地址管理

示例图片

常见问题

  • 首页底部的Tab
    html页面
<nav class="mui-bar mui-bar-tab footerBar">  
    <a id="defaultTab" class="mui-tab-item ft-tab-item mui-active" href="home.html">  
        <span class=" mui-icon iconfont icon-m-ao"></span>  
        <span class="mui-tab-label">首页</span>  
    </a>  
    <a id="tab1" class="mui-tab-item ft-tab-item" href="classify.html">  
        <span class="mui-icon iconfont icon-m-am"></span>  
        <span class="mui-tab-label">分类</span>  
    </a>  
    <a id="tab2" class="mui-tab-item ft-tab-item" href="cart.html">  
        <span class="mui-icon iconfont icon-m-y"></span>  
        <span class="mui-tab-label">购物车</span>  
    </a>  
    <a id="tab3" class="mui-tab-item ft-tab-item" href="my.html">  
        <span class="mui-icon iconfont icon-m-aq"></span>  
        <span class="mui-tab-label">我的</span>  
    </a>  
</nav>

js代码

//底部选项卡切换跳转  
(function jumpPage() {  
    //跳转页面  
    var subpages = ['view/home/home.html', 'view/home/classify.html', 'view/home/cart.html', 'view/home/my.html'];  
    var ids = ['home.html', 'classify.html', 'cart.html', 'my.html'];  
    var aniShow = {};  
    //创建子页面,首个选项卡页面显示,其它均隐藏;  
    mui.plusReady(function() {  
        plus.screen.lockOrientation("portrait-primary");   
        var subpage_style = {  
            top: '0px',  
            bottom: '51px'  
        };  
        //设置bottom绝对位置  
        //iphoneX中出现遮挡底部tab现象,采用js判断屏幕大小方式改变bottom值  
        //isIPhoneX() 要在plusReady后调用  
        if (isIPhoneX()) {  
            subpage_style = {  
                top: '0px',  
                bottom: '88px', //34px  
                styles: {  
                    "render": "always",  
                }  
            };  
        }  
        var self = plus.webview.currentWebview();  
        for (var i = 0; i < 4; i++) {  
            var temp = {};  
            var sub = plus.webview.create(subpages[i], ids[i], subpage_style);  
            if (i > 0) {  
                sub.hide();  
            } else {  
                temp[ids[i]] = "true";  
                mui.extend(aniShow, temp);  
            }  
            self.append(sub);  
        }  
    });  
    //当前激活选项  
    var activeTab = ids[0];  

    //选项卡点击事件  
    mui('.mui-bar-tab').on('tap', 'a', function(e) {  
        e.preventDefault();  
        var targetTab = this.getAttribute('href');  
        if (targetTab == activeTab) {  
            return;  
        }  
        //显示目标选项卡  
        //若为iOS平台或非首次显示,则直接显示  
        if (mui.os.ios || aniShow[targetTab]) {  
            plus.webview.show(targetTab);  
        } else {  
            //否则,使用fade-in动画,且保存变量  
            var temp = {};  
            temp[targetTab] = "true";  
            mui.extend(aniShow, temp);  
            plus.webview.show(targetTab, "fade-in", 300);  
        }  
        //隐藏当前;  
        plus.webview.hide(activeTab);  
        //更改当前活跃的选项卡  
        activeTab = targetTab;  
    });  
})()  

注意iphoneX中出现遮挡底部tab现象,采用js判断屏幕大小方式改变bottom值,isIPhoneX(),isIPhoneX() 要在plusReady后调用。

  • 沉浸式状态栏

1.在manifest.json文件,切换到代码视图,在plus -> statusbar 下添加immersed节点并设置值为true

"statusbar" : {  
    "immersed" : true  
},

2.设置了沉浸式状态栏后,状态栏的高度变为0,如图所示
https://github.com/gs-wenbing/mui-mall/blob/master/img/show/status1.jpg
输入框把状态挡住了,这时候需要重写mui.css或者mui.min.css样式表,在样式表底部添加如下一段样式

*解决沉寖式状态栏导致导航栏高度少20px的问题*/  
.mui-bar-nav {  
    height: 64px;  
    padding-top: 22px;  
}  

.mui-bar-nav ~ .mui-content  
{  
    padding-top: 64px;  
}  

.mui-bar-nav ~ .mui-content .mui-pull-top-pocket  
{  
    top: 64px;  
}  

.mui-bar-nav ~ .mui-content.mui-scroll-wrapper .mui-scrollbar-vertical  
{  
    top: 64px;  
}  

.mui-bar-nav ~ .mui-content .mui-slider.mui-fullscreen  
{  
    top: 64px;  
}

显示效果如图
https://github.com/gs-wenbing/mui-mall/blob/master/img/show/status2.jpg
注意:以上操作后Android沉浸式状态就完成了,但是IOS还需在distribute节点下的apple节点下添加

"UIReserveStatusbarOffset" : false

本以为沉浸式状态栏就完成了,结果老板iPhoneX手机显示有问题,于是又单独适配iPhoneX,具体操作:
在mui.js或者mui.min.js中底部添加如下一段代码

/**  
 * 适配iPhone X 系列手机的导航栏(包括状态栏)  
 */  
mui.plusReady(function(){  
    if(plus.navigator.isImmersedStatusbar() && isIPhoneX()){  
        //.mui-bar-nav  
        var nav = document.querySelector(".mui-bar-nav");  
        if(nav){  
            nav.style.cssText="height:88px; padding-top: 44px;";  
        } else {  
            return;  
        }  
        //.mui-bar-nav ~ .mui-content  
        var content = document.querySelector(".mui-content");  
        if (content) {  
            content.style.paddingTop = "88px";  
        } else {  
            return;  
        }  
        //.mui-bar-nav ~ .mui-content .mui-pull-top-pocket  
        var pullTopPocket_Arr = content.querySelectorAll(".mui-pull-top-pocket");  
        if (pullTopPocket_Arr) {  
            pullTopPocket_Arr.forEach(function(value){  
                value.style.top = "88px";  
            });  
        }  
        //.mui-bar-nav ~ .mui-content.mui-scroll-wrapper .mui-scrollbar-vertical  
        var scrollbarVertical = document.querySelector(".mui-content.mui-scroll-wrapper .mui-scrollbar-vertical");  
        if (scrollbarVertical) {  
            scrollbarVertical.style.top = "88px";  
        }  
        //.mui-bar-nav ~ .mui-content .mui-slider.mui-fullscreen  
        var slider_fullscreen_Arr = content.querySelectorAll(".mui-content .mui-slider.mui-fullscreen");  
        if (slider_fullscreen_Arr) {  
            slider_fullscreen_Arr.forEach(function(value){  
                value.style.top = "88px";  
            });  
        }  
    }  
});  

/**  
 * 判断是否为iPhone X 系列机型  
 */  
function isIPhoneX(){  
    if(plus.device.model.indexOf('iPhone') > -1 && screen.height >= 812){  
        return true;  
    }else{  
        return false;  
    }  
}
  • 标题栏在IOS上存在的问题
    原生标题栏
var styles = { // 窗口参数 参考5+规范中的WebviewStyle,也就是说WebviewStyle下的参数都可以在此设置  
    titleNView: { // 窗口的标题栏控件  
        titleText: title, // 标题栏文字,当不设置此属性时,默认加载当前页面的标题,并自动更新页面的标题  
        titleColor: "#FFFFFF", // 字体颜色,颜色值格式为"#RRGGBB",默认值为"#000000"  
        backgroundColor: "#E60012", // 控件背景颜色,颜色值格式为"#RRGGBB",默认值为"#F7F7F7"  
        autoBackButton: true  
    }  
}  
plus.webview.create(url, id, styles, extras);

1.mui原生标题栏,假如titleColor的值为小写(#ffffff)的话,在IOS上不显示标题,必须要大写(#FFFFFF)才显示,亲测<br>
2.非原生标题栏,假如页面中有输入框的话,软键盘弹出,IOS上会把标题栏顶上去,因为ios弹出软键盘的时候,webview的高度没有变化导致超出屏幕范围,
而plus这时候又会自动把header的 position:fixed 属性设置为 position:relative,header就跟着滚动了。在mui社区找到一个的解决方案:

http://ask.dcloud.net.cn/question/10629

plus.webview.currentWebview().setStyle({    
    softinputMode: "adjustResize"  // 弹出软键盘时自动改变webview的高度    
});   

html, body {    
    height: 100%;    
    margin: 0px;    
    padding: 0px;    
    overflow: hidden;    
    -webkit-touch-callout: none;    
    -webkit-user-select: none;    
}    

.mui-content {    
    height: 100%;    
    overflow: auto;     
}   

这样会解决IOS标题栏顶上去的问题,但是这样处理后,页面打开标题栏会有抖动,虽然很短暂,但是看着不爽,有更好的方案可以联系我(QQ742330561)

具体的demo地址: https://github.com/gs-wenbing/mui-mall
如果对您有帮助,您可以"Star" 支持一下 谢谢! ^^

收起阅读 »

支付宝小程序分享后,用户通过分享的应用点进来之后,tabbar消失。

支付宝小程序分享后,用户通过分享的应用点进来之后,tabbar消失。
第二个图是分享后用户点进来的页面!
第三个是正常的!

支付宝小程序分享后,用户通过分享的应用点进来之后,tabbar消失。
第二个图是分享后用户点进来的页面!
第三个是正常的!

Windows电脑直接申请iOS证书p12及.mobileprovision文件过程

5+App开发 iOS打包

Appuploader软件可以辅助在Windows电脑直接申请iOS证书,并且可以上传ipa到App Store审核!

对于没有Mac电脑的开发者,是一个很好的iOS上架辅助工具

下面介绍申请一套iOS开发证书申请及在HBuilder平台打包的过程!

一、下载安装iOS证书申请及上架辅助软件Appuploader

Appuploader下载链接

下载软件包后解压直接使用,无需安装。

二、登录Appuploader申请iOS证书文件p12

2.1打开Appuploader,用苹果开发者账号登录进去。

2.2、选择证书项目进入

2.3、点击右下角+ADD选择

类型:选择开发证书(开发证书打包的才能安装到手机)

输入证书名称:不要中文、随意设置

邮箱:(随意)

密码:证书的密码,不是开发者账号密码,如123这样不用很复杂,记好、打包时要用、很重要。

应用id:这里不用选!

点击ok创建。

注意:iOS开发证书最多能申请2个,如果账号已经有2个开发证书了,将申请报错(如下图)。

一个开发证书可以用于多个APP测试,不用每个app都对应申请个开发证书p12,开发证书p12是可以公用的,区分开下面步骤申请的的描述文件就行了,一个p12可以对应无数描述文件。

当然也可以删除之前的重新申请

2.4、创建成功后,找到刚创建的iOS开发证书(iOS Development这个类型的就是开发证书,如果之前创建过看过期时间就知道哪个是新创建的了),点击p12 文件,下载保存.p12证书文件到电脑。

三、登录Appuploader申请iOS开发证书描述文件mobileprovision

iOS证书文件有两个,刚申请了p12文件,接下来申请mobileprovision描述文件

在申请ios描述文件之前,先添加好两样基础信息,应用id及苹果手机设备号udid

如果你之前添加过应用id和设备,应用id点击三角符号下拉可以看到,设备选择开发版profile选项可以看到之前添加的。

如果之前加过要用以前加过的应用id,跳过此步。

初次使用或者需要上架另一个项目app需要先创建一个应用id及添加相关的开发测试人员设备。

3.1、先介绍添加应用ID,点击添加应用id!(不同的APP需要编写不同的应用id相当于app的身份证)

应用id:三段式格式、如app名称是淘宝,可以编写为com.app.taobao,自由编写!不能重复!具有唯一性@

名称:数字或者字母,自由编写,不要中文,不能重复。

如果添加报错(应用id具有唯一性,可能重复添加或者别人用过这个应用id),解决办法就是修改下应用id,重新编下。

在AU软件添加的应用id只有默认权限,如果你需要开通推送通知、苹果支付等权限需要到苹果开发者中心配置!

苹果开发者中心添加应用id配置相关权限教程

点击ok只要没弹出报错就是添加成功了,注意先关掉窗口,重新点右下角+ADD进入下拉应用id可查看刚添加的应用id是否存在。

填加好应用id下步添加设置udid

3.2 添加测试设备udid

要安装到哪个苹果手机测试就添加哪个手机的udid,添加了udid的手机才能安装,最多能加100个!

如果你以前加过udid,不需要加新的测试苹果手机上去,跳过此步,直接申请证书!

先来获取udid

udid如果你的手机链接了电脑并且安装好了相关驱动,AU软件会自动获取,直接点ok添加就行了

其他两种获取udid的方式

3.3、苹果手机助手获取UDID

如爱思助手,电脑下载爱思助手,连上苹果手机,设备信息里面那个设备标识就是udid。

3.4、扫码获取苹果手机udid

使用 iPhone 或 iPad 微信扫码选择自带的浏览器safari浏览器打开二维码里的链接,即可快速获取 UDID

这个一长串的就是设备的Udid

udid实例:2D4B87350609342980CB144F72FD2E66B66AEF6C

获取到udid输入

名称name:数字或者名字,自由编写,不能重复,不能中文。

点击ok无提示即为添加成功

在苹果开发中心也可以添加设备,在AU软件添加的设备在开发者中心也会有显示。

苹果开发者中心添加udid设备教程

如果苹果设备udid添加报错(如下图),检查这个udid是否正确或者之前添加过!

Type选择开发版即可看到刚添加的设备。

3.5、勾选相关信息生成ios开发证书描述文件

Type:选择开发版profile(安装到手机测试需要开发证书)

应用id:选择添加的对应的应用id

Devices:勾选需要测试苹果手机

名称:数字或者字母,随意输入,注意不要重复。

点击ok创建

3.6、选择刚创建的iOS开发版描述文件(iOS Developer这个类型的就是开发描述文件,找到刚创建的输入的名字),点击Download下载,保存到电脑

申请得到了两个iOS证书文件p12和.mobileprovision就可以去打包ipa了。

打开HBuilder工具,选择开发好的项目,点击发行,选择发行为原生安装包。

AppID:跟申请证书描述.mobileprovision时选择的要一致(又称套装id,appid,BundleID,应用id,包名)

profile文件:选择上传配置文件.mobileprovision

私钥证书:上传.p12文件

私钥密码:输入创建p12设置的密码。

然后点击打包。

(​

打包成功后,下载保存ipa,这个ipa包就能安装到手机测试了。

​(​

继续阅读 »

Appuploader软件可以辅助在Windows电脑直接申请iOS证书,并且可以上传ipa到App Store审核!

对于没有Mac电脑的开发者,是一个很好的iOS上架辅助工具

下面介绍申请一套iOS开发证书申请及在HBuilder平台打包的过程!

一、下载安装iOS证书申请及上架辅助软件Appuploader

Appuploader下载链接

下载软件包后解压直接使用,无需安装。

二、登录Appuploader申请iOS证书文件p12

2.1打开Appuploader,用苹果开发者账号登录进去。

2.2、选择证书项目进入

2.3、点击右下角+ADD选择

类型:选择开发证书(开发证书打包的才能安装到手机)

输入证书名称:不要中文、随意设置

邮箱:(随意)

密码:证书的密码,不是开发者账号密码,如123这样不用很复杂,记好、打包时要用、很重要。

应用id:这里不用选!

点击ok创建。

注意:iOS开发证书最多能申请2个,如果账号已经有2个开发证书了,将申请报错(如下图)。

一个开发证书可以用于多个APP测试,不用每个app都对应申请个开发证书p12,开发证书p12是可以公用的,区分开下面步骤申请的的描述文件就行了,一个p12可以对应无数描述文件。

当然也可以删除之前的重新申请

2.4、创建成功后,找到刚创建的iOS开发证书(iOS Development这个类型的就是开发证书,如果之前创建过看过期时间就知道哪个是新创建的了),点击p12 文件,下载保存.p12证书文件到电脑。

三、登录Appuploader申请iOS开发证书描述文件mobileprovision

iOS证书文件有两个,刚申请了p12文件,接下来申请mobileprovision描述文件

在申请ios描述文件之前,先添加好两样基础信息,应用id及苹果手机设备号udid

如果你之前添加过应用id和设备,应用id点击三角符号下拉可以看到,设备选择开发版profile选项可以看到之前添加的。

如果之前加过要用以前加过的应用id,跳过此步。

初次使用或者需要上架另一个项目app需要先创建一个应用id及添加相关的开发测试人员设备。

3.1、先介绍添加应用ID,点击添加应用id!(不同的APP需要编写不同的应用id相当于app的身份证)

应用id:三段式格式、如app名称是淘宝,可以编写为com.app.taobao,自由编写!不能重复!具有唯一性@

名称:数字或者字母,自由编写,不要中文,不能重复。

如果添加报错(应用id具有唯一性,可能重复添加或者别人用过这个应用id),解决办法就是修改下应用id,重新编下。

在AU软件添加的应用id只有默认权限,如果你需要开通推送通知、苹果支付等权限需要到苹果开发者中心配置!

苹果开发者中心添加应用id配置相关权限教程

点击ok只要没弹出报错就是添加成功了,注意先关掉窗口,重新点右下角+ADD进入下拉应用id可查看刚添加的应用id是否存在。

填加好应用id下步添加设置udid

3.2 添加测试设备udid

要安装到哪个苹果手机测试就添加哪个手机的udid,添加了udid的手机才能安装,最多能加100个!

如果你以前加过udid,不需要加新的测试苹果手机上去,跳过此步,直接申请证书!

先来获取udid

udid如果你的手机链接了电脑并且安装好了相关驱动,AU软件会自动获取,直接点ok添加就行了

其他两种获取udid的方式

3.3、苹果手机助手获取UDID

如爱思助手,电脑下载爱思助手,连上苹果手机,设备信息里面那个设备标识就是udid。

3.4、扫码获取苹果手机udid

使用 iPhone 或 iPad 微信扫码选择自带的浏览器safari浏览器打开二维码里的链接,即可快速获取 UDID

这个一长串的就是设备的Udid

udid实例:2D4B87350609342980CB144F72FD2E66B66AEF6C

获取到udid输入

名称name:数字或者名字,自由编写,不能重复,不能中文。

点击ok无提示即为添加成功

在苹果开发中心也可以添加设备,在AU软件添加的设备在开发者中心也会有显示。

苹果开发者中心添加udid设备教程

如果苹果设备udid添加报错(如下图),检查这个udid是否正确或者之前添加过!

Type选择开发版即可看到刚添加的设备。

3.5、勾选相关信息生成ios开发证书描述文件

Type:选择开发版profile(安装到手机测试需要开发证书)

应用id:选择添加的对应的应用id

Devices:勾选需要测试苹果手机

名称:数字或者字母,随意输入,注意不要重复。

点击ok创建

3.6、选择刚创建的iOS开发版描述文件(iOS Developer这个类型的就是开发描述文件,找到刚创建的输入的名字),点击Download下载,保存到电脑

申请得到了两个iOS证书文件p12和.mobileprovision就可以去打包ipa了。

打开HBuilder工具,选择开发好的项目,点击发行,选择发行为原生安装包。

AppID:跟申请证书描述.mobileprovision时选择的要一致(又称套装id,appid,BundleID,应用id,包名)

profile文件:选择上传配置文件.mobileprovision

私钥证书:上传.p12文件

私钥密码:输入创建p12设置的密码。

然后点击打包。

(​

打包成功后,下载保存ipa,这个ipa包就能安装到手机测试了。

​(​

收起阅读 »

Android平台App支持CPU类型配置说明

Android

此文档不再维护,请参考新文档地址:https://uniapp.dcloud.io/tutorial/app-android-abifilters

HBuilderX2.7.0+ 调整 云端打包默认不再包含 x86 CPU类型库,减少apk包体积 <a href="https://ask.dcloud.net.cn/article/36195#nox86" target="_self">详情</a>

HBuilderX2.1.5+ 开始支持Android平台的新增适配64位CPU类型,云端打包支持配置App支持的CPU类型
满足Google Play从2019年8月1日起上传的App必需支持64位CPU的要求。
不管5+App、wap2app、uni-app的各种编译模式,均已支持。
上一代HBuilder不支持,请升级更新为HBuilderX。

概要

Android平台配置CPU类型针对的是为了提高运行效率使用C/C++语言开发生成的so库,需要为各cpu类型平台单独编译生成对应指令的so库。Java语言开发的代码运行在虚拟机中,由虚拟机适配CPU类型,不涉及到此问题。

HBuilder/HBuilderX中使用so库的功能(模块)

  • Audio(录音):支持mp3格式
  • Geolocation(定位):百度
  • LivePush(直播推流)
  • Maps(地图):高德、百度
  • OAuth(登录鉴权):新浪微博
  • Push(消息推送):个推、UniPush
  • Share(分享):新浪微博
  • Speech(语音输入):百度,注意:讯飞不支持64位
  • Weex(原生渲染):uni-app(自定义组件模式、nvue页面), 注意:HBuilderX2.1.5及以上版本支持

CPU类型

目前HBuilder/HBulderX适配支持以下主流CPU类型:

  • armeabi-v7a
    第7代及以上的ARM处理器(ARM32位),市面上大多数手机使用此CPU类型。
  • arm64-v8a
    第8代、64位ARM处理器(ARM64位),最近两年新发的设备使用此CPU类型,可以兼容使用armeabi-v7a的so库。
  • x86
    少部分平板使用x86,AS模拟器中选了intel x86时使用x86处理器,以及其它常用三方模拟器通常使用x86

注意:不勾选x86在模拟器上可能无法正常运行,以下是常见模拟器是否需要包含x86的情况

  • 雷电模拟器:
    3.x必须包含x86,否则无法正常运行;4.x无需包含x86。
  • 夜神模拟器:
    必须包含x86,否则无法正常运行
  • MuMu模拟器:
    无需包含x86
  • 逍遥模拟器:
    无需包含x86
  • BlueStacks(蓝叠模拟器):
    无需包含x86
  • 腾讯模拟器(手游助手):
    必须包含x86,否则无法正常运行
  • 其它模拟器:
    未测试验证,建议包含x86,确保在模拟器正常运行

云端打包配置

可视化界面配置

打开项目的manifest.json文件,切换到“App常用其它设置”页面,勾选需要支持的cpu类型:

源码视图配置

打开项目的manifest.json文件,切换到“源码视图”
5+APP项目在 "plus" -> "distribute" -> "google" -> "abiFilters",uni-app项目在 "app-plus" -> "distribute" -> "android" -> "abiFilters" 添加要支持的CPU类型:

"abiFilters":[  
    "armeabi-v7a",  
    "arm64-v8a"  
],

在上面节点配置需要支持的cpu类型,不需要的删除即可,不设置此字段则默认使用armeabi-v7a(ARM32位)

提交Google Play时要求支持64位,请选择"armeabi-v7a"、"arm64-v8a"两个即可。不要勾选“x86”
保存后提交云端打包生效。

查看打包后插件apk支持的CPU类型

使用解压工具打开apk,在lib目录下可以查看到支持的CPU类型,如下图所示:

离线打包配置

配置CPU类型

使用Android studio打开Android原生项目,打开对应项目的build.gradle文件。
在Android -> defaultConfig下添加支持的CPU类型,如下示例:

defaultConfig{  
    ndk {  
            abiFilters 'arm64-v8a','armeabi-v7a'  
        }  
}

注意:离线打包仅支持arm64-v8a、armeabi-v7a、x86三种类型,建议根据自己需求选择打包的CPU类型

CPU类型选择建议

ARM64位(arm64-v8a)CPU可以兼容ARM32的指令,也就是说只选择armeabi-v7a类型的so库也可以在64位手机上运行,只是没有完全发挥CPU的性能。
选择支持的CPU类型时请参考以下建议:

  • 如果不在意apk大小,三种CPU类型都勾选
  • 如果在意apk大小,选择ARM32位即可(几乎在所有ARM指令的所有设备上都可正常运行)
  • 如果要兼容一些平板和模拟器,选择ARM32位和X86
    不是所有模拟都仅支持x86指令,如雷电(4.x)、MuMu等模拟器也是支持ARM指令。

CPU类型错误安装提示

如果打包选择的CPU类型与设备不兼容,会导致无法正常安装。
通过adb命令安装通常会提示如下错误:

Performing Streamed Install  
adb: failed to install android_debug.apk: Failure [INSTALL_FAILED_NO_MATCHING_ABIS: Failed to extract native libraries, res=-113]

使用Android Studio自带的x86模拟器,将不包含x86 cpu类型的apk拖到模拟器安装时会弹出如下提示框:

<a id="nox86"/>

HBuilderx2.7.0+ 云端打包默认CPU类型不再包含x86

目前市面上常见的手机都是使用ARM处理器,很少有设备使用x86处理器,因此从HBuilderX2.7.0开始云端打包调整为默认不再包含x86的CPU类型,减少apk包大小:

  • uni-app项目
    基础功能apk减少5M+,使用的三方SDK及uni原生插件越多,减少的包尺寸越大,具体值取决于其包含的x86类型的so库大小
  • 5+App、Wap2App项目
    基础功能apk减少100K+,如果使用的三方SDK中存在so库则减少的尺寸较大,具体值取决于其包含的x86类型的so库大小

注意:大多数模拟器(如夜神)必须包含x86,否则应用启动时可能会白屏,请参考上面“配置CPU类型”章节勾选“x86”

继续阅读 »

此文档不再维护,请参考新文档地址:https://uniapp.dcloud.io/tutorial/app-android-abifilters

HBuilderX2.7.0+ 调整 云端打包默认不再包含 x86 CPU类型库,减少apk包体积 <a href="https://ask.dcloud.net.cn/article/36195#nox86" target="_self">详情</a>

HBuilderX2.1.5+ 开始支持Android平台的新增适配64位CPU类型,云端打包支持配置App支持的CPU类型
满足Google Play从2019年8月1日起上传的App必需支持64位CPU的要求。
不管5+App、wap2app、uni-app的各种编译模式,均已支持。
上一代HBuilder不支持,请升级更新为HBuilderX。

概要

Android平台配置CPU类型针对的是为了提高运行效率使用C/C++语言开发生成的so库,需要为各cpu类型平台单独编译生成对应指令的so库。Java语言开发的代码运行在虚拟机中,由虚拟机适配CPU类型,不涉及到此问题。

HBuilder/HBuilderX中使用so库的功能(模块)

  • Audio(录音):支持mp3格式
  • Geolocation(定位):百度
  • LivePush(直播推流)
  • Maps(地图):高德、百度
  • OAuth(登录鉴权):新浪微博
  • Push(消息推送):个推、UniPush
  • Share(分享):新浪微博
  • Speech(语音输入):百度,注意:讯飞不支持64位
  • Weex(原生渲染):uni-app(自定义组件模式、nvue页面), 注意:HBuilderX2.1.5及以上版本支持

CPU类型

目前HBuilder/HBulderX适配支持以下主流CPU类型:

  • armeabi-v7a
    第7代及以上的ARM处理器(ARM32位),市面上大多数手机使用此CPU类型。
  • arm64-v8a
    第8代、64位ARM处理器(ARM64位),最近两年新发的设备使用此CPU类型,可以兼容使用armeabi-v7a的so库。
  • x86
    少部分平板使用x86,AS模拟器中选了intel x86时使用x86处理器,以及其它常用三方模拟器通常使用x86

注意:不勾选x86在模拟器上可能无法正常运行,以下是常见模拟器是否需要包含x86的情况

  • 雷电模拟器:
    3.x必须包含x86,否则无法正常运行;4.x无需包含x86。
  • 夜神模拟器:
    必须包含x86,否则无法正常运行
  • MuMu模拟器:
    无需包含x86
  • 逍遥模拟器:
    无需包含x86
  • BlueStacks(蓝叠模拟器):
    无需包含x86
  • 腾讯模拟器(手游助手):
    必须包含x86,否则无法正常运行
  • 其它模拟器:
    未测试验证,建议包含x86,确保在模拟器正常运行

云端打包配置

可视化界面配置

打开项目的manifest.json文件,切换到“App常用其它设置”页面,勾选需要支持的cpu类型:

源码视图配置

打开项目的manifest.json文件,切换到“源码视图”
5+APP项目在 "plus" -> "distribute" -> "google" -> "abiFilters",uni-app项目在 "app-plus" -> "distribute" -> "android" -> "abiFilters" 添加要支持的CPU类型:

"abiFilters":[  
    "armeabi-v7a",  
    "arm64-v8a"  
],

在上面节点配置需要支持的cpu类型,不需要的删除即可,不设置此字段则默认使用armeabi-v7a(ARM32位)

提交Google Play时要求支持64位,请选择"armeabi-v7a"、"arm64-v8a"两个即可。不要勾选“x86”
保存后提交云端打包生效。

查看打包后插件apk支持的CPU类型

使用解压工具打开apk,在lib目录下可以查看到支持的CPU类型,如下图所示:

离线打包配置

配置CPU类型

使用Android studio打开Android原生项目,打开对应项目的build.gradle文件。
在Android -> defaultConfig下添加支持的CPU类型,如下示例:

defaultConfig{  
    ndk {  
            abiFilters 'arm64-v8a','armeabi-v7a'  
        }  
}

注意:离线打包仅支持arm64-v8a、armeabi-v7a、x86三种类型,建议根据自己需求选择打包的CPU类型

CPU类型选择建议

ARM64位(arm64-v8a)CPU可以兼容ARM32的指令,也就是说只选择armeabi-v7a类型的so库也可以在64位手机上运行,只是没有完全发挥CPU的性能。
选择支持的CPU类型时请参考以下建议:

  • 如果不在意apk大小,三种CPU类型都勾选
  • 如果在意apk大小,选择ARM32位即可(几乎在所有ARM指令的所有设备上都可正常运行)
  • 如果要兼容一些平板和模拟器,选择ARM32位和X86
    不是所有模拟都仅支持x86指令,如雷电(4.x)、MuMu等模拟器也是支持ARM指令。

CPU类型错误安装提示

如果打包选择的CPU类型与设备不兼容,会导致无法正常安装。
通过adb命令安装通常会提示如下错误:

Performing Streamed Install  
adb: failed to install android_debug.apk: Failure [INSTALL_FAILED_NO_MATCHING_ABIS: Failed to extract native libraries, res=-113]

使用Android Studio自带的x86模拟器,将不包含x86 cpu类型的apk拖到模拟器安装时会弹出如下提示框:

<a id="nox86"/>

HBuilderx2.7.0+ 云端打包默认CPU类型不再包含x86

目前市面上常见的手机都是使用ARM处理器,很少有设备使用x86处理器,因此从HBuilderX2.7.0开始云端打包调整为默认不再包含x86的CPU类型,减少apk包大小:

  • uni-app项目
    基础功能apk减少5M+,使用的三方SDK及uni原生插件越多,减少的包尺寸越大,具体值取决于其包含的x86类型的so库大小
  • 5+App、Wap2App项目
    基础功能apk减少100K+,如果使用的三方SDK中存在so库则减少的尺寸较大,具体值取决于其包含的x86类型的so库大小

注意:大多数模拟器(如夜神)必须包含x86,否则应用启动时可能会白屏,请参考上面“配置CPU类型”章节勾选“x86”

收起阅读 »

用wap2app在线视频网站打包播放视频没有全屏的已经解决

视频全屏

1、网站打包方法请参加这个网址
http://ask.dcloud.net.cn/docs/#//ask.dcloud.net.cn/article/13425。
2、在线视频网站打包后,有一些视频格式可以全屏观看的例;MP4,其它格式就不可以全屏了
我这个方法只能全屏,其它操作不行。


红色圈那个是全屏。

播放器增加全屏操作方法
1、在你的网站空间找到,播放器文件,js/player/有很多播放文件。你随便打一个播放文件找到下面这段代码。

跟你代码对比后发现了这个代码: allowfullscreen="allowfullscreen" 复制到你播放文件后,再刷新一下就可以了。

如果不明白操作的,加我的QQ370413232 备注播放器全屏,不备注不通过。

继续阅读 »

1、网站打包方法请参加这个网址
http://ask.dcloud.net.cn/docs/#//ask.dcloud.net.cn/article/13425。
2、在线视频网站打包后,有一些视频格式可以全屏观看的例;MP4,其它格式就不可以全屏了
我这个方法只能全屏,其它操作不行。


红色圈那个是全屏。

播放器增加全屏操作方法
1、在你的网站空间找到,播放器文件,js/player/有很多播放文件。你随便打一个播放文件找到下面这段代码。

跟你代码对比后发现了这个代码: allowfullscreen="allowfullscreen" 复制到你播放文件后,再刷新一下就可以了。

如果不明白操作的,加我的QQ370413232 备注播放器全屏,不备注不通过。

收起阅读 »

IMEI获得不到

IMEI

请下载hbuilder alpha 版本。

请下载hbuilder alpha 版本。

uni-app使用vue-i18n实现小程序端国际化

多语言 国际化 微信小程序 i18n
  1. vue-i18n的基础写法就不详细说明了,相关文档请到官网查看
  2. 官网的文档真的写的还不错,闲话少说直接上代码,我将主要代码分成三个文件,index.js,zh-CHS.js,en-US.js:

index.js主代码

import Vue from 'vue'  
import VueI18n from 'vue-i18n'  

Vue.use(VueI18n)  

// 国际化  
const i18n = new VueI18n({  
  locale: 'zh-CHS', // set locale  
  messages: {  
    'zh-CHS': require('./zh-CHS.js'), // 中文语言包  
    'en-US': require('./en-US.js') // 英文语言包  
  },  
  silentTranslationWarn: true  
})  

// window.i18n = i18n  

export default i18n

中文zh-CHS.js

module.exports = {  
  "home": "首页"  
}

英文en-US.js
module.exports = {
"home": "Home"
}

  1. 在mian.js中引用
    
    import Vue from 'vue'  
    import App from 'App'  
    import i18n from './language'  

Vue.config.productionTip = false
Vue.prototype.i18n = i18n

App.mpType = 'app'

const app = new Vue({
i18n,
...App
})
app.$mount()

4. 使用过vue-i18n的同学都知道,使用i18n-loader用于单文件或组件中,自定义块的语言环境信息,而uni-app并不支持  
无奈要实现国际化,读了vue-i18n-loader的源码,但其中并没有时间部分,只有对不同文件的解析,官网也没有详细写,然后只能自己实现了,悲催呀!  
各种摩擦,整了一天终于出来了,还是官网一个例子给我了启发:  
官网例子:
```javascript  
new Vue({  
  i18n: new VueI18n({  
    locale: 'en',  
    messages: {  
      en: { hello: 'hi there!' },  
      ja: { hello: 'こんにちは!' }  
    }  
  }),  
  data: { path: 'hello' }  
}).$mount('#string-syntax')

根据这个实现了功能。

5.最终实现代码我放github上,地址https://github.com/sjpeter/web/blob/master/vue/i18n/main.vue

  1. 这种写法应该也适用于其他端,但为测试。
继续阅读 »
  1. vue-i18n的基础写法就不详细说明了,相关文档请到官网查看
  2. 官网的文档真的写的还不错,闲话少说直接上代码,我将主要代码分成三个文件,index.js,zh-CHS.js,en-US.js:

index.js主代码

import Vue from 'vue'  
import VueI18n from 'vue-i18n'  

Vue.use(VueI18n)  

// 国际化  
const i18n = new VueI18n({  
  locale: 'zh-CHS', // set locale  
  messages: {  
    'zh-CHS': require('./zh-CHS.js'), // 中文语言包  
    'en-US': require('./en-US.js') // 英文语言包  
  },  
  silentTranslationWarn: true  
})  

// window.i18n = i18n  

export default i18n

中文zh-CHS.js

module.exports = {  
  "home": "首页"  
}

英文en-US.js
module.exports = {
"home": "Home"
}

  1. 在mian.js中引用
    
    import Vue from 'vue'  
    import App from 'App'  
    import i18n from './language'  

Vue.config.productionTip = false
Vue.prototype.i18n = i18n

App.mpType = 'app'

const app = new Vue({
i18n,
...App
})
app.$mount()

4. 使用过vue-i18n的同学都知道,使用i18n-loader用于单文件或组件中,自定义块的语言环境信息,而uni-app并不支持  
无奈要实现国际化,读了vue-i18n-loader的源码,但其中并没有时间部分,只有对不同文件的解析,官网也没有详细写,然后只能自己实现了,悲催呀!  
各种摩擦,整了一天终于出来了,还是官网一个例子给我了启发:  
官网例子:
```javascript  
new Vue({  
  i18n: new VueI18n({  
    locale: 'en',  
    messages: {  
      en: { hello: 'hi there!' },  
      ja: { hello: 'こんにちは!' }  
    }  
  }),  
  data: { path: 'hello' }  
}).$mount('#string-syntax')

根据这个实现了功能。

5.最终实现代码我放github上,地址https://github.com/sjpeter/web/blob/master/vue/i18n/main.vue

  1. 这种写法应该也适用于其他端,但为测试。
收起阅读 »

Android离线打包 高德地图配置经验分享

请先按照附件中的图片修改好

注意application节点里面配置时 附件图片里面不是太清楚 这里我给贴出一个完整的节点配置

application节点下配置实例

<meta-data android:name="com.amap.api.v2.apikey" android:value="52488663213be8ssssf3247b47924916" />  
<service android:name="com.amap.api.location.APSService"></service>

最重要一步配置,官方没有贴出来

修改build.gradle文件(app目录下),加入如下两句话:
implementation(name: 'amap-libs-release', ext: 'aar')
implementation(name: 'geolocation-amap-release', ext: 'aar')

最后请离线打包测试看是否能够正常获取定位点。如果有问题请提问,大家一起交流,仅限高德地图离线配置的问题。
本人本着无私分享的精神,如果不能解决你的问题 请不要生气。


附件图片在这里

继续阅读 »

请先按照附件中的图片修改好

注意application节点里面配置时 附件图片里面不是太清楚 这里我给贴出一个完整的节点配置

application节点下配置实例

<meta-data android:name="com.amap.api.v2.apikey" android:value="52488663213be8ssssf3247b47924916" />  
<service android:name="com.amap.api.location.APSService"></service>

最重要一步配置,官方没有贴出来

修改build.gradle文件(app目录下),加入如下两句话:
implementation(name: 'amap-libs-release', ext: 'aar')
implementation(name: 'geolocation-amap-release', ext: 'aar')

最后请离线打包测试看是否能够正常获取定位点。如果有问题请提问,大家一起交流,仅限高德地图离线配置的问题。
本人本着无私分享的精神,如果不能解决你的问题 请不要生气。


附件图片在这里

收起阅读 »

App发起微信支付

App 微信支付
1.测试微信支付时使用自定义基座,不然会提示包名和微信开放平台中配置的包名不一致(我这里是这样)
2.uni-app发起支付方法requestPayment中有一个参数provider,参数值是固定的,微信支付就是wxpay,需要在manifest.json中进行参数配置,在发起支付的时候打印一下,看一下是否是wxpay,如果没有获取到就会报支付模块不存在
3.orderInfo是一个字符串,uni-app的文档没有说什么内容,json中的值都是从后台获取,微信支付文档上有其含义app支付,尤其注意Json中的package和参数package的值不是一样的,json中是固定格式,参数package中的值是prepay_id=xx
uni.requestPayment({  
                                                provider: c.provider[0],  
                                                orderInfo: JSON.stringify({  
                                                    appid: res.data.map.appid,  
                                                    noncestr: res.data.map.nonceStr,  
                                                    package:"Sign=WXPay",  
                                                    partnerid: res.data.map.partnerid,  
                                                    prepayid: res.data.map.prepayid,  
                                                    timestamp: res.data.map.timeStamp,  
                                                    sign: res.data.map.paySign,  
                                                }),  
                                                timeStamp: JSON.stringify(res.data.map.timeStamp),  
                                                nonceStr: res.data.map.nonceStr,  
                                                package: res.data.map.package,  
                                                signType: res.data.map.signType,  

                                                success: (res) => {  

                                                },  
                                                fail: (res) => {  

                                                }  
                                            })
继续阅读 »
1.测试微信支付时使用自定义基座,不然会提示包名和微信开放平台中配置的包名不一致(我这里是这样)
2.uni-app发起支付方法requestPayment中有一个参数provider,参数值是固定的,微信支付就是wxpay,需要在manifest.json中进行参数配置,在发起支付的时候打印一下,看一下是否是wxpay,如果没有获取到就会报支付模块不存在
3.orderInfo是一个字符串,uni-app的文档没有说什么内容,json中的值都是从后台获取,微信支付文档上有其含义app支付,尤其注意Json中的package和参数package的值不是一样的,json中是固定格式,参数package中的值是prepay_id=xx
uni.requestPayment({  
                                                provider: c.provider[0],  
                                                orderInfo: JSON.stringify({  
                                                    appid: res.data.map.appid,  
                                                    noncestr: res.data.map.nonceStr,  
                                                    package:"Sign=WXPay",  
                                                    partnerid: res.data.map.partnerid,  
                                                    prepayid: res.data.map.prepayid,  
                                                    timestamp: res.data.map.timeStamp,  
                                                    sign: res.data.map.paySign,  
                                                }),  
                                                timeStamp: JSON.stringify(res.data.map.timeStamp),  
                                                nonceStr: res.data.map.nonceStr,  
                                                package: res.data.map.package,  
                                                signType: res.data.map.signType,  

                                                success: (res) => {  

                                                },  
                                                fail: (res) => {  

                                                }  
                                            })
收起阅读 »

安卓原生与uniapp 互相通讯 (原生发送数据给uniapp uniapp 发送数据给原生)

uniapp

首先我们要写三个java (类|接口)

1 .事件对象 用来传递数据的

package test;  

public class MyEvent  {  

    //数据  
    private  Object data;  

    //事件来源  字符串  
    private  String source;  

    //触发对象  
    private  Object  trigger;  

    public MyEvent(Object data) {  
        this.data = data;  
    }  

    public Object getTrigger() {  
        return trigger;  
    }  

    @Override  
    public String toString() {  
        return "MyEvent{" +  
                "data=" + data +  
                ", source='" + source + '\'' +  
                ", trigger=" + trigger +  
                '}';  
    }  

    public void setTrigger(Object trigger) {  
        this.trigger = trigger;  
    }  

    public MyEvent(Object data, String source) {  
        this.data = data;  
        this.source = source;  
    }  

    public MyEvent() {  
    }  

    public Object getData() {  
        return data;  
    }  

    public void setData(Object data) {  
        this.data = data;  
    }  

    public String getSource() {  
        return source;  
    }  

    public void setSource(String source) {  
        this.source = source;  
    }  
}  

2.监听器接口


package test;  

import java.util.EventListener;  

public  interface MyListener  extends EventListener {  
     void onChange(MyEvent myEvent);  
}  

3.事件管理器 用来 处理 事件 与通知 监听器


package test;  

import android.util.Log;  

import java.util.*;  

/***  
 * 自定义事件管理类  
 */  
public class MyEventManager {  
    private static MyEventManager myEventManager;  

    private Map<String, Collection<MyListener>> listeners;  

    /**  
     * 不能外部 new  实例化  
     */  
    private MyEventManager() {  
        this.listeners = new HashMap<String, Collection<MyListener>>();  
    }  

    /**  
     * 返回监听 总数  
     *  
     * @return  
     */  

    public int getSize() {  
        int size = 0;  
        for (String str : listeners.keySet()) {  
            size = size + listeners.get(str).size();  

        }  
        return size;  
    }  

    public Map<String, Collection<MyListener>> getListeners() {  

        return listeners;  
    }  

    /**  
     * 单例模式  
     *  
     * @return  
     */  
    public static MyEventManager getMyEventManager() {  
        if (myEventManager == null) {  
            synchronized (MyEventManager.class) {  
                if (myEventManager == null) {  
                    myEventManager = new MyEventManager();  
                }  
            }  
        }  
        return myEventManager;  
    }  

    /***  
     * 添加事件  
     * @param listener    事件对象  
     * @param source      来源  
     */  
    public MyListener addListener(MyListener listener, String source) {  

        if (listener != null && source != null) {  
            Collection<MyListener> myListeners = listeners.get(source);  

            if (myListeners == null) {  
                myListeners = new HashSet<MyListener>();  
                listeners.put(source, myListeners);  
            }  
            myListeners.add(listener);  

        }  
        return listener;  

    }  

    /***  
     * 添加事件  
     *  @param source      来源  
     * @param listener    事件对象  
     */  
    public MyListener addListener(String source, MyListener listener) {  

        return addListener(listener, source);  

    }  

    /**  
     * 移除监听  
     *  
     * @param listener  
     */  
    public void removeListener(MyListener listener) {  
        if (listeners == null || listener == null) {  
            return;  
        }  

        //变量所有  找出相同的  删除  
        for (String str : listeners.keySet()) {  

            Collection collection = listeners.get(str);  
            Iterator<MyListener> iter = collection.iterator();  
            while (iter.hasNext()) {  
                MyListener next = (MyListener) iter.next();  
                if (next == listener) {  
                    collection.remove(next);  
                    return;  
                }  
            }  
        }  

    }  

    /***  
     *   发送数据  
     * @param data   数据  
     * @param source 来源  
     * @return  
     */  
    public static MyEvent postMsg(Object data, String source) {  
        MyEventManager myEventManager = MyEventManager.getMyEventManager();  
        MyEvent myEvent = new MyEvent(data);  
        myEvent.setSource(source);  
        if (myEventManager.listeners == null)  

            return myEvent;  
        myEventManager.notifyListeners(myEvent, myEvent.getSource());  
        return myEvent;  
    }  

    /**  
     * 通知所有的myListener    相同的 (source) 来源才通知  
     */  
    private void notifyListeners(MyEvent event, String source) {  

        //取出  key为source 的  监听器集合  
        Collection<MyListener> collection = listeners.get(source);  

        Log.i(MyEventManager.class.getName(), source + "--->" + event.getData());  

        if (collection == null) {  
            return;  
        }  
        //遍历监听器集合  
        Iterator<MyListener> iter = collection.iterator();  
        while (iter.hasNext()) {  
            MyListener next = iter.next();  
            //通知回调  
            next.onChange(event);  
        }  

        //销毁事件对象  
        event = null;  
    }  

}  

uniapp 添加监听器 监听 原生或者 uniapp 其他页面 传过来的数据


this.myEventManager = plus.android.importClass("test.MyEventManager");  
this.eventManager = this.myEventManager.getMyEventManager();  

//新建监听器  
this.myListener = plus.android.implements("test.MyListener", {  
onChange:function(event){  
                     //导入类  
         plus.android.importClass(event);  
                    //获取数据  
        console.log(event.getData());  
                    //获取来源  
        console.log(event.getSource();  
            }  
        })  

//添加监听器    
this.eventManager.addListener("onShowXX",this.myListener);  

uniapp 发送数据给 原生 获取其他页面 看清 onShowXX字符串 我随意写的 只要和添加监听器的字符串 保持一致就能通知到 类似 weex 的 数据传递


this.myEventManager = plus.android.importClass("test.MyEventManager");  
this.eventManager = this.myEventManager.getMyEventManager();  
this.myEventManager.postMsg("app 显示了","onShowXX");  

原生 接收 从uniapp 或者原生传过来的数据


        MyEventManager.getMyEventManager().addListener(new MyListener() {  
            @Override  
            public void onChange(MyEvent myEvent) {  

                //从uniapp 或者原生传过来的数据  
                Object data = myEvent.getData();  

            }  
        },"onShowXX");  

原生发数据 到 原生或者 uniapp


   MyEventManager.postMsg("我是从原生发来的数据","onShowXX");  

具体例子 app显示就发数据给原生或者 uniapp


<script>  
    export default {  
        onLaunch: function() {  
            console.log('App Launch')  

            this.myEventManager = plus.android.importClass("test.MyEventManager");  
            this.eventManager = this.myEventManager.getMyEventManager();  

            //新建监听器  
            this.myListener = plus.android.implements("test.MyListener", {  
                onChange:function(event){  
                     //导入类  
                     plus.android.importClass(event);  
                    //获取数据  
                    console.log(event.getData());  
                    //获取来源  
                    console.log(event.getSource();  
                }  
            })  

            //添加监听器    
            this.eventManager.addListener("onShow",this.myListener);  

        },  
        onShow: function() {  

            //发送数据  
            this.myEventManager.postMsg("app 显示了","onShow");  
        },  
        onHide: function() {  
            console.log('App Hide')  
        }  
    }  
</script>  

<style>  
    /*每个页面公共css */  
</style>  
继续阅读 »

首先我们要写三个java (类|接口)

1 .事件对象 用来传递数据的

package test;  

public class MyEvent  {  

    //数据  
    private  Object data;  

    //事件来源  字符串  
    private  String source;  

    //触发对象  
    private  Object  trigger;  

    public MyEvent(Object data) {  
        this.data = data;  
    }  

    public Object getTrigger() {  
        return trigger;  
    }  

    @Override  
    public String toString() {  
        return "MyEvent{" +  
                "data=" + data +  
                ", source='" + source + '\'' +  
                ", trigger=" + trigger +  
                '}';  
    }  

    public void setTrigger(Object trigger) {  
        this.trigger = trigger;  
    }  

    public MyEvent(Object data, String source) {  
        this.data = data;  
        this.source = source;  
    }  

    public MyEvent() {  
    }  

    public Object getData() {  
        return data;  
    }  

    public void setData(Object data) {  
        this.data = data;  
    }  

    public String getSource() {  
        return source;  
    }  

    public void setSource(String source) {  
        this.source = source;  
    }  
}  

2.监听器接口


package test;  

import java.util.EventListener;  

public  interface MyListener  extends EventListener {  
     void onChange(MyEvent myEvent);  
}  

3.事件管理器 用来 处理 事件 与通知 监听器


package test;  

import android.util.Log;  

import java.util.*;  

/***  
 * 自定义事件管理类  
 */  
public class MyEventManager {  
    private static MyEventManager myEventManager;  

    private Map<String, Collection<MyListener>> listeners;  

    /**  
     * 不能外部 new  实例化  
     */  
    private MyEventManager() {  
        this.listeners = new HashMap<String, Collection<MyListener>>();  
    }  

    /**  
     * 返回监听 总数  
     *  
     * @return  
     */  

    public int getSize() {  
        int size = 0;  
        for (String str : listeners.keySet()) {  
            size = size + listeners.get(str).size();  

        }  
        return size;  
    }  

    public Map<String, Collection<MyListener>> getListeners() {  

        return listeners;  
    }  

    /**  
     * 单例模式  
     *  
     * @return  
     */  
    public static MyEventManager getMyEventManager() {  
        if (myEventManager == null) {  
            synchronized (MyEventManager.class) {  
                if (myEventManager == null) {  
                    myEventManager = new MyEventManager();  
                }  
            }  
        }  
        return myEventManager;  
    }  

    /***  
     * 添加事件  
     * @param listener    事件对象  
     * @param source      来源  
     */  
    public MyListener addListener(MyListener listener, String source) {  

        if (listener != null && source != null) {  
            Collection<MyListener> myListeners = listeners.get(source);  

            if (myListeners == null) {  
                myListeners = new HashSet<MyListener>();  
                listeners.put(source, myListeners);  
            }  
            myListeners.add(listener);  

        }  
        return listener;  

    }  

    /***  
     * 添加事件  
     *  @param source      来源  
     * @param listener    事件对象  
     */  
    public MyListener addListener(String source, MyListener listener) {  

        return addListener(listener, source);  

    }  

    /**  
     * 移除监听  
     *  
     * @param listener  
     */  
    public void removeListener(MyListener listener) {  
        if (listeners == null || listener == null) {  
            return;  
        }  

        //变量所有  找出相同的  删除  
        for (String str : listeners.keySet()) {  

            Collection collection = listeners.get(str);  
            Iterator<MyListener> iter = collection.iterator();  
            while (iter.hasNext()) {  
                MyListener next = (MyListener) iter.next();  
                if (next == listener) {  
                    collection.remove(next);  
                    return;  
                }  
            }  
        }  

    }  

    /***  
     *   发送数据  
     * @param data   数据  
     * @param source 来源  
     * @return  
     */  
    public static MyEvent postMsg(Object data, String source) {  
        MyEventManager myEventManager = MyEventManager.getMyEventManager();  
        MyEvent myEvent = new MyEvent(data);  
        myEvent.setSource(source);  
        if (myEventManager.listeners == null)  

            return myEvent;  
        myEventManager.notifyListeners(myEvent, myEvent.getSource());  
        return myEvent;  
    }  

    /**  
     * 通知所有的myListener    相同的 (source) 来源才通知  
     */  
    private void notifyListeners(MyEvent event, String source) {  

        //取出  key为source 的  监听器集合  
        Collection<MyListener> collection = listeners.get(source);  

        Log.i(MyEventManager.class.getName(), source + "--->" + event.getData());  

        if (collection == null) {  
            return;  
        }  
        //遍历监听器集合  
        Iterator<MyListener> iter = collection.iterator();  
        while (iter.hasNext()) {  
            MyListener next = iter.next();  
            //通知回调  
            next.onChange(event);  
        }  

        //销毁事件对象  
        event = null;  
    }  

}  

uniapp 添加监听器 监听 原生或者 uniapp 其他页面 传过来的数据


this.myEventManager = plus.android.importClass("test.MyEventManager");  
this.eventManager = this.myEventManager.getMyEventManager();  

//新建监听器  
this.myListener = plus.android.implements("test.MyListener", {  
onChange:function(event){  
                     //导入类  
         plus.android.importClass(event);  
                    //获取数据  
        console.log(event.getData());  
                    //获取来源  
        console.log(event.getSource();  
            }  
        })  

//添加监听器    
this.eventManager.addListener("onShowXX",this.myListener);  

uniapp 发送数据给 原生 获取其他页面 看清 onShowXX字符串 我随意写的 只要和添加监听器的字符串 保持一致就能通知到 类似 weex 的 数据传递


this.myEventManager = plus.android.importClass("test.MyEventManager");  
this.eventManager = this.myEventManager.getMyEventManager();  
this.myEventManager.postMsg("app 显示了","onShowXX");  

原生 接收 从uniapp 或者原生传过来的数据


        MyEventManager.getMyEventManager().addListener(new MyListener() {  
            @Override  
            public void onChange(MyEvent myEvent) {  

                //从uniapp 或者原生传过来的数据  
                Object data = myEvent.getData();  

            }  
        },"onShowXX");  

原生发数据 到 原生或者 uniapp


   MyEventManager.postMsg("我是从原生发来的数据","onShowXX");  

具体例子 app显示就发数据给原生或者 uniapp


<script>  
    export default {  
        onLaunch: function() {  
            console.log('App Launch')  

            this.myEventManager = plus.android.importClass("test.MyEventManager");  
            this.eventManager = this.myEventManager.getMyEventManager();  

            //新建监听器  
            this.myListener = plus.android.implements("test.MyListener", {  
                onChange:function(event){  
                     //导入类  
                     plus.android.importClass(event);  
                    //获取数据  
                    console.log(event.getData());  
                    //获取来源  
                    console.log(event.getSource();  
                }  
            })  

            //添加监听器    
            this.eventManager.addListener("onShow",this.myListener);  

        },  
        onShow: function() {  

            //发送数据  
            this.myEventManager.postMsg("app 显示了","onShow");  
        },  
        onHide: function() {  
            console.log('App Hide')  
        }  
    }  
</script>  

<style>  
    /*每个页面公共css */  
</style>  
收起阅读 »

uni-app项目瀑布流布局

Vue uniapp

github地址,喜欢的可以star下哦

插件预览图

使用教程

1.插件代码拷贝

  • 下载后把components目录下waterfall.vue文件拷贝到自己项目目录下

2.插件全局配置

  • 在项目里main.js中配置如下代码
import waterfall from './components/waterfall.vue'  

Vue.component('waterfall',waterfall)  

3.插件使用

  • vue页面使用
<template>  
    <view>  
        <!-- 瀑布流(display: flex) H5 IOS Android支持 -->  
        <waterfall></waterfall>  
    </view>  
</template>
兼容性

uni-app项目中使用都兼容

继续阅读 »

github地址,喜欢的可以star下哦

插件预览图

使用教程

1.插件代码拷贝

  • 下载后把components目录下waterfall.vue文件拷贝到自己项目目录下

2.插件全局配置

  • 在项目里main.js中配置如下代码
import waterfall from './components/waterfall.vue'  

Vue.component('waterfall',waterfall)  

3.插件使用

  • vue页面使用
<template>  
    <view>  
        <!-- 瀑布流(display: flex) H5 IOS Android支持 -->  
        <waterfall></waterfall>  
    </view>  
</template>
兼容性

uni-app项目中使用都兼容

收起阅读 »