HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

使用 uploader 进行七牛图片上传

七牛 uploader

.

说明

我是七牛的忠实拥趸。
个人网站、公司项目,文件和图片的存储方案使用的都是七牛。
这两天在封装 DC 的七牛图片上传,遇到了点问题,不过最后还是解决了。
这里只分享下简单的思路和代码。
.

开发思路

  1. 生成七牛上传令牌
    因为有安全隐患,七牛官方推荐开发者向自己的业务服务器发送 ajax 进行获取。
    我在贴出的代码中略过了这一步,你们自己处理。
    .
  2. 选取图片
    使用摄像头(plus.camera)或者相册(plus.gallery)都可以。
    .
  3. 上传图片
    最主要就是设置 “令牌”、“图片名称” 等参数。
    不过需要的注意就是添加文件(addFile)后面一定要加 {"key":"file"},这是固定值,不要改,别问我为什么。

.

// 从相册获取图片  
plus.gallery.pick(function(ret){  

    // 获取图片名称  
    var path = ret;  
    var file = ret.substr(ret.lastIndexOf("/")+1);  
    var token = "xxxxxxx";    // 填写你的七牛上传令牌  

    // 上传图片  
    var url = "http://upload.qiniu.com/";  
    var uploader = plus.uploader.createUpload(url,{},function(up,state){  
        if( state==200 )  
            Console("上传成功");  
        else  
            Console("上传失败 - ",state);  
    });  

    uploader.addData("key",file);  
    uploader.addData("token",token);  
    uploader.addFile(path,{"key":"file"});      // 固定值,千万不要改!!!!!!  
    uploader.start();  

});
继续阅读 »

.

说明

我是七牛的忠实拥趸。
个人网站、公司项目,文件和图片的存储方案使用的都是七牛。
这两天在封装 DC 的七牛图片上传,遇到了点问题,不过最后还是解决了。
这里只分享下简单的思路和代码。
.

开发思路

  1. 生成七牛上传令牌
    因为有安全隐患,七牛官方推荐开发者向自己的业务服务器发送 ajax 进行获取。
    我在贴出的代码中略过了这一步,你们自己处理。
    .
  2. 选取图片
    使用摄像头(plus.camera)或者相册(plus.gallery)都可以。
    .
  3. 上传图片
    最主要就是设置 “令牌”、“图片名称” 等参数。
    不过需要的注意就是添加文件(addFile)后面一定要加 {"key":"file"},这是固定值,不要改,别问我为什么。

.

// 从相册获取图片  
plus.gallery.pick(function(ret){  

    // 获取图片名称  
    var path = ret;  
    var file = ret.substr(ret.lastIndexOf("/")+1);  
    var token = "xxxxxxx";    // 填写你的七牛上传令牌  

    // 上传图片  
    var url = "http://upload.qiniu.com/";  
    var uploader = plus.uploader.createUpload(url,{},function(up,state){  
        if( state==200 )  
            Console("上传成功");  
        else  
            Console("上传失败 - ",state);  
    });  

    uploader.addData("key",file);  
    uploader.addData("token",token);  
    uploader.addFile(path,{"key":"file"});      // 固定值,千万不要改!!!!!!  
    uploader.start();  

});
收起阅读 »

分享MUI搜索框动态赋值样式问题

项目中用到了MUI input search框

在动态赋值的时候没有获取焦点,所以搜素框里面的图标和placeholder文字都没有变,会重叠覆盖。

经过火狐调试官方input.html,查看在获取焦点的时候添加了mui-active 这个class

代码:<div class="mui-input-row mui-search mui-active>
<input class="mui-input-clear" placeholder="" data-input-clear="1" data-input-search="1" type="search"><span class="mui-icon mui-icon-clear mui-hidden"></span><span class="mui-placeholder"><span class="mui-icon mui-icon-search"></span><span></span></span>
</div>

所以大家以后在用到动态赋值的时候把值更新后,再控制下mui-active这个class是否添加就好了

大家以后遇到问题也不要过度依赖论坛,先自己试试看能否解决,实在没有办法再搜索

继续阅读 »

项目中用到了MUI input search框

在动态赋值的时候没有获取焦点,所以搜素框里面的图标和placeholder文字都没有变,会重叠覆盖。

经过火狐调试官方input.html,查看在获取焦点的时候添加了mui-active 这个class

代码:<div class="mui-input-row mui-search mui-active>
<input class="mui-input-clear" placeholder="" data-input-clear="1" data-input-search="1" type="search"><span class="mui-icon mui-icon-clear mui-hidden"></span><span class="mui-placeholder"><span class="mui-icon mui-icon-search"></span><span></span></span>
</div>

所以大家以后在用到动态赋值的时候把值更新后,再控制下mui-active这个class是否添加就好了

大家以后遇到问题也不要过度依赖论坛,先自己试试看能否解决,实在没有办法再搜索

收起阅读 »

【分享】快速获取matchUrls可用的匹配依据的值

分享 wap2app

需求简述

在 wap2app 开发中,matchUrls 可以使用多种匹配依据来匹配目标 page。
部分开发者对Location对象不够熟悉,不知道应该去匹配哪个部分,才能正确匹配到目标 page。

解决方案

准备工作

  • 在电脑上打开浏览器,最好是chrome
  • 访问要适配的 wap 站,F12打开控制台,并且切换到手机模式
  • 跳转到目标地址,也就是要匹配的地址

方案一

在控制台执行下面的代码,会将所有可用的匹配依据的值,都 log 出来。

(function() {  
  var _location = window.location;  
  var props = ['hash', 'host', 'hostname', 'href', 'pathname', 'port', 'protocol', 'search'];  
  for(var i = 0, len = props.length; i < len; i++) {  
    console.log(props[i] + ':' + _location[props[i]]);  
  }  
}());

方案二

也可以直接使用 console 本身的方法,以表格的形式将 window.location 的值展示出来。

console.table(window.location);

这个方法,会把所有的属性都列出来,找到可用的匹配依据的值就行了。

继续阅读 »

需求简述

在 wap2app 开发中,matchUrls 可以使用多种匹配依据来匹配目标 page。
部分开发者对Location对象不够熟悉,不知道应该去匹配哪个部分,才能正确匹配到目标 page。

解决方案

准备工作

  • 在电脑上打开浏览器,最好是chrome
  • 访问要适配的 wap 站,F12打开控制台,并且切换到手机模式
  • 跳转到目标地址,也就是要匹配的地址

方案一

在控制台执行下面的代码,会将所有可用的匹配依据的值,都 log 出来。

(function() {  
  var _location = window.location;  
  var props = ['hash', 'host', 'hostname', 'href', 'pathname', 'port', 'protocol', 'search'];  
  for(var i = 0, len = props.length; i < len; i++) {  
    console.log(props[i] + ':' + _location[props[i]]);  
  }  
}());

方案二

也可以直接使用 console 本身的方法,以表格的形式将 window.location 的值展示出来。

console.table(window.location);

这个方法,会把所有的属性都列出来,找到可用的匹配依据的值就行了。

收起阅读 »

iOS离线打包项目升级5+SDK

5+sdk iOS打包

原文链接:iOS离线打包项目升级5+SDK

(1)替换SDK
用最新SDK文件替换项目中SDK

(2) 将PandoraApi.bundle引入项目,引入时候勾选项如下图:
引入PandoraApi.bundle

(3) 将PandoraApi.bundle中feature.plist文件用原来项目中替换,当然如果没有写过插件,应该不需要。

(4)导入inc文件到项目中,打开SDK所在目录,拖动到项目中相应位置,此处,因我原生不会改动,所以设置如下:
导入inc文件

(5)修改control.xml中对应的appid等,修改时要注意和www文件中manifest.json文件中对应字段要完全一致,如下图:
修改control.xml文件

(6)导入相应的依赖库,及项目中相关设置,此处注意,下图中红框标注libcoreSupport.a文件一定要导入,否则会报错,当然还有其他库依赖的问题,可以根据提示解决。
项目中相关设置

(7)根据SDK最新demo,修改本项目中的主控制器中启动5+SDK的相关代码,我的启动位置在ViewController.m中,故更新为最新即可,由于适配了iPhone X有改动,需留意!

(8)到此处,文件配置基本没问题了,现在是不是可以启动应用了呢?哈哈哈,启动下试试吧,beng~,呃呃呃,程序是可以启动了,但是卡顿在index页面了,www文件我没有更改,不可能出错啊。用Safari调试后发现,是调用插件地方报错,怎么会这样?升级前是好好的,郁闷中~~

(9)最新SDK中注册插件有变动
最新SDK中注册插件不在document.addEventListener(‘plusready’)方法中了,直接注册即可。好程序可以启动了,恭喜��

(10)等等等,啊啊啊啊,报错,搜索了下,个推静态库在模拟机上报错,这个,这个,我只有模拟机,穷的买不起真机测试啊,啊啊啊啊,老板,我要真机,我要iPhone X。。。还是算了,升级个推SDK吧。升级这块在本文中不赘述,后期补充一篇文章,因为需要将本项目中.a静态库替换成个推最新的.frameworks框架了。

(11)血的教训!!!
最新的SDK注册插件不需要document.addEventListener(“plusready”, function(){},true);中注册插件,直接放外面注册就可以了
方法调用最好还是放在plusready事件之后, 是document.addEventListener(‘plusready’) ,不要用mui.plusready,或者直接加一个timeout也可以

(12)原生调用webview方式有变动
原来原生调用webview中需要根据id遍历查找,如下代码:

NSArray frames = [[[[PDRCore Instance] appManager] activeApp] appWindow].allFrames;
for (PDRCoreAppFrame
frame in frames)
if ([frame.frameName isEqualToString:@"rmsNews"]){
[frame stringByEvaluatingJavaScriptFromString:@"initChat()"];
}

改成如下方式即可:

PDRCoreAppWindow appWindow = [[[[PDRCore Instance] appManager] activeApp] appWindow];
PDRCoreAppFrame
frame = [appWindow getFrameByName:@"rmsNews"];
//同步
[frame stringByEvaluatingJavaScriptFromString:@"initChat()"];
//或者可以用异步
[frame evaluateJavaScript:@"initChat()" completionHandler:^(id result, NSError *error) {
}];
总结
升级之后,页面自动适配了iPhone X,同时运行流畅度和页面反应速度都有很大提升,感觉5+做的越来越好,虽然苹果在封杀类似这种混合式开发,但是不可否认,存在即合理,希望5+做的越来越好。

继续阅读 »

原文链接:iOS离线打包项目升级5+SDK

(1)替换SDK
用最新SDK文件替换项目中SDK

(2) 将PandoraApi.bundle引入项目,引入时候勾选项如下图:
引入PandoraApi.bundle

(3) 将PandoraApi.bundle中feature.plist文件用原来项目中替换,当然如果没有写过插件,应该不需要。

(4)导入inc文件到项目中,打开SDK所在目录,拖动到项目中相应位置,此处,因我原生不会改动,所以设置如下:
导入inc文件

(5)修改control.xml中对应的appid等,修改时要注意和www文件中manifest.json文件中对应字段要完全一致,如下图:
修改control.xml文件

(6)导入相应的依赖库,及项目中相关设置,此处注意,下图中红框标注libcoreSupport.a文件一定要导入,否则会报错,当然还有其他库依赖的问题,可以根据提示解决。
项目中相关设置

(7)根据SDK最新demo,修改本项目中的主控制器中启动5+SDK的相关代码,我的启动位置在ViewController.m中,故更新为最新即可,由于适配了iPhone X有改动,需留意!

