HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

【交流分享】Dcloud开发APP心得系列之接口安全加密篇

技术分享 安全 加密 性能优化

================ 补充 ====================

此方法已被 @wenju 大神验证无效,仅作参考,还是开启混淆 js吧

==============================================================================

继代码性能优化篇再来一波。。。

使用Dcloud开发APP以来大家讨论关注比较多的问题估计就是APP代码安全性问题,很多人都在考虑加密、代码混淆啊,因为打包好的apk、ipa可以直接解压从中找到完整的页面文件,包括HTML、css、js代码等,怕怕。。。这样另有目的的人会不会直接copy你的代码打包新的APP?

其实我觉得那些静态页面根本就不值钱,就像web网页一样,照样能扒下所有的静态文件。打包时使用代码混淆功能感觉也得不偿失,消耗资源,影响APP性能。整个APP里面其实唯一值得保护的就只有manifest.json文件里面的sdk配置以及你的后台API数据接口,manifest.json文件配置Dcloud已经处理过了,所有是相对安全的。

能看到你的代码就能使用你的接口,那怎么保证你的接口数据是安全的?提供一个解决方案:

1、用户信息的储存

用户登录后存储用户唯一标识userkey加密串至localStorage,后续所有的用户相关数据获取传userkey参数获取。

2、接口签名认证机制

接口除传正常参数外再补加几个加密用参数:
timestamp:时间戳,获取客户端当前时间,如果时间差与服务器超过五分钟,则请求失败
appkey:签名appkey
sign:接口签名参数,使用md5摘要算法处理一定规律的字符串,所有接口需要参数 + timestamp + appkey + 按参数名排序后拼接成字符串(参数名+值)+ appsecret;

封装成一个统一处理接口data参数的方法

app.md5Data=function(oldData){  
    var data=JSON.parse(JSON.stringify(oldData||{})) //保存原始数据  
    data.appkey=appkey;  
    data.timestamp =new Date().toUTCString();    //**由于国外访问存在时差,会导致时间戳验证失败,故此处改为UTC标准时间,服务端处理转换**  
    var keys=[];  
    for(var k in data){  
        if(data[k]===0||data[k]===false||data[k]){  
            keys.push(k);  
        }else{  
            delete data[k]; //剔除空参数  
        }  
    }  

    keys=keys.sort();  
    var signStr='';  
    for(var i=0; i<keys.length; i  ){  
        signStr =keys[i].toLowerCase()+data[keys[i]];  
    }  

    data.sign=md5(signStr+appsecret);  
    return data;  
}

处理后的data为:

{  
    pageno: page,  
    pagesize: 20,  
    userkey: userkey,  
    type: type,  
    timestamp:'2017-08-23 16:39:50',  
    appkey:'.....',  
    sign:'A765E24767546E1109B5576538C87FF6'  
}  

mui.ajax({  
    data:app.md5Data({ .... })  
})

后台进行同样的方式生成sign,如果配对不一致则请求失败。

那么问题又来了,appkey、appsecret存储在哪里?肯定不能直接写死在js中,那样就没意义了,目前采用的方法是获取manifest.json配置的某个第三方sdk的appid,appkey,最后发现只有个推的配置信息是提供了方法获取的,果断使用plus.push.getClientInfo().appkey 、appsecret获取配置文件中的值(解压是获取不到sdk配置的)。

当然也可以直接写了上述方法的js文件打包的时候启用js原生混淆,这样明文写死也没有关系,解压打开你的app.js看到的全是乱码,只是不支持安卓4.0以下手机。

补充:没有提供获取方法的sdk配置也是可以通过native.js获取到(真不知道安不安全),方法链接: https://ask.dcloud.net.cn/article/488

这样就算别人拿到你的源码,也无法请求到你API接口的任何数据。

注:目前只想到这个方法存储appkey、appsecret,或许大家有更靠谱的方法存储,欢迎交流。

代码精简、性能优化篇

继续阅读 »

================ 补充 ====================

此方法已被 @wenju 大神验证无效,仅作参考,还是开启混淆 js吧

==============================================================================

继代码性能优化篇再来一波。。。

使用Dcloud开发APP以来大家讨论关注比较多的问题估计就是APP代码安全性问题,很多人都在考虑加密、代码混淆啊,因为打包好的apk、ipa可以直接解压从中找到完整的页面文件,包括HTML、css、js代码等,怕怕。。。这样另有目的的人会不会直接copy你的代码打包新的APP?

其实我觉得那些静态页面根本就不值钱,就像web网页一样,照样能扒下所有的静态文件。打包时使用代码混淆功能感觉也得不偿失,消耗资源,影响APP性能。整个APP里面其实唯一值得保护的就只有manifest.json文件里面的sdk配置以及你的后台API数据接口,manifest.json文件配置Dcloud已经处理过了,所有是相对安全的。

能看到你的代码就能使用你的接口,那怎么保证你的接口数据是安全的?提供一个解决方案:

1、用户信息的储存

用户登录后存储用户唯一标识userkey加密串至localStorage,后续所有的用户相关数据获取传userkey参数获取。

2、接口签名认证机制

