
iOS最新SDK升级后原生跳转web页面导致崩溃问题解决
升级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) {
}];

个推推送的整体解决方案(思路、源码)
.
说明
这是我 DC(DCloud)开发中遇到的第二个问题。
虽然个推功能强大,但是个推官网文档那叫一个简陋,导致众多开发者爬坑无数、心酸无比。
为什么要这样一个技术方案分享?
因为社区还没看到一个关于个推的整体解决方案,包含前台与后台的开发思路,而且开发源码。
希望对其他开发者提供点帮助,少踩几个坑,也希望 DC 更加完善。
.
推送效果
- 外推(外部推送)
简单的说,就是我们能够看到的推送。
比如,通知栏的推送列表,比如,苹果的临时横幅,我都称之为“外推”。
. - 内推(内部推送)
这种推送是隐藏性质的,它们不会出现在手机的通知栏或横幅,它们适用于这种场景:
①. APP 在前台时,收到推送时,会出现一个对话框来询问用户是否跳转。
②. 完全不提示用户,比如,用户账号在其他手机登录,APP 接收到推送时,强制用户退出。
. - 智推(智能推送)
APP 在前台时,发送内推。
APP 在后台或者关闭时,发送外推。
.
开发流程
- 个推注册
个推官网:http://getui.com/cn/index.html。
. - 配置推送
①. 配置个推参数(appid、appkey、appsecret)。
②. 配置自定义基座,方便调试。
. - 后台发送推送
定义一个推送的数据格式,便于前台、后台进行对接。
我在开发时,用的是个推官网提供的 PHP SDK(我对其进行了封装,具体见下方)。
. - 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 更加完善。
.
推送效果
- 外推(外部推送)
简单的说,就是我们能够看到的推送。
比如,通知栏的推送列表,比如,苹果的临时横幅,我都称之为“外推”。
. - 内推(内部推送)
这种推送是隐藏性质的,它们不会出现在手机的通知栏或横幅,它们适用于这种场景:
①. APP 在前台时,收到推送时,会出现一个对话框来询问用户是否跳转。
②. 完全不提示用户,比如,用户账号在其他手机登录,APP 接收到推送时,强制用户退出。
. - 智推(智能推送)
APP 在前台时,发送内推。
APP 在后台或者关闭时,发送外推。
.
开发流程
- 个推注册
个推官网:http://getui.com/cn/index.html。
. - 配置推送
①. 配置个推参数(appid、appkey、appsecret)。
②. 配置自定义基座,方便调试。
. - 后台发送推送
定义一个推送的数据格式,便于前台、后台进行对接。
我在开发时,用的是个推官网提供的 PHP SDK(我对其进行了封装,具体见下方)。
. - 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一个月的体验
我今年才开始转业到互联网,之前的工作与互联网无关,从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后插件不能执行或者报错
升级了最新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关于定位问题被拒
app里加入定位功能,info.plist里只是写了获取定位信息,没有写的更具体,开始也没有认真的区分实时定位和使用时定位,用的实时定位,其实项目需求来说使用时定位就可以了,结果就被苹果拒了。
收到了一份邮件在提交两天之后(苹果的效果提高了,两天就给审核了)。
- 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.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
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Javascript Math 和 ceil()、floor()、round()三个函数的区别
Math.ceil()执行向上舍入,即它总是将数值向上舍入为最接近的整数;
Math.floor()执行向下舍入,即它总是将数值向下舍入为最接近的整数;
Math.round()执行标准舍入,即它总是将数值四舍五入为最接近的整数。
alert(Math.ceil(25.9)); //26
alert(Math.ceil(25.5)); //26
alert(Math.ceil(25.1)); //26
alert(Math.round(25.9)); //26
alert(Math.round(25.5)); //26
alert(Math.round(25.1)); //25
alert(Math.floor(25.9)); //25
alert(Math.floor(25.5)); //25
alert(Math.floor(25.1)); //25
ECMAScript为保存数学公式和信息提供了公共位置,即Math对象。
Math对象提供的计算功能比我们自己直接在js中编写的计算功能快得多。
1.Math对象的属性
Math.E // 自然对数的底数,即常量e的值
Math.LN10 //10的自然对数
Math.LN2 //2的自然对数
Math.LOG2E //以2为底e的对数
Math.LOG10E // 以10为底的e的对数
Math.PI //π的值
Math.SQRT1_2 //1/2的平方根(即2的平凡根的倒数)
Math.SQRT2 // 2的平方根
2.min()和max()方法
这两个方法用于确定一组数组中的最小值和最大值。这两个方法都可以接收任意多个数值参数。
var max = Max.max(3,54,32,16);
alert(max); //54
这两个方法经常用于避免多余的循环和if语句中确定一组数的最大值。
要找到数组中的最大或最小值,可以像下面这样使用apply()方法。
```javascript
var values = [1,2,3,5,7,9];
var max = Math.max.apply(Math,values);
这个技巧的关键是把Math对象作为apply()的第一个参数,从而正确的设置this值。然后,可以将任何数组作为第二个参数。
3.舍入方法
Math.ceil( ) //向上舍入
Math.floor( ) //向下舍入
Math.round( ) //四舍五入
4.random()方法
Math.random()方法返回大于等于0小于1的一个随机数。对于某些站点来说,这个方法非常实用,因为可以利用它来随机显示一些名人名言和新闻事件。套用下面的公式。就可以从某个整数范围内随机选择一个值。
值 = Math.floor(Math.random( ) * 可能值的总数 + 第一个可能的值)
公式中用到了Math.floor()方法,这是因为Math.random()总返回一个小数值
公式中用到了Math.floor()方法,这是因为Math.random()总返回一个小数值。而用这个小数值乘以一个整数,然后再加上一个整数,最终结果仍然还是一个小数。例如:
var num = Math.floor(Math.random() * 10 + 1);//获取1到10之间的随机数
如果想要选择一个介于2到10之间的值,如下:
var num = Math.floor(Math.random( ) * 9 + 2); //思考,无限接近于0时是2,无限接近于1时是11,但由于永远小于1,因此Math.floor的最大值为10。
从2数到10要数9个数,因此可能值的总数就是9,而第一个可能的值就是2。
多数情况下,其实都可以通过一个函数来计算可能值的总数和第一个可能的值。
function selectFrom(lowerValue,upperValue) {
var choices = upperValue - lowerValue+ 1;
return Math.floor(Math.random() * choices + lowerValue);
}
var num = selectFrom(2,10);
alert(num); //介于2和10之间(包括2和10)的一个数值
函数selectFrom()
函数selectFrom()接受两个参数:应该返回的是最大值和最小值。而用最大值减最小值再加上1得到了可能值的总和,然后它又把这些数值套用了前面的公式中。利用这个函数,可以方便地从数组中随机取出一项。例如:
var colors = ["red","green","blue","yellow","purple"];
var color = colors[selectFrom(0,colors.length-1)];
alert(color); //任何一个颜色
5.其他方法
Math.abs(num) //返回num的绝对值
Math.exp(num) //返回Math.E的num次幂
Math.log(num) //返回num的自然对数
Math.pow(num,power) //返回num的power次幂
Math.sqrt(num) //返回num的平方根
Math.acos(x) //返回x的反余弦值
Math.asin(x) //返回x的反正弦值
Math.atan(x) //返回x的反正切值
Math.atan2(y,x) //返回y/x的反正切值
Math.cons(x) //返回x的余弦值
Math.sin(x) //返回x的正弦值
Math.tan(x) //返回x的正切值
Math.ceil()执行向上舍入,即它总是将数值向上舍入为最接近的整数;
Math.floor()执行向下舍入,即它总是将数值向下舍入为最接近的整数;
Math.round()执行标准舍入,即它总是将数值四舍五入为最接近的整数。
alert(Math.ceil(25.9)); //26
alert(Math.ceil(25.5)); //26
alert(Math.ceil(25.1)); //26
alert(Math.round(25.9)); //26
alert(Math.round(25.5)); //26
alert(Math.round(25.1)); //25
alert(Math.floor(25.9)); //25
alert(Math.floor(25.5)); //25
alert(Math.floor(25.1)); //25
ECMAScript为保存数学公式和信息提供了公共位置,即Math对象。
Math对象提供的计算功能比我们自己直接在js中编写的计算功能快得多。
1.Math对象的属性
Math.E // 自然对数的底数,即常量e的值
Math.LN10 //10的自然对数
Math.LN2 //2的自然对数
Math.LOG2E //以2为底e的对数
Math.LOG10E // 以10为底的e的对数
Math.PI //π的值
Math.SQRT1_2 //1/2的平方根(即2的平凡根的倒数)
Math.SQRT2 // 2的平方根
2.min()和max()方法
这两个方法用于确定一组数组中的最小值和最大值。这两个方法都可以接收任意多个数值参数。
var max = Max.max(3,54,32,16);
alert(max); //54
这两个方法经常用于避免多余的循环和if语句中确定一组数的最大值。
要找到数组中的最大或最小值,可以像下面这样使用apply()方法。
```javascript
var values = [1,2,3,5,7,9];
var max = Math.max.apply(Math,values);
这个技巧的关键是把Math对象作为apply()的第一个参数,从而正确的设置this值。然后,可以将任何数组作为第二个参数。
3.舍入方法
Math.ceil( ) //向上舍入
Math.floor( ) //向下舍入
Math.round( ) //四舍五入
4.random()方法
Math.random()方法返回大于等于0小于1的一个随机数。对于某些站点来说,这个方法非常实用,因为可以利用它来随机显示一些名人名言和新闻事件。套用下面的公式。就可以从某个整数范围内随机选择一个值。
值 = Math.floor(Math.random( ) * 可能值的总数 + 第一个可能的值)
公式中用到了Math.floor()方法,这是因为Math.random()总返回一个小数值
公式中用到了Math.floor()方法,这是因为Math.random()总返回一个小数值。而用这个小数值乘以一个整数,然后再加上一个整数,最终结果仍然还是一个小数。例如:
var num = Math.floor(Math.random() * 10 + 1);//获取1到10之间的随机数
如果想要选择一个介于2到10之间的值,如下:
var num = Math.floor(Math.random( ) * 9 + 2); //思考,无限接近于0时是2,无限接近于1时是11,但由于永远小于1,因此Math.floor的最大值为10。
从2数到10要数9个数,因此可能值的总数就是9,而第一个可能的值就是2。
多数情况下,其实都可以通过一个函数来计算可能值的总数和第一个可能的值。
function selectFrom(lowerValue,upperValue) {
var choices = upperValue - lowerValue+ 1;
return Math.floor(Math.random() * choices + lowerValue);
}
var num = selectFrom(2,10);
alert(num); //介于2和10之间(包括2和10)的一个数值
函数selectFrom()
函数selectFrom()接受两个参数:应该返回的是最大值和最小值。而用最大值减最小值再加上1得到了可能值的总和,然后它又把这些数值套用了前面的公式中。利用这个函数,可以方便地从数组中随机取出一项。例如:
var colors = ["red","green","blue","yellow","purple"];
var color = colors[selectFrom(0,colors.length-1)];
alert(color); //任何一个颜色
5.其他方法
Math.abs(num) //返回num的绝对值
Math.exp(num) //返回Math.E的num次幂
Math.log(num) //返回num的自然对数
Math.pow(num,power) //返回num的power次幂
Math.sqrt(num) //返回num的平方根
Math.acos(x) //返回x的反余弦值
Math.asin(x) //返回x的反正弦值
Math.atan(x) //返回x的反正切值
Math.atan2(y,x) //返回y/x的反正切值
Math.cons(x) //返回x的余弦值
Math.sin(x) //返回x的正弦值
Math.tan(x) //返回x的正切值
收起阅读 »