(8)到此处,文件配置基本没问题了,现在是不是可以启动应用了呢?哈哈哈,启动下试试吧,beng~,呃呃呃,程序是可以启动了,但是卡顿在index页面了,www文件我没有更改,不可能出错啊。用Safari调试后发现,是调用插件地方报错,怎么会这样?升级前是好好的,郁闷中~~

(9)最新SDK中注册插件有变动
最新SDK中注册插件不在document.addEventListener(‘plusready’)方法中了,直接注册即可。好程序可以启动了,恭喜��

(10)等等等,啊啊啊啊,报错,搜索了下,个推静态库在模拟机上报错,这个,这个,我只有模拟机,穷的买不起真机测试啊,啊啊啊啊,老板,我要真机,我要iPhone X。。。还是算了,升级个推SDK吧。升级这块在本文中不赘述,后期补充一篇文章,因为需要将本项目中.a静态库替换成个推最新的.frameworks框架了。

(11)血的教训!!!
最新的SDK注册插件不需要document.addEventListener(“plusready”, function(){},true);中注册插件,直接放外面注册就可以了
方法调用最好还是放在plusready事件之后, 是document.addEventListener(‘plusready’) ,不要用mui.plusready,或者直接加一个timeout也可以

(12)原生调用webview方式有变动
原来原生调用webview中需要根据id遍历查找,如下代码:

NSArray frames = [[[[PDRCore Instance] appManager] activeApp] appWindow].allFrames;
for (PDRCoreAppFrame
frame in frames)
if ([frame.frameName isEqualToString:@"rmsNews"]){
[frame stringByEvaluatingJavaScriptFromString:@"initChat()"];
}

改成如下方式即可:

PDRCoreAppWindow appWindow = [[[[PDRCore Instance] appManager] activeApp] appWindow];
PDRCoreAppFrame
frame = [appWindow getFrameByName:@"rmsNews"];
//同步
[frame stringByEvaluatingJavaScriptFromString:@"initChat()"];
//或者可以用异步
[frame evaluateJavaScript:@"initChat()" completionHandler:^(id result, NSError *error) {
}];
总结
升级之后,页面自动适配了iPhone X,同时运行流畅度和页面反应速度都有很大提升,感觉5+做的越来越好,虽然苹果在封杀类似这种混合式开发,但是不可否认,存在即合理,希望5+做的越来越好。

收起阅读 »

flex布局

布局的传统解决方案,基于盒模型,依赖display属性+position属性+float属性。它对于特殊布局非常不方便,如垂直居中就不容易实现。
2009年,w3c提出了一种新的方案---flex布局,可以简便、完整、响应式地实现各种页面布局。目前,已经得到了所有浏览器的支持。
flex布局将成为未来布局的首选方案。
Flex是Flexible Box的缩写,意为“弹性布局“,用来为盒状模型提供最大的灵活性。任何一个容器都可以指定为flex布局。
.box { display:flex; }
行内元素也可以使用Flex布局。
.box{display: inline-flex;}
webkit内核的浏览器布局加上-webkit-前缀。
.box{display:-webkit-flex;display:flex;}
注意:设为flex布局后,子元素的float、clear和vertial-align属性将失效。
二、基本概念
采用Flex布局的元素,成为Flex容器(flex contatiner),简称“容器”。它的所有子元素自动成为容器成员,称为Flex(flex item),简称“项目”。
容器默认存在两根轴:水平的主轴(main axis)和垂直的交叉轴(cross axis)。
主轴的开始位置(与边框的交叉点)叫做main start,结束位置叫做main end;交叉轴的开始位置叫做cross start,结束位置叫做cross end。
三、容器的属性
以下6个属性设置在容器上。
flex-direction
flex-wrap
flex-flow
justify-content
align-items
align-content
3.1flex-direction属性决定主轴的方向(即项目的排列方向)。
.box{flex-direction:row | row-reverse | column | column-reverse;}
row(默认值):主轴为水平方向,起点在左端。
row-reverse:主轴为水平方向,起点在右端。
column:主轴为垂直方向,起点在上沿。
column-reverse:主轴为垂直方向,起点在下沿。
3.2flex-wrap属性
定义如何换行
.box{flex-wrap:nowrap | wrap | wrap-reverse;}
nowrap:(默认)不换行
wrap:换行,第一行在上方。
wrap-reverse:换行,第一行在下方。
3.3 flex-flow
flex-flow属性是flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap。
.box{flex-flow: <flex-direction> || <flex-wrap>};
3.4 justify-content属性
justify-content属性定义了项目在主轴上的对齐方式。
3.5 align-items属性
align-items属性定义项目在交叉轴上如何对齐。
.box {
align-items: flex-start | flex-end | center | baseline | stretch;
}
它可能取5个值。具体的对齐方式与交叉轴的方向有关,下面假设交叉轴从上到下。
flex-start:交叉轴的起点对齐。
flex-end:交叉轴的终点对齐。
center:交叉轴的中点对齐。
baseline: 项目的第一行文字的基线对齐。
stretch(默认值):如果项目未设置高度或设为auto,将占满整个容器的高度。
3.6 align-content属性
align-content属性定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。
.box {
align-content: flex-start | flex-end | center | space-between | space-around | stretch;
}
该属性可能取6个值。
flex-start:与交叉轴的起点对齐。
flex-end:与交叉轴的终点对齐。
center:与交叉轴的中点对齐。
space-between:与交叉轴两端对齐,轴线之间的间隔平均分布。
space-around:每根轴线两侧的间隔都相等。所以,轴线之间的间隔比轴线与边框的间隔大一倍。
stretch(默认值):轴线占满整个交叉轴。
四、项目的属性
以下6个属性设置在项目上。
order
flex-grow
flex-shrink
flex-basis
flex
align-self
4.1 order属性
order属性定义项目的排列顺序。数值越小,排列越靠前,默认为0。
.item {
order: <integer>;
}
4.2 flex-grow属性
flex-grow属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。
.item {
flex-grow: <number>; / default 0 /
}
如果所有项目的flex-grow属性都为1,则它们将等分剩余空间(如果有的话)。如果一个项目的flex-grow属性为2,其他项目都为1,则前者占据的剩余空间将比其他项多一倍。
4.3 flex-shrink属性
flex-shrink属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。

.item {
flex-shrink: <number>; / default 1 /
}
如果所有项目的flex-shrink属性都为1,当空间不足时,都将等比例缩小。如果一个项目的flex-shrink属性为0,其他项目都为1,则空间不足时,前者不缩小。

4.4 flex-basis属性
flex-basis属性定义了在分配多余空间之前,项目占据的主轴空间(main size)。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。
.item {
flex-basis: <length> | auto; / default auto /
}
它可以设为跟width或height属性一样的值(比如350px),则项目将占据固定空间。
4.5 flex属性
flex属性是flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto。后两个属性可选。
.item {
flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]
}
该属性有两个快捷值:auto (1 1 auto) 和 none (0 0 auto)。
建议优先使用这个属性,而不是单独写三个分离的属性,因为浏览器会推算相关值。
4.6 align-self属性
align-self属性允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。
.item {
align-self: auto | flex-start | flex-end | center | baseline | stretch;
}
该属性可能取6个值,除了auto,其他都与align-items属性完全一致。

来源:http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html

继续阅读 »

布局的传统解决方案,基于盒模型,依赖display属性+position属性+float属性。它对于特殊布局非常不方便,如垂直居中就不容易实现。
2009年,w3c提出了一种新的方案---flex布局,可以简便、完整、响应式地实现各种页面布局。目前,已经得到了所有浏览器的支持。
flex布局将成为未来布局的首选方案。
Flex是Flexible Box的缩写,意为“弹性布局“,用来为盒状模型提供最大的灵活性。任何一个容器都可以指定为flex布局。
.box { display:flex; }
行内元素也可以使用Flex布局。
.box{display: inline-flex;}
webkit内核的浏览器布局加上-webkit-前缀。
.box{display:-webkit-flex;display:flex;}
注意:设为flex布局后,子元素的float、clear和vertial-align属性将失效。
二、基本概念
采用Flex布局的元素,成为Flex容器(flex contatiner),简称“容器”。它的所有子元素自动成为容器成员,称为Flex(flex item),简称“项目”。
容器默认存在两根轴:水平的主轴(main axis)和垂直的交叉轴(cross axis)。
主轴的开始位置(与边框的交叉点)叫做main start,结束位置叫做main end;交叉轴的开始位置叫做cross start,结束位置叫做cross end。
三、容器的属性
以下6个属性设置在容器上。
flex-direction
flex-wrap
flex-flow
justify-content
align-items
align-content
3.1flex-direction属性决定主轴的方向(即项目的排列方向)。
.box{flex-direction:row | row-reverse | column | column-reverse;}
row(默认值):主轴为水平方向,起点在左端。
row-reverse:主轴为水平方向,起点在右端。
column:主轴为垂直方向,起点在上沿。
column-reverse:主轴为垂直方向,起点在下沿。
3.2flex-wrap属性
定义如何换行
.box{flex-wrap:nowrap | wrap | wrap-reverse;}
nowrap:(默认)不换行
wrap:换行,第一行在上方。
wrap-reverse:换行,第一行在下方。
3.3 flex-flow
flex-flow属性是flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap。
.box{flex-flow: <flex-direction> || <flex-wrap>};
3.4 justify-content属性
justify-content属性定义了项目在主轴上的对齐方式。
3.5 align-items属性
align-items属性定义项目在交叉轴上如何对齐。
.box {
align-items: flex-start | flex-end | center | baseline | stretch;
}
它可能取5个值。具体的对齐方式与交叉轴的方向有关,下面假设交叉轴从上到下。
flex-start:交叉轴的起点对齐。
flex-end:交叉轴的终点对齐。
center:交叉轴的中点对齐。
baseline: 项目的第一行文字的基线对齐。
stretch(默认值):如果项目未设置高度或设为auto,将占满整个容器的高度。
3.6 align-content属性
align-content属性定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。
.box {
align-content: flex-start | flex-end | center | space-between | space-around | stretch;
}
该属性可能取6个值。
flex-start:与交叉轴的起点对齐。
flex-end:与交叉轴的终点对齐。
center:与交叉轴的中点对齐。
space-between:与交叉轴两端对齐,轴线之间的间隔平均分布。
space-around:每根轴线两侧的间隔都相等。所以,轴线之间的间隔比轴线与边框的间隔大一倍。
stretch(默认值):轴线占满整个交叉轴。
四、项目的属性
以下6个属性设置在项目上。
order
flex-grow
flex-shrink
flex-basis
flex
align-self
4.1 order属性
order属性定义项目的排列顺序。数值越小,排列越靠前,默认为0。
.item {
order: <integer>;
}
4.2 flex-grow属性
flex-grow属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。
.item {
flex-grow: <number>; / default 0 /
}
如果所有项目的flex-grow属性都为1,则它们将等分剩余空间(如果有的话)。如果一个项目的flex-grow属性为2,其他项目都为1,则前者占据的剩余空间将比其他项多一倍。
4.3 flex-shrink属性
flex-shrink属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。

.item {
flex-shrink: <number>; / default 1 /
}
如果所有项目的flex-shrink属性都为1,当空间不足时,都将等比例缩小。如果一个项目的flex-shrink属性为0,其他项目都为1,则空间不足时,前者不缩小。

4.4 flex-basis属性
flex-basis属性定义了在分配多余空间之前,项目占据的主轴空间(main size)。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。
.item {
flex-basis: <length> | auto; / default auto /
}
它可以设为跟width或height属性一样的值(比如350px),则项目将占据固定空间。
4.5 flex属性
flex属性是flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto。后两个属性可选。
.item {
flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]
}
该属性有两个快捷值:auto (1 1 auto) 和 none (0 0 auto)。
建议优先使用这个属性,而不是单独写三个分离的属性,因为浏览器会推算相关值。
4.6 align-self属性
align-self属性允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。
.item {
align-self: auto | flex-start | flex-end | center | baseline | stretch;
}
该属性可能取6个值,除了auto,其他都与align-items属性完全一致。