接口除传正常参数外再补加几个加密用参数:
timestamp:时间戳,获取客户端当前时间,如果时间差与服务器超过五分钟,则请求失败
appkey:签名appkey
sign:接口签名参数,使用md5摘要算法处理一定规律的字符串,所有接口需要参数 + timestamp + appkey + 按参数名排序后拼接成字符串(参数名+值)+ appsecret;

封装成一个统一处理接口data参数的方法

app.md5Data=function(oldData){  
    var data=JSON.parse(JSON.stringify(oldData||{})) //保存原始数据  
    data.appkey=appkey;  
    data.timestamp =new Date().toUTCString();    //**由于国外访问存在时差,会导致时间戳验证失败,故此处改为UTC标准时间,服务端处理转换**  
    var keys=[];  
    for(var k in data){  
        if(data[k]===0||data[k]===false||data[k]){  
            keys.push(k);  
        }else{  
            delete data[k]; //剔除空参数  
        }  
    }  

    keys=keys.sort();  
    var signStr='';  
    for(var i=0; i<keys.length; i  ){  
        signStr =keys[i].toLowerCase()+data[keys[i]];  
    }  

    data.sign=md5(signStr+appsecret);  
    return data;  
}

处理后的data为:

{  
    pageno: page,  
    pagesize: 20,  
    userkey: userkey,  
    type: type,  
    timestamp:'2017-08-23 16:39:50',  
    appkey:'.....',  
    sign:'A765E24767546E1109B5576538C87FF6'  
}  

mui.ajax({  
    data:app.md5Data({ .... })  
})

后台进行同样的方式生成sign,如果配对不一致则请求失败。

那么问题又来了,appkey、appsecret存储在哪里?肯定不能直接写死在js中,那样就没意义了,目前采用的方法是获取manifest.json配置的某个第三方sdk的appid,appkey,最后发现只有个推的配置信息是提供了方法获取的,果断使用plus.push.getClientInfo().appkey 、appsecret获取配置文件中的值(解压是获取不到sdk配置的)。

当然也可以直接写了上述方法的js文件打包的时候启用js原生混淆,这样明文写死也没有关系,解压打开你的app.js看到的全是乱码,只是不支持安卓4.0以下手机。

补充:没有提供获取方法的sdk配置也是可以通过native.js获取到(真不知道安不安全),方法链接: https://ask.dcloud.net.cn/article/488

这样就算别人拿到你的源码,也无法请求到你API接口的任何数据。

注:目前只想到这个方法存储appkey、appsecret,或许大家有更靠谱的方法存储,欢迎交流。

代码精简、性能优化篇

收起阅读 »

ios端view制件app底部bar,使用的字体图标不显示问题。

tabbar view

android端使用正常

ios中字体图标不显示。

问题原因:在使用iconfont的时候,font family和dcloud内部的字体文件冲突(大概是这个样子)

解决方案:在icontfont网站,字体项目里-》更多操作-》编辑项目里修改font family的值


到这里问题解决,重新下载字体文件试试。

继续阅读 »

android端使用正常

ios中字体图标不显示。

问题原因:在使用iconfont的时候,font family和dcloud内部的字体文件冲突(大概是这个样子)

解决方案:在icontfont网站,字体项目里-》更多操作-》编辑项目里修改font family的值


到这里问题解决,重新下载字体文件试试。

收起阅读 »

【交流分享】Dcloud开发APP心得系列之代码性能优化篇

技术分享 HTML5 JavaScript 5 App开发

算起来免费使用Dcloud开发APP也两年多了,踩过的坑,蹚过的水一波又一波,在此先感谢多位大大的无私帮助。
废话不多说,先来一炮代码精简、性能优化论,个人观点,不喜勿喷。

引:h5+ APP本身就已经多了层调用,所以如果代码再不进行优化精简那就真的承受不起了

能不引入其他js框架绝不引入

虽说文件是直接存储在手机本地,但是一个webview页面载入如此多的方法,需要用到的方法却少之又少。
比如很多人都喜欢直接引入jquery之类的框架,大把的方法用起来是方便了,冗余的代码却影响了APP的性能,所以建议的方法是自己扩展封装一些常用的方法,如removeClass、hasClass、getIndex、siblings、selectValue等,这些方法再配合mui.js框架基本能满足页面交互需求,还有满足不了的那就原生js处理(当然有现成js插件肯定优先,但首选原生js插件)。

 //获取兄弟元素  
app.siblings=function(el,childEl) {  
    var r = [],n;  
    if(!childEl){  
        n = el.parentNode.children;  
    }else{  
        n = el.parentNode.querySelectorAll(childEl);  
        }  
    for(var i =0,pl= n.length;i<pl;i  ) {  
        if(n[i] !== el){ r.push(n[i]);}  
    }  
    return r;  
}  

//获取索引  
app.getIndex=function(el){  
    var child=el.parentNode.children;  
    for(var i=0; i<child.length; i  ){  
        if(el==child[i])  
            return i;  
    }  
}  
//是否包含某个class  
app.hasClass = function(el,name) {  
    return (el.className.indexOf(name)>=0);  
}

另外模块化、MVVM基本不太适用于开发h5 APP,因为都是单页面,一个页面引入的文件就那么三两个,再加入模块化反而显得复杂了,vuejs、ng之类的同样发挥不出他们的优势,反而增加冗余,页面的渲染引入轻量级js模板引擎拼接即可,如artTemplate(template.helper过滤器处理复杂数据)。