js的reduce()方法
reduce()和reduceRight()
var numbers= [65,44,12,4];
function getSum (total,num) {
return total+num;
}
function myFunction(item) {
document.getElementById("demo").innerHTML = numbers.reduce(getSum);
}
p158 7.9.5
数值的数组
var a = [1,2,3,4,5];
var sum = a.reduce(function(x,y){return x+y});//15
var product = a.reduce(function(x,y){return xy});//数组求积
var max = a.reduce(function(x,y){return (x>y)?x:y});//求最大值
对象
/
返回一个新对象,这个对象同时拥有o的属性和p的属性
如果o和p中有重名属性,使用p中的属性值
/
function union(o,p){return extend(extend({},o),p);}
/
把p中的可枚举属性复制到o中,并返回o
如果o和p中含有同名属性,则覆盖o中的属性
这个函数并不处理getter和setter以及复制属性
/
function extend(o,p) {
for (prop in p){ //遍历p中的所有属性
o[prop] = p[prop]; //将属性添加至o中
}
return o;
}
var objects = [{x:1},{y:2},{z:3}];
var merged = objects.reduce(union); // {xL1,y:2,z:3}
var objects = [{x:1,a:1},{y:2,a:2},{z:3,a:3}];
var leftunion = objects.reduce(union); //{x:1,y:2,z:3,a:1}
var rightunion = objects.recuceRight(union);// {x:1,y:2,z:3,a:3}
reduce()和reduceRight()
var numbers= [65,44,12,4];
function getSum (total,num) {
return total+num;
}
function myFunction(item) {
document.getElementById("demo").innerHTML = numbers.reduce(getSum);
}
p158 7.9.5
数值的数组
var a = [1,2,3,4,5];
var sum = a.reduce(function(x,y){return x+y});//15
var product = a.reduce(function(x,y){return xy});//数组求积
var max = a.reduce(function(x,y){return (x>y)?x:y});//求最大值
对象
/
返回一个新对象,这个对象同时拥有o的属性和p的属性
如果o和p中有重名属性,使用p中的属性值
/
function union(o,p){return extend(extend({},o),p);}
/
把p中的可枚举属性复制到o中,并返回o
如果o和p中含有同名属性,则覆盖o中的属性
这个函数并不处理getter和setter以及复制属性
/
function extend(o,p) {
for (prop in p){ //遍历p中的所有属性
o[prop] = p[prop]; //将属性添加至o中
}
return o;
}
var objects = [{x:1},{y:2},{z:3}];
var merged = objects.reduce(union); // {xL1,y:2,z:3}
var objects = [{x:1,a:1},{y:2,a:2},{z:3,a:3}];
var leftunion = objects.reduce(union); //{x:1,y:2,z:3,a:1}
var rightunion = objects.recuceRight(union);// {x:1,y:2,z:3,a:3}

