
【分享】【开源】优雅的H5下拉刷新。零依赖,高性能,多主题,易拓展
特点
-
零依赖(原生JS实现,不依赖于任何库)
-
多平台支持。一套代码,多端运行,支持Android,iOS,主流浏览器
-
丰富的主题,官方提供多种主题(包括默认,applet-仿小程序,drawer3d-3d抽屉效果,taobao-仿淘宝等)
-
高性能。动画采用css3+硬件加速,在主流手机上流畅运行
-
良好的兼容性。支持和各种Scroll的嵌套(包括mui-scroll,IScroll,Swipe等),支持Vue环境下的使用
-
易拓展,三层架构,专门抽取UI层面,方便实现各种的主题,实现一套主题非常方便,而且几乎可以实现任何的效果
-
优雅的API和源码,API设计科学,简单,源码严谨,所有源码通过ESlint检测
-
完善的文档与示例,提供完善的showcase,以及文档
源码
https://github.com/minirefresh/minirefresh
https://www.npmjs.com/package/minirefresh
官网与文档
https://minirefresh.github.io/
效果
基础示例
1. 【基础新闻列表】最基本的下拉刷新使用
2. 【多列表单容器】每次切换菜单时刷新容器
3. 【多列表多容器】多个列表都有一个Minirefresh对象
4. 【Vue支持】支持Vue下的使用
嵌套示例
1. 【Mui-Slider】内部嵌套图片轮播
2. 【Mui-Scroll】嵌套在Mui-Scroll中
3. 【Swipe】嵌套在Swipe中
主题示例
1. 【applet】仿微信小程序主题
2. 【taobao】仿淘宝刷新主题
3. 【drawer3d】3D抽屉效果主题
4. 【drawer-slider】滑动抽屉效果主题
showcase
可以直接在线体验效果
https://minirefresh.github.io/minirefresh/examples/
贡献
minirefresh
需要你!
来为项目添砖加瓦,新的Idea,新的主题,重大Bug发现,新的设计资源(如图标,官网设计等)
都可以通过Issue
或PR
的方式提交!
贡献被采纳后会加入贡献者名单,如果有杰出贡献(如持续贡献),可以加入Manager
小组,共同开发维护MiniRefresh
有共同参与项目意愿的,可以申请成为Member
,成为Minirefresh真正的主人!
更多参考:https://minirefresh.github.io/minirefresh-doc/site/contribute/howtocontributor.html
讨论
-
QQ群(601988892)
注意,申请加入群时请添加验证信息,例如:minirefresh使用遇到问题等等
最后关于灵感与参考
核心架构是参考的我自己以前的项目 https://github.com/dailc/pulltorefresh-h5-iscroll,只不过把依赖IScroll换成了原生JS与CSS3实现,并且完全的重构与优化
做这个项目的灵感与原动力是受 https://github.com/mescroll/mescroll 启发,但是由于那个项目里的代码不符合我的个人风格,一些主题拓展也没有达到我的要求,因此我自己重新写了一个项目而不是基于mescroll拓展
还有就是写这个项目也是对自己的一种锻炼,里面包含了
- JS与CSS3的熟练运用,并进行合理架构
- ESlint严格的代码检测
- Gulp 自动构建
- Karma+Mocha单元测试(待完善)
- Circleci,Codecov,Sauce等自动集成与测试网址,
- Gitbook构建API与教程文档
- Hexo构建官方网站(待完善)
- 域名备案,CDN加速等(待完善)
- Npm发布与Github项目团队
当然了,迫于一些原因,没有用全新的ES6或TS写,而是用的ES5严格模式。
另外,这个项目是托管在Github的minirefresh组织上的,希望有更多的人能参与,成为组织的一员,共同维护,毕竟在不断的分享交流中才能进步更快...
特点
-
零依赖(原生JS实现,不依赖于任何库)
-
多平台支持。一套代码,多端运行,支持Android,iOS,主流浏览器
-
丰富的主题,官方提供多种主题(包括默认,applet-仿小程序,drawer3d-3d抽屉效果,taobao-仿淘宝等)
-
高性能。动画采用css3+硬件加速,在主流手机上流畅运行
-
良好的兼容性。支持和各种Scroll的嵌套(包括mui-scroll,IScroll,Swipe等),支持Vue环境下的使用
-
易拓展,三层架构,专门抽取UI层面,方便实现各种的主题,实现一套主题非常方便,而且几乎可以实现任何的效果
-
优雅的API和源码,API设计科学,简单,源码严谨,所有源码通过ESlint检测
-
完善的文档与示例,提供完善的showcase,以及文档
源码
https://github.com/minirefresh/minirefresh
https://www.npmjs.com/package/minirefresh
官网与文档
https://minirefresh.github.io/
效果
基础示例
1. 【基础新闻列表】最基本的下拉刷新使用
2. 【多列表单容器】每次切换菜单时刷新容器
3. 【多列表多容器】多个列表都有一个Minirefresh对象
4. 【Vue支持】支持Vue下的使用
嵌套示例
1. 【Mui-Slider】内部嵌套图片轮播
2. 【Mui-Scroll】嵌套在Mui-Scroll中
3. 【Swipe】嵌套在Swipe中
主题示例
1. 【applet】仿微信小程序主题
2. 【taobao】仿淘宝刷新主题
3. 【drawer3d】3D抽屉效果主题
4. 【drawer-slider】滑动抽屉效果主题
showcase
可以直接在线体验效果
https://minirefresh.github.io/minirefresh/examples/
贡献
minirefresh
需要你!
来为项目添砖加瓦,新的Idea,新的主题,重大Bug发现,新的设计资源(如图标,官网设计等)
都可以通过Issue
或PR
的方式提交!
贡献被采纳后会加入贡献者名单,如果有杰出贡献(如持续贡献),可以加入Manager
小组,共同开发维护MiniRefresh
有共同参与项目意愿的,可以申请成为Member
,成为Minirefresh真正的主人!
更多参考:https://minirefresh.github.io/minirefresh-doc/site/contribute/howtocontributor.html
讨论
-
QQ群(601988892)
注意,申请加入群时请添加验证信息,例如:minirefresh使用遇到问题等等
最后关于灵感与参考
核心架构是参考的我自己以前的项目 https://github.com/dailc/pulltorefresh-h5-iscroll,只不过把依赖IScroll换成了原生JS与CSS3实现,并且完全的重构与优化
做这个项目的灵感与原动力是受 https://github.com/mescroll/mescroll 启发,但是由于那个项目里的代码不符合我的个人风格,一些主题拓展也没有达到我的要求,因此我自己重新写了一个项目而不是基于mescroll拓展
还有就是写这个项目也是对自己的一种锻炼,里面包含了
- JS与CSS3的熟练运用,并进行合理架构
- ESlint严格的代码检测
- Gulp 自动构建
- Karma+Mocha单元测试(待完善)
- Circleci,Codecov,Sauce等自动集成与测试网址,
- Gitbook构建API与教程文档
- Hexo构建官方网站(待完善)
- 域名备案,CDN加速等(待完善)
- Npm发布与Github项目团队
当然了,迫于一些原因,没有用全新的ES6或TS写,而是用的ES5严格模式。
另外,这个项目是托管在Github的minirefresh组织上的,希望有更多的人能参与,成为组织的一员,共同维护,毕竟在不断的分享交流中才能进步更快...
收起阅读 »
关于webview嵌入web项目打包APP,android物理返回按钮的处理
- 首先我项目中没有用到mui.js,只是壳打包web项目和调用了h5+的支付
- 注册h5+的返回按钮事件 plus.key.addEventListener('backbutton',backListener,false);
- 事件中判断是不是在首页,然后进行处理
- w是创建的webview
- 目前实现到首页提示再按一次退出程序,和一级页面的返回,二级页面会只能返回一级,谁有好的解决方案望探讨
var first=null;
function backListener(){
//首次按键,提示‘再按一次退出应用’
if(w!=null){
var u=w.getURL();
if(u.lastIndexOf("welcome/index.htm")>0||u.lastIndexOf("ziyuan/index.htm")>0||u.lastIndexOf("xuqiu/index.htm")>0||u.lastIndexOf("paimai/index.htm")>0){
if (!first) {
console.log(u);
first = new Date().getTime();
plus.nativeUI.toast('再按一次退出程序');
setTimeout(function() {
first = null;
}, 1000);
} else {
if (new Date().getTime() - first < 1000) {
plus.runtime.quit();
}
}
}else{
w.canBack(function(e){
if(e.canBack){
w.back();
}else{
w.loadURL(weburl+'/welcome/index.htm');
}
});
w.canForward(function(e){
if(e.canForward){
w.forward();
}
});
}
}
}
- 首先我项目中没有用到mui.js,只是壳打包web项目和调用了h5+的支付
- 注册h5+的返回按钮事件 plus.key.addEventListener('backbutton',backListener,false);
- 事件中判断是不是在首页,然后进行处理
- w是创建的webview
- 目前实现到首页提示再按一次退出程序,和一级页面的返回,二级页面会只能返回一级,谁有好的解决方案望探讨
var first=null;
function backListener(){
//首次按键,提示‘再按一次退出应用’
if(w!=null){
var u=w.getURL();
if(u.lastIndexOf("welcome/index.htm")>0||u.lastIndexOf("ziyuan/index.htm")>0||u.lastIndexOf("xuqiu/index.htm")>0||u.lastIndexOf("paimai/index.htm")>0){
if (!first) {
console.log(u);
first = new Date().getTime();
plus.nativeUI.toast('再按一次退出程序');
setTimeout(function() {
first = null;
}, 1000);
} else {
if (new Date().getTime() - first < 1000) {
plus.runtime.quit();
}
}
}else{
w.canBack(function(e){
if(e.canBack){
w.back();
}else{
w.loadURL(weburl+'/welcome/index.htm');
}
});
w.canForward(function(e){
if(e.canForward){
w.forward();
}
});
}
}
}
收起阅读 »