来源:http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html

收起阅读 »

Mui vue slider

mui HTML5+

如果你在开发中看见有这种类型的页面


你一定会记得在Mui的MuiDemo中有一个这样的页面

哇!
这不就是一样的么!!!!!!
于是屁颠屁颠的开工搞起了
心里想着今天又是愉快的一天 这个demo真的是好
是真的开心!
然而不管你用上面数据模版结合这个使用的时候你会发现
这个页面做出来是不能滑的

LZ用的是VUE(感觉有点像我的性格 小清新简单强大)

于是想办法
打断点 然而你会发现这种情况有点无从下手
决定是你了 于是开始百度大法了
LZ试过N种百度问法

解说大多数都是说:

$nextTick(function () {  
          var sliderMuiObj = mui("#slider");  
                sliderMuiObj.slider({  
                    interval: 3000  
                });  
})

然而上面的方法并没有什么卵用

好了 直接讲解决办法

1.上面的代码写到数据更新以后

  1. 在title标签下面添加<script src="html5plus://ready"></script>
  2. 把mui.plusReady(function () {})里面的方法写到外面 然后重新运行代码

然后可以惊奇的发现可以滑动了!!!!!!!!

当你解决一个问题的时候如果能够知道原理 那才是最有用的东西
那么我们来分析一下原理

首先数据绑定有三种方式:
发布者-订阅者模式(backbone.js)
脏值检查(angular.js)
数据劫持(vue.js)

三种实现双向绑定(响应式)的做法

发布者-订阅者模式(backbone.js)
脏值检查(angular.js)
数据劫持(vue.js)

  1. 发布者-订阅者模式: 一般通过sub, pub的方式实现数据和视图的绑定监听,更新数据方式通常做法是 vm.set('property', value),这种方式现在毕竟太low了,我们更希望通过 vm.property = value 这种方式更新数据,同时自动更新视图,于是有了下面两种方式

  2. 脏值检查: angular.js 是通过脏值检测的方式比对数据是否有变更,来决定是否更新视图,最简单的方式就是通过 setInterval() 定时轮询检测数据变动,当然Google不会这么low,angular只有在指定的事件触发时进入脏值检测,大致如下:

DOM事件,譬如用户输入文本,点击按钮等。( ng-click )
XHR响应事件 ( $http )
浏览器Location变更事件 ( $location )
Timer事件( $timeout , $interval )
执行 $digest() 或 $apply()

  1. 数据劫持: vue.js 则是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

思路整理

已经了解到vue是通过数据劫持的方式来做数据绑定的,其中最核心的方法便是通过Object.defineProperty()来实现对属性的劫持,达到监听数据变动的目的,无疑这个方法是本文中最重要、最基础的内容之一,如果不熟悉defineProperty,可以去MDN上看看

整理了一下,要实现mvvm的双向绑定,就必须要实现以下几点:

1、实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者
2、实现一个指令解析器Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
3、实现一个Watcher,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图
4、mvvm入口函数,整合以上三者
这里写一个小Demo
JS部分

(function (g, nui) {g.nui = nui;})(this, function(obj){  
  this.verson = '2.5.1';  
  this.el = obj.el;  
  this.data = {};  
  this.nodes = {};  
  var _self = this  
  if(!obj.data){obj.data = {};}  
  Object.keys(obj.data).forEach(function(key){  
    defineReactive(_self.data, key, obj.data[key]);  
  });  
  function defineReactive(data, key, val) {  
    Object.defineProperty(_self.data, key, {  
      enumerable: true,  
      configurable: false,  
      get: function() {return val;},  
      set: function(newVal){  
        val = newVal;  
        var cnode = _self.nodes[key];  
        if(cnode){for(var i = 0;  i < cnode.length; i++){cnode[i].textContent = newVal;}}  
      }  
    });  
  }  
  //设置变量值  
  this.setData = function(objs){Object.keys(objs).forEach(function(k){_self.data[k] = objs[k];});}  
  this.isElementNode = function(node) {return node.nodeType == 1;}  
  this.isTextNode = function(node){return node.nodeType == 3;}  
  this.compileText = function(node, varName){  
    var varName = varName.substring(2, varName.length - 2);  
    console.log(varName);  
    var value = _self.data[varName];  
    node.textContent = typeof value == 'undefined' ? '' : value;  
    if(!this.nodes[varName]){this.nodes[varName] = [];}  
    this.nodes[varName].push(node);  
  }  
  this.compileFor = function(node, varName){  
    var value = _self.data[varName];  
    if(!value){node.parentNode.removeChild(node); return false;}  
    var parentDom = node.parentNode;  
    for(var i = 0; i < value.length; i++){  
      var newNode = node.cloneNode(true);  
      newNode.removeAttribute('nui-for');  
      var html = newNode.innerHTML;  
      var newHtml = html.replace(/{{item}}/g, '{{'+varName+'['+i+']}}');  
      console.log(newHtml);  
      newNode.innerHTML = newHtml;  
      parentDom.appendChild(newNode);  
    }  
    parentDom.removeChild(node);  
    //编译列表  
    this.compileSons(parentDom);  
  }  
  this.compileSons = function(el){  
    var childNodes = el.childNodes;  
    [].slice.call(childNodes).forEach(function(node){  
      var reg = /{{.*?}}/g;  
      if (_self.isElementNode(node)){  
        _self.compileSons(node);  
      }else if (_self.isTextNode(node)){  
        var regs = node.textContent.match(reg);  
        if(regs){  
          if(regs.length == 1){_self.compileText(node, regs[0]);}else{  
            var ortherText = node.textContent.split(reg), newTextNodes = [];  
            for(var  i = 0; i < ortherText.length; i++){  
              node.parentNode.insertBefore(document.createTextNode(ortherText[i]), node);  
              if(regs[i]){  
                var cnode = document.createTextNode(regs[i]);  
                node.parentNode.insertBefore(cnode, node);  
                _self.compileText(cnode, regs[i]);  
              }  
            }  
            node.parentNode.removeChild(node);  
          }  
        }  
      }  
    });  
  }  
  this.compile = function () {  
    this.els = document.querySelector(this.el);  
    if(this.els  == null){return ;}  
    this.fragment = document.createDocumentFragment();  
    var child;  
    while(child = this.els.firstChild){this.fragment.appendChild(child);}  
    this.compileSons(this.fragment);  
    this.els.appendChild(this.fragment);  
  }  
  this.compile();  
});

html 部分

<!DOCTYPE html>  
<html>  
<head>  
<meta charset="UTF-8">  
<title></title>  
</head>  
<body>  

  {{myname}}...{{test}}123..<br />  
  {{age}}...  

<span>{{test}}</span>  

  <button onclick="t();">test</button>  

<script type="text/javascript" src="common.js"></script>  
<script type="text/javascript">  
var app = new nui({  
  el : "body",  
  data : {  
    myname : 'name',  
    age : 18,  
    test : 'test'  
  }  
});  
function t(){  
  app.setData({myname : 'name new ', test:'test new'});    
}  
</script>  
</body>  
</html>

滑动不了的原因是因为html部分代码是在plusReady环境之前运行
我们引入以后第二个步骤就是为了让所有的代码在plusReady之后执行
这样就不会引起阻塞这样就可以完美运行了
如果你够幸运开始就看见我的这篇又可以屁颠屁颠的开搞了!!!

可能由于我学识浅薄,导致您发现有严重谬误的地方,请一定在评论中指出,我会在第一时间修正我的博文,以避免误人子弟。

继续阅读 »

如果你在开发中看见有这种类型的页面


你一定会记得在Mui的MuiDemo中有一个这样的页面

哇!
这不就是一样的么!!!!!!
于是屁颠屁颠的开工搞起了
心里想着今天又是愉快的一天 这个demo真的是好
是真的开心!
然而不管你用上面数据模版结合这个使用的时候你会发现
这个页面做出来是不能滑的

LZ用的是VUE(感觉有点像我的性格 小清新简单强大)

于是想办法
打断点 然而你会发现这种情况有点无从下手
决定是你了 于是开始百度大法了
LZ试过N种百度问法

解说大多数都是说:

$nextTick(function () {  
          var sliderMuiObj = mui("#slider");  
                sliderMuiObj.slider({  
                    interval: 3000  
                });  
})

然而上面的方法并没有什么卵用

好了 直接讲解决办法

1.上面的代码写到数据更新以后

  1. 在title标签下面添加<script src="html5plus://ready"></script>
  2. 把mui.plusReady(function () {})里面的方法写到外面 然后重新运行代码

然后可以惊奇的发现可以滑动了!!!!!!!!

当你解决一个问题的时候如果能够知道原理 那才是最有用的东西
那么我们来分析一下原理

首先数据绑定有三种方式:
发布者-订阅者模式(backbone.js)
脏值检查(angular.js)
数据劫持(vue.js)

三种实现双向绑定(响应式)的做法

发布者-订阅者模式(backbone.js)
脏值检查(angular.js)
数据劫持(vue.js)

  1. 发布者-订阅者模式: 一般通过sub, pub的方式实现数据和视图的绑定监听,更新数据方式通常做法是 vm.set('property', value),这种方式现在毕竟太low了,我们更希望通过 vm.property = value 这种方式更新数据,同时自动更新视图,于是有了下面两种方式

  2. 脏值检查: angular.js 是通过脏值检测的方式比对数据是否有变更,来决定是否更新视图,最简单的方式就是通过 setInterval() 定时轮询检测数据变动,当然Google不会这么low,angular只有在指定的事件触发时进入脏值检测,大致如下:

DOM事件,譬如用户输入文本,点击按钮等。( ng-click )
XHR响应事件 ( $http )
浏览器Location变更事件 ( $location )
Timer事件( $timeout , $interval )
执行 $digest() 或 $apply()

  1. 数据劫持: vue.js 则是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

思路整理

已经了解到vue是通过数据劫持的方式来做数据绑定的,其中最核心的方法便是通过Object.defineProperty()来实现对属性的劫持,达到监听数据变动的目的,无疑这个方法是本文中最重要、最基础的内容之一,如果不熟悉defineProperty,可以去MDN上看看

整理了一下,要实现mvvm的双向绑定,就必须要实现以下几点:

1、实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者
2、实现一个指令解析器Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
3、实现一个Watcher,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图
4、mvvm入口函数,整合以上三者
这里写一个小Demo
JS部分