封装常用方法便于重复使用

APP里面用得最多的可能就是openWindow方法,如果每个地方都把这个方法copy一遍那整体增加的行数就多了,所以建议进行些简单的封装,如:

//打开新窗口  
app.openWV=function(id,extras,url){  
    mui.openWindow({  
        id:id,  
        url:url||id,  
        extras:extras,  
        show: {  
            autoShow:true,  
            aniShow: 'pop-in',  
            duration:300  
        },  
        waiting: {  
            autoShow: false  
        }  
    });  
}  
app.openWV('order-detial.html',parmas);

看代码应该会发现url是可以不传的,因为这里是直接将页面的url作为了webview的id来使用,这样使用的好处是直接通过页面文件名称获取webview,清晰明了,为保证url无需在文件中跳来跳去建议所有文件都放在根目录,使用一定的命名规范整理,如user相关页面均为user-name.html,order相关 order-name.html,这样url基本就用不上,也无需考虑文件夹层级关系。

其他常用的方法还有timeFormat(时间格式化)、

app.timeFormat=function(time,params){  
    var d=time?new Date(time):new Date(),  
        year=d.getFullYear(),  
        month=d.getMonth() 1,  
        day=d.getDate(),  
        hours=d.getHours(),  
        minutes=d.getMinutes(),  
        seconds=d.getSeconds();  

    if(month<10) month='0' month;  
    if(day<10) day='0' day;  
    if(hours<10) hours='0' hours;  
    if(minutes<10) minutes='0' minutes;  
    if(seconds<10) seconds='0' seconds;  

    if(params){  
        return {year:year,month:month,day:day};  
    }else{  
        return (year '-' month '-' day ' ' hours ':' minutes ':' seconds);  
    }  
}

parseDomImg(格式化详情页图片为占位图,用于实现图片延迟加载)、

app.parseDomImg=function(str){  
    var objE = document.createElement("div");  
    objE.innerHTML = str;  

    var imgs=objE.querySelectorAll('img');  
    for(var i=0; i<imgs.length; i  ){  
        var img=imgs[i];  
        img.setAttribute('data-delay',img.src);  
        img.src='images/blank.gif';  
        img.setAttribute('height','100px');  
        img.setAttribute('width','100%');  
    }  
    return objE.innerHTML;  
};

showLogin(params)(可在params对象中传递登录后回调需要的参数)、

app.showLogin=function(params){  
    app.openWV('login.html',{params:params});   
//此处params为json对象,可在login页面判断isEmptyObject(params),进行回调执行登录前用户操作  
};

isLogin(判断是否登录)、loadShare(加载分享功能)、reloadWV(请求失败加载重新载入提示及方法)等等等。。。

遵循能少操作DOM就尽量简化、能监听不批量绑定事件的原则

当我们频繁获取、操作DOM对象时消耗的资源肯定比操作内存中的变量要多得多,所以建议将频繁操作对象缓存到内存中,如某个结构内容需要每次交互去改变,那就把他缓存起来,var List = document.getElementById('List'); 后续通过List变量去操作,而不是每次获取。

for循环往DOM中插入新获取的数据列表那就更加不可取了,1w条数据就会插入1w次,这让DOM怎么“承受”,每次发生几何变化,页面都会进行重排、重绘,合理的方式是通过template或者其他方式拼接好需要新增的列表<li>,然后一次性插入DOM中。

List.insertAdjacentHTML('beforeend', template('initData', data));  //insertAdjacentHTML方法可以实现任意位置插入,用法自行百度

关于事件绑定,如果一块区域多个元素需要绑定同一个或者不同方法,那么千万不要for循环去给每个元素绑定方法或多个方法绑定,应使用on方法监听初始已存在父级元素的点击事件,一次监听解决千万次绑定。

mui('#List').on('tap', 'li', function() {  
    app.openWV(this.getAttribute('data-id'));                 
});  
mui('#parentBox').on('tap', '.child', function() {  
        var type=this.id;  
    switch(type){  
        case 'del':  
        break;  
        case 'save':  
        break;  
    }             
});

遵循能用css解决的问题不用js、图片处理的原则

能用一句addClass active解决整个列表多个元素的特殊处理的不要用for循环去重复操作,能添加一个class执行动画的不用js去处理。
能用css轻松写出来的效果不要使用图片或js实现,活用css3。
如切换至待付款状态订单需要提供checkbox批量支付,就只需要给列表容器添加isPay class显示出来即可;

合理利用预加载

虽然整个APP同时存在的webview是有限制的,但是频繁创建webview更是要消耗资源的,所以对于常用页面使用预加载处理,如详情页,初始预加载基本结构,每次需要打开时通过fire方法去重新loadData打开即可,如筛选页面,不同的页面可能需要展示不同的筛选条件、不同的筛选规则,那么也预加载他,每次打开时传递对应的handle参数判断规则,判断是否需要reset选中。

//操作页  
var filterParam={};  
mui.extend({  
    pageno: page,  
    pagesize: 20,  
    themeid: themeid,  
    userkey: userkey,  
    type: type,  
    isasc:isAsc  
},filterParam)   //拼接筛选页返回参数json  

filterBtn.addEventListener('tap', function() {  
    showFilter(ws.id,isEmptyObject(filterParam),'isGoods');  
});   // 打开筛选页面,传递当前页面id,初始filterParam为空reset选中,handle特殊处理规则  