APP的webview中拦截资源API可以进行大的优化
H5+里的那个拦截资源实在不好用啊,可以参考chrome插件的拦截。chrome的拦截只需要一个API,而不是像webview分拦截监听各种限制。
然后它可以分为多个阶段,如请求开始可以修改协议头,请求完成可以修改内容,重定向或则忽略都可以在回调中来实现。
所以来此提出建议,当然肯定涉及到很多方面,如果可以的话还是希望官方抽出些时间来优化下
H5+里的那个拦截资源实在不好用啊,可以参考chrome插件的拦截。chrome的拦截只需要一个API,而不是像webview分拦截监听各种限制。
然后它可以分为多个阶段,如请求开始可以修改协议头,请求完成可以修改内容,重定向或则忽略都可以在回调中来实现。
所以来此提出建议,当然肯定涉及到很多方面,如果可以的话还是希望官方抽出些时间来优化下
收起阅读 »
多级PopPicke用setSelectedValuer设置默认值二三级不生效问题
用下面这个方法设置,结果发现只有第一级菜单设置成功了,后面两级压根就没执行。
// 设定省初始值
cityPicker.pickers[0].setSelectedValue(100000, 0, function() {
// 设定市初始值
cityPicker.pickers[1].setSelectedValue(101000, 0, function() {
// 设定区初始值
cityPicker.pickers[2].setSelectedValue(101001);
});
});
查看mui.picker源代码发现:
Picker.prototype.setSelectedValue = function(value, duration, callback) {
var self = this;
for (var index in self.items) {
var item = self.items[index];
if (item.value == value) {
self.setSelectedIndex(index, duration, callback);
return;
}
}
};
for循环中是个return,结束了循环不再往下执行,换成break即可。
用下面这个方法设置,结果发现只有第一级菜单设置成功了,后面两级压根就没执行。
// 设定省初始值
cityPicker.pickers[0].setSelectedValue(100000, 0, function() {
// 设定市初始值
cityPicker.pickers[1].setSelectedValue(101000, 0, function() {
// 设定区初始值
cityPicker.pickers[2].setSelectedValue(101001);
});
});
查看mui.picker源代码发现:
Picker.prototype.setSelectedValue = function(value, duration, callback) {
var self = this;
for (var index in self.items) {
var item = self.items[index];
if (item.value == value) {
self.setSelectedIndex(index, duration, callback);
return;
}
}
};
for循环中是个return,结束了循环不再往下执行,换成break即可。
收起阅读 »
官方文档中h5+中的share组件实例代码有拼写错误
function shareAction(){
var s = shares[0];
if ( !s.authenticated ) {
s.authorize( functioin(){
console.log("认证完成!");
}, function(e){
console.log("未进行认证");
} )
}
}
上面是官方代码,将function写成了functioin
function shareAction(){
var s = shares[0];
if ( !s.authenticated ) {
s.authorize( functioin(){
console.log("认证完成!");
}, function(e){
console.log("未进行认证");
} )
}
}
上面是官方代码,将function写成了functioin