(function (g, nui) {g.nui = nui;})(this, function(obj){  
  this.verson = '2.5.1';  
  this.el = obj.el;  
  this.data = {};  
  this.nodes = {};  
  var _self = this  
  if(!obj.data){obj.data = {};}  
  Object.keys(obj.data).forEach(function(key){  
    defineReactive(_self.data, key, obj.data[key]);  
  });  
  function defineReactive(data, key, val) {  
    Object.defineProperty(_self.data, key, {  
      enumerable: true,  
      configurable: false,  
      get: function() {return val;},  
      set: function(newVal){  
        val = newVal;  
        var cnode = _self.nodes[key];  
        if(cnode){for(var i = 0;  i < cnode.length; i++){cnode[i].textContent = newVal;}}  
      }  
    });  
  }  
  //设置变量值  
  this.setData = function(objs){Object.keys(objs).forEach(function(k){_self.data[k] = objs[k];});}  
  this.isElementNode = function(node) {return node.nodeType == 1;}  
  this.isTextNode = function(node){return node.nodeType == 3;}  
  this.compileText = function(node, varName){  
    var varName = varName.substring(2, varName.length - 2);  
    console.log(varName);  
    var value = _self.data[varName];  
    node.textContent = typeof value == 'undefined' ? '' : value;  
    if(!this.nodes[varName]){this.nodes[varName] = [];}  
    this.nodes[varName].push(node);  
  }  
  this.compileFor = function(node, varName){  
    var value = _self.data[varName];  
    if(!value){node.parentNode.removeChild(node); return false;}  
    var parentDom = node.parentNode;  
    for(var i = 0; i < value.length; i++){  
      var newNode = node.cloneNode(true);  
      newNode.removeAttribute('nui-for');  
      var html = newNode.innerHTML;  
      var newHtml = html.replace(/{{item}}/g, '{{'+varName+'['+i+']}}');  
      console.log(newHtml);  
      newNode.innerHTML = newHtml;  
      parentDom.appendChild(newNode);  
    }  
    parentDom.removeChild(node);  
    //编译列表  
    this.compileSons(parentDom);  
  }  
  this.compileSons = function(el){  
    var childNodes = el.childNodes;  
    [].slice.call(childNodes).forEach(function(node){  
      var reg = /{{.*?}}/g;  
      if (_self.isElementNode(node)){  
        _self.compileSons(node);  
      }else if (_self.isTextNode(node)){  
        var regs = node.textContent.match(reg);  
        if(regs){  
          if(regs.length == 1){_self.compileText(node, regs[0]);}else{  
            var ortherText = node.textContent.split(reg), newTextNodes = [];  
            for(var  i = 0; i < ortherText.length; i++){  
              node.parentNode.insertBefore(document.createTextNode(ortherText[i]), node);  
              if(regs[i]){  
                var cnode = document.createTextNode(regs[i]);  
                node.parentNode.insertBefore(cnode, node);  
                _self.compileText(cnode, regs[i]);  
              }  
            }  
            node.parentNode.removeChild(node);  
          }  
        }  
      }  
    });  
  }  
  this.compile = function () {  
    this.els = document.querySelector(this.el);  
    if(this.els  == null){return ;}  
    this.fragment = document.createDocumentFragment();  
    var child;  
    while(child = this.els.firstChild){this.fragment.appendChild(child);}  
    this.compileSons(this.fragment);  
    this.els.appendChild(this.fragment);  
  }  
  this.compile();  
});

html 部分

<!DOCTYPE html>  
<html>  
<head>  
<meta charset="UTF-8">  
<title></title>  
</head>  
<body>  

  {{myname}}...{{test}}123..<br />  
  {{age}}...  

<span>{{test}}</span>  

  <button onclick="t();">test</button>  

<script type="text/javascript" src="common.js"></script>  
<script type="text/javascript">  
var app = new nui({  
  el : "body",  
  data : {  
    myname : 'name',  
    age : 18,  
    test : 'test'  
  }  
});  
function t(){  
  app.setData({myname : 'name new ', test:'test new'});    
}  
</script>  
</body>  
</html>

滑动不了的原因是因为html部分代码是在plusReady环境之前运行
我们引入以后第二个步骤就是为了让所有的代码在plusReady之后执行
这样就不会引起阻塞这样就可以完美运行了
如果你够幸运开始就看见我的这篇又可以屁颠屁颠的开搞了!!!

可能由于我学识浅薄,导致您发现有严重谬误的地方,请一定在评论中指出,我会在第一时间修正我的博文,以避免误人子弟。

收起阅读 »

iOS最新SDK升级后原生跳转web页面导致崩溃问题解决

5 SDK

升级2017年11月30日发布的最新SDK遇到的问题:
用原来这个方法找相应页面会崩溃,最终解决,整个过程感谢客服耐心支持,解决方法分享如下:

原方法:
NSArray frames = [[[[PDRCore Instance] appManager] activeApp] appWindow].allFrames;
for (PDRCoreAppFrame
frame in frames) {
if ([frame.frameName isEqualToString:@"rmsNews"]){
[frame stringByEvaluatingJavaScriptFromString:@"initChat()"];
}
}

新方法:
PDRCoreAppWindow appWindow = [[[[PDRCore Instance] appManager] activeApp] appWindow];
PDRCoreAppFrame
frame = [appWindow getFrameByName:@"rmsNews"];
//同步用这个
[frame stringByEvaluatingJavaScriptFromString:@"initChat()"];
//异步用这个
[frame evaluateJavaScript:@"initChat()" completionHandler:^(id result, NSError *error) {
}];

继续阅读 »

升级2017年11月30日发布的最新SDK遇到的问题:
用原来这个方法找相应页面会崩溃,最终解决,整个过程感谢客服耐心支持,解决方法分享如下:

原方法:
NSArray frames = [[[[PDRCore Instance] appManager] activeApp] appWindow].allFrames;
for (PDRCoreAppFrame
frame in frames) {
if ([frame.frameName isEqualToString:@"rmsNews"]){
[frame stringByEvaluatingJavaScriptFromString:@"initChat()"];
}
}

新方法:
PDRCoreAppWindow appWindow = [[[[PDRCore Instance] appManager] activeApp] appWindow];
PDRCoreAppFrame
frame = [appWindow getFrameByName:@"rmsNews"];
//同步用这个
[frame stringByEvaluatingJavaScriptFromString:@"initChat()"];
//异步用这个
[frame evaluateJavaScript:@"initChat()" completionHandler:^(id result, NSError *error) {
}];

收起阅读 »

个推推送的整体解决方案(思路、源码)

解决方案 推送 个推SDK 个推

.

说明

这是我 DC(DCloud)开发中遇到的第二个问题。
虽然个推功能强大,但是个推官网文档那叫一个简陋,导致众多开发者爬坑无数、心酸无比。

为什么要这样一个技术方案分享?
因为社区还没看到一个关于个推的整体解决方案,包含前台与后台的开发思路,而且开发源码。
希望对其他开发者提供点帮助,少踩几个坑,也希望 DC 更加完善。

.

推送效果

  1. 外推(外部推送)
    简单的说,就是我们能够看到的推送。
    比如,通知栏的推送列表,比如,苹果的临时横幅,我都称之为“外推”。
    .
  2. 内推(内部推送)
    这种推送是隐藏性质的,它们不会出现在手机的通知栏或横幅,它们适用于这种场景:
    ①. APP 在前台时,收到推送时,会出现一个对话框来询问用户是否跳转。
    ②. 完全不提示用户,比如,用户账号在其他手机登录,APP 接收到推送时,强制用户退出。
    .
  3. 智推(智能推送)
    APP 在前台时,发送内推。
    APP 在后台或者关闭时,发送外推。

.

开发流程

  1. 个推注册
    个推官网:http://getui.com/cn/index.html
    .
  2. 配置推送
    ①. 配置个推参数(appid、appkey、appsecret)。
    ②. 配置自定义基座,方便调试。
    .
  3. 后台发送推送
    定义一个推送的数据格式,便于前台、后台进行对接。
    我在开发时,用的是个推官网提供的 PHP SDK(我对其进行了封装,具体见下方)。
    .
  4. APP接收推送并处理
    对推送的数据进行解析,然后执行对应的业务逻辑(我对其进行了封装,具体见下方)。

.

测试数据

开发中使用了 5 台设备。
安卓:小米3(4.4.4)、锤子T2(5.1.1)、海马玩模拟器(4.2.2)、雷电模拟器(5.1.1)。
苹果:iPhone 6(iOS 11.0.3)。

个推支持 4 种推送模板。
①. 透传推送:点击通知打开网页模板(IGtLinkTemplate)。
②. 普通推送:点击通知打开应用模板(IGtNotificationTemplate)。
③. 链接推送:点击通知弹框下载模板(IGtNotyPopLoadTemplate)
④. 下载推送:透传消息模版(IGtTransmissionTemplate)。

因为 链接推送、下载推送 平时基本上不用,所以本方案没有纳入测试和封装,需要的朋友自己处理吧。

透传推送 和 普通推送 能够达到大体一致的效果。
透传推送,后台发送标准推送数据格式的推送时,APP 能收到一条带参数的推送。
普通推送,后台发送带透传数据的推送时,APP 也能收到一条带参数的推送。
所以,我对这两种推送模板做了分组对比测试。

还有一点,关于个推官方定义的 推送数据的标准格式和非标准格式,很多开发者都不是很理解,包括我在内,最开始因为不是很理解,按照自己想当然的思路对 5 台设备进行了测试,经过大量重复操作后,得到一组可观的数据,但是,无意中发现自己理解有误,导致之前的数据作废,也算是一个大坑,所以我还是简单的展示下两种模式的区别吧。

标准格式
必须符合这个样子,{title:"标题",content:"内容",payload:"数据"}。

$Message1 = [  
    "title"=> "健康告知",  
    "content"=> "您的中二病已经很严重了!",  
    "payload"=>[  
        "push"=> "inner",  
        "event"=> "warning",   
        "silent"=> false,  
        "data"=> ""  
    ]  
];

非标准格式
只要不是上面的那种格式就满足(比如,缺少 payload 参数)。

$Message2 = [  
    "title"=> "健康告知",  
    "content"=> "您的中二病已经很严重了!",  
    "push"=> "inner",  
    "event"=> "warning",   
    "silent"=> false,  
    "data"=> ""  
];

最重要的一点:
只有发送标准格式数据,APP才能收到推送(外推)!!!
只有发送标准格式数据,APP才能收到推送(外推)!!!
只有发送标准格式数据,APP才能收到推送(外推)!!!

.
好了,下面是我测试的五组数据,如果你发现有些数据是错误的,请在评论里指出,免得误导别人。





.
数据分析

①. 安卓
测试后发现,小米3、锤子T2 关闭应用后,自动清除了个推的后台驻留进程,所以这种情况是没有办法解决的,很显然,真实手机(小米3、锤子T2)的数据已经废了。

海马玩和雷电的数据完全一致,我们就看雷电的数据。
第一组数据方案(带透传信息的普通推送),如果你亲自测试,你会发现,APP 会收到两条推送,普通推送模板默认发送一条,设置了带有标准数据格式的透传信息也会发送一条,很显然,不符合实际场景,排除这种方案。

第四组数据方案(非标准格式的透传推送),实现内推,所以这种方案是有用的。

第二组和第三组数据方案,实现的效果一致,那么既然是方案整合(应该有内推功能),所以应该选第三组,因为它和第四组(内推)共用一个模板(IGtTransmissionTemplate),在实际操作中,只需要切换数据格式(标准、非标准)来实现 内推、外推 的切换,能够节省不少代码。

②. 苹果
普通推送,APP 会自动弹出一个 bug 一样的确认对话框,点击“取消”也会触发确认时间,所以,数据作废。
透传推送,苹果手机比较特别,APP 在前台时,个推会发送内推,在后台或关闭时,个推会借助苹果的 APN 发送苹果的推送。

综上,选择透传推送的两种数据格式(标准、非标准)能够完美解决推送问题。

.

后台源码

.
参数说明

  • clientid
    要发送的用户的 clientid。
    .
  • title
    推送标题。
    .
  • content
    推送内容。
    .
  • push
    推送类型,仅安卓支持,苹果设置无效。
    outer:外推
    inner:内推
    smart:智推
    .
  • system
    接收推送的设备系统类型,因为它决定了工具类选择哪个推送数据格式。
    ios:苹果系统。
    android:安卓系统。
    .
  • event
    事件名称,APP 用来处理业务逻辑。
    .
  • data
    附加数据,可以携带业务数据发送给 APP,PHP 数组类型。
    .
  • silent
    是否启用静默模式,APP 接收到内推时,是否展示推送,布尔值。

.
hhPushClass.php
已经封装好的 PHP 类,逻辑都在里面,自己看吧,有问题可以在评论里指明。
类发送推送后,会将结果保存到 $result 中,包含了 arr、json 两种形式。
类里面的 $Message1、$Message2 是上文所说的定义的数据格式。