document.addEventListener('updateFilter',function(e){  
    filterParam=e.detail.filterParam;  
    reloadPage();  
});  

//筛选页  
var checkArr={};  

if(handle=='isGoods'){.....}  
if(isReset){.....}  

mui.fire(plus.webview.getWebviewById(openUrl),'updateFilter',{filterParam:checkArr});  
mui.back();

经测试iPhone 6plus手机,重复操作打开商品详情---关闭--打开(商品详情包含大量的图片,非预加载),循环50次左右,问题就来了,商品详情页花屏了,渲染不出完整的页面了,只能重启应用。所以,常用页面建议使用预加载避免重复创建销毁。

合理处理图片延迟加载、合理使用分页加载、避免同时发起多个请求

移动端图片加载问题是很突出的,同时请求多个图片拖缓了重要内容的加载,所以延迟加载图片是非常必要的,当然控制图片大小也是基础,这里提供一个自己写的一个原生js的图片延迟加载插件,简单粗糙,但是实用,支持背景图片及图片元素渐入延迟加载、快速滑动停留超过N毫秒加载、指定div容器滑动加载、追加新增图片等。https://github.com/xielingxiao/delayimg

delayimg.init();   //初始化 (可混用图片元素、背景图片)  
delayimg.render();  // 追加图片方法

超过20条数据(具体看数据量)的列表最好进行分页加载,同时请求大量数据不仅让服务器查询耗时,客户端处理也需要时间,用户体验极度不好,所以分屏分页加载数据很有必要。

提供加载提示、渐入式展现页面,打开新页面提供菊花等待或空页面放入mui-icon mui-spinner菊花元素,待ajax请求到数据后替换。如果页面初始存在一些错乱或者不美观的结构,那么建议初始状态给mui-content添加.transparent{ opacity: 0;}透明处理,配合css3动画,数据插入DOM后移除透明渐入,给用户展现流畅协调的页面。

避免同时发起多个请求,最常见的场景就是父子页面加载问题(多子页面),如果一次性创建所有子页面并且加载子页面的数据那速度肯定慢了,所以index为入口页面,先插入多个子页面空壳,除home第一个子页面自动请求数据外,其他子页面默认不加载内容,只添加加载监听方法,用户操作点击时再加载内容。对于需要每次切入都需要更新数据的页面再监听其他的更新数据方法。

//index页面  
mui('.mui-bar-tab').on('tap','a',function(){  
    if(href!='home.html'){  
        mui.fire(plus.webview.getWebviewById(this.getAttribute('data-href')),'loadData');  
    }  
});  

//子页面  
var flag=false;  
document.addEventListener('loadData', function() {  
    if(!flag){  
               flag=true;  
               mui.init();  
               .....  // 执行页面数据加载等方法  
        }  
});

规范化你的代码

代码的规范在开发中是重中之重,有语意有规范的代码不仅方便自己,也不会困扰与你协作的‘同志’。
精简代码,能复用扩展的绝不滥用copy,统一的缩进、统一的命名规律、统一的编写方式,代码才能一目了然,具体的规范问题就不在这里详列了,可参考网上提供的。

好了,这篇就到此吧。。。暂时没想起更多,想起再补充吧。。。

已加载完....

更多分享预告:

轻松调试解决问题篇(已分享)

接口安全加密篇(已分享)

各种‘坑’解决方案汇总篇(持续单个问题更新)

继续阅读 »

算起来免费使用Dcloud开发APP也两年多了,踩过的坑,蹚过的水一波又一波,在此先感谢多位大大的无私帮助。
废话不多说,先来一炮代码精简、性能优化论,个人观点,不喜勿喷。

引:h5+ APP本身就已经多了层调用,所以如果代码再不进行优化精简那就真的承受不起了

能不引入其他js框架绝不引入

虽说文件是直接存储在手机本地,但是一个webview页面载入如此多的方法,需要用到的方法却少之又少。
比如很多人都喜欢直接引入jquery之类的框架,大把的方法用起来是方便了,冗余的代码却影响了APP的性能,所以建议的方法是自己扩展封装一些常用的方法,如removeClass、hasClass、getIndex、siblings、selectValue等,这些方法再配合mui.js框架基本能满足页面交互需求,还有满足不了的那就原生js处理(当然有现成js插件肯定优先,但首选原生js插件)。

 //获取兄弟元素  
app.siblings=function(el,childEl) {  
    var r = [],n;  
    if(!childEl){  
        n = el.parentNode.children;  
    }else{  
        n = el.parentNode.querySelectorAll(childEl);  
        }  
    for(var i =0,pl= n.length;i<pl;i  ) {  
        if(n[i] !== el){ r.push(n[i]);}  
    }  
    return r;  
}  

//获取索引  
app.getIndex=function(el){  
    var child=el.parentNode.children;  
    for(var i=0; i<child.length; i  ){  
        if(el==child[i])  
            return i;  
    }  
}  
//是否包含某个class  
app.hasClass = function(el,name) {  
    return (el.className.indexOf(name)>=0);  
}

另外模块化、MVVM基本不太适用于开发h5 APP,因为都是单页面,一个页面引入的文件就那么三两个,再加入模块化反而显得复杂了,vuejs、ng之类的同样发挥不出他们的优势,反而增加冗余,页面的渲染引入轻量级js模板引擎拼接即可,如artTemplate(template.helper过滤器处理复杂数据)。