原生定位 - wap2app教程
场景说明
有定位需求的M站,通过手机浏览器访问时,经常因为定位问题导致用户体验下降、甚至用户流失,主要有以下几种情况:
- 部分手机不支持HTML5定位,因网络原因,非国产手机HTML5定位失败的概率更高
- 每次打开不同的M站,手机浏览器均会弹框提示定位权限,用户可能会误点击导致定位失败
- HTML5定位效率较低,重依赖定位的业务,在定位成功之前,无法显示业务数据,导致用户长时间等待
如下截图是大家经常碰见的情况:
wap2app运行在5+引擎环境下,5+引擎可以调用系统原生的定位,定位效率及精度远高于浏览器的HTML5定位,因此建议在有定位需求的页面,将HTML5定位替换升级为5+的原生定位。
升级方案
我们在GitHub上开源了一个plusto项目,该项目可以根据平台实现API的自动转换,比如在5+引擎环境下,将浏览器默认定位升级为5+原生定位,实现一套代码平滑迁移至多个平台。
目前关于定位的替换API已完善,参考plusGeolocation.js文件,该js文件判断在5+引擎环境下,将navigator.geolocation.getCurrentPosition()等方法重写为原生定位的方法,这样开发者就无需修改M站定位相关逻辑,就可以自动更换为原生定位。
目前geolocation/h5toplus.js已实现对如下3个HTML5定位方法的重写替换:
- navigator.geolocation.getCurrentPosition()
- navigator.geolocation.watchPosition()
- navigator.geolocation.clearwatch()
开发者只需按照如下步骤操作,即可将浏览器的HTML5定位升级为原生定位:
- 下载plusGeolocation.js(下载地址),并上传到M站的cdn服务器上
- 在M站上有定位需求的页面引用plusGeolocation.js,例如:
<script src="http://cdn.example.com/js/plusGeolocation.js" type="text/javascript" charset="utf-8"></script>
Tips1: plusGeolocation.js 是通过重写navigator.geolocation API来升级定位功能的,因此需确保在调用HTML5定位代码之前引用plusGeolocation.js;
Tips2:h5toplus.js文件开头就有运行环境的判断,若不是5+环境,则直接return,不会执行替换定位的他逻辑,因此开发者无需担心影响原有定位业务。
if(!navigator.userAgent.match(/Html5Plus/i)) {
//非5+引擎环境,直接return;
return;
}
场景说明
有定位需求的M站,通过手机浏览器访问时,经常因为定位问题导致用户体验下降、甚至用户流失,主要有以下几种情况:
- 部分手机不支持HTML5定位,因网络原因,非国产手机HTML5定位失败的概率更高
- 每次打开不同的M站,手机浏览器均会弹框提示定位权限,用户可能会误点击导致定位失败
- HTML5定位效率较低,重依赖定位的业务,在定位成功之前,无法显示业务数据,导致用户长时间等待
如下截图是大家经常碰见的情况:
wap2app运行在5+引擎环境下,5+引擎可以调用系统原生的定位,定位效率及精度远高于浏览器的HTML5定位,因此建议在有定位需求的页面,将HTML5定位替换升级为5+的原生定位。
升级方案
我们在GitHub上开源了一个plusto项目,该项目可以根据平台实现API的自动转换,比如在5+引擎环境下,将浏览器默认定位升级为5+原生定位,实现一套代码平滑迁移至多个平台。
目前关于定位的替换API已完善,参考plusGeolocation.js文件,该js文件判断在5+引擎环境下,将navigator.geolocation.getCurrentPosition()等方法重写为原生定位的方法,这样开发者就无需修改M站定位相关逻辑,就可以自动更换为原生定位。
目前geolocation/h5toplus.js已实现对如下3个HTML5定位方法的重写替换:
- navigator.geolocation.getCurrentPosition()
- navigator.geolocation.watchPosition()
- navigator.geolocation.clearwatch()
开发者只需按照如下步骤操作,即可将浏览器的HTML5定位升级为原生定位:
- 下载plusGeolocation.js(下载地址),并上传到M站的cdn服务器上
- 在M站上有定位需求的页面引用plusGeolocation.js,例如:
<script src="http://cdn.example.com/js/plusGeolocation.js" type="text/javascript" charset="utf-8"></script>
Tips1: plusGeolocation.js 是通过重写navigator.geolocation API来升级定位功能的,因此需确保在调用HTML5定位代码之前引用plusGeolocation.js;
Tips2:h5toplus.js文件开头就有运行环境的判断,若不是5+环境,则直接return,不会执行替换定位的他逻辑,因此开发者无需担心影响原有定位业务。
if(!navigator.userAgent.match(/Html5Plus/i)) {
//非5+引擎环境,直接return;
return;
}
收起阅读 »