<?php  

# 名称 : hhGpush  
# 来源 : hhtools.php  
# 版本 : 1.0.0  
# 作者 : 立树  
# 网站 : studio.houheaven.com  
# 日期 : 2017-12-15  

class hhGpush  
{  
    public $gp_appid;       // 应用 ID(AppID)  
    public $gp_appkey;      // 应用 key(AppKey)  
    public $gp_token;       // 应用令牌(MasterSecret)  
    public $gp_host;        // 个推服务器  

    private $gpush;         // 个推  
    public $result;         // 推送结果  

    # 构造函数  
    public function __construct($appid,$appkey,$token)  
    {  
        // 属性初始化  
        $this->gp_appid = $appid;  
        $this->gp_appkey = $appkey;  
        $this->gp_token = $token;  
        $this->gp_host = "http://sdk.open.api.igexin.com/apiex.htm";  

        // 个推初始化  
        $this->gpush = new IGeTui($this->gp_host,$appkey,$token);  
        $this->result = [];  
    }  

    # 单个推送(支持安卓、苹果)  
    public function PushMsgToSingle($param)  
    {  
        // 参数检测  
        if( gettype($param)!="array" || !isset($param["clientid"]) )  
        {  
            exit("参数错误");  
        }  

        // 参数初始化  
        // 标题  
        $title = isset($param["title"])?$param["title"]:"";  
        // 内容  
        $content = isset($param["content"])?$param["content"]:"";  
        // 推送类型(外推:outer、内推:inner、智能推送:smart)  
        $push = isset($param["push"])?$param["push"]:"outer";  
        // 事件名称(APP根据此参数决定执行哪些功能)  
        $event = isset($param["event"])?$param["event"]:"";  
        // 内推时,是否给用户展示提示信息(比如强制用户退出就不会展示提示信息)  
        $silent = isset($param["silent"])?$param["silent"]:false;  
        // 推送数据,附加的业务数据  
        $data = isset($param["data"])?$param["data"]:"";  

        // 标准推送数据格式  
        $Message1 = [  
            "title"=> $title,  
            "content"=> $content,  
            "payload"=>[  
                "push"=> $push,  
                "event"=> $event,   
                "silent"=> $silent,  
                "data"=> $data  
            ]  
        ];  

        // 非标准推送数据格式  
        $Message2 = [  
            "title"=> $title,  
            "content"=> $content,  
            "push"=> $push,  
            "event"=> $event,  
            "silent"=> $silent,  
            "data"=> $data  
        ];  

        // 用户状态  
        $aid = $this->gp_appid;  
        $cid = $param["clientid"];  
        $status = $this->gpush->getClientIdStatus($aid,$cid);  
        $this->result["arr"]["client_state"] = $status;  

        // 推送模板  
        $system = isset($param["system"])?$param["system"]:"android";  
        $system = in_array($system,array("android","ios"))?$system:"android";  
        if( $system=="android" )  
        {  
            // 安卓模板  
            switch($param["push"])  
            {  
                case "inner":  
                    $Message2["push"] = "inner";  
                    $msg = $Message2;  
                    break;  
                case "outer":  
                    $Message1["payload"]["push"] = "outer";  
                    $msg = $Message1;  
                    break;  
                case "smart":  
                    $status = $this->gpush->getClientIdStatus($aid,$cid);  
                    if( $status["result"]=="Online" )  
                    {  
                        $Message2["push"] = "smart";  
                        $msg = $Message2;  
                    }  
                    else  
                    {  
                        $Message1["payload"]["push"] = "outer";  
                        $msg = $Message1;  
                    }  
                    break;  
            }  
        }  
        else  
        {  
            // 苹果模板  
            $Message1["payload"]["push"] = "inner";  
            $msg = $Message1;  
        }  

        $tpl = $this->AwesomeTemplate($msg);  
        $this->result["arr"]["push_param"] = $msg;  

        // 推送消息  
        $msg = new IGtSingleMessage();  
        $msg->set_isOffline(true);                     // 是否离线  
        $msg->set_offlineExpireTime(12*3600*100);      // 离线时间  
        $msg->set_data($tpl);                          // 推送消息模板  
        $msg->set_PushNetWorkType(0);                  // 设置是否根据WIFI推送消息,2为4G/3G/2G,1为wifi推送,0为不限制推送  

        // 接收方  
        $target = new IGtTarget();  
        $target->set_appId($aid);  
        $target->set_clientId($cid);  

        // 发送  
        $ret = $this->gpush->pushMessageToSingle($msg,$target);  
        $this->result["arr"]["push_state"] = $ret;  
        $this->result["json"] = json_encode($this->result);  
    }  

    // 透传推送模板  
    function AwesomeTemplate($param)  
    {  
        // 模板初始化  
        $template = new IGtTransmissionTemplate();  
        $template->set_appId($this->gp_appid);         // 应用appid  
        $template->set_appkey($this->gp_appkey);       // 应用appkey  

        // 安卓推送(外推+内推)、苹果内推  
        $template->set_transmissionType(2);  
        $template->set_transmissionContent(json_encode($param));  // 透传内容  

        // 苹果处于后台时的推送(外推)  
        $alertmsg = new DictionaryAlertMsg();  
        $alertmsg->actionLocKey = "ActionLockey";       // 个推官网提供,文档无说明  
        $alertmsg->launchImage = "launchimage";         // 个推官网提供,文档无说明  
        $alertmsg->locArgs = array("locargs");          // 个推官网提供,文档无说明  
        $alertmsg->locKey = $param["title"];            // 消息标题  
        $alertmsg->body = $param["content"];            // 消息内容  
        // iOS8.2 支持  
        $alertmsg->title = $param["title"];             // 消息标题  
        $alertmsg->titleLocKey = $param["title"];       // 消息标题  
        $alertmsg->titleLocArgs = array("TitleLocArg"); // 个推官网提供,文档无说明  

        $apn = new IGtAPNPayload();  
        $apn->alertMsg = $alertmsg;  
        //$apn->badge = 1;  
        //$apn->sound = "";  
        $param["payload"]["push"] = "outer";        // 调用此处代码证明是外推  
        foreach($param as $key=>$val)  
        {  
            $apn->add_customMsg($key,$val);  
        }  
        $apn->contentAvailable = 1;                 // 个推官网提供,文档无说明  
        $apn->category = "ACTIONABLE";              // 个推官网提供,文档无说明  
        $template->set_apnInfo($apn);  

        // 设置通知定时展示时间,结束时间与开始时间相差需大于6分钟  
        // 消息推送后,客户端将在指定时间差内展示消息(误差6分钟)  
        //$begin = "2017-12-14 15:20:00";  
        //$end = "2017-12-14 15:30:00";  
        //$template->set_duration($begin,$end);  

        return $template;  
    }  

}  

?>

代码调用

<?php  

header("Content-Type: text/html; charset=utf-8");  

require_once(dirname(__FILE__)."/IGt.Push.php");      // 个推 sdk  
require_once(dirname(__FILE__)."/hhPushClass.php");   // 封装好的工具类  

// 配置(替换成自己的)  
$Appid = "111111";  
$Appkey = "222222";  
$Mastersecret = "333333";  

// 实例化  
$gpush = new hhGpush($Appid,$Appkey,$Mastersecret);  

// 发送推送  
$gpush->PushMsgToSingle([  
    "clientid"=> "xxxxxxxxxx",  
    "event"=> "warning",  
    "title"=> "健康告知",  
    "content"=> "您的中二病已经很严重了!",  
    "push"=> "smart",  
    "system"=> "android",  
    "silent"=> false  
]);  

// 打印结果  
var_dump($gpush->result);

.

前台源码

.
参数

插件的唯一参数是一个回调函数。
收到推送事件(Receive)、推送点击事件(Click)已被内置在插件中,当这两个事件被触发时,插件会先已处理好推送逻辑,然后将推送的数据进行格式化,然后会触发这个回调函数,并将格式化的消息返回,以供开发者处理业务逻辑。

格式化的推送消息:

  • title 消息标题
  • descp 消息内容
  • event 事件名称(开发者根据此参数决定执行业务逻辑)
  • data 推送数据(服务器返回的业务数据)

.
Gpush.js


// Gpush.app.dc.1.1.0  
// 作者 : 立树  
// 日期 : 2017-12-19  
// 来源 : hhtools.app.js  
// 文档 : http://studio.houheaven.com  

function Gpush(fnPushExec)  
{  
    // 设置应用为前台事件  
    localStorage.setItem("isAppActive",true);  
    // 注册应用切换到后台事件  
    document.addEventListener("pause",function(){  
        localStorage.setItem("isAppActive",false);  
    });  
    // 注册应用切换到前台事件  
    document.addEventListener("resume",function(){  
        localStorage.setItem("isAppActive",true);  
    });  

    // 推送点击事件  
    plus.push.addEventListener("click",function(msg){  
        push_proc(msg);  
    });  

    // 推送接收事件  
    plus.push.addEventListener("receive",function(msg){  
        push_proc(msg);  
    });  

    // 推送消息预处理  
    function push_proc(msg)  
    {  
        // 解析  
        var payload = typeof(msg.payload)=="string"?JSON.parse(msg.payload):msg.payload;  

        // 消息格式化  
        var notice = {  
            title: msg.title,         // 标题  
            descp: msg.content,       // 内容  
            event: payload.event,     // 事件名称(APP根据此参数决定执行哪些功能)  
            data: payload.data,       // 推送数据,附加的业务数据  
            silent: payload.silent    // 内推时,是否给用户展示提示信息(比如强制用户退出就不会展示提示信息)  
        };  

        // 系统检测  
        if( plus.os.name=="Android" )  
        {  
            // 推送检测  
            switch(payload.push)  
            {  
                case "smart":  
                    // 智能推送  
                    // 能进入这里的都是内推,然后判断APP的状态(前台、后台)  
                    // 前台:直接进入内推的业务逻辑  
                    // 后台:补发一个本地推送  
                    if( localStorage.getItem("isAppActive")=="false" )  
                    {  
                        // 按照逻辑,或者以我强迫症的调性来说,是不会更改原本推送的状态的,因为我已经监听了用户从后台到前台的事件(把 isAppActive 改为 true),刚刚模拟出来的本地推送被点击后,会直接触发推送业务逻辑,简直完美。  
                        // 但是,计划赶不上变化,在所有操作过程中,总会有先后顺序,这里就是,在用户切换应用到前台时(resume),还没来得及将应用状态 isAppActive 改为 true 的时候,这里的判断已经执行了,而 isAppActive 依然是 false,所以会导致再次创建本地推送。  
                        // 所以啊,不说了,只好妥协了。  
                        payload.push = "outer";  
                        plus.push.createMessage(msg.content,JSON.stringify(payload),{  
                            title: msg.title  
                        });  
                    }  
                    else  
                        push_check(notice);  
                    break;  
                case "outer":  
                    // 外推  
                    push_exec(notice);  
                    break;  
                case "inner":  
                    // 内推  
                    push_check(notice);  
                    break;  
            }  
        }  
        else  
        {  
            // 推送检测  
            if( payload.push=="outer" )  
            {  
                // 外推  
                push_exec(notice);  
            }  
            else  
            {  
                // 内推  
                push_check(notice);  
            }  
        }  
    }  

    // 内推时提示信息处理  
    function push_check(notice)  
    {  
        if( notice.silent!=true )  
        {  
            plus.nativeUI.confirm("\n"+notice.descp+"\n\n",function(ret){  
                if( ret.index==1 )  
                {  
                    push_exec(notice);  
                }  
            },{  
                title: notice.title,  
                buttons: ["取消","查看"]  
            });  
        }  
        else  
            push_exec(notice);  
    }  

    // 推送点击处理  
    function push_exec(notice)  
    {  
        // 事件处理  
        !fnPushExec || fnPushExec(notice);  
    }  
}