封装常用方法便于重复使用

APP里面用得最多的可能就是openWindow方法,如果每个地方都把这个方法copy一遍那整体增加的行数就多了,所以建议进行些简单的封装,如:

//打开新窗口  
app.openWV=function(id,extras,url){  
    mui.openWindow({  
        id:id,  
        url:url||id,  
        extras:extras,  
        show: {  
            autoShow:true,  
            aniShow: 'pop-in',  
            duration:300  
        },  
        waiting: {  
            autoShow: false  
        }  
    });  
}  
app.openWV('order-detial.html',parmas);

看代码应该会发现url是可以不传的,因为这里是直接将页面的url作为了webview的id来使用,这样使用的好处是直接通过页面文件名称获取webview,清晰明了,为保证url无需在文件中跳来跳去建议所有文件都放在根目录,使用一定的命名规范整理,如user相关页面均为user-name.html,order相关 order-name.html,这样url基本就用不上,也无需考虑文件夹层级关系。

其他常用的方法还有timeFormat(时间格式化)、

app.timeFormat=function(time,params){  
    var d=time?new Date(time):new Date(),  
        year=d.getFullYear(),  
        month=d.getMonth() 1,  
        day=d.getDate(),  
        hours=d.getHours(),  
        minutes=d.getMinutes(),  
        seconds=d.getSeconds();  

    if(month<10) month='0' month;  
    if(day<10) day='0' day;  
    if(hours<10) hours='0' hours;  
    if(minutes<10) minutes='0' minutes;  
    if(seconds<10) seconds='0' seconds;  

    if(params){  
        return {year:year,month:month,day:day};  
    }else{  
        return (year '-' month '-' day ' ' hours ':' minutes ':' seconds);  
    }  
}

parseDomImg(格式化详情页图片为占位图,用于实现图片延迟加载)、

app.parseDomImg=function(str){  
    var objE = document.createElement("div");  
    objE.innerHTML = str;  

    var imgs=objE.querySelectorAll('img');  
    for(var i=0; i<imgs.length; i  ){  
        var img=imgs[i];  
        img.setAttribute('data-delay',img.src);  
        img.src='images/blank.gif';  
        img.setAttribute('height','100px');  
        img.setAttribute('width','100%');  
    }  
    return objE.innerHTML;  
};

showLogin(params)(可在params对象中传递登录后回调需要的参数)、

app.showLogin=function(params){  
    app.openWV('login.html',{params:params});   
//此处params为json对象,可在login页面判断isEmptyObject(params),进行回调执行登录前用户操作  
};

isLogin(判断是否登录)、loadShare(加载分享功能)、reloadWV(请求失败加载重新载入提示及方法)等等等。。。

遵循能少操作DOM就尽量简化、能监听不批量绑定事件的原则

当我们频繁获取、操作DOM对象时消耗的资源肯定比操作内存中的变量要多得多,所以建议将频繁操作对象缓存到内存中,如某个结构内容需要每次交互去改变,那就把他缓存起来,var List = document.getElementById('List'); 后续通过List变量去操作,而不是每次获取。

for循环往DOM中插入新获取的数据列表那就更加不可取了,1w条数据就会插入1w次,这让DOM怎么“承受”,每次发生几何变化,页面都会进行重排、重绘,合理的方式是通过template或者其他方式拼接好需要新增的列表<li>,然后一次性插入DOM中。

List.insertAdjacentHTML('beforeend', template('initData', data));  //insertAdjacentHTML方法可以实现任意位置插入,用法自行百度

关于事件绑定,如果一块区域多个元素需要绑定同一个或者不同方法,那么千万不要for循环去给每个元素绑定方法或多个方法绑定,应使用on方法监听初始已存在父级元素的点击事件,一次监听解决千万次绑定。

mui('#List').on('tap', 'li', function() {  
    app.openWV(this.getAttribute('data-id'));                 
});  
mui('#parentBox').on('tap', '.child', function() {  
        var type=this.id;  
    switch(type){  
        case 'del':  
        break;  
        case 'save':  
        break;  
    }             
});

遵循能用css解决的问题不用js、图片处理的原则

能用一句addClass active解决整个列表多个元素的特殊处理的不要用for循环去重复操作,能添加一个class执行动画的不用js去处理。
能用css轻松写出来的效果不要使用图片或js实现,活用css3。
如切换至待付款状态订单需要提供checkbox批量支付,就只需要给列表容器添加isPay class显示出来即可;

合理利用预加载

虽然整个APP同时存在的webview是有限制的,但是频繁创建webview更是要消耗资源的,所以对于常用页面使用预加载处理,如详情页,初始预加载基本结构,每次需要打开时通过fire方法去重新loadData打开即可,如筛选页面,不同的页面可能需要展示不同的筛选条件、不同的筛选规则,那么也预加载他,每次打开时传递对应的handle参数判断规则,判断是否需要reset选中。

//操作页  
var filterParam={};  
mui.extend({  
    pageno: page,  
    pagesize: 20,  
    themeid: themeid,  
    userkey: userkey,  
    type: type,  
    isasc:isAsc  
},filterParam)   //拼接筛选页返回参数json  