js对象取值的两种方式
var obj ={a:3,b:4,c:5};
1.使用点的方式
var v = obj.a;
2. 使用中括号的方式
配合模板字符串可以动态取值${key}
var m = obj["a"];
在实际项目中,一般使用点会方便许多,但是如果是变量的话就不能使用点了,js会
理解为对象的key值,造成混淆
key是一个变量
var n = obj[key];
js 深层对象取值
var obj = {
a: {
a3: {
a31: 1,
}
}
};
var arr = ['a', 'a3', 'a31'];
// 返回1
arr.reduce(function (acc, key) { return acc && key in acc ? acc[key] : null; }, obj);
var arr2 = ['a', 'a3', 'a32'];
// 返回null
arr2.reduce(function (acc, key) { return acc && key in acc ? acc[key] : null; }, obj);
var obj ={a:3,b:4,c:5};
1.使用点的方式
var v = obj.a;
2. 使用中括号的方式
配合模板字符串可以动态取值${key}
var m = obj["a"];
在实际项目中,一般使用点会方便许多,但是如果是变量的话就不能使用点了,js会
理解为对象的key值,造成混淆
key是一个变量
var n = obj[key];
js 深层对象取值
var obj = {
a: {
a3: {
a31: 1,
}
}
};
var arr = ['a', 'a3', 'a31'];
// 返回1
arr.reduce(function (acc, key) { return acc && key in acc ? acc[key] : null; }, obj);
var arr2 = ['a', 'a3', 'a32'];
// 返回null
arr2.reduce(function (acc, key) { return acc && key in acc ? acc[key] : null; }, obj);