代码调用

<?php  
// 个推推送  
Gpush(function(notice){  
    switch(notice.event)  
    {  
        case "warning":     // 警告  
            alert(notice.descp);  
            break;  
        case "logout":      // 退出登录  
            UserLogout(notice.data);  
            break;  
    }  
});

.
以上,有疑问的可以在评论区留言,希望对你有帮助。

继续阅读 »

.

说明

这是我 DC(DCloud)开发中遇到的第二个问题。
虽然个推功能强大,但是个推官网文档那叫一个简陋,导致众多开发者爬坑无数、心酸无比。

为什么要这样一个技术方案分享?
因为社区还没看到一个关于个推的整体解决方案,包含前台与后台的开发思路,而且开发源码。
希望对其他开发者提供点帮助,少踩几个坑,也希望 DC 更加完善。

.

推送效果

  1. 外推(外部推送)
    简单的说,就是我们能够看到的推送。
    比如,通知栏的推送列表,比如,苹果的临时横幅,我都称之为“外推”。
    .
  2. 内推(内部推送)
    这种推送是隐藏性质的,它们不会出现在手机的通知栏或横幅,它们适用于这种场景:
    ①. APP 在前台时,收到推送时,会出现一个对话框来询问用户是否跳转。
    ②. 完全不提示用户,比如,用户账号在其他手机登录,APP 接收到推送时,强制用户退出。
    .
  3. 智推(智能推送)
    APP 在前台时,发送内推。
    APP 在后台或者关闭时,发送外推。

.

开发流程

  1. 个推注册
    个推官网:http://getui.com/cn/index.html
    .
  2. 配置推送
    ①. 配置个推参数(appid、appkey、appsecret)。
    ②. 配置自定义基座,方便调试。
    .
  3. 后台发送推送
    定义一个推送的数据格式,便于前台、后台进行对接。
    我在开发时,用的是个推官网提供的 PHP SDK(我对其进行了封装,具体见下方)。
    .
  4. APP接收推送并处理
    对推送的数据进行解析,然后执行对应的业务逻辑(我对其进行了封装,具体见下方)。

.

测试数据

开发中使用了 5 台设备。
安卓:小米3(4.4.4)、锤子T2(5.1.1)、海马玩模拟器(4.2.2)、雷电模拟器(5.1.1)。
苹果:iPhone 6(iOS 11.0.3)。

个推支持 4 种推送模板。
①. 透传推送:点击通知打开网页模板(IGtLinkTemplate)。
②. 普通推送:点击通知打开应用模板(IGtNotificationTemplate)。
③. 链接推送:点击通知弹框下载模板(IGtNotyPopLoadTemplate)
④. 下载推送:透传消息模版(IGtTransmissionTemplate)。

因为 链接推送、下载推送 平时基本上不用,所以本方案没有纳入测试和封装,需要的朋友自己处理吧。

透传推送 和 普通推送 能够达到大体一致的效果。
透传推送,后台发送标准推送数据格式的推送时,APP 能收到一条带参数的推送。
普通推送,后台发送带透传数据的推送时,APP 也能收到一条带参数的推送。
所以,我对这两种推送模板做了分组对比测试。

还有一点,关于个推官方定义的 推送数据的标准格式和非标准格式,很多开发者都不是很理解,包括我在内,最开始因为不是很理解,按照自己想当然的思路对 5 台设备进行了测试,经过大量重复操作后,得到一组可观的数据,但是,无意中发现自己理解有误,导致之前的数据作废,也算是一个大坑,所以我还是简单的展示下两种模式的区别吧。

标准格式
必须符合这个样子,{title:"标题",content:"内容",payload:"数据"}。

$Message1 = [  
    "title"=> "健康告知",  
    "content"=> "您的中二病已经很严重了!",  
    "payload"=>[  
        "push"=> "inner",  
        "event"=> "warning",   
        "silent"=> false,  
        "data"=> ""  
    ]  
];

非标准格式
只要不是上面的那种格式就满足(比如,缺少 payload 参数)。

$Message2 = [  
    "title"=> "健康告知",  
    "content"=> "您的中二病已经很严重了!",  
    "push"=> "inner",  
    "event"=> "warning",   
    "silent"=> false,  
    "data"=> ""  
];

最重要的一点:
只有发送标准格式数据,APP才能收到推送(外推)!!!
只有发送标准格式数据,APP才能收到推送(外推)!!!
只有发送标准格式数据,APP才能收到推送(外推)!!!

.
好了,下面是我测试的五组数据,如果你发现有些数据是错误的,请在评论里指出,免得误导别人。





.
数据分析

①. 安卓
测试后发现,小米3、锤子T2 关闭应用后,自动清除了个推的后台驻留进程,所以这种情况是没有办法解决的,很显然,真实手机(小米3、锤子T2)的数据已经废了。

海马玩和雷电的数据完全一致,我们就看雷电的数据。
第一组数据方案(带透传信息的普通推送),如果你亲自测试,你会发现,APP 会收到两条推送,普通推送模板默认发送一条,设置了带有标准数据格式的透传信息也会发送一条,很显然,不符合实际场景,排除这种方案。

第四组数据方案(非标准格式的透传推送),实现内推,所以这种方案是有用的。

第二组和第三组数据方案,实现的效果一致,那么既然是方案整合(应该有内推功能),所以应该选第三组,因为它和第四组(内推)共用一个模板(IGtTransmissionTemplate),在实际操作中,只需要切换数据格式(标准、非标准)来实现 内推、外推 的切换,能够节省不少代码。

②. 苹果
普通推送,APP 会自动弹出一个 bug 一样的确认对话框,点击“取消”也会触发确认时间,所以,数据作废。
透传推送,苹果手机比较特别,APP 在前台时,个推会发送内推,在后台或关闭时,个推会借助苹果的 APN 发送苹果的推送。

综上,选择透传推送的两种数据格式(标准、非标准)能够完美解决推送问题。

.

后台源码

.
参数说明

  • clientid
    要发送的用户的 clientid。
    .
  • title
    推送标题。
    .
  • content
    推送内容。
    .
  • push
    推送类型,仅安卓支持,苹果设置无效。
    outer:外推
    inner:内推
    smart:智推
    .
  • system
    接收推送的设备系统类型,因为它决定了工具类选择哪个推送数据格式。
    ios:苹果系统。
    android:安卓系统。
    .
  • event
    事件名称,APP 用来处理业务逻辑。
    .
  • data
    附加数据,可以携带业务数据发送给 APP,PHP 数组类型。
    .
  • silent
    是否启用静默模式,APP 接收到内推时,是否展示推送,布尔值。

.
hhPushClass.php
已经封装好的 PHP 类,逻辑都在里面,自己看吧,有问题可以在评论里指明。
类发送推送后,会将结果保存到 $result 中,包含了 arr、json 两种形式。
类里面的 $Message1、$Message2 是上文所说的定义的数据格式。

<?php  

# 名称 : hhGpush  
# 来源 : hhtools.php  
# 版本 : 1.0.0  
# 作者 : 立树  
# 网站 : studio.houheaven.com  
# 日期 : 2017-12-15  

class hhGpush  
{  
    public $gp_appid;       // 应用 ID(AppID)  
    public $gp_appkey;      // 应用 key(AppKey)  
    public $gp_token;       // 应用令牌(MasterSecret)  
    public $gp_host;        // 个推服务器  

    private $gpush;         // 个推  
    public $result;         // 推送结果  

    # 构造函数  
    public function __construct($appid,$appkey,$token)  
    {  
        // 属性初始化  
        $this->gp_appid = $appid;  
        $this->gp_appkey = $appkey;  
        $this->gp_token = $token;  
        $this->gp_host = "http://sdk.open.api.igexin.com/apiex.htm";  

        // 个推初始化  
        $this->gpush = new IGeTui($this->gp_host,$appkey,$token);  
        $this->result = [];  
    }  

    # 单个推送(支持安卓、苹果)  
    public function PushMsgToSingle($param)  
    {  
        // 参数检测  
        if( gettype($param)!="array" || !isset($param["clientid"]) )  
        {  
            exit("参数错误");  
        }  

        // 参数初始化  
        // 标题  
        $title = isset($param["title"])?$param["title"]:"";  
        // 内容  
        $content = isset($param["content"])?$param["content"]:"";  
        // 推送类型(外推:outer、内推:inner、智能推送:smart)  
        $push = isset($param["push"])?$param["push"]:"outer";  
        // 事件名称(APP根据此参数决定执行哪些功能)  
        $event = isset($param["event"])?$param["event"]:"";  
        // 内推时,是否给用户展示提示信息(比如强制用户退出就不会展示提示信息)  
        $silent = isset($param["silent"])?$param["silent"]:false;  
        // 推送数据,附加的业务数据  
        $data = isset($param["data"])?$param["data"]:"";  

        // 标准推送数据格式  
        $Message1 = [  
            "title"=> $title,  
            "content"=> $content,  
            "payload"=>[  
                "push"=> $push,  
                "event"=> $event,   
                "silent"=> $silent,  
                "data"=> $data  
            ]  
        ];  

        // 非标准推送数据格式  
        $Message2 = [  
            "title"=> $title,  
            "content"=> $content,  
            "push"=> $push,  
            "event"=> $event,  
            "silent"=> $silent,  
            "data"=> $data  
        ];  

        // 用户状态  
        $aid = $this->gp_appid;  
        $cid = $param["clientid"];  
        $status = $this->gpush->getClientIdStatus($aid,$cid);  
        $this->result["arr"]["client_state"] = $status;  

        // 推送模板  
        $system = isset($param["system"])?$param["system"]:"android";  
        $system = in_array($system,array("android","ios"))?$system:"android";  
        if( $system=="android" )  
        {  
            // 安卓模板  
            switch($param["push"])  
            {  
                case "inner":  
                    $Message2["push"] = "inner";  
                    $msg = $Message2;  
                    break;  
                case "outer":  
                    $Message1["payload"]["push"] = "outer";  
                    $msg = $Message1;  
                    break;  
                case "smart":  
                    $status = $this->gpush->getClientIdStatus($aid,$cid);  
                    if( $status["result"]=="Online" )  
                    {  
                        $Message2["push"] = "smart";  
                        $msg = $Message2;  
                    }  
                    else  
                    {  
                        $Message1["payload"]["push"] = "outer";  
                        $msg = $Message1;  
                    }  
                    break;  
            }  
        }  
        else  
        {  
            // 苹果模板  
            $Message1["payload"]["push"] = "inner";  
            $msg = $Message1;  
        }  

        $tpl = $this->AwesomeTemplate($msg);  
        $this->result["arr"]["push_param"] = $msg;  

        // 推送消息  
        $msg = new IGtSingleMessage();  
        $msg->set_isOffline(true);                     // 是否离线  
        $msg->set_offlineExpireTime(12*3600*100);      // 离线时间  
        $msg->set_data($tpl);                          // 推送消息模板  
        $msg->set_PushNetWorkType(0);                  // 设置是否根据WIFI推送消息,2为4G/3G/2G,1为wifi推送,0为不限制推送  

        // 接收方  
        $target = new IGtTarget();  
        $target->set_appId($aid);  
        $target->set_clientId($cid);  

        // 发送  
        $ret = $this->gpush->pushMessageToSingle($msg,$target);  
        $this->result["arr"]["push_state"] = $ret;  
        $this->result["json"] = json_encode($this->result);  
    }  