filterBtn.addEventListener('tap', function() {  
    showFilter(ws.id,isEmptyObject(filterParam),'isGoods');  
});   // 打开筛选页面,传递当前页面id,初始filterParam为空reset选中,handle特殊处理规则  

document.addEventListener('updateFilter',function(e){  
    filterParam=e.detail.filterParam;  
    reloadPage();  
});  

//筛选页  
var checkArr={};  

if(handle=='isGoods'){.....}  
if(isReset){.....}  

mui.fire(plus.webview.getWebviewById(openUrl),'updateFilter',{filterParam:checkArr});  
mui.back();

经测试iPhone 6plus手机,重复操作打开商品详情---关闭--打开(商品详情包含大量的图片,非预加载),循环50次左右,问题就来了,商品详情页花屏了,渲染不出完整的页面了,只能重启应用。所以,常用页面建议使用预加载避免重复创建销毁。

合理处理图片延迟加载、合理使用分页加载、避免同时发起多个请求

移动端图片加载问题是很突出的,同时请求多个图片拖缓了重要内容的加载,所以延迟加载图片是非常必要的,当然控制图片大小也是基础,这里提供一个自己写的一个原生js的图片延迟加载插件,简单粗糙,但是实用,支持背景图片及图片元素渐入延迟加载、快速滑动停留超过N毫秒加载、指定div容器滑动加载、追加新增图片等。https://github.com/xielingxiao/delayimg

delayimg.init();   //初始化 (可混用图片元素、背景图片)  
delayimg.render();  // 追加图片方法

超过20条数据(具体看数据量)的列表最好进行分页加载,同时请求大量数据不仅让服务器查询耗时,客户端处理也需要时间,用户体验极度不好,所以分屏分页加载数据很有必要。

提供加载提示、渐入式展现页面,打开新页面提供菊花等待或空页面放入mui-icon mui-spinner菊花元素,待ajax请求到数据后替换。如果页面初始存在一些错乱或者不美观的结构,那么建议初始状态给mui-content添加.transparent{ opacity: 0;}透明处理,配合css3动画,数据插入DOM后移除透明渐入,给用户展现流畅协调的页面。

避免同时发起多个请求,最常见的场景就是父子页面加载问题(多子页面),如果一次性创建所有子页面并且加载子页面的数据那速度肯定慢了,所以index为入口页面,先插入多个子页面空壳,除home第一个子页面自动请求数据外,其他子页面默认不加载内容,只添加加载监听方法,用户操作点击时再加载内容。对于需要每次切入都需要更新数据的页面再监听其他的更新数据方法。

//index页面  
mui('.mui-bar-tab').on('tap','a',function(){  
    if(href!='home.html'){  
        mui.fire(plus.webview.getWebviewById(this.getAttribute('data-href')),'loadData');  
    }  
});  

//子页面  
var flag=false;  
document.addEventListener('loadData', function() {  
    if(!flag){  
               flag=true;  
               mui.init();  
               .....  // 执行页面数据加载等方法  
        }  
});

规范化你的代码

代码的规范在开发中是重中之重,有语意有规范的代码不仅方便自己,也不会困扰与你协作的‘同志’。
精简代码,能复用扩展的绝不滥用copy,统一的缩进、统一的命名规律、统一的编写方式,代码才能一目了然,具体的规范问题就不在这里详列了,可参考网上提供的。

好了,这篇就到此吧。。。暂时没想起更多,想起再补充吧。。。

已加载完....

更多分享预告:

轻松调试解决问题篇(已分享)

接口安全加密篇(已分享)

各种‘坑’解决方案汇总篇(持续单个问题更新)

收起阅读 »

Android打包时使用自己生成的密钥出现的小坑

