HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

分享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也可以

希望帮到各位!

收起阅读 »

iOS开发提交app关于定位问题被拒

iOS

app里加入定位功能,info.plist里只是写了获取定位信息,没有写的更具体,开始也没有认真的区分实时定位和使用时定位,用的实时定位,其实项目需求来说使用时定位就可以了,结果就被苹果拒了。

收到了一份邮件在提交两天之后(苹果的效果提高了,两天就给审核了)。

  1. 1.5 Legal: Privacy - Location Services
    Guideline 5.1.5 - Legal - Privacy - Location Services

Your app uses location services but does not clarify the purpose of its use in the location modal alert as required in the iOS Human Interface Guidelines.

Please see attached screenshots for details.

Next Steps

To resolve this issue, please specify the intended purpose of using the user's location in the location permission modal alert.

Resources

For additional information and instructions on configuring and presenting an alert, please review the Requesting Permission section of the iOS Human Interface Guidelines and the Information Property List Key Reference. You may also want to review the Technical Q&A QA1937: Resolving the Privacy-Sensitive Data App Rejection page for details on how to provide a usage description for permission request alerts.
收到的邮件内容
翻译:我们给你发了一个新消息关于你的应用,查看或回复消息,去解决中心在iTunes上连接。

点击Resolution Center,跳转到iTunes Connect,这个消息如下:

发件人 Apple

4.5 - Apps using background location services must provide a reason that clarifies the purpose of the use, using mechanisms described in the Human Interface Guidelines

解决:

1、增加一个提示:在info.plist文件里,NSLocationAlwaysUsageDescription 配上简洁的文字说明,告诉用户你为什么要访问他的位置!

2、回复邮件告诉苹果你的定位的使用:为什么加入了定位,在什么地方使用了定位

3、重新打一个上传包。

4、如果你的 app是实时定位,在 app描述里加上:使用后台定位会减少电池的使用寿命

作者:Sudoke
链接:http://www.jianshu.com/p/e9438ee7fee0
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

继续阅读 »

app里加入定位功能,info.plist里只是写了获取定位信息,没有写的更具体,开始也没有认真的区分实时定位和使用时定位,用的实时定位,其实项目需求来说使用时定位就可以了,结果就被苹果拒了。

收到了一份邮件在提交两天之后(苹果的效果提高了,两天就给审核了)。

  1. 1.5 Legal: Privacy - Location Services
    Guideline 5.1.5 - Legal - Privacy - Location Services

Your app uses location services but does not clarify the purpose of its use in the location modal alert as required in the iOS Human Interface Guidelines.

Please see attached screenshots for details.

Next Steps

To resolve this issue, please specify the intended purpose of using the user's location in the location permission modal alert.

Resources

For additional information and instructions on configuring and presenting an alert, please review the Requesting Permission section of the iOS Human Interface Guidelines and the Information Property List Key Reference. You may also want to review the Technical Q&A QA1937: Resolving the Privacy-Sensitive Data App Rejection page for details on how to provide a usage description for permission request alerts.
收到的邮件内容
翻译:我们给你发了一个新消息关于你的应用,查看或回复消息,去解决中心在iTunes上连接。

点击Resolution Center,跳转到iTunes Connect,这个消息如下:

发件人 Apple

4.5 - Apps using background location services must provide a reason that clarifies the purpose of the use, using mechanisms described in the Human Interface Guidelines

解决:

1、增加一个提示:在info.plist文件里,NSLocationAlwaysUsageDescription 配上简洁的文字说明,告诉用户你为什么要访问他的位置!

2、回复邮件告诉苹果你的定位的使用:为什么加入了定位,在什么地方使用了定位

3、重新打一个上传包。

4、如果你的 app是实时定位,在 app描述里加上:使用后台定位会减少电池的使用寿命

作者:Sudoke
链接:http://www.jianshu.com/p/e9438ee7fee0
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

收起阅读 »