    // 透传推送模板  
    function AwesomeTemplate($param)  
    {  
        // 模板初始化  
        $template = new IGtTransmissionTemplate();  
        $template->set_appId($this->gp_appid);         // 应用appid  
        $template->set_appkey($this->gp_appkey);       // 应用appkey  

        // 安卓推送(外推+内推)、苹果内推  
        $template->set_transmissionType(2);  
        $template->set_transmissionContent(json_encode($param));  // 透传内容  

        // 苹果处于后台时的推送(外推)  
        $alertmsg = new DictionaryAlertMsg();  
        $alertmsg->actionLocKey = "ActionLockey";       // 个推官网提供,文档无说明  
        $alertmsg->launchImage = "launchimage";         // 个推官网提供,文档无说明  
        $alertmsg->locArgs = array("locargs");          // 个推官网提供,文档无说明  
        $alertmsg->locKey = $param["title"];            // 消息标题  
        $alertmsg->body = $param["content"];            // 消息内容  
        // iOS8.2 支持  
        $alertmsg->title = $param["title"];             // 消息标题  
        $alertmsg->titleLocKey = $param["title"];       // 消息标题  
        $alertmsg->titleLocArgs = array("TitleLocArg"); // 个推官网提供,文档无说明  

        $apn = new IGtAPNPayload();  
        $apn->alertMsg = $alertmsg;  
        //$apn->badge = 1;  
        //$apn->sound = "";  
        $param["payload"]["push"] = "outer";        // 调用此处代码证明是外推  
        foreach($param as $key=>$val)  
        {  
            $apn->add_customMsg($key,$val);  
        }  
        $apn->contentAvailable = 1;                 // 个推官网提供,文档无说明  
        $apn->category = "ACTIONABLE";              // 个推官网提供,文档无说明  
        $template->set_apnInfo($apn);  

        // 设置通知定时展示时间,结束时间与开始时间相差需大于6分钟  
        // 消息推送后,客户端将在指定时间差内展示消息(误差6分钟)  
        //$begin = "2017-12-14 15:20:00";  
        //$end = "2017-12-14 15:30:00";  
        //$template->set_duration($begin,$end);  

        return $template;  
    }  

}  

?>

代码调用

<?php  

header("Content-Type: text/html; charset=utf-8");  

require_once(dirname(__FILE__)."/IGt.Push.php");      // 个推 sdk  
require_once(dirname(__FILE__)."/hhPushClass.php");   // 封装好的工具类  

// 配置(替换成自己的)  
$Appid = "111111";  
$Appkey = "222222";  
$Mastersecret = "333333";  

// 实例化  
$gpush = new hhGpush($Appid,$Appkey,$Mastersecret);  

// 发送推送  
$gpush->PushMsgToSingle([  
    "clientid"=> "xxxxxxxxxx",  
    "event"=> "warning",  
    "title"=> "健康告知",  
    "content"=> "您的中二病已经很严重了!",  
    "push"=> "smart",  
    "system"=> "android",  
    "silent"=> false  
]);  

// 打印结果  
var_dump($gpush->result);

.

前台源码

.
参数

插件的唯一参数是一个回调函数。
收到推送事件(Receive)、推送点击事件(Click)已被内置在插件中,当这两个事件被触发时,插件会先已处理好推送逻辑,然后将推送的数据进行格式化,然后会触发这个回调函数,并将格式化的消息返回,以供开发者处理业务逻辑。

格式化的推送消息:

  • title 消息标题
  • descp 消息内容
  • event 事件名称(开发者根据此参数决定执行业务逻辑)
  • data 推送数据(服务器返回的业务数据)

.
Gpush.js


// Gpush.app.dc.1.1.0  
// 作者 : 立树  
// 日期 : 2017-12-19  
// 来源 : hhtools.app.js  
// 文档 : http://studio.houheaven.com  

function Gpush(fnPushExec)  
{  
    // 设置应用为前台事件  
    localStorage.setItem("isAppActive",true);  
    // 注册应用切换到后台事件  
    document.addEventListener("pause",function(){  
        localStorage.setItem("isAppActive",false);  
    });  
    // 注册应用切换到前台事件  
    document.addEventListener("resume",function(){  
        localStorage.setItem("isAppActive",true);  
    });  

    // 推送点击事件  
    plus.push.addEventListener("click",function(msg){  
        push_proc(msg);  
    });  

    // 推送接收事件  
    plus.push.addEventListener("receive",function(msg){  
        push_proc(msg);  
    });  

    // 推送消息预处理  
    function push_proc(msg)  
    {  
        // 解析  
        var payload = typeof(msg.payload)=="string"?JSON.parse(msg.payload):msg.payload;  

        // 消息格式化  
        var notice = {  
            title: msg.title,         // 标题  
            descp: msg.content,       // 内容  
            event: payload.event,     // 事件名称(APP根据此参数决定执行哪些功能)  
            data: payload.data,       // 推送数据,附加的业务数据  
            silent: payload.silent    // 内推时,是否给用户展示提示信息(比如强制用户退出就不会展示提示信息)  
        };  

        // 系统检测  
        if( plus.os.name=="Android" )  
        {  
            // 推送检测  
            switch(payload.push)  
            {  
                case "smart":  
                    // 智能推送  
                    // 能进入这里的都是内推,然后判断APP的状态(前台、后台)  
                    // 前台:直接进入内推的业务逻辑  
                    // 后台:补发一个本地推送  
                    if( localStorage.getItem("isAppActive")=="false" )  
                    {  
                        // 按照逻辑,或者以我强迫症的调性来说,是不会更改原本推送的状态的,因为我已经监听了用户从后台到前台的事件(把 isAppActive 改为 true),刚刚模拟出来的本地推送被点击后,会直接触发推送业务逻辑,简直完美。  
                        // 但是,计划赶不上变化,在所有操作过程中,总会有先后顺序,这里就是,在用户切换应用到前台时(resume),还没来得及将应用状态 isAppActive 改为 true 的时候,这里的判断已经执行了,而 isAppActive 依然是 false,所以会导致再次创建本地推送。  
                        // 所以啊,不说了,只好妥协了。  
                        payload.push = "outer";  
                        plus.push.createMessage(msg.content,JSON.stringify(payload),{  
                            title: msg.title  
                        });  
                    }  
                    else  
                        push_check(notice);  
                    break;  
                case "outer":  
                    // 外推  
                    push_exec(notice);  
                    break;  
                case "inner":  
                    // 内推  
                    push_check(notice);  
                    break;  
            }  
        }  
        else  
        {  
            // 推送检测  
            if( payload.push=="outer" )  
            {  
                // 外推  
                push_exec(notice);  
            }  
            else  
            {  
                // 内推  
                push_check(notice);  
            }  
        }  
    }  

    // 内推时提示信息处理  
    function push_check(notice)  
    {  
        if( notice.silent!=true )  
        {  
            plus.nativeUI.confirm("\n"+notice.descp+"\n\n",function(ret){  
                if( ret.index==1 )  
                {  
                    push_exec(notice);  
                }  
            },{  
                title: notice.title,  
                buttons: ["取消","查看"]  
            });  
        }  
        else  
            push_exec(notice);  
    }  

    // 推送点击处理  
    function push_exec(notice)  
    {  
        // 事件处理  
        !fnPushExec || fnPushExec(notice);  
    }  
}

代码调用

<?php  
// 个推推送  
Gpush(function(notice){  
    switch(notice.event)  
    {  
        case "warning":     // 警告  
            alert(notice.descp);  
            break;  
        case "logout":      // 退出登录  
            UserLogout(notice.data);  
            break;  
    }  
});

.
以上,有疑问的可以在评论区留言,希望对你有帮助。

收起阅读 »

css3使用box-sizing布局

css3增添了盒模型box-sizing,有三个属性值
content-box:默认值,让元素维持w3c的标准盒模型。元素的width/height等于border的宽度加上padding值加上元素内容的width/height,(默认内容区大小不会变)
即Element Width/Height = boder + padding + content width/height;
border-box:让元素维持IE6及以下版本盒模型,元素的width/height等于元素内容的width/height,
即:Element Width/Height = width/height-border-padding。
inherit:继承父元素的盒模型模式。
其中最重要的就是border-box,如果遇到不影响其他区域布局,还要给元素加padding、border的情况,使用border-box元素所占空间不会变,加padding、border会往内挤,保持外面容器不被破坏。(注意:margin不包含在元素空间,加了margin会向外撑开)。
兼容性:IE8+及其他主流浏览器均支持box-sizing。其中IE6及以下默认是以类似border-box盒模型来计算尺寸。
(ps:Firefox浏览器,box-sizing还可以设置一个padding-box,指定元素的宽度/高度等于内容的宽度/高度和內距,
   即:Element Width/Height = content width/height+padding。)

继续阅读 »

css3增添了盒模型box-sizing,有三个属性值
content-box:默认值,让元素维持w3c的标准盒模型。元素的width/height等于border的宽度加上padding值加上元素内容的width/height,(默认内容区大小不会变)
即Element Width/Height = boder + padding + content width/height;
border-box:让元素维持IE6及以下版本盒模型,元素的width/height等于元素内容的width/height,
即:Element Width/Height = width/height-border-padding。
inherit:继承父元素的盒模型模式。
其中最重要的就是border-box,如果遇到不影响其他区域布局,还要给元素加padding、border的情况,使用border-box元素所占空间不会变,加padding、border会往内挤,保持外面容器不被破坏。(注意:margin不包含在元素空间,加了margin会向外撑开)。
兼容性:IE8+及其他主流浏览器均支持box-sizing。其中IE6及以下默认是以类似border-box盒模型来计算尺寸。
(ps:Firefox浏览器,box-sizing还可以设置一个padding-box,指定元素的宽度/高度等于内容的宽度/高度和內距,
   即:Element Width/Height = content width/height+padding。)

收起阅读 »

从零使用H5+和MUI一个月的体验

plus mui HTML5+

我今年才开始转业到互联网,之前的工作与互联网无关,从2017年4月的时候敲下第一句hello world,到如今,2017.12.19,时间的确太短了。说是新手完全没错,但是也有些不合适了,与其他无关,只因为有个人告诉我,工作半个月,你就不是新手了。

公司让我独立开发app,而且也没有其他前端帮助,遇到问题只能自己解决,考虑到自己的英语太差,所以在HBuilder全家桶和reactNative中,我选择了前者。

为了能够按时完成工作,我每天看H5 资料到晚上12点多,那时的我,完全webview和NativeObj的概念,为了完成app的启动广告逻辑,废了两天时间,终于明白,需要两个主页来启动app,,,//此处应该放一个分享连接,//2017.12.27添加 启动广告

没有老师教,刚开始一直把子页面当作新页面添加了,郁闷了好久。为了搞清楚子页面与新页面的区别,废了我好大心思,现在想想,我还记得当要把写H5 文档的人杀了的心情。那文档,根本就不是给新手看的,突然明白为什么使用HBuilder的人那么少了。这已经不是技术和审美的问题了,这设计师的头上一定有个坑,而且一定是大坑。记得我学习vue的时候,老师曾经调侃过AngularJS 的文档写的差,现在来看,他估计是没有读过H5 和mui的文档。 //此处应也该放一个分享连接,//2017.12.27新窗口&子页面

终于开始做主要内容了,首要内容是制作地图,按部就班的使用地图,废了一天的多的时间,终于搞明白地图该怎么搞,我记得当时,为了在地图中间添加一个固定的点,怎么搞都不行,层级不够,查了好久的资料终于明白地图的层级在HTML是最高的,怎么搞都不行,除非重新创建子html挂到上去,最后经过优化,添加了一个NativeObj上去。 //此处应该有链接,日后添加