我开发快捎项目时,在最后进行打包测试时,使用keytool生成了一个私钥,准备进行Android打包,但却报错说私钥密码有误,我设置了一个keypass和一个storepass, 两个密码都填入去试过了,都说是私钥密码有误,顿时蒙逼。在网上搜索了好久,才发现大多数是在ios打包时出现问题,直到看到这个问题及答案(http://ask.dcloud.net.cn/question/4507),才找到了解决方案。这里有必要提示一下,在生成密钥时,一定要注意:

》》》 keypass 和storepass要一致,要一致
》》》 keypass 和storepass要一致,要一致
》》》 keypass 和storepass要一致,要一致
》》》 keypass 和storepass要一致,要一致
》》》 keypass 和storepass要一致,要一致
》》》 keypass 和storepass要一致,要一致

继续阅读 »

我开发快捎项目时,在最后进行打包测试时,使用keytool生成了一个私钥,准备进行Android打包,但却报错说私钥密码有误,我设置了一个keypass和一个storepass, 两个密码都填入去试过了,都说是私钥密码有误,顿时蒙逼。在网上搜索了好久,才发现大多数是在ios打包时出现问题,直到看到这个问题及答案(http://ask.dcloud.net.cn/question/4507),才找到了解决方案。这里有必要提示一下,在生成密钥时,一定要注意:

》》》 keypass 和storepass要一致,要一致
》》》 keypass 和storepass要一致,要一致
》》》 keypass 和storepass要一致,要一致
》》》 keypass 和storepass要一致,要一致
》》》 keypass 和storepass要一致,要一致
》》》 keypass 和storepass要一致,要一致

收起阅读 »

header 和 mui-scroll冲突,header渐变失效[ 已解决 ]

页面中同时使用header渐变和 mui-scroll,header渐变失效的解决方法:

mui('.mui-bar-transparent').transparent({
top: 0,
offset: 150,
duration: 16,
scrollby: document.querySelector('.mui-scroll-wrapper')
})

继续阅读 »

页面中同时使用header渐变和 mui-scroll,header渐变失效的解决方法:

mui('.mui-bar-transparent').transparent({
top: 0,
offset: 150,
duration: 16,
scrollby: document.querySelector('.mui-scroll-wrapper')
})

收起阅读 »

解决刷新列表

返回刷新

问题:
在列表上新增转到新页面,新增成功要返回列表,刷新列表,实现方法??

当前页面代码
mui.init({
beforeback: function(){
//获得你要前往页面的webview id
var Scanner = plus.webview.getWebviewById('lists');

//触发前往页面的自定义事件(例:AddNew),从而进行数据刷新  
mui.fire(Scanner,'AddNew');  

//返回true,继续页面关闭逻辑  
return true;  
}  
});  

前往页面lists.html代码
//添加AddNew自定义事件监听
window.addEventListener('AddNew',function(){
plus.webview.getWebviewById('刷新页面的ID lists').reload();
});

继续阅读 »

问题:
在列表上新增转到新页面,新增成功要返回列表,刷新列表,实现方法??

当前页面代码
mui.init({
beforeback: function(){
//获得你要前往页面的webview id
var Scanner = plus.webview.getWebviewById('lists');

//触发前往页面的自定义事件(例:AddNew),从而进行数据刷新  
mui.fire(Scanner,'AddNew');  

//返回true,继续页面关闭逻辑  
return true;  
}  
});  

前往页面lists.html代码
//添加AddNew自定义事件监听
window.addEventListener('AddNew',function(){
plus.webview.getWebviewById('刷新页面的ID lists').reload();
});

收起阅读 »

页面之间传值

页面传值

从列表页传递商品id,页面跳转到详情页:

列表页:
1) 首先预加载详情页
mui.init({
preloadPages:[{
id:'info.html',
url:'info.html',
}]
});

2) 批量点击事件
mui("#Gallery").on('tap','.mui-table-view-cell a',function(){
//获取id
var id = this.getAttribute("id");

        var infoPage = null;  
          //传值给详情页面,通知加载新数据  
         if(!infoPage){    
            infoPage = plus.webview.getWebviewById('info.html');    
          }    
          //触发详情页面的newsId事件    
          mui.fire(infoPage,'newsId',{    
            id:id    
          });    
        //打开详情页面              
          mui.openWindow({    
            id:'info.html'    

          });   

    })  

详情页监听事件:

    window.addEventListener('newsId',function(event){    

      var id = event.detail.id;   
         //得到传递来的产品id,访问相应接口, 进行相应的 ajax处理      

      })
继续阅读 »

从列表页传递商品id,页面跳转到详情页:

列表页:
1) 首先预加载详情页
mui.init({
preloadPages:[{
id:'info.html',
url:'info.html',
}]
});

2) 批量点击事件
mui("#Gallery").on('tap','.mui-table-view-cell a',function(){
//获取id
var id = this.getAttribute("id");

        var infoPage = null;  
          //传值给详情页面,通知加载新数据  
         if(!infoPage){    
            infoPage = plus.webview.getWebviewById('info.html');    
          }    
          //触发详情页面的newsId事件    
          mui.fire(infoPage,'newsId',{    
            id:id    
          });    
        //打开详情页面              
          mui.openWindow({    
            id:'info.html'    

          });   

    })  

详情页监听事件:

    window.addEventListener('newsId',function(event){    

      var id = event.detail.id;   
         //得到传递来的产品id,访问相应接口, 进行相应的 ajax处理      

      })
收起阅读 »

复选框实现全选全不选功能,可用于购物车

         (function (m){  
            document.getElementById('xuan').addEventListener('change',function(e){  
            var list = m('.cart_select');  
            if(e.target.checked){  

                list.each(function(){  
                    var ele = this;  
                    ele.checked = true;  

                })  
            }else{  
                list.each(function(){  
                    var ele = this;  
                    ele.checked = false;  

                })  
            }  

         })  
        })(mui); 
继续阅读 »
         (function (m){  
            document.getElementById('xuan').addEventListener('change',function(e){  
            var list = m('.cart_select');  
            if(e.target.checked){  

                list.each(function(){  
                    var ele = this;  
                    ele.checked = true;  

                })  
            }else{  
                list.each(function(){  
                    var ele = this;  
                    ele.checked = false;  

                })  
            }  

         })  
        })(mui); 
收起阅读 »

getAttribute() 用法

getAttribute() 方法返回指定属性名的属性值。

    例如:   
     document.getElementsByTagName("a")[0].getAttribute("target");   
    结果: _blank  
    实例:   
    mui(".mui-table-view").on('tap','.mui-table-view-cell',function(){  
       //获取id   
       var id = this.getAttribute("id");   
       //传值给详情页面,通知加载新数据   
       mui.fire(detail,'getDetail',{id:id});   
       //打开新闻详情   
       mui.openWindow({ id:'detail', url:'detail.html' });   
     }) 