原生分享 - wap2app教程
体验差距
因web能力限制,M站仅支持wap方式的分享,分享体验很糟糕,如下是一种典型实现(参考下方截图):
- 点击微信分享后,显示一个二维码,用户需要启动微信扫描二维码,先在微信中打开这篇文章,然后再通过微信右上角的菜单分享出去;分享路径太长,操作麻烦;
- 点击微博分享,需要登录微博wap站,完成授权后才能分享
wap2app运行在5+ 引擎下,是可以通过HTML5+的share模块直接调起系统原生分享的,同样场景,稍作改造,在5+引擎环境下调用原生分享,则体验会大大改观,如下为调用原生分享后的截图:
5+引擎还可以调起系统支持的更多分享,比如微博、QQ、短信、邮件等,如下为点击“更多分享”后的示例:
很明显,通过5+引擎调起原生分享后,分享路径更短、体验更好,更有利于分享内容的传播。
改造方案
要实现如上的分享体验,开发者只需要对M站稍作修改,判断是5+引擎的环境下,调用HTML5+的share模块API即可实现。
为简化开发,DCloud封装了plusShare.js函数,开发者引入该函数后,只需调用一个API,即可完成原生分享的改造。
引用plusShare.js文件
对M站上有分享功能的页面引入plusShare.js,js下载地址:GitHub,建议放在M站的cdn服务器上,如下:
<script src="http://cdn.example.com/js/plusShare.js" type="text/javascript" charset="utf-8"></script>
修改点击分享的实现
修改分享按钮的点击事件,假设之前的分享按钮点击实现如下:
document.getElementById("share").addEventListener("click", function() {
//原有wap分享实现
});
引入plusShare后,参考如下方式修改代码即可:
document.getElementById("share").addEventListener("click", function() {
if(navigator.userAgent.indexOf("Html5Plus") > -1) {
//5+ 原生分享
window.plusShare({
title: "my-app-name",//应用名字
content: "分享具体内容",
href: location.href,//分享出去后,点击跳转地址
thumbs: ["http://m.example.com/imgs/1.png"] //分享缩略图
}, function(result) {
//分享回调
});
} else {
//原有wap分享实现
}
});
注意:
- 具体分享内容,开发者可以根据M站业务自定义设置,比如动态读取当前页面顶部大图作为thumbs参数
- plusShare.js的具体用户参考plusShare教程
体验差距
因web能力限制,M站仅支持wap方式的分享,分享体验很糟糕,如下是一种典型实现(参考下方截图):
- 点击微信分享后,显示一个二维码,用户需要启动微信扫描二维码,先在微信中打开这篇文章,然后再通过微信右上角的菜单分享出去;分享路径太长,操作麻烦;
- 点击微博分享,需要登录微博wap站,完成授权后才能分享
wap2app运行在5+ 引擎下,是可以通过HTML5+的share模块直接调起系统原生分享的,同样场景,稍作改造,在5+引擎环境下调用原生分享,则体验会大大改观,如下为调用原生分享后的截图:
5+引擎还可以调起系统支持的更多分享,比如微博、QQ、短信、邮件等,如下为点击“更多分享”后的示例:
很明显,通过5+引擎调起原生分享后,分享路径更短、体验更好,更有利于分享内容的传播。
改造方案
要实现如上的分享体验,开发者只需要对M站稍作修改,判断是5+引擎的环境下,调用HTML5+的share模块API即可实现。
为简化开发,DCloud封装了plusShare.js函数,开发者引入该函数后,只需调用一个API,即可完成原生分享的改造。
引用plusShare.js文件
对M站上有分享功能的页面引入plusShare.js,js下载地址:GitHub,建议放在M站的cdn服务器上,如下:
<script src="http://cdn.example.com/js/plusShare.js" type="text/javascript" charset="utf-8"></script>
修改点击分享的实现
修改分享按钮的点击事件,假设之前的分享按钮点击实现如下:
document.getElementById("share").addEventListener("click", function() {
//原有wap分享实现
});
引入plusShare后,参考如下方式修改代码即可:
document.getElementById("share").addEventListener("click", function() {
if(navigator.userAgent.indexOf("Html5Plus") > -1) {
//5+ 原生分享
window.plusShare({
title: "my-app-name",//应用名字
content: "分享具体内容",
href: location.href,//分享出去后,点击跳转地址
thumbs: ["http://m.example.com/imgs/1.png"] //分享缩略图
}, function(result) {
//分享回调
});
} else {
//原有wap分享实现
}
});
注意:
- 具体分享内容,开发者可以根据M站业务自定义设置,比如动态读取当前页面顶部大图作为thumbs参数
- plusShare.js的具体用户参考plusShare教程