vue搭建脚手架入门
一、那么我们就从最简单的环境搭建开始:
安装node.js,从node.js官网下载并安装node,安装过程很简单,一路“下一步”就可以了(傻瓜式安装)。安装完成之后,打开命令行工具(win+r,然后输入cmd),输入 node -v,如下图,如果出现相应的版本号,则说明安装成功。
这里需要说明下,因为在官网下载安装node.js后,就已经自带npm(包管理工具)了,另需要注意的是npm的版本最好是3.x.x以上,以免对后续产生影响。
安装淘宝镜像,打开命令行工具,把这个(npm install -g cnpm --registry= https://registry.npm.taobao.org)复制(这里要手动复制就是用鼠标右键那个,具体为啥不多解释),安装这里是因为我们用的npm的服务器是外国,有的时候我们安装“依赖”的时候很很慢很慢超级慢,所以就用这个cnpm来安装我们说需要的“依赖”。安装完成之后输入 cnpm -v,如下图,如果出现相应的版本号,则说明安装成功。
安装webpack,打开命令行工具输入:npm install webpack -g,安装完成之后输入 webpack -v,如下图,如果出现相应的版本号,则说明安装成功。
安装vue-cli脚手架构建工具,打开命令行工具输入:npm install vue-cli -g,安装完成之后输入 vue -V(注意这里是大写的“V”),如下图,如果出现相应的版本号,则说明安装成功。
二、通过以上四步,我们需要准备的环境和工具都准备好了,接下来就开始使用vue-cli来构建项目
在硬盘上找一个文件夹放工程用的。这里有两种方式指定到相关目录:①cd 目录路径 ②如果以安装git的,在相关目录右键选择Git Bash Here
安装vue脚手架输入:vue init webpack exprice ,注意这里的“exprice” 是项目的名称可以说是随便的起名,但是需要主要的是“不能用中文”。
$ vue init webpack exprice --------------------- 这个是那个安装vue脚手架的命令
This will install Vue 2.x version of the template. ---------------------这里说明将要创建一个vue 2.x版本的项目
For Vue 1.x use: vue init webpack#1.0 exprice
? Project name (exprice) ---------------------项目名称
? Project name exprice
? Project description (A Vue.js project) ---------------------项目描述
? Project description A Vue.js project
? Author Datura --------------------- 项目创建者
? Author Datura
? Vue build (Use arrow keys)
? Vue build standalone
? Install vue-router? (Y/n) --------------------- 是否安装Vue路由,也就是以后是spa(但页面应用需要的模块)
? Install vue-router? Yes
? Use ESLint to lint your code? (Y/n) n ---------------------是否启用eslint检测规则,这里个人建议选no
? Use ESLint to lint your code? No
? Setup unit tests with Karma + Mocha? (Y/n)
? Setup unit tests with Karma + Mocha? Yes
? Setup e2e tests with Nightwatch? (Y/n)
? Setup e2e tests with Nightwatch? Yes
vue-cli · Generated "exprice".
To get started: --------------------- 这里说明如何启动这个服务
cd exprice
npm install
npm run dev
如下图:
cd 命令进入创建的工程目录,首先cd exprice(这里是自己建工程的名字);
安装项目依赖:npm install,因为自动构建过程中已存在package.json文件,所以这里直接安装依赖就行。不要从国内镜像cnpm安装(会导致后面缺了很多依赖库),但是但是如果真的安装“个把”小时也没成功那就用:cnpm install 吧
安装 vue 路由模块 vue-router 和网络请求模块 vue-resource,输入:cnpm install vue-router vue-resource --save。
创建完成的“exprice”目录如下:
下面我简单的说明下各个目录都是干嘛的:
启动项目,输入:npm run dev。服务启动成功后浏览器会默认打开一个“欢迎页面”,如下图:
注意:这里是默认服务启动的是本地的8080端口,所以请确保你的8080端口不被别的程序所占用。
作者:datura_lj
链接:http://www.jianshu.com/p/1626b8643676
來源:简书
一、那么我们就从最简单的环境搭建开始:
安装node.js,从node.js官网下载并安装node,安装过程很简单,一路“下一步”就可以了(傻瓜式安装)。安装完成之后,打开命令行工具(win+r,然后输入cmd),输入 node -v,如下图,如果出现相应的版本号,则说明安装成功。
这里需要说明下,因为在官网下载安装node.js后,就已经自带npm(包管理工具)了,另需要注意的是npm的版本最好是3.x.x以上,以免对后续产生影响。
安装淘宝镜像,打开命令行工具,把这个(npm install -g cnpm --registry= https://registry.npm.taobao.org)复制(这里要手动复制就是用鼠标右键那个,具体为啥不多解释),安装这里是因为我们用的npm的服务器是外国,有的时候我们安装“依赖”的时候很很慢很慢超级慢,所以就用这个cnpm来安装我们说需要的“依赖”。安装完成之后输入 cnpm -v,如下图,如果出现相应的版本号,则说明安装成功。
安装webpack,打开命令行工具输入:npm install webpack -g,安装完成之后输入 webpack -v,如下图,如果出现相应的版本号,则说明安装成功。
安装vue-cli脚手架构建工具,打开命令行工具输入:npm install vue-cli -g,安装完成之后输入 vue -V(注意这里是大写的“V”),如下图,如果出现相应的版本号,则说明安装成功。
二、通过以上四步,我们需要准备的环境和工具都准备好了,接下来就开始使用vue-cli来构建项目
在硬盘上找一个文件夹放工程用的。这里有两种方式指定到相关目录:①cd 目录路径 ②如果以安装git的,在相关目录右键选择Git Bash Here
安装vue脚手架输入:vue init webpack exprice ,注意这里的“exprice” 是项目的名称可以说是随便的起名,但是需要主要的是“不能用中文”。
$ vue init webpack exprice --------------------- 这个是那个安装vue脚手架的命令
This will install Vue 2.x version of the template. ---------------------这里说明将要创建一个vue 2.x版本的项目
For Vue 1.x use: vue init webpack#1.0 exprice
? Project name (exprice) ---------------------项目名称
? Project name exprice
? Project description (A Vue.js project) ---------------------项目描述
? Project description A Vue.js project
? Author Datura --------------------- 项目创建者
? Author Datura
? Vue build (Use arrow keys)
? Vue build standalone
? Install vue-router? (Y/n) --------------------- 是否安装Vue路由,也就是以后是spa(但页面应用需要的模块)
? Install vue-router? Yes
? Use ESLint to lint your code? (Y/n) n ---------------------是否启用eslint检测规则,这里个人建议选no
? Use ESLint to lint your code? No
? Setup unit tests with Karma + Mocha? (Y/n)
? Setup unit tests with Karma + Mocha? Yes
? Setup e2e tests with Nightwatch? (Y/n)
? Setup e2e tests with Nightwatch? Yes
vue-cli · Generated "exprice".
To get started: --------------------- 这里说明如何启动这个服务
cd exprice
npm install
npm run dev
如下图:
cd 命令进入创建的工程目录,首先cd exprice(这里是自己建工程的名字);
安装项目依赖:npm install,因为自动构建过程中已存在package.json文件,所以这里直接安装依赖就行。不要从国内镜像cnpm安装(会导致后面缺了很多依赖库),但是但是如果真的安装“个把”小时也没成功那就用:cnpm install 吧
安装 vue 路由模块 vue-router 和网络请求模块 vue-resource,输入:cnpm install vue-router vue-resource --save。
创建完成的“exprice”目录如下:
下面我简单的说明下各个目录都是干嘛的:
启动项目,输入:npm run dev。服务启动成功后浏览器会默认打开一个“欢迎页面”,如下图:
注意:这里是默认服务启动的是本地的8080端口,所以请确保你的8080端口不被别的程序所占用。
作者:datura_lj
链接:http://www.jianshu.com/p/1626b8643676
來源:简书