继续阅读 »

getAttribute() 方法返回指定属性名的属性值。

    例如:   
     document.getElementsByTagName("a")[0].getAttribute("target");   
    结果: _blank  
    实例:   
    mui(".mui-table-view").on('tap','.mui-table-view-cell',function(){  
       //获取id   
       var id = this.getAttribute("id");   
       //传值给详情页面,通知加载新数据   
       mui.fire(detail,'getDetail',{id:id});   
       //打开新闻详情   
       mui.openWindow({ id:'detail', url:'detail.html' });   
     }) 
收起阅读 »

解决mui跨境访问问题

浅析遇到的问题~~~
入门第一坑: 跨境问题
解决方法:
1)在PHP文件(接口文档)中设置文件头:

     header("Access-Control-Allow-Origin: *");    
    header("Access-Control-Allow-Methods: GET, POST");    
    header("Access-Control-Allow-Headers: Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With");  

   注: 官方中提供的ajax实例模板  
  mui.ajax('http://server-name/login.php',{  
data:{  
    username:'username',  
    password:'password'  
},  
dataType:'json',//服务器返回json格式数据  
type:'post',//HTTP请求类型  
timeout:10000,//超时时间设置为10秒;  
headers:{'Content-Type':'application/json'},                    
success:function(data){  
    //服务器返回响应,根据响应结果,分析是否登录成功;  
    ...  
},  
error:function(xhr,type,errorThrown){  
    //异常处理;  
    console.log(type);  
}  

});
在具体操作时,要去掉 headers:{'Content-Type':'application/json'},

继续阅读 »

浅析遇到的问题~~~
入门第一坑: 跨境问题
解决方法:
1)在PHP文件(接口文档)中设置文件头:

     header("Access-Control-Allow-Origin: *");    
    header("Access-Control-Allow-Methods: GET, POST");    
    header("Access-Control-Allow-Headers: Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With");  

   注: 官方中提供的ajax实例模板  
  mui.ajax('http://server-name/login.php',{  
data:{  
    username:'username',  
    password:'password'  
},  
dataType:'json',//服务器返回json格式数据  
type:'post',//HTTP请求类型  
timeout:10000,//超时时间设置为10秒;  
headers:{'Content-Type':'application/json'},                    
success:function(data){  
    //服务器返回响应,根据响应结果,分析是否登录成功;  
    ...  
},  
error:function(xhr,type,errorThrown){  
    //异常处理;  
    console.log(type);  
}  

});
在具体操作时,要去掉 headers:{'Content-Type':'application/json'},

收起阅读 »

iOS打开系统设置定位、通讯录、相册权限设置

设置URL Scheme即可,如打开无线可用:
plus.runtime.launchApplication({
action: 'App-Prefs:root=WIFI'
}, function(e) {
console.log(JSON.stringify(e));
});

跳转设置本应用权限较复杂,详情请加819954692

继续阅读 »

设置URL Scheme即可,如打开无线可用:
plus.runtime.launchApplication({
action: 'App-Prefs:root=WIFI'
}, function(e) {
console.log(JSON.stringify(e));
});

跳转设置本应用权限较复杂,详情请加819954692

收起阅读 »

Mac系统svn无法使用,svn版本过低,提示javaHL not available

Mac版 SVN

发现Dcloud根本不考虑mac系统下开发者的死活啊,特别像我们这种老程序员只会用svn不会git,居然就不给支持了~
今天在mac的Hbuider里面准备svn一个项目下来,结果就是卡死,一直连不上。中途还报了一个错误:

立马我就反应过来,javaHL不对了,马上查询了svn接口如下:

当时就开始安装javaHL,mac系统安装javaHL比较麻烦,还要使用homebrew,所以先用mac终端安装了homebrew。
然后再用homebrew安装javaHL。(具体自行Google去)

安装完成之后,又蒙圈了。这种方式只能安装最新版的javaHL,我安装的时候是1.12.x,版本很高了。根据官方说的,对应的svn必须用1.12,
默认的Hbuider只有svn 1.6版本,看了论坛里面,居然有官方的人说内核不支持1.6以上。真是坑爹啊~

不服气的我决定看看通过eclipse市场能不能搞定:


居然还真的安装成功了~

然后svn倒入项目,一切顺利,OK!

继续阅读 »

发现Dcloud根本不考虑mac系统下开发者的死活啊,特别像我们这种老程序员只会用svn不会git,居然就不给支持了~
今天在mac的Hbuider里面准备svn一个项目下来,结果就是卡死,一直连不上。中途还报了一个错误:

立马我就反应过来,javaHL不对了,马上查询了svn接口如下:

当时就开始安装javaHL,mac系统安装javaHL比较麻烦,还要使用homebrew,所以先用mac终端安装了homebrew。
然后再用homebrew安装javaHL。(具体自行Google去)

安装完成之后,又蒙圈了。这种方式只能安装最新版的javaHL,我安装的时候是1.12.x,版本很高了。根据官方说的,对应的svn必须用1.12,
默认的Hbuider只有svn 1.6版本,看了论坛里面,居然有官方的人说内核不支持1.6以上。真是坑爹啊~

不服气的我决定看看通过eclipse市场能不能搞定:


居然还真的安装成功了~

然后svn倒入项目,一切顺利,OK!

收起阅读 »