plusShare教程 - 分享到微信好友、朋友圈、微博等
简介
plusShare是基于HTML5+的share模块封装的社交分享函数,开发者只需调用一个API,即可调起微信好友、微信朋友圈、系统更多分享功能。
plusShare目前支持:
- 自动获取分享服务
- 自动检测微信是否已安装
- 设置分享消息标题、内容、缩略图、链接地址
plusShare的开源地址为GitHub
API介绍
plusShare只有一个方法,如下调用即可:
plusShare(message,callback);
其中:
- message:分享内容设置
- callback:分享结果回调
message
分享消息内容,类型为Object,主要包括如下属性:
- title:分享消息的标题,类型为String,目前仅分享到微信好友时支持。
- content:分享消息的文字内容,类型为String
- href:分享的页面链接(用户点击消息时的跳转地址),类型为String
- thumbs:分享消息的缩略图,类型为Array;
callback
分享结束的回调函数,函数包含一个参数res,boolean类型,分别表示:
- true:分享成功
- false:分享失败
备注:系统分享(更多分享)暂不支持判断分享是否成功
代码示例
如下是一个示例代码:
document.getElementById("share").addEventListener("click", function() {
//分享内容,开发者可自定义
var message = {
title: "plusShare示例", //应用名字
content: "plusShare基于HTML5+的share模块,开发者只需调用一个API,即可调起微信好友、微信朋友圈、系统更多分享功能",
href: "http://www.dcloud.io/hellomui", //分享出去后,点击跳转地址
thumbs: ["http://img-cdn-qiniu.dcloud.net.cn/icon3.png"] //分享缩略图
}
//调起分享
plusShare(message, function(res) {
//分享回调函数
if(res) {
plus.nativeUI.toast("分享成功");
} else {
plus.nativeUI.toast("分享失败");
}
})
});
真机运行,点击分享到微信消息、微信朋友圈结果如下:
点击“更多分享”,然后选择短信,结果如下:
简介
plusShare是基于HTML5+的share模块封装的社交分享函数,开发者只需调用一个API,即可调起微信好友、微信朋友圈、系统更多分享功能。
plusShare目前支持:
- 自动获取分享服务
- 自动检测微信是否已安装
- 设置分享消息标题、内容、缩略图、链接地址
plusShare的开源地址为GitHub
API介绍
plusShare只有一个方法,如下调用即可:
plusShare(message,callback);
其中:
- message:分享内容设置
- callback:分享结果回调
message
分享消息内容,类型为Object,主要包括如下属性:
- title:分享消息的标题,类型为String,目前仅分享到微信好友时支持。
- content:分享消息的文字内容,类型为String
- href:分享的页面链接(用户点击消息时的跳转地址),类型为String
- thumbs:分享消息的缩略图,类型为Array;
callback
分享结束的回调函数,函数包含一个参数res,boolean类型,分别表示:
- true:分享成功
- false:分享失败
备注:系统分享(更多分享)暂不支持判断分享是否成功
代码示例
如下是一个示例代码:
document.getElementById("share").addEventListener("click", function() {
//分享内容,开发者可自定义
var message = {
title: "plusShare示例", //应用名字
content: "plusShare基于HTML5+的share模块,开发者只需调用一个API,即可调起微信好友、微信朋友圈、系统更多分享功能",
href: "http://www.dcloud.io/hellomui", //分享出去后,点击跳转地址
thumbs: ["http://img-cdn-qiniu.dcloud.net.cn/icon3.png"] //分享缩略图
}
//调起分享
plusShare(message, function(res) {
//分享回调函数
if(res) {
plus.nativeUI.toast("分享成功");
} else {
plus.nativeUI.toast("分享失败");
}
})
});
真机运行,点击分享到微信消息、微信朋友圈结果如下:
点击“更多分享”,然后选择短信,结果如下:

个推+nettysocketio实现IM即时通讯
目标
技术选型
前端
后端java
MAVEN配置
离线消息
坑
相关材料
目标
实现WEB和APP通用的即时通讯功能。一份代码同时支持各种WEB浏览器以及IOS,Android环境下的APP
技术选型
使用基于netty的nettysocketio实现,后台为JS,前台为HTML5页面,部署时采用nginx中转,java部署在tomcat上
前端
var socket
if(/^plus|android/.test(this.agent)) {
//android的默认浏览器内核不支持websocket10草案
socket = socketIO.connect(LContext.imUrl, {
transports: ['polling']
})
} else {
socket = socketIO.connect(LContext.imUrl)
}
socket.on('reconnect_attempt', function() {
socket.io.opts.transports = ['polling']
})
socket.on('connect', function() {
var data = lpage.user.getData()
////console.log('连接成功..')
////console.log('<span class="connect-msg">Client has connected to the server!</span>\n' + JSON.stringify(data))
//登记当前会话关联的用户
socket.emit('startConnection', {
extra: {
id: data.id,
token: data.token
}
})
})
socket.on('startConnection', function() {
////console.log('连接到服务器')
lpage.socket = socket
// document.querySelector('header .mui-title').innerText = lpage.fromUserName
})
//注册事件,服务器通过事件调用本方法
socket.on('push', function(data, ackCallback) {
////console.log('接收消息' + JSON.stringify(data))
lpage.addNews(data)
if(ackCallback) {
////console.log('接收到消息后,返回消息给服务器确认')
ackCallback('返回到服务器的确认消息')
}
})
socket.on('disconnect', function() {
////console.log('断开连接')
lpage.socket = undefined
setTimeout(function() {
lpage.initConnection()
}, 5000)
//5秒后尝试恢复连接
// document.querySelector('header .mui-title').innerText = '未连接(点击重新连接)'
})
// socket.on('error', (error) => {
// ////console.log('error' + JSON.stringify(error))
// })
//
// socket.on('connect_error', (error) => {
// ////console.log('error' + JSON.stringify(error))
// })
//
// socket.on('connect_timeout', (error) => {
// ////console.log('error' + JSON.stringify(error))
// })
awk
后端java
@Service
public class IMHandleService implements ApplicationListener<ContextRefreshedEvent> {
Logger logger = Logger.getLogger(IMHandleService.class);
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
logger.info("运行init event on IMHandleService");
if (event.getApplicationContext().getParent() != null)// root
// application
// context
// 没有parent,他就是老大.
{
// 需要执行的逻辑代码,当spring容器初始化完成后就会执行该方法。
logger.info("\n\n\n\n\n__\n\n\n已经加载了ContextRefreshedEvent\n\n\n\n");
return;
}
// 或者下面这种方式
// if (event.getApplicationContext().getDisplayName().equals("Root
// WebApplicationContext")) {
// System.out.println("\n\n\n\n\n加载一次的 \n\n ____\n\n\n\n");
//
// logger.info("\n\n\n\n\n__\n\n\n加载一次的
// \n\n_____\n\n");
// }
Configuration config = new Configuration();
config.setHostname("localhost");
config.setPort(10086);
config.setUpgradeTimeout(60000000);// 设置websocket过期时间
// config.setFirstDataTimeout(60000000);
// config.setPingInterval(28);
SocketIOServer server = new SocketIOServer(config);
logger.info(server.getConfiguration().getFirstDataTimeout() + " " + server.getConfiguration().getPingInterval()
- " " + server.getConfiguration().getUpgradeTimeout() + " "
- server.getConfiguration().getPingTimeout());
server.addConnectListener(new ConnectListener() {// 添加客户端连接监听器
@Override
public void onConnect(SocketIOClient client) {
logger.info(client.getRemoteAddress().toString() + " " + client.getTransport().getValue() + " "- client.getHandshakeData().getUrl() + " 接入" + client.getSessionId());
// 调用客户端的事件
client.sendEvent("startConnection", "hello");
}
});
server.addDisconnectListener(new DisconnectListener() {
@Override
public void onDisconnect(SocketIOClient client) {
logger.info(client.getRemoteAddress().toString() + " " + client.getTransport().getValue() + " " - client.getHandshakeData().getUrl() + " 断开" + client.getSessionId());
// 清理连接的session
// 检测登录状态,已登录
// 保存当前会话
String userId = sessionUserMap.get("sessionUserMap", client.getSessionId().toString());
if (userId == null) {
return;
}
// 清除在线状态
sessionUserMap.delete("sessionUserMap", client.getSessionId().toString());
userChatSession.remove(SESSION_STORAGE_PREFIX + userId, client.getSessionId().toString());
logger.info("清理session userId= " + userId);
logger.info("session列表长度 " + userChatSession.members(SESSION_STORAGE_PREFIX + userId).toArray().length);
}
});
/**- 监听事件,客户端连接成功后调用本方法
*/
server.addEventListener("startConnection", Operation.class, new DataListener<Operation>() {
@Override
public void onData(final SocketIOClient client, Operation data, AckRequest ackRequest) {
if (data.extra == null || data.extra.id == null || data.extra.token == null
|| !tokenManager.checkToken(new TokenModel(data.extra.id.toString(), data.extra.token))) {
return;
}
// 检测登录状态,已登录
// 保存当前会话
sessionUserMap.put("sessionUserMap", client.getSessionId().toString(), data.extra.id.toString());
userChatSession.add(SESSION_STORAGE_PREFIX + data.extra.id.toString(),
client.getSessionId().toString());
logger.info("确认连接成功,绑定userId= " + data.extra.id.toString() + " sessionId "
- 监听事件,客户端连接成功后调用本方法
- client.getSessionId().toString());
logger.info("session列表长度 " - userChatSession.members(SESSION_STORAGE_PREFIX + data.extra.id.toString()).toArray().length);
// 握手
// if (data.getMessage().equals("hello")) {
// Long userid = data.getUser();
// logger.info(Thread.currentThread().getName() +
// "web读取到的userid:" + userid);
// // send message back to client with ack callback
// // WITH data
// client.sendEvent("push", new
// AckCallback<String>(String.class) {
// @Override
// public void onSuccess(String result) {
// logger.info("ack from client: " + client.getSessionId() + "
// data: " + result);
// }
// }, context.SESSION_TIME);
//
// } else {
// logger.info("行情接收到了不应该有的web客户端请求1111...");
// }
}
});
/**- 监听事件,客户端通过事件调用本方法
*/
server.addEventListener("message", Operation.class, new DataListener<Operation>() {
@Override
public void onData(final SocketIOClient client, Operation data, AckRequest ackRequest) {
// 握手
if (data.getMessage().equals("hello")) {
Long userid = data.getTargetuser();
logger.info(Thread.currentThread().getName() + "web读取到的userid:" + userid);
// send message back to client with ack callback
// WITH data
client.sendEvent("push", new AckCallback<String>(String.class) {
@Override
public void onSuccess(String result) {
logger.info("接收到客户端反馈 " + client.getSessionId() + " data: " + result);
}
});
} else {
logger.info("接收到了不应该有的web客户端请求...");
}
}
});
if (serverBuff == null) {
logger.info("初始化 IMHandleService的 监听server");
try {
server.start();
serverBuff = server;
} catch (Exception e) {
e.printStackTrace();
logger.error("\n\nIM服务器启动失败...\n\n");
}
// try {
// Thread.sleep(Integer.MAX_VALUE);
// } catch (InterruptedException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
// server.stop();
} else {
logger.info(" IMHandleService的 监听server已启动");
}
}
@PreDestroy
public void closeSocket() {
logger.info(" IMHandleService 关闭中...");
if (serverBuff != null) {
serverBuff.stop();
for (String sessionId : sessionUserMap.keys("sessionUserMap")) {
String userId = sessionUserMap.get("sessionUserMap", sessionId);
userChatSession.remove(SESSION_STORAGE_PREFIX + userId, sessionId);
sessionUserMap.delete("sessionUserMap", sessionId);
logger.info("清理session " + userId + " 当前长度为" + sessionUserMap.keys("sessionUserMap").size() + " "
- 监听事件,客户端通过事件调用本方法
- userChatSession.size(SESSION_STORAGE_PREFIX + userId));
}
logger.info(" IMHandleService 已关闭完成");
}
}
}
http
MAVEN配置
<dependency>
<groupId>com.corundumstudio.socketio</groupId>
<artifactId>netty-socketio</artifactId>
<version>1.7.12</version>
</dependency>
xml
离线消息
对于所有消息,默认都通过在线服务发送,当发送失败时,启用个推的JAVA SDK进行消息推送即可,具体参考个推官网
- client.getHandshakeData().getUrl() + " 接入" + client.getSessionId());
坑
在WEB和IOS设备上都可以正常使用,但是在androidAPP中会出现一分钟左右就断线一次的问题。最后在一篇博客上找到了问题所在,由于android的默认浏览器内核支持的websocket版本为10,而Netty的默认实现目前还只支持到7.6,如果有兴趣自己升级的话,可以参考netty升级websocket草案10,
本文链接:http://blog.betweenfriends.cn/post/imnettysocketio.html
目标
技术选型
前端
后端java
MAVEN配置
离线消息
坑
相关材料
目标
实现WEB和APP通用的即时通讯功能。一份代码同时支持各种WEB浏览器以及IOS,Android环境下的APP
技术选型
使用基于netty的nettysocketio实现,后台为JS,前台为HTML5页面,部署时采用nginx中转,java部署在tomcat上
前端
var socket
if(/^plus|android/.test(this.agent)) {
//android的默认浏览器内核不支持websocket10草案
socket = socketIO.connect(LContext.imUrl, {
transports: ['polling']
})
} else {
socket = socketIO.connect(LContext.imUrl)
}
socket.on('reconnect_attempt', function() {
socket.io.opts.transports = ['polling']
})
socket.on('connect', function() {
var data = lpage.user.getData()
////console.log('连接成功..')
////console.log('<span class="connect-msg">Client has connected to the server!</span>\n' + JSON.stringify(data))
//登记当前会话关联的用户
socket.emit('startConnection', {
extra: {
id: data.id,
token: data.token
}
})
})
socket.on('startConnection', function() {
////console.log('连接到服务器')
lpage.socket = socket
// document.querySelector('header .mui-title').innerText = lpage.fromUserName
})
//注册事件,服务器通过事件调用本方法
socket.on('push', function(data, ackCallback) {
////console.log('接收消息' + JSON.stringify(data))
lpage.addNews(data)
if(ackCallback) {
////console.log('接收到消息后,返回消息给服务器确认')
ackCallback('返回到服务器的确认消息')
}
})
socket.on('disconnect', function() {
////console.log('断开连接')
lpage.socket = undefined
setTimeout(function() {
lpage.initConnection()
}, 5000)
//5秒后尝试恢复连接
// document.querySelector('header .mui-title').innerText = '未连接(点击重新连接)'
})
// socket.on('error', (error) => {
// ////console.log('error' + JSON.stringify(error))
// })
//
// socket.on('connect_error', (error) => {
// ////console.log('error' + JSON.stringify(error))
// })
//
// socket.on('connect_timeout', (error) => {
// ////console.log('error' + JSON.stringify(error))
// })
awk
后端java
@Service
public class IMHandleService implements ApplicationListener<ContextRefreshedEvent> {
Logger logger = Logger.getLogger(IMHandleService.class);
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
logger.info("运行init event on IMHandleService");
if (event.getApplicationContext().getParent() != null)// root
// application
// context
// 没有parent,他就是老大.
{
// 需要执行的逻辑代码,当spring容器初始化完成后就会执行该方法。
logger.info("\n\n\n\n\n__\n\n\n已经加载了ContextRefreshedEvent\n\n\n\n");
return;
}
// 或者下面这种方式
// if (event.getApplicationContext().getDisplayName().equals("Root
// WebApplicationContext")) {
// System.out.println("\n\n\n\n\n加载一次的 \n\n ____\n\n\n\n");
//
// logger.info("\n\n\n\n\n__\n\n\n加载一次的
// \n\n_____\n\n");
// }
Configuration config = new Configuration();
config.setHostname("localhost");
config.setPort(10086);
config.setUpgradeTimeout(60000000);// 设置websocket过期时间
// config.setFirstDataTimeout(60000000);
// config.setPingInterval(28);
SocketIOServer server = new SocketIOServer(config);
logger.info(server.getConfiguration().getFirstDataTimeout() + " " + server.getConfiguration().getPingInterval()
- " " + server.getConfiguration().getUpgradeTimeout() + " "
- server.getConfiguration().getPingTimeout());
server.addConnectListener(new ConnectListener() {// 添加客户端连接监听器
@Override
public void onConnect(SocketIOClient client) {
logger.info(client.getRemoteAddress().toString() + " " + client.getTransport().getValue() + " "- client.getHandshakeData().getUrl() + " 接入" + client.getSessionId());
// 调用客户端的事件
client.sendEvent("startConnection", "hello");
}
});
server.addDisconnectListener(new DisconnectListener() {
@Override
public void onDisconnect(SocketIOClient client) {
logger.info(client.getRemoteAddress().toString() + " " + client.getTransport().getValue() + " " - client.getHandshakeData().getUrl() + " 断开" + client.getSessionId());
// 清理连接的session
// 检测登录状态,已登录
// 保存当前会话
String userId = sessionUserMap.get("sessionUserMap", client.getSessionId().toString());
if (userId == null) {
return;
}
// 清除在线状态
sessionUserMap.delete("sessionUserMap", client.getSessionId().toString());
userChatSession.remove(SESSION_STORAGE_PREFIX + userId, client.getSessionId().toString());
logger.info("清理session userId= " + userId);
logger.info("session列表长度 " + userChatSession.members(SESSION_STORAGE_PREFIX + userId).toArray().length);
}
});
/**- 监听事件,客户端连接成功后调用本方法
*/
server.addEventListener("startConnection", Operation.class, new DataListener<Operation>() {
@Override
public void onData(final SocketIOClient client, Operation data, AckRequest ackRequest) {
if (data.extra == null || data.extra.id == null || data.extra.token == null
|| !tokenManager.checkToken(new TokenModel(data.extra.id.toString(), data.extra.token))) {
return;
}
// 检测登录状态,已登录
// 保存当前会话
sessionUserMap.put("sessionUserMap", client.getSessionId().toString(), data.extra.id.toString());
userChatSession.add(SESSION_STORAGE_PREFIX + data.extra.id.toString(),
client.getSessionId().toString());
logger.info("确认连接成功,绑定userId= " + data.extra.id.toString() + " sessionId "
- 监听事件,客户端连接成功后调用本方法
- client.getSessionId().toString());
logger.info("session列表长度 " - userChatSession.members(SESSION_STORAGE_PREFIX + data.extra.id.toString()).toArray().length);
// 握手
// if (data.getMessage().equals("hello")) {
// Long userid = data.getUser();
// logger.info(Thread.currentThread().getName() +
// "web读取到的userid:" + userid);
// // send message back to client with ack callback
// // WITH data
// client.sendEvent("push", new
// AckCallback<String>(String.class) {
// @Override
// public void onSuccess(String result) {
// logger.info("ack from client: " + client.getSessionId() + "
// data: " + result);
// }
// }, context.SESSION_TIME);
//
// } else {
// logger.info("行情接收到了不应该有的web客户端请求1111...");
// }
}
});
/**- 监听事件,客户端通过事件调用本方法
*/
server.addEventListener("message", Operation.class, new DataListener<Operation>() {
@Override
public void onData(final SocketIOClient client, Operation data, AckRequest ackRequest) {
// 握手
if (data.getMessage().equals("hello")) {
Long userid = data.getTargetuser();
logger.info(Thread.currentThread().getName() + "web读取到的userid:" + userid);
// send message back to client with ack callback
// WITH data
client.sendEvent("push", new AckCallback<String>(String.class) {
@Override
public void onSuccess(String result) {
logger.info("接收到客户端反馈 " + client.getSessionId() + " data: " + result);
}
});
} else {
logger.info("接收到了不应该有的web客户端请求...");
}
}
});
if (serverBuff == null) {
logger.info("初始化 IMHandleService的 监听server");
try {
server.start();
serverBuff = server;
} catch (Exception e) {
e.printStackTrace();
logger.error("\n\nIM服务器启动失败...\n\n");
}
// try {
// Thread.sleep(Integer.MAX_VALUE);
// } catch (InterruptedException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
// server.stop();
} else {
logger.info(" IMHandleService的 监听server已启动");
}
}
@PreDestroy
public void closeSocket() {
logger.info(" IMHandleService 关闭中...");
if (serverBuff != null) {
serverBuff.stop();
for (String sessionId : sessionUserMap.keys("sessionUserMap")) {
String userId = sessionUserMap.get("sessionUserMap", sessionId);
userChatSession.remove(SESSION_STORAGE_PREFIX + userId, sessionId);
sessionUserMap.delete("sessionUserMap", sessionId);
logger.info("清理session " + userId + " 当前长度为" + sessionUserMap.keys("sessionUserMap").size() + " "
- 监听事件,客户端通过事件调用本方法
- userChatSession.size(SESSION_STORAGE_PREFIX + userId));
}
logger.info(" IMHandleService 已关闭完成");
}
}
}
http
MAVEN配置
<dependency>
<groupId>com.corundumstudio.socketio</groupId>
<artifactId>netty-socketio</artifactId>
<version>1.7.12</version>
</dependency>
xml
离线消息
对于所有消息,默认都通过在线服务发送,当发送失败时,启用个推的JAVA SDK进行消息推送即可,具体参考个推官网
- client.getHandshakeData().getUrl() + " 接入" + client.getSessionId());
坑
在WEB和IOS设备上都可以正常使用,但是在androidAPP中会出现一分钟左右就断线一次的问题。最后在一篇博客上找到了问题所在,由于android的默认浏览器内核支持的websocket版本为10,而Netty的默认实现目前还只支持到7.6,如果有兴趣自己升级的话,可以参考netty升级websocket草案10,
本文链接:http://blog.betweenfriends.cn/post/imnettysocketio.html
收起阅读 »
分享mui启动第三方应用遇到的坑 android&ios
if ( plus.os.name == "Android" ) {
plus.runtime.launchApplication( {
pname:"com.tencent.mm"}, function ( e ) {
plus.nativeUI.confirm( "检查到您未安装\"微信\",是否到商城搜索下载?", function(i){
if ( i.index == 0 ) {
androidMarket( "com.tencent.mm" );
}
} );
} );
} else if ( plus.os.name == "iOS" ) {
plus.runtime.launchApplication( {
action:"weixin://RnUbAwvEilb1rU9g9yBU"}, function ( e ) {
plus.nativeUI.confirm( "检查到您未安装\"微信\",是否到商城搜索下载?", function(i){
if ( i.index == 0 ) {
i osAppstore( "itunes.apple.com/cn/app/wechat/id414478124?mt=8" );
}
} );
} );
}
上边例子是启动微信的,如果是自己公司开发的app 就换成响应的
android和ios不同点
1 pname 和 action
2 android用包名启动 ios用scheme启动
if ( plus.os.name == "Android" ) {
plus.runtime.launchApplication( {
pname:"com.tencent.mm"}, function ( e ) {
plus.nativeUI.confirm( "检查到您未安装\"微信\",是否到商城搜索下载?", function(i){
if ( i.index == 0 ) {
androidMarket( "com.tencent.mm" );
}
} );
} );
} else if ( plus.os.name == "iOS" ) {
plus.runtime.launchApplication( {
action:"weixin://RnUbAwvEilb1rU9g9yBU"}, function ( e ) {
plus.nativeUI.confirm( "检查到您未安装\"微信\",是否到商城搜索下载?", function(i){
if ( i.index == 0 ) {
i osAppstore( "itunes.apple.com/cn/app/wechat/id414478124?mt=8" );
}
} );
} );
}
上边例子是启动微信的,如果是自己公司开发的app 就换成响应的
android和ios不同点
1 pname 和 action
2 android用包名启动 ios用scheme启动

找个兼职的MUI前端 稳定长期合作的
来个能稳定长期合作的联系下QQ16800606
太黑的离谱的就算了哦~
项目会出好设计搞,需要用MUI实现
接口会提供接口文档
来个能稳定长期合作的联系下QQ16800606
太黑的离谱的就算了哦~
项目会出好设计搞,需要用MUI实现
接口会提供接口文档