说到地图,现在还是没有解决的两个问题,一个是通过经纬度查询地理信息,百度地图得到的信息不够详细,一个是高德地图在某些机型上在zoom层级大于14就会出现白屏,比如oppoR11,暂时无解

轮播图,做这个比较简单。说实话,MUI做的挺麻烦的,循环播放还得添加首尾两张图片。这个其实没什么难的,困难的是如何缓存新获得的轮播图片。因为下载图片是异步进行,就不能在下载完之后直接添加,本来我花了好多心思做缓存的,可是老板嫌我的进度慢,所以就把这个半成品放弃了,不过前几天NativeObj添加了自动轮播选项,我也把之前写的东西重构了一下,发现H5 团队比mui的技术强多了 //此处应该有链接//2017.12.27轮播图

说到下载与读取文件。这个地方又该喷官方文档了,就算这两个地方不是同一个人负责的,路径问题就不能统一解决一下吗???从图库选择的图片的路径,在plus.io居然不能直接使用,plus.io的克隆属性,到现在我都没有还成功过。

再后来,是图片预览,终于在muiapp示例上看到眼前一亮的东西了,最后发现把它用到app开发上边太坑,被老板吵了好久,终于发现NativeObj里边有图片预览功能,第一次觉得H5 不是完全的一无是处。终于有个功能是完整一点的了。后来测试图片预览会黑屏不显示图片,现在只能说图片有问题了。。。。

窗口的滑动切换,那时候终于明白H5 的文档该怎么看了,这次没有费太久,本来好好的窗口滑动切换,最后不得不放弃,因为当手指放在轮播图上边滑动,轮播图的滑动事件不能阻止窗口之间的滑动。 //(当时用的是MUI的轮播图),老板第一次向我妥协,抛弃了主窗口的滑动。改为消息页面的滑动,前几天测试,看来这个滑动也不能用了,因为在安卓4.3以下,滑动虽然能够进行,但是滑动之后的函数不能正确执行。。。。//此处我觉得不应该出现链接。。

时间继续转动,上传图片,本来好好的,只是后台抱怨,取不到图片名字。后来发现,addData只能添加字符串。。。说到这,发现plus.storage,也是只能储存字符串。。。。好歹存个数组对象啥的啊。。。。

最后测试推送,中间的坑我就不说了,至今我们测试还是能够拿到值为字符串的“null”的clientid,。。。。

上边的坑全部踩了一边之后,app终于能看了,可是发布到安卓4.4以下,哇塞,白屏了。。。。。。。找了好久的原因,最后发现是在安卓4.4以下,对于当前窗口这个概念有点误解,不是窗口本身,而是正在显示的窗口,终于改完了,发现点击按钮打不开页面。原因暂不清楚,不过抛弃mui。直接 plus.webview.open打开窗口就可以了。。。终于差不多能够兼容安卓4.4了,最后发现返回键处理又有问题,返回键触发的不是当前展示的窗口,而是当前展示的窗口的父级窗口。。。。。改了好久,还是没有头绪。。。。

产品都快发布了,老板看我的眼神都是红色的。。。。。。

为啥HBuilder火不起来,自己长啥样你心里没点B数吗????????

继续阅读 »

我今年才开始转业到互联网,之前的工作与互联网无关,从2017年4月的时候敲下第一句hello world,到如今,2017.12.19,时间的确太短了。说是新手完全没错,但是也有些不合适了,与其他无关,只因为有个人告诉我,工作半个月,你就不是新手了。

公司让我独立开发app,而且也没有其他前端帮助,遇到问题只能自己解决,考虑到自己的英语太差,所以在HBuilder全家桶和reactNative中,我选择了前者。

为了能够按时完成工作,我每天看H5 资料到晚上12点多,那时的我,完全webview和NativeObj的概念,为了完成app的启动广告逻辑,废了两天时间,终于明白,需要两个主页来启动app,,,//此处应该放一个分享连接,//2017.12.27添加 启动广告

没有老师教,刚开始一直把子页面当作新页面添加了,郁闷了好久。为了搞清楚子页面与新页面的区别,废了我好大心思,现在想想,我还记得当要把写H5 文档的人杀了的心情。那文档,根本就不是给新手看的,突然明白为什么使用HBuilder的人那么少了。这已经不是技术和审美的问题了,这设计师的头上一定有个坑,而且一定是大坑。记得我学习vue的时候,老师曾经调侃过AngularJS 的文档写的差,现在来看,他估计是没有读过H5 和mui的文档。 //此处应也该放一个分享连接,//2017.12.27新窗口&子页面

终于开始做主要内容了,首要内容是制作地图,按部就班的使用地图,废了一天的多的时间,终于搞明白地图该怎么搞,我记得当时,为了在地图中间添加一个固定的点,怎么搞都不行,层级不够,查了好久的资料终于明白地图的层级在HTML是最高的,怎么搞都不行,除非重新创建子html挂到上去,最后经过优化,添加了一个NativeObj上去。 //此处应该有链接,日后添加

说到地图,现在还是没有解决的两个问题,一个是通过经纬度查询地理信息,百度地图得到的信息不够详细,一个是高德地图在某些机型上在zoom层级大于14就会出现白屏,比如oppoR11,暂时无解

轮播图,做这个比较简单。说实话,MUI做的挺麻烦的,循环播放还得添加首尾两张图片。这个其实没什么难的,困难的是如何缓存新获得的轮播图片。因为下载图片是异步进行,就不能在下载完之后直接添加,本来我花了好多心思做缓存的,可是老板嫌我的进度慢,所以就把这个半成品放弃了,不过前几天NativeObj添加了自动轮播选项,我也把之前写的东西重构了一下,发现H5 团队比mui的技术强多了 //此处应该有链接//2017.12.27轮播图

说到下载与读取文件。这个地方又该喷官方文档了,就算这两个地方不是同一个人负责的,路径问题就不能统一解决一下吗???从图库选择的图片的路径,在plus.io居然不能直接使用,plus.io的克隆属性,到现在我都没有还成功过。

再后来,是图片预览,终于在muiapp示例上看到眼前一亮的东西了,最后发现把它用到app开发上边太坑,被老板吵了好久,终于发现NativeObj里边有图片预览功能,第一次觉得H5 不是完全的一无是处。终于有个功能是完整一点的了。后来测试图片预览会黑屏不显示图片,现在只能说图片有问题了。。。。

窗口的滑动切换,那时候终于明白H5 的文档该怎么看了,这次没有费太久,本来好好的窗口滑动切换,最后不得不放弃,因为当手指放在轮播图上边滑动,轮播图的滑动事件不能阻止窗口之间的滑动。 //(当时用的是MUI的轮播图),老板第一次向我妥协,抛弃了主窗口的滑动。改为消息页面的滑动,前几天测试,看来这个滑动也不能用了,因为在安卓4.3以下,滑动虽然能够进行,但是滑动之后的函数不能正确执行。。。。//此处我觉得不应该出现链接。。

时间继续转动,上传图片,本来好好的,只是后台抱怨,取不到图片名字。后来发现,addData只能添加字符串。。。说到这,发现plus.storage,也是只能储存字符串。。。。好歹存个数组对象啥的啊。。。。

最后测试推送,中间的坑我就不说了,至今我们测试还是能够拿到值为字符串的“null”的clientid,。。。。

上边的坑全部踩了一边之后,app终于能看了,可是发布到安卓4.4以下,哇塞,白屏了。。。。。。。找了好久的原因,最后发现是在安卓4.4以下,对于当前窗口这个概念有点误解,不是窗口本身,而是正在显示的窗口,终于改完了,发现点击按钮打不开页面。原因暂不清楚,不过抛弃mui。直接 plus.webview.open打开窗口就可以了。。。终于差不多能够兼容安卓4.4了,最后发现返回键处理又有问题,返回键触发的不是当前展示的窗口,而是当前展示的窗口的父级窗口。。。。。改了好久,还是没有头绪。。。。

产品都快发布了,老板看我的眼神都是红色的。。。。。。

为啥HBuilder火不起来,自己长啥样你心里没点B数吗????????

收起阅读 »

关于个推的补充说明

个推 个性推送 原生推送

写在前面

在看这篇文章时,请先参考我们的推送SDK配置指南,此文章是对配置指南的简要补充说明。

概要

不同平台对推送的处理机制是不一样的。所以推送后触发事件的机制也不一样。这里主要说明一下Android和iOS平台对两种消息:推送通知和透传消息 在什么情况下会触发监听事件click和receive。

Android平台

  • 应用不在线(杀掉进程),收不到通知或者透传消息。打开应用后会收到之前发的推送(有效时长默认为2小时)。
  • 应用在线(打开或者后台运行),通知或者透传都会进入通知栏,点击通知栏触发click事件
  • 不符合格式的透传消息,(推送通知不存在格式问题)才会触发receive事件,并且不会进入消息中心。

iOS平台

  • 应用打开时,不会进入消息中心,触发receive事件。
  • 应用不在线或者应用在后台运行,进入消息中心,点击通知栏触发click事件。
  • 个推平台暂不支持iOS发送推送通知,只能发送透传消息。

透传消息正确格式:

    {title:"通知标题",content:"通知内容",payload:"通知去干嘛这里可以自定义"}  
    {title:"通知标题",content:"通知内容",payload:{id:"5108397"}}

说明:payload节点可以填写普通字符串或者json格式字符串,如填写JSON格式字符串则在终端监听回调的PushMessage对象的payload属性则为JSON对象。

继续阅读 »

写在前面

在看这篇文章时,请先参考我们的推送SDK配置指南,此文章是对配置指南的简要补充说明。

概要

不同平台对推送的处理机制是不一样的。所以推送后触发事件的机制也不一样。这里主要说明一下Android和iOS平台对两种消息:推送通知和透传消息 在什么情况下会触发监听事件click和receive。

Android平台

  • 应用不在线(杀掉进程),收不到通知或者透传消息。打开应用后会收到之前发的推送(有效时长默认为2小时)。
  • 应用在线(打开或者后台运行),通知或者透传都会进入通知栏,点击通知栏触发click事件
  • 不符合格式的透传消息,(推送通知不存在格式问题)才会触发receive事件,并且不会进入消息中心。

iOS平台

  • 应用打开时,不会进入消息中心,触发receive事件。
  • 应用不在线或者应用在后台运行,进入消息中心,点击通知栏触发click事件。
  • 个推平台暂不支持iOS发送推送通知,只能发送透传消息。

透传消息正确格式:

    {title:"通知标题",content:"通知内容",payload:"通知去干嘛这里可以自定义"}  
    {title:"通知标题",content:"通知内容",payload:{id:"5108397"}}

说明:payload节点可以填写普通字符串或者json格式字符串,如填写JSON格式字符串则在终端监听回调的PushMessage对象的payload属性则为JSON对象。

收起阅读 »

iOS升级SDK后插件不能执行或者报错

iOS iOS打包 5+sdk

升级了最新sdk后通过Safari调试发现在调用插件的地方崩溃,升级前插件是好的,折腾了好长时间,无奈只能找客服,通过咨询发现:
最新的SDK注册插件不需要document.addEventListener("plusready", function(){},true);中注册插件,直接放外面注册就可以了
方法调用最好还是放在plusready事件之后, 是document.addEventListener('plusready') 不要用mui.plusready,或者加一个timeout也可以

希望帮到各位!

继续阅读 »

升级了最新sdk后通过Safari调试发现在调用插件的地方崩溃,升级前插件是好的,折腾了好长时间,无奈只能找客服,通过咨询发现:
最新的SDK注册插件不需要document.addEventListener("plusready", function(){},true);中注册插件,直接放外面注册就可以了
方法调用最好还是放在plusready事件之后, 是document.addEventListener('plusready') 不要用mui.plusready,或者加一个timeout也可以

希望帮到各位!

收起阅读 »