HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

【分享】【开源】优雅的H5下拉刷新。零依赖,高性能,多主题,易拓展

原生js 框架 开源 技术分享 源码分享 上拉加载 下拉刷新 HTML5


minirefresh-优雅的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发现,新的设计资源(如图标,官网设计等)

都可以通过IssuePR的方式提交!

贡献被采纳后会加入贡献者名单,如果有杰出贡献(如持续贡献),可以加入Manager小组,共同开发维护MiniRefresh

有共同参与项目意愿的,可以申请成为Member,成为Minirefresh真正的主人!

更多参考:https://minirefresh.github.io/minirefresh-doc/site/contribute/howtocontributor.html

讨论

注意,申请加入群时请添加验证信息,例如: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组织上的,希望有更多的人能参与,成为组织的一员,共同维护,毕竟在不断的分享交流中才能进步更快...

继续阅读 »


minirefresh-优雅的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发现,新的设计资源(如图标,官网设计等)

都可以通过IssuePR的方式提交!

贡献被采纳后会加入贡献者名单,如果有杰出贡献(如持续贡献),可以加入Manager小组,共同开发维护MiniRefresh

有共同参与项目意愿的,可以申请成为Member,成为Minirefresh真正的主人!

更多参考:https://minirefresh.github.io/minirefresh-doc/site/contribute/howtocontributor.html

讨论

注意,申请加入群时请添加验证信息,例如: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物理返回按钮的处理

  1. 首先我项目中没有用到mui.js,只是壳打包web项目和调用了h5+的支付
  2. 注册h5+的返回按钮事件 plus.key.addEventListener('backbutton',backListener,false);
  3. 事件中判断是不是在首页,然后进行处理
  4. w是创建的webview
  5. 目前实现到首页提示再按一次退出程序,和一级页面的返回,二级页面会只能返回一级,谁有好的解决方案望探讨

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();
}
});
}
}
}

继续阅读 »
  1. 首先我项目中没有用到mui.js,只是壳打包web项目和调用了h5+的支付
  2. 注册h5+的返回按钮事件 plus.key.addEventListener('backbutton',backListener,false);
  3. 事件中判断是不是在首页,然后进行处理
  4. w是创建的webview
  5. 目前实现到首页提示再按一次退出程序,和一级页面的返回,二级页面会只能返回一级,谁有好的解决方案望探讨

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可以进行大的优化

移动APP

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教程

定位 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定位升级为原生定位:

  1. 下载plusGeolocation.js(下载地址),并上传到M站的cdn服务器上
  2. 在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定位升级为原生定位:

  1. 下载plusGeolocation.js(下载地址),并上传到M站的cdn服务器上
  2. 在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教程

原生分享 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教程 - 分享到微信好友、朋友圈、微博等

分享 Share

简介

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即时通讯

WEBSOCKET

目标
技术选型
前端
后端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进行消息推送即可,具体参考个推官网


在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进行消息推送即可,具体参考个推官网


在WEB和IOS设备上都可以正常使用,但是在androidAPP中会出现一分钟左右就断线一次的问题。最后在一篇博客上找到了问题所在,由于android的默认浏览器内核支持的websocket版本为10,而Netty的默认实现目前还只支持到7.6,如果有兴趣自己升级的话,可以参考netty升级websocket草案10,

本文链接:http://blog.betweenfriends.cn/post/imnettysocketio.html

收起阅读 »

寻青岛可以做承接APP开发的团队或个人

外包

要求青岛本地,项目比较急,有意者联系 QQ 106865801

要求青岛本地,项目比较急,有意者联系 QQ 106865801

分享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实现
接口会提供接口文档