HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

easyConfig配置 - wap2app教程

easyConfig wap2app

综述

sitemap中的webviewParameter配置其实是HTML5Plus规范中的Webview的参数设置。
除了配置Webview的参数外,开发者还会有其他业务需求。
在wap2app的设计中,复杂的扩展需求是通过app.js自行编程实现的。
但实际开发中,有很多较为通用的处理工作,为了减少开发者的重复工作量,官方在wap2app的sitemap下面增加了easyConfig节点,封装了一些常用操作,通过简单的配置,就可以完成原来需要很多编码才能完成的工作。

下面是目前支持的easyConfig配置。

back

用户按下back按键或点击顶部标题栏的返回箭头时,将触发wap2app的后退逻辑。
常规情况下,back按钮是关闭当前Webview并显示上一个Webview。
但我们有时需定制back的逻辑。
关于后退的定制,在back节点下进行配置。
目前back节点下有before和history这2个配置。

before

若页面有弹出层时,此时用户按下back按键,其实不应该把这个Webview关闭。
理想的效果是按下back时关闭弹出的浮层,当浮层关闭后再按back键,才是关闭Webview。
wap2app会智能处理弹出层的关闭,当用户点击back按键时,先探测当前页面是否存在弹出层,若存在则关闭弹出层,否则关闭当前页面。
但前端写法过于灵活,wap2app目前无法处理所有的弹出层情况。在实际业务中,弹出层的显示通常有两种模式:

  • 全屏弹出层(比如modal组件),通常会提供按钮关闭弹出层,比如取消按钮或关闭图标
  • 非全屏弹出层,通常也会有取消按钮,另外大多还会有半透明遮罩层,用户点击遮罩层,也可以关闭弹出层;

鉴于这些常见的弹出层关闭逻辑,wap2app提供了一种简化配置,可以配置弹出层选择器、取消按钮(或遮罩层)选择器、取消按钮(或遮罩层)点击事件,示例如下:

    "easyConfig":{  
        "back":{  
            "before":[  
                {  
                    "popupSelector":".popup", //弹出层选择器  
                    "closeSelector":".mask", //取消按钮选择器或遮罩层选择器  
                    "eventType":"click" //事件类型,默认为"click"  
                }  
            ]  
        }  
    }

如上示例,在用户点击back按键时,wap2app执行如下逻辑:

  • 若document.querySelector(".popup")元素存在且处于屏幕可视区域,则触发document.querySelector(".mask")元素的click事件,之后不再执行webview的后退逻辑
  • 若document.querySelector(".popup")元素不存在或在屏幕可视区域不可见,则继续执行webview的后退逻辑

before节点为Array类型,若当前页面有多个弹出层,则配置多个节点即可,例如:

    "easyConfig":{  
        "back":{  
            "before":[  
                {//第一个弹出层  
                    "popupSelector":".popup", //弹出层选择器  
                    "closeSelector":".mask", //取消按钮选择器或遮罩层选择器  
                    "eventType":"click" //事件类型,默认为"click"  
                },  
                {//第二个弹出层  
                    "popupSelector":".modal", //弹出层选择器  
                    "closeSelector":"#cancel_btn", //取消按钮选择器  
                    "eventType":"touchstart" //事件类型  
                }  
            ]  
        }  
    }

history

wap2app默认窗体逻辑是点击打开新Webview,点back关闭该Webview并漏出上一个Webview。但如果是同一组页面在一个Webview里跳转,此时不开新窗体,点back会走history.back。
但有时我们需要面对其他情况。比如
用户从首页点击链接,打开a.html,然后在a.html链接中通过相似推荐再次点击打开b.html

a和b都是一组页面,Webview配置也相同,matchUrls规则匹配的页面,会在同一个webview中打开,如下:

    {  
        "webviewId":"detail",  
        "matchUrls":[  
            {  
                "pathname":"a.html"  
            },  
            {  
                "pathname":"b.html"  
            }  
        ]  
    }

在wap2app中,相应执行逻辑如下:

  • 打开a.html时,匹配到webviewId为detail的页面配置,则创建id为detail的webview(简称为detailWebview),并加载a.html
  • 打开b.html时,再次匹配到webviewId为detail的页面配置,此时发现id为detail的webview已存在,即detailWebview,则不再创建新webview,而在detailWebview内执行location.href = b.html的逻辑,webview内部跳转到b.html

若此时用户按下back按键,则会在detailWebview内执行history.back(),返回到a.html,并不会直接关闭当前webview,返回到首页。

如果此时,希望用户按下back时,不执行history.back(),而是直接关闭webview,返回到上一层webview,则可以通过easyConfig的back下的设置history配置实现。

    {  
        "webviewId":"detail",  
        "matchUrls":[  
            {  
                "pathname":"a.html"  
            },  
            {  
                "pathname":"b.html"  
            }  
        ],  
        "easyConfig":{  
            "back":{  
                "history":false //不允许执行history.back  
            }  
        }  
    }

按以上代码配置,点back时,会关闭detail Webview,回到首页,即便是页面跳转到了b.html。

open

为了更好的窗体切换体验,wap2app实现了大量的封装;其中,webview自身相关的属性在webviewParameter节点中配置,打开新窗口的其它优化则在easyConfig->open节点下配置。

animation

wap2app在打开新窗口时,新窗口默认使用从右向左的移动动画;开发者也可以通过open->animation属性配置窗口动画类型及动画执行时间,代码示例如下:

{  
    "webviewId": "page1",  
    "matchUrls": [  
        //url匹配规则  
    ],  
    "easyConfig":{  
        "open":{  
            "animation":{//窗口切换动画配置  
                "type":"slide-in-right",//窗口动画类型  
                "duration":300//窗口动画执行时间  
            }  
        }  
    }  
}

目前支持的动画类型包括:

  • "none": 无动画效果,立即显示页面,无任何动画效果,此效果忽略动画时间参数。
  • "slide-in-right": 页面从屏幕右侧外向内横向滑动显示
  • "slide-in-left": 页面从屏幕左侧向右横向滑动显示
  • "slide-in-top": 页面从屏幕上侧向下竖向滑动显示
  • "slide-in-bottom": 页面从屏幕下侧向上竖向滑动显示
  • "fade-in": 页面从完全透明到不透明逐渐显示
  • "zoom-out": 页面在屏幕中间从小到大逐渐放大显示
  • "zoom-fade-out": 页面在屏幕中间从小到大逐渐放大并且从透明到不透明逐渐显示
  • "pop-in": 页面从屏幕右侧滑入显示,同时上一个页面带阴影效果从屏幕左侧滑出隐藏

waiting

wap2app在打开新窗口时,默认会显示waiting等待框,新webview的titleUpdate事件触发后,自动关闭waiting等待框。(titleUpdate是页面title节点解析触发的事件,详见HTML5Plus Webview的event)

等待框默认使用5+引擎的默认waiting框效果,开发者可以通过参数配置自定义效果,可配置参数包括:

  • title:可选,类型为String,等待对话框上显示的提示标题内容
  • options:可选,类型为Object,等待对话框的显示参数,设置等待框的宽、高、边距、背景等样式等;完整参数参考5+原生等待框参数

如下是一个示例,配置等待框为粉色背景、白色文字,等待提示语为“加载中”:

{  
    "webviewId": "page1",  
    "matchUrls": [  
        //url匹配规则  
    ],  
    "easyConfig":{  
        "open":{  
            "waiting":{  
                "title":"加载中...",  
                "options":{  
                    "background":"#EF5FA7",//粉色背景  
                    "color":"#ffffff"//白色文字  
                }  
            }  
        }  

    }  
}

运行结果如下:

如果M站页面响应较快,不需要显示waiting框,则可以通过配置waiting:false来禁用waiting等待框,例如:

{  
    "webviewId": "page1",  
    "matchUrls": [  
        //url匹配规则  
    ],  
    "easyConfig":{  
        "open":{  
            "waiting":false  // 禁用waiting,打开page1页面时,不显示waiting等待框  
        }  
    }  
}

Tips:
waiting常见于内容渲染慢的场景。如果内容渲染快,那么waiting圈反而碍事。
如果使用了NView模板,建议禁用waiting等待框。

quit

用户在应用首页按下back按键,会触发wap2app的退出逻辑,应用退出的相关配置在global->easyConfig->quit节点下配置。

Tips:quit节点仅支持在global下配置,不支持页面继承。

wap2app退出时,默认会弹出toast消息框,提示用户“再按一次返回键退出”,同时在toast信息中给出“反馈意见”的链接,效果图如下:

在toast消息显示时,用户可以点击“反馈意见”链接,反馈App的使用问题;开发者可以登录DCloud开发者中心查看用户反馈的问题,解决并持续改进。

wap2app可以通过配置,在应用退出的toast消息中不显示“反馈意见”的链接,方式如下:

"global": {  
    "webviewParameter": {  
        //webview相关通用配置  
    },  
    "easyConfig": {  
        "quit": {  
            "toast": {  
                "showFeedback": false //不显示“反馈意见”链接,默认为true  
            }  
        }  
    }  
}

FAQ

Q:退出时候的反馈如何去掉
A:参考上面文档中的说明,调整首页 easyConfig -> quit 的配置即可。

继续阅读 »

综述

sitemap中的webviewParameter配置其实是HTML5Plus规范中的Webview的参数设置。
除了配置Webview的参数外,开发者还会有其他业务需求。
在wap2app的设计中,复杂的扩展需求是通过app.js自行编程实现的。
但实际开发中,有很多较为通用的处理工作,为了减少开发者的重复工作量,官方在wap2app的sitemap下面增加了easyConfig节点,封装了一些常用操作,通过简单的配置,就可以完成原来需要很多编码才能完成的工作。

下面是目前支持的easyConfig配置。

back

用户按下back按键或点击顶部标题栏的返回箭头时,将触发wap2app的后退逻辑。
常规情况下,back按钮是关闭当前Webview并显示上一个Webview。
但我们有时需定制back的逻辑。
关于后退的定制,在back节点下进行配置。
目前back节点下有before和history这2个配置。

before

若页面有弹出层时,此时用户按下back按键,其实不应该把这个Webview关闭。
理想的效果是按下back时关闭弹出的浮层,当浮层关闭后再按back键,才是关闭Webview。
wap2app会智能处理弹出层的关闭,当用户点击back按键时,先探测当前页面是否存在弹出层,若存在则关闭弹出层,否则关闭当前页面。
但前端写法过于灵活,wap2app目前无法处理所有的弹出层情况。在实际业务中,弹出层的显示通常有两种模式:

  • 全屏弹出层(比如modal组件),通常会提供按钮关闭弹出层,比如取消按钮或关闭图标
  • 非全屏弹出层,通常也会有取消按钮,另外大多还会有半透明遮罩层,用户点击遮罩层,也可以关闭弹出层;

鉴于这些常见的弹出层关闭逻辑,wap2app提供了一种简化配置,可以配置弹出层选择器、取消按钮(或遮罩层)选择器、取消按钮(或遮罩层)点击事件,示例如下:

    "easyConfig":{  
        "back":{  
            "before":[  
                {  
                    "popupSelector":".popup", //弹出层选择器  
                    "closeSelector":".mask", //取消按钮选择器或遮罩层选择器  
                    "eventType":"click" //事件类型,默认为"click"  
                }  
            ]  
        }  
    }

如上示例,在用户点击back按键时,wap2app执行如下逻辑:

  • 若document.querySelector(".popup")元素存在且处于屏幕可视区域,则触发document.querySelector(".mask")元素的click事件,之后不再执行webview的后退逻辑
  • 若document.querySelector(".popup")元素不存在或在屏幕可视区域不可见,则继续执行webview的后退逻辑

before节点为Array类型,若当前页面有多个弹出层,则配置多个节点即可,例如:

    "easyConfig":{  
        "back":{  
            "before":[  
                {//第一个弹出层  
                    "popupSelector":".popup", //弹出层选择器  
                    "closeSelector":".mask", //取消按钮选择器或遮罩层选择器  
                    "eventType":"click" //事件类型,默认为"click"  
                },  
                {//第二个弹出层  
                    "popupSelector":".modal", //弹出层选择器  
                    "closeSelector":"#cancel_btn", //取消按钮选择器  
                    "eventType":"touchstart" //事件类型  
                }  
            ]  
        }  
    }

history

wap2app默认窗体逻辑是点击打开新Webview,点back关闭该Webview并漏出上一个Webview。但如果是同一组页面在一个Webview里跳转,此时不开新窗体,点back会走history.back。
但有时我们需要面对其他情况。比如
用户从首页点击链接,打开a.html,然后在a.html链接中通过相似推荐再次点击打开b.html

a和b都是一组页面,Webview配置也相同,matchUrls规则匹配的页面,会在同一个webview中打开,如下:

    {  
        "webviewId":"detail",  
        "matchUrls":[  
            {  
                "pathname":"a.html"  
            },  
            {  
                "pathname":"b.html"  
            }  
        ]  
    }

在wap2app中,相应执行逻辑如下:

  • 打开a.html时,匹配到webviewId为detail的页面配置,则创建id为detail的webview(简称为detailWebview),并加载a.html
  • 打开b.html时,再次匹配到webviewId为detail的页面配置,此时发现id为detail的webview已存在,即detailWebview,则不再创建新webview,而在detailWebview内执行location.href = b.html的逻辑,webview内部跳转到b.html

若此时用户按下back按键,则会在detailWebview内执行history.back(),返回到a.html,并不会直接关闭当前webview,返回到首页。

如果此时,希望用户按下back时,不执行history.back(),而是直接关闭webview,返回到上一层webview,则可以通过easyConfig的back下的设置history配置实现。

    {  
        "webviewId":"detail",  
        "matchUrls":[  
            {  
                "pathname":"a.html"  
            },  
            {  
                "pathname":"b.html"  
            }  
        ],  
        "easyConfig":{  
            "back":{  
                "history":false //不允许执行history.back  
            }  
        }  
    }

按以上代码配置,点back时,会关闭detail Webview,回到首页,即便是页面跳转到了b.html。

open

为了更好的窗体切换体验,wap2app实现了大量的封装;其中,webview自身相关的属性在webviewParameter节点中配置,打开新窗口的其它优化则在easyConfig->open节点下配置。

animation

wap2app在打开新窗口时,新窗口默认使用从右向左的移动动画;开发者也可以通过open->animation属性配置窗口动画类型及动画执行时间,代码示例如下:

{  
    "webviewId": "page1",  
    "matchUrls": [  
        //url匹配规则  
    ],  
    "easyConfig":{  
        "open":{  
            "animation":{//窗口切换动画配置  
                "type":"slide-in-right",//窗口动画类型  
                "duration":300//窗口动画执行时间  
            }  
        }  
    }  
}

目前支持的动画类型包括:

  • "none": 无动画效果,立即显示页面,无任何动画效果,此效果忽略动画时间参数。
  • "slide-in-right": 页面从屏幕右侧外向内横向滑动显示
  • "slide-in-left": 页面从屏幕左侧向右横向滑动显示
  • "slide-in-top": 页面从屏幕上侧向下竖向滑动显示
  • "slide-in-bottom": 页面从屏幕下侧向上竖向滑动显示
  • "fade-in": 页面从完全透明到不透明逐渐显示
  • "zoom-out": 页面在屏幕中间从小到大逐渐放大显示
  • "zoom-fade-out": 页面在屏幕中间从小到大逐渐放大并且从透明到不透明逐渐显示
  • "pop-in": 页面从屏幕右侧滑入显示,同时上一个页面带阴影效果从屏幕左侧滑出隐藏

waiting

wap2app在打开新窗口时,默认会显示waiting等待框,新webview的titleUpdate事件触发后,自动关闭waiting等待框。(titleUpdate是页面title节点解析触发的事件,详见HTML5Plus Webview的event)

等待框默认使用5+引擎的默认waiting框效果,开发者可以通过参数配置自定义效果,可配置参数包括:

  • title:可选,类型为String,等待对话框上显示的提示标题内容
  • options:可选,类型为Object,等待对话框的显示参数,设置等待框的宽、高、边距、背景等样式等;完整参数参考5+原生等待框参数

如下是一个示例,配置等待框为粉色背景、白色文字,等待提示语为“加载中”:

{  
    "webviewId": "page1",  
    "matchUrls": [  
        //url匹配规则  
    ],  
    "easyConfig":{  
        "open":{  
            "waiting":{  
                "title":"加载中...",  
                "options":{  
                    "background":"#EF5FA7",//粉色背景  
                    "color":"#ffffff"//白色文字  
                }  
            }  
        }  

    }  
}

运行结果如下:

如果M站页面响应较快,不需要显示waiting框,则可以通过配置waiting:false来禁用waiting等待框,例如:

{  
    "webviewId": "page1",  
    "matchUrls": [  
        //url匹配规则  
    ],  
    "easyConfig":{  
        "open":{  
            "waiting":false  // 禁用waiting,打开page1页面时,不显示waiting等待框  
        }  
    }  
}

Tips:
waiting常见于内容渲染慢的场景。如果内容渲染快,那么waiting圈反而碍事。
如果使用了NView模板,建议禁用waiting等待框。

quit

用户在应用首页按下back按键,会触发wap2app的退出逻辑,应用退出的相关配置在global->easyConfig->quit节点下配置。

Tips:quit节点仅支持在global下配置,不支持页面继承。

wap2app退出时,默认会弹出toast消息框,提示用户“再按一次返回键退出”,同时在toast信息中给出“反馈意见”的链接,效果图如下:

在toast消息显示时,用户可以点击“反馈意见”链接,反馈App的使用问题;开发者可以登录DCloud开发者中心查看用户反馈的问题,解决并持续改进。

wap2app可以通过配置,在应用退出的toast消息中不显示“反馈意见”的链接,方式如下:

"global": {  
    "webviewParameter": {  
        //webview相关通用配置  
    },  
    "easyConfig": {  
        "quit": {  
            "toast": {  
                "showFeedback": false //不显示“反馈意见”链接,默认为true  
            }  
        }  
    }  
}

FAQ

Q:退出时候的反馈如何去掉
A:参考上面文档中的说明,调整首页 easyConfig -> quit 的配置即可。

收起阅读 »

webviewParameter配置 - wap2app教程

教程 webviewParameter wap2app

综述

wap2app使用webview打开每个页面链接,而每个webview相关的属性设置,在对应页面的webviewParameter节点下配置,如下TODO位置:

    {  
        "webviewId":"page1",  
        "matchUrls": [  
            //url 匹配规则  
        ],  
        "webviewParameter": {  
            //TODO webview相关属性在这里配置  
        },  
        "easyConfig":{}  
    }

webviewParameter节点下可配置如下子节点:

  • titleNView:原生标题栏样式配置
  • statusbar:系统状态栏样式配置
  • subNViews:NView模板配置
  • pullToRefresh:下拉刷新配置
  • appendCss:向服务端页面插入的css代码
  • appendJs:向服务端页面插入的JavaScript代码
  • tabBar:选项卡切换效果优化,目前仅支持首页底部选项卡

接下来对每个节点进行详细介绍。

titleNView

titleNView用于设置原生标题栏样式, 顾名思义,它是一个原生的NView,并且用途是顶部title。
titleNView是由原生渲染引擎渲染,而不是Webview渲染的。在窗体动画切换时可以瞬间出现,不会让页面整屏白屏。

titleNView的具体教程参考titleNView配置
如果你是第一次学习,阅读到此处时,直接点击上述链接继续学习。完毕后回到本文继续。

statusbar

系统状态栏位于手机屏幕顶端,如下图所示:

通过statusbar可以配置系统状态栏的样式,可配置的参数包括:

  • style:状态栏前景色(文字颜色)
  • background:状态栏背景色,默认应该和原生导航条背景色一致;

style

状态栏的前景色,仅支持浅色、深色两种选项,可取值如下:

  • dark:深色前景色样式(即状态栏前景文字为黑色)
  • light:浅色前景色样式(即状态栏前景文字为白色)

关于两种前景色的效果对比,参考下图所示:

注意:系统状态栏的前景色目前是App全局配置,仅可在global节点中配置,不支持在不同webview中进行覆盖。

background

若webview使用了原生导航条,则系统状态栏默认使用和原生导航条一样的背景色,此时无需再单独配置background参数;换言之,仅需在未使用原生导航条的webview中,配置background参数。

在wap2app项目中,除首页外,其它页面默认均启用了原生导航条;因此,首页需要配置background,其它页面,均无需配置。

对于首页而言,也分两种情况:

  • 首页未启用原生导航(默认):必需配置background参数,同时建议将background配置为global->webviewParameter->titleNView->backgroundColor颜色值,保持App整体风格一致;
  • 首页启用原生导航:无需配置,同时建议将statusbar节点配置为false

首页未启用原生导航条的配置示例:

{  
    "webviewId": "__W2A__m.example.com",//首页  
    "matchUrls": [  
        //url配置规则  
    ],  
    "webviewParameter": {  
        "titleNView": false,//首页默认不使用原生导航  
        "statusbar": {  
            //状态条背景色,  
            //首页不使用原生导航条,颜色值建议和global->webviewParameter->titleNView->backgroundColor颜色值保持一致  
            "background": "#f7f7f7"  
        }  
    }  
}

首页启用原生导航条配置示例:

{  
    "webviewId": "__W2A__m.example.com",//首页  
    "matchUrls": [  
        //url配置规则  
    ],  
    "webviewParameter": {  
        "titleNView": {//首页启用原生导航条  
            "backgroundColor": "#FF00FF",//导航栏背景色  
            "titleColor": "#00ffff",//标题颜色为白色  
            "titleText": "wap2app首页"  
        },  
        //若首页启用了原生导航条,则建议将首页的statusbar配置为false(或直接删除statusbar节点),这样状态条可以和原生导航条背景色保持一致;  
        "statusbar": false   

    }  
}

注意:非首页的其它页面(默认使用原生导航条),如果禁用了原生导航条,则也需要在对应页面的webviewParameter节点下配置statusbar->background,配置方式同首页逻辑。

subNViews

subNView也是原生渲染区域,它位于titleNView下方,从属于webview。
类似的技术有点像flash,内嵌在网页中,但实际是由原生引擎渲染的。
但subNView的加载速度快多了,在窗体切换动画时,subNView可以瞬间出现,而Webview则仍在慢慢经历:联网下载页面代码、构建Dom、渲染Dom等过程。因此subNView是加快web页面渲染的一大利器。如下为一个使用subNView增强后的商品详情页示例:

这样页面在一个Webview里创建了2个subNView,在页面加载动画时,subNView可以直接渲染出来,而后wap页面才慢慢渲染完毕。

DCloud为NView提供了一种前端式模板写法,可在sitemap.json中配置NView模板文件路径,编写一个模板。如下为一个示例:

    {  
        "webviewId":"page1",  
        "matchUrls": [  
            //url 匹配规则  
        ],  
        "webviewParameter": {  
            "subNViews": "page1.nview" //NView模板路径  
        }  
    }

关于subNView及NView模板更详细的介绍,参考:NView模板教程
NView略复杂,它不像sitemap仅仅是配置,它涉及真正编程。
不使用subNView也可以完成一个wap2app项目的基础版。
你可以一口气学完NView,也可以先做一个wap2app的基础版,然后再引入NView进一步强化体验。

appendCss

wap2app需要M站进行少量改造,比如因为使用了原生标题栏titleNView,就不再需要DIV标题栏,M站服务端应根据navigator.userAgent判断是流应用环境下,不生成(或隐藏)原来的DIV标题栏,教程参考去除M站DOM元素

这部分改造工作,建议由服务端改造,这样可以保证css尽早生效;如果服务端不方便修改,wap2app还支持通过appendCss节点向M站页面插入css代码,从而实现类似的效果。
也就是通过appendCss,可以在wap2app中直接影响wap站的渲染样式。这些css由wap2app引擎在运行时动态插入到原wap站的页面dom中,和在wap页面里写同样的css效果类似。

wap2app支持两种方式配置appendCss节点:

  • 直接编写css代码
  • 配置css文件路径

wap2app框架会智能识别配置的是代码还是文件路径,然后将对应的css代码,插入到对应的M站页面中。

直接配置css代码

如果要插入的css代码比较简单,则可以直接在sitemap.json文件中appendCss节点后编写css代码,如下:

     {  

       "webviewId":"page1",  
        "matchUrls": [  
            //url 匹配规则  
        ],  
        "webviewParameter": {  
            "appendCss":"#header{display:none}" //插入css代码  
        },  
        "easyConfig":{}  
    }

配置css文件路径

如果要插入的css代码比较复杂,则在sitemap.json的字符串中编写就不太方便(比如:缺少css语法提示),此时建议新建一个css文件编写需插入的css代码,然后将css文件相对路径配置到appendCss节点下即可,例如:

     {  

       "webviewId":"page1",  
        "matchUrls": [  
            //url 匹配规则  
        ],  
        "webviewParameter": {  
            "appendCss":"page1.append.css" //需插入的css文件路径  
        },  
        "easyConfig":{}  
    }

建议将需插入的css文件放在项目根目录下,然后遵循%webviewId%.append.css这样的命名格式;如此命名的话,无需在sitemap.json中配置appendCss节点,wap2app框架会自动扫描该文件;如上示例中css文件名为“page1.append.css”,遵循我们建议的命名格式,因此可简化配置为:

     {  

       "webviewId":"page1",  
        "matchUrls": [  
            //url 匹配规则  
        ],  
        "webviewParameter": {  
            //无需配置appendCss节点,wap2app会自动扫描"page1.append.css"文件并插入  
        },  
        "easyConfig":{}  
    }

当然,开发者也可以按照自己的习惯,自定义css文件名,此时记得配置appendCss节点即可,例如:

     {  

       "webviewId":"page2",  
        "matchUrls": [  
            //url 匹配规则  
        ],  
        "webviewParameter": {  
            "appendCss":"pages/page2/append.css" //自定义的css文件路径  
        },  
        "easyConfig":{}  
    }

首页css插入

因wap2app启动机制限制,目前仅支持通过css文件的方式向M站首页插入css代码;又因M站首页的webviewId就是当前应用的appid,故在HBuilder中新建wap2app项目时,会自动新建一个%appid%.append.css的文件,且名字不可修改。
开发者在%appid%.append.css里编写的样式,会直接插入到wap站首页里运行,从而影响首页的渲染效果。

appendJs

wap2app项目中,M站也会涉及部分JavaScript逻辑的改造,比如在wap2app环境下,将wap分享替换为原生分享,可以分享给微信好友、朋友圈,或者调起系统分享组件,分享到短信、邮件等。这部分增强逻辑改造,尽量由M站改造完成。如果不方便修改M站,wap2app还支持通过appendJs节点向M站页面插入JavaScript代码,从而实现类似的效果,如下为一个示例:

     {  

       "webviewId":"page1",  
        "matchUrls": [  
            //url 匹配规则  
        ],  
        "webviewParameter": {  
            "appendJs":"page1.append.js" //需插入的JavaScript文件路径  
        },  
        "easyConfig":{}  
    }

类似appendCss插入文件的命名机制,插入的js文件建议遵循%webviewId%.append.js规范,wap2app默认会扫描项目根目录下名字为%webviewId%.append.js的文件;若覆盖该命名规范,则无需配置appendJs节点;如上示例中js文件名为“page1.append.js”,遵循我们建议的命名格式,因此可简化配置为:

     {  

       "webviewId":"page1",  
        "matchUrls": [  
            //url 匹配规则  
        ],  
        "webviewParameter": {  
            //无需配置appendJs节点,wap2app会自动扫描"page1.append.js"文件并插入  
        },  
        "easyConfig":{}  
    }

当然,开发者也可以按照自己的习惯,自定义js文件名,此时记得配置appendJs节点即可,例如:

     {  

       "webviewId":"page2",  
        "matchUrls": [  
            //url 匹配规则  
        ],  
        "webviewParameter": {  
            "appendJs":"pages/page2/append.js" //自定义的js文件路径  
        },  
        "easyConfig":{}  
    }

注意:通过appendJs节点配置的JavaScript代码,虽然是在HBuilder客户端编写,但最终会运行在M站的页面中,可以操作M站的DOM结构、执行M站的业务逻辑。

appendJs插入的js,运行时机是晚于原页面的其他js的,所以开发者应尽量在原wap页面中直接修改js,减少appendJs的使用。

pullToRefresh

我们知道页面里基于div方式的下拉刷新是不流畅的,wap2app框架提供了原生的下拉刷新。
配置pullToRefresh -> support 节点就可以启用原生下拉刷新,如下:

    "webviewId":"page1",  
        "matchUrls": [  
            //url 匹配规则  
        ],  
        "webviewParameter": {  
            "pullToRefresh":{  
                "support":true //启用下拉刷新  
            }  
        },  
        "easyConfig":{}  
    }

当然这种下拉刷新本质是刷新一个网页,效果是一个圆圈从titleNView位置被拉下来,拉到一定程度会自动刷新页面。
一般用于list页面的配置。
如果开发者想要下拉刷新用于更新list的dom而不是刷新网页,需要在app.js中进行强化编程。

tabBar

tabBar可以对首页底部选项卡进行优化,实现选项卡切换时,仅变化内容区,选项卡区域不变。详细配置参考选项卡切换优化教程

继续阅读 »

综述

wap2app使用webview打开每个页面链接,而每个webview相关的属性设置,在对应页面的webviewParameter节点下配置,如下TODO位置:

    {  
        "webviewId":"page1",  
        "matchUrls": [  
            //url 匹配规则  
        ],  
        "webviewParameter": {  
            //TODO webview相关属性在这里配置  
        },  
        "easyConfig":{}  
    }

webviewParameter节点下可配置如下子节点:

  • titleNView:原生标题栏样式配置
  • statusbar:系统状态栏样式配置
  • subNViews:NView模板配置
  • pullToRefresh:下拉刷新配置
  • appendCss:向服务端页面插入的css代码
  • appendJs:向服务端页面插入的JavaScript代码
  • tabBar:选项卡切换效果优化,目前仅支持首页底部选项卡

接下来对每个节点进行详细介绍。

titleNView

titleNView用于设置原生标题栏样式, 顾名思义,它是一个原生的NView,并且用途是顶部title。
titleNView是由原生渲染引擎渲染,而不是Webview渲染的。在窗体动画切换时可以瞬间出现,不会让页面整屏白屏。

titleNView的具体教程参考titleNView配置
如果你是第一次学习,阅读到此处时,直接点击上述链接继续学习。完毕后回到本文继续。

statusbar

系统状态栏位于手机屏幕顶端,如下图所示:

通过statusbar可以配置系统状态栏的样式,可配置的参数包括:

  • style:状态栏前景色(文字颜色)
  • background:状态栏背景色,默认应该和原生导航条背景色一致;

style

状态栏的前景色,仅支持浅色、深色两种选项,可取值如下:

  • dark:深色前景色样式(即状态栏前景文字为黑色)
  • light:浅色前景色样式(即状态栏前景文字为白色)

关于两种前景色的效果对比,参考下图所示:

注意:系统状态栏的前景色目前是App全局配置,仅可在global节点中配置,不支持在不同webview中进行覆盖。

background

若webview使用了原生导航条,则系统状态栏默认使用和原生导航条一样的背景色,此时无需再单独配置background参数;换言之,仅需在未使用原生导航条的webview中,配置background参数。

在wap2app项目中,除首页外,其它页面默认均启用了原生导航条;因此,首页需要配置background,其它页面,均无需配置。

对于首页而言,也分两种情况:

  • 首页未启用原生导航(默认):必需配置background参数,同时建议将background配置为global->webviewParameter->titleNView->backgroundColor颜色值,保持App整体风格一致;
  • 首页启用原生导航:无需配置,同时建议将statusbar节点配置为false

首页未启用原生导航条的配置示例:

{  
    "webviewId": "__W2A__m.example.com",//首页  
    "matchUrls": [  
        //url配置规则  
    ],  
    "webviewParameter": {  
        "titleNView": false,//首页默认不使用原生导航  
        "statusbar": {  
            //状态条背景色,  
            //首页不使用原生导航条,颜色值建议和global->webviewParameter->titleNView->backgroundColor颜色值保持一致  
            "background": "#f7f7f7"  
        }  
    }  
}

首页启用原生导航条配置示例:

{  
    "webviewId": "__W2A__m.example.com",//首页  
    "matchUrls": [  
        //url配置规则  
    ],  
    "webviewParameter": {  
        "titleNView": {//首页启用原生导航条  
            "backgroundColor": "#FF00FF",//导航栏背景色  
            "titleColor": "#00ffff",//标题颜色为白色  
            "titleText": "wap2app首页"  
        },  
        //若首页启用了原生导航条,则建议将首页的statusbar配置为false(或直接删除statusbar节点),这样状态条可以和原生导航条背景色保持一致;  
        "statusbar": false   

    }  
}

注意:非首页的其它页面(默认使用原生导航条),如果禁用了原生导航条,则也需要在对应页面的webviewParameter节点下配置statusbar->background,配置方式同首页逻辑。

subNViews

subNView也是原生渲染区域,它位于titleNView下方,从属于webview。
类似的技术有点像flash,内嵌在网页中,但实际是由原生引擎渲染的。
但subNView的加载速度快多了,在窗体切换动画时,subNView可以瞬间出现,而Webview则仍在慢慢经历:联网下载页面代码、构建Dom、渲染Dom等过程。因此subNView是加快web页面渲染的一大利器。如下为一个使用subNView增强后的商品详情页示例:

这样页面在一个Webview里创建了2个subNView,在页面加载动画时,subNView可以直接渲染出来,而后wap页面才慢慢渲染完毕。

DCloud为NView提供了一种前端式模板写法,可在sitemap.json中配置NView模板文件路径,编写一个模板。如下为一个示例:

    {  
        "webviewId":"page1",  
        "matchUrls": [  
            //url 匹配规则  
        ],  
        "webviewParameter": {  
            "subNViews": "page1.nview" //NView模板路径  
        }  
    }

关于subNView及NView模板更详细的介绍,参考:NView模板教程
NView略复杂,它不像sitemap仅仅是配置,它涉及真正编程。
不使用subNView也可以完成一个wap2app项目的基础版。
你可以一口气学完NView,也可以先做一个wap2app的基础版,然后再引入NView进一步强化体验。

appendCss

wap2app需要M站进行少量改造,比如因为使用了原生标题栏titleNView,就不再需要DIV标题栏,M站服务端应根据navigator.userAgent判断是流应用环境下,不生成(或隐藏)原来的DIV标题栏,教程参考去除M站DOM元素

这部分改造工作,建议由服务端改造,这样可以保证css尽早生效;如果服务端不方便修改,wap2app还支持通过appendCss节点向M站页面插入css代码,从而实现类似的效果。
也就是通过appendCss,可以在wap2app中直接影响wap站的渲染样式。这些css由wap2app引擎在运行时动态插入到原wap站的页面dom中,和在wap页面里写同样的css效果类似。

wap2app支持两种方式配置appendCss节点:

  • 直接编写css代码
  • 配置css文件路径

wap2app框架会智能识别配置的是代码还是文件路径,然后将对应的css代码,插入到对应的M站页面中。

直接配置css代码

如果要插入的css代码比较简单,则可以直接在sitemap.json文件中appendCss节点后编写css代码,如下:

     {  

       "webviewId":"page1",  
        "matchUrls": [  
            //url 匹配规则  
        ],  
        "webviewParameter": {  
            "appendCss":"#header{display:none}" //插入css代码  
        },  
        "easyConfig":{}  
    }

配置css文件路径

如果要插入的css代码比较复杂,则在sitemap.json的字符串中编写就不太方便(比如:缺少css语法提示),此时建议新建一个css文件编写需插入的css代码,然后将css文件相对路径配置到appendCss节点下即可,例如:

     {  

       "webviewId":"page1",  
        "matchUrls": [  
            //url 匹配规则  
        ],  
        "webviewParameter": {  
            "appendCss":"page1.append.css" //需插入的css文件路径  
        },  
        "easyConfig":{}  
    }

建议将需插入的css文件放在项目根目录下,然后遵循%webviewId%.append.css这样的命名格式;如此命名的话,无需在sitemap.json中配置appendCss节点,wap2app框架会自动扫描该文件;如上示例中css文件名为“page1.append.css”,遵循我们建议的命名格式,因此可简化配置为:

     {  

       "webviewId":"page1",  
        "matchUrls": [  
            //url 匹配规则  
        ],  
        "webviewParameter": {  
            //无需配置appendCss节点,wap2app会自动扫描"page1.append.css"文件并插入  
        },  
        "easyConfig":{}  
    }

当然,开发者也可以按照自己的习惯,自定义css文件名,此时记得配置appendCss节点即可,例如:

     {  

       "webviewId":"page2",  
        "matchUrls": [  
            //url 匹配规则  
        ],  
        "webviewParameter": {  
            "appendCss":"pages/page2/append.css" //自定义的css文件路径  
        },  
        "easyConfig":{}  
    }

首页css插入

因wap2app启动机制限制,目前仅支持通过css文件的方式向M站首页插入css代码;又因M站首页的webviewId就是当前应用的appid,故在HBuilder中新建wap2app项目时,会自动新建一个%appid%.append.css的文件,且名字不可修改。
开发者在%appid%.append.css里编写的样式,会直接插入到wap站首页里运行,从而影响首页的渲染效果。

appendJs

wap2app项目中,M站也会涉及部分JavaScript逻辑的改造,比如在wap2app环境下,将wap分享替换为原生分享,可以分享给微信好友、朋友圈,或者调起系统分享组件,分享到短信、邮件等。这部分增强逻辑改造,尽量由M站改造完成。如果不方便修改M站,wap2app还支持通过appendJs节点向M站页面插入JavaScript代码,从而实现类似的效果,如下为一个示例:

     {  

       "webviewId":"page1",  
        "matchUrls": [  
            //url 匹配规则  
        ],  
        "webviewParameter": {  
            "appendJs":"page1.append.js" //需插入的JavaScript文件路径  
        },  
        "easyConfig":{}  
    }

类似appendCss插入文件的命名机制,插入的js文件建议遵循%webviewId%.append.js规范,wap2app默认会扫描项目根目录下名字为%webviewId%.append.js的文件;若覆盖该命名规范,则无需配置appendJs节点;如上示例中js文件名为“page1.append.js”,遵循我们建议的命名格式,因此可简化配置为:

     {  

       "webviewId":"page1",  
        "matchUrls": [  
            //url 匹配规则  
        ],  
        "webviewParameter": {  
            //无需配置appendJs节点,wap2app会自动扫描"page1.append.js"文件并插入  
        },  
        "easyConfig":{}  
    }

当然,开发者也可以按照自己的习惯,自定义js文件名,此时记得配置appendJs节点即可,例如:

     {  

       "webviewId":"page2",  
        "matchUrls": [  
            //url 匹配规则  
        ],  
        "webviewParameter": {  
            "appendJs":"pages/page2/append.js" //自定义的js文件路径  
        },  
        "easyConfig":{}  
    }

注意:通过appendJs节点配置的JavaScript代码,虽然是在HBuilder客户端编写,但最终会运行在M站的页面中,可以操作M站的DOM结构、执行M站的业务逻辑。

appendJs插入的js,运行时机是晚于原页面的其他js的,所以开发者应尽量在原wap页面中直接修改js,减少appendJs的使用。

pullToRefresh

我们知道页面里基于div方式的下拉刷新是不流畅的,wap2app框架提供了原生的下拉刷新。
配置pullToRefresh -> support 节点就可以启用原生下拉刷新,如下:

    "webviewId":"page1",  
        "matchUrls": [  
            //url 匹配规则  
        ],  
        "webviewParameter": {  
            "pullToRefresh":{  
                "support":true //启用下拉刷新  
            }  
        },  
        "easyConfig":{}  
    }

当然这种下拉刷新本质是刷新一个网页,效果是一个圆圈从titleNView位置被拉下来,拉到一定程度会自动刷新页面。
一般用于list页面的配置。
如果开发者想要下拉刷新用于更新list的dom而不是刷新网页,需要在app.js中进行强化编程。

tabBar

tabBar可以对首页底部选项卡进行优化,实现选项卡切换时,仅变化内容区,选项卡区域不变。详细配置参考选项卡切换优化教程

收起阅读 »

【交流分享】局域网ipad应用解决方案-APP部分

技术分享

最近在做一个现场订货的项目,分享交流下,需求是这样的:
秀台现场预订明年新款,为防止资料泄漏,会场无法访问外网,均使用ipad连接自建服务器预订商品,超过600台客户端同时连接,实时推送消息、自动切换至台上model展示商品,实时查看各种订货数据报表图表,包含4-5种不同权限角色操作。

硬件设备这里暂时不说,有专业的局域网搭建解决方案,来说APP如何保证所有客户端能保证流畅即时的使用。

由于是局域网,所以推送肯定就不能使用个推之类的外网推送,那就只有websocket(目前使用ASP .NET SignalR),登录后创建连接,实时推送通告。
自动切换也是如此,websocket连接所有客户端(包括主讲人),主讲人切换商品传给服务器,其他ipad获取到当前商品id及主题自动打开指定商品详情。

同时连接了600+ ipad那数据的响应就成了大问题,大量的商品图片已经是相当大的数据量,所以图片采用本地化机制,APP中接口返回的所有图片地址初始均为相对路径,而修改过的图片则由服务端记录统一返回绝对路径访问线上图片。(第一次访问线上图片下载至本地,告知服务端,后续继续访问本地图片)
copy服务器图片文件夹至APP,这样APP的大小就达到了500M以上。然后使用企业证书离线打包APP,自建本地服务器应用托管平台,这样APP的下载安装就变成了局域网传输,只受限于服务器的吞吐量,经测试单ipad下载安装APP不到一分钟可以安装完成(中间包括了苹果检测安装)。

做了上面这些,基本就腾出了足够的带宽来处理其他接口数据。接下来就是些常规的大数据分页分段处理,后台服务器缓存等等,目前仍在继续优化性能中。。。

各位大神如有类似的优化方案,欢迎交流分享。另附上成品小test视频...

继续阅读 »

最近在做一个现场订货的项目,分享交流下,需求是这样的:
秀台现场预订明年新款,为防止资料泄漏,会场无法访问外网,均使用ipad连接自建服务器预订商品,超过600台客户端同时连接,实时推送消息、自动切换至台上model展示商品,实时查看各种订货数据报表图表,包含4-5种不同权限角色操作。

硬件设备这里暂时不说,有专业的局域网搭建解决方案,来说APP如何保证所有客户端能保证流畅即时的使用。

由于是局域网,所以推送肯定就不能使用个推之类的外网推送,那就只有websocket(目前使用ASP .NET SignalR),登录后创建连接,实时推送通告。
自动切换也是如此,websocket连接所有客户端(包括主讲人),主讲人切换商品传给服务器,其他ipad获取到当前商品id及主题自动打开指定商品详情。

同时连接了600+ ipad那数据的响应就成了大问题,大量的商品图片已经是相当大的数据量,所以图片采用本地化机制,APP中接口返回的所有图片地址初始均为相对路径,而修改过的图片则由服务端记录统一返回绝对路径访问线上图片。(第一次访问线上图片下载至本地,告知服务端,后续继续访问本地图片)
copy服务器图片文件夹至APP,这样APP的大小就达到了500M以上。然后使用企业证书离线打包APP,自建本地服务器应用托管平台,这样APP的下载安装就变成了局域网传输,只受限于服务器的吞吐量,经测试单ipad下载安装APP不到一分钟可以安装完成(中间包括了苹果检测安装)。

做了上面这些,基本就腾出了足够的带宽来处理其他接口数据。接下来就是些常规的大数据分页分段处理,后台服务器缓存等等,目前仍在继续优化性能中。。。

各位大神如有类似的优化方案,欢迎交流分享。另附上成品小test视频...

收起阅读 »

怎么实现一站式跨平台开发

![

](http://qiniu.betweenfriends.cn/blog/20170824/Hdl-NLsa68f5Q_QBsqlrQC5u.png)### 理想状态下的跨平台
我们之所以选择WEBAPP,最大的原因应该就是可以实现一套代码在多个平台上的复用。

理想状态下,一套代码就可以同时打包成android和ios的app,并且可以作为手机站和PC站直接使用。

当然,之所以说是理想状态,是因为由于PC浏览器和移动浏览器支持的事件和操作差异都比较大,如tap和click事件,以及移动设备上的拖动操作等。而且从mui的定位上来说,主要也是针对移动设备进行的封装。另外,从应用场景的发展趋势来说,移动设备使用率越来越高,PC端则是下降趋势,因此除非必要,可以忽略PC端的兼容性,只考虑移动设备上的兼容性。

移动设备的一站式开发

因此,我们这里讨论的一站式开发,主要是指一套代码同时可以部署成移动网站,并打包成Android和iosAPP

所需环境

  1. MUI
  2. HBuilder

原理

在开发时,利用js和H5在各个平台上的共性,实现在各个平台上的公用部分A,并针对不同的环境开发必要的补充部分B C D...

运行时,首先加载公共部分A,通过技术手段检测当前的运行环境,并根据当前环境加载补充部分B C D...,这样就实现了一套代码在多个平台上的自适应,也就实现了所谓的跨平台一站式开发。

实现

实现跨平台的关键就是剥离公共部分并根据需要定制不同平台上的个性部分。一般来说,公共部分就是手机站的代码,然后在手机站的基础上,进行必要的代码覆盖,比如在APP中把mui的弹窗替换成系统原生的弹窗。

公共部分代码

根据自己的业务需要,定义一个页面初始化的架子,包含页面的初始化,组件的加载,事件的监听等。同时包含各个方法的基本实现。一般来说,这个架子本身就是一个完整可用的页面框架,在不需要其他环境适配的代码补充的情况下,就可以独立完成手机站的初始化。

alt

定制代码适配不同的环境

具体需要补充几个版本的代码需要根据自己的需求,比如需要针对微信,或者针对IOS,Android等进行定制。
定制的方法是在公共部分代码的基础上,只针对性的实现在当前要适配的环境下需要定制的部分。然后在页面初始化的时候,利用JS的继承用定制版的代码替换掉公共部分的代码,从而实现对当前定制环境的定制开发。
以下示例即通过对公共部分的代码的一部分进行覆盖从而在APP环境中可以体现出和手机站不一样的效果。

alt

完整的代码框架

其中PcPage是基础的框架,WapPage包含手机站的适配代码。WxPage包含微信环境内的手机站适配代码,AppPage则是针对App打包环境下的环境适配代码。

当然,根据需要,适配代码的补充可以有更多或者更少的版本,比如如果不需要考虑微信环境,那么就可以跳过WxPage的开发,如果需要针对App环境区分IOS和Android并实现不同的效果,那么甚至可以在AppPage之后分别实现IOSPage和AndroidPage。一切随你心意。

alt

环境检测代码

即 initPage方法的实现,在对一个页面进行初始化时,通过调用initPage方法,并传入必要的页面初始化参数,initPage会根据当前运行的环境,选择和环境适配的代码,然后根据传入的页面初始化参数来完成页面的初始化操作。

initPage的调用完成,标志着一个页面的初始化完成。
水平有限,代码有点乱,能看得懂实现原理就好,具体实现可以自己去做。initPage的核心功能,就是完成环境检测,然后根据环境检测结果,加载和环境适配的代码。

完整内容(原文链接):https://blog.betweenfriends.cn/post/crossdomaindev.html

继续阅读 »

![

](http://qiniu.betweenfriends.cn/blog/20170824/Hdl-NLsa68f5Q_QBsqlrQC5u.png)### 理想状态下的跨平台
我们之所以选择WEBAPP,最大的原因应该就是可以实现一套代码在多个平台上的复用。

理想状态下,一套代码就可以同时打包成android和ios的app,并且可以作为手机站和PC站直接使用。

当然,之所以说是理想状态,是因为由于PC浏览器和移动浏览器支持的事件和操作差异都比较大,如tap和click事件,以及移动设备上的拖动操作等。而且从mui的定位上来说,主要也是针对移动设备进行的封装。另外,从应用场景的发展趋势来说,移动设备使用率越来越高,PC端则是下降趋势,因此除非必要,可以忽略PC端的兼容性,只考虑移动设备上的兼容性。

移动设备的一站式开发

因此,我们这里讨论的一站式开发,主要是指一套代码同时可以部署成移动网站,并打包成Android和iosAPP

所需环境

  1. MUI
  2. HBuilder

原理

在开发时,利用js和H5在各个平台上的共性,实现在各个平台上的公用部分A,并针对不同的环境开发必要的补充部分B C D...

运行时,首先加载公共部分A,通过技术手段检测当前的运行环境,并根据当前环境加载补充部分B C D...,这样就实现了一套代码在多个平台上的自适应,也就实现了所谓的跨平台一站式开发。

实现

实现跨平台的关键就是剥离公共部分并根据需要定制不同平台上的个性部分。一般来说,公共部分就是手机站的代码,然后在手机站的基础上,进行必要的代码覆盖,比如在APP中把mui的弹窗替换成系统原生的弹窗。

公共部分代码

根据自己的业务需要,定义一个页面初始化的架子,包含页面的初始化,组件的加载,事件的监听等。同时包含各个方法的基本实现。一般来说,这个架子本身就是一个完整可用的页面框架,在不需要其他环境适配的代码补充的情况下,就可以独立完成手机站的初始化。

alt

定制代码适配不同的环境

具体需要补充几个版本的代码需要根据自己的需求,比如需要针对微信,或者针对IOS,Android等进行定制。
定制的方法是在公共部分代码的基础上,只针对性的实现在当前要适配的环境下需要定制的部分。然后在页面初始化的时候,利用JS的继承用定制版的代码替换掉公共部分的代码,从而实现对当前定制环境的定制开发。
以下示例即通过对公共部分的代码的一部分进行覆盖从而在APP环境中可以体现出和手机站不一样的效果。

alt

完整的代码框架

其中PcPage是基础的框架,WapPage包含手机站的适配代码。WxPage包含微信环境内的手机站适配代码,AppPage则是针对App打包环境下的环境适配代码。

当然,根据需要,适配代码的补充可以有更多或者更少的版本,比如如果不需要考虑微信环境,那么就可以跳过WxPage的开发,如果需要针对App环境区分IOS和Android并实现不同的效果,那么甚至可以在AppPage之后分别实现IOSPage和AndroidPage。一切随你心意。

alt

环境检测代码

即 initPage方法的实现,在对一个页面进行初始化时,通过调用initPage方法,并传入必要的页面初始化参数,initPage会根据当前运行的环境,选择和环境适配的代码,然后根据传入的页面初始化参数来完成页面的初始化操作。

initPage的调用完成,标志着一个页面的初始化完成。
水平有限,代码有点乱,能看得懂实现原理就好,具体实现可以自己去做。initPage的核心功能,就是完成环境检测,然后根据环境检测结果,加载和环境适配的代码。

完整内容(原文链接):https://blog.betweenfriends.cn/post/crossdomaindev.html

收起阅读 »

索引列表动态添加数据并排序

mui 索引列表

最近做项目用到索引列表,这里跟大家分享下我的经验,效率不是最好,大家一起讨论
接口返回数据格式:
{"result":{"code":"200","data":[{"phone":"未知","userId":"D63E526F8BF64FB6AF82E662B997F8FE","userName":"ceshi01","userRealName":"测试账号"},{"phone":"13256441111","userId":"8773107092004B40BB9FD8C159888D96","userName":"dongsheng_ys","userRealName":"dongsheng_ys"},{"phone":"17773069705","qq":"252231478","userId":"C542D82CE9AE4E0CA67E097008E5EC9C","userName":"fangjing6066","userRealName":"方静"},{"phone":"未知","userId":"5EB7CE0783774147A54E69A7E77A8B8A","userName":"fyw1","userRealName":"fyw1"},{"phone":"17773069331","qq":"645708253","userId":"F59788ADA12D42F38955749B10183FD2","userName":"gaomingzhu","userRealName":"高明珠"},{"phone":"17773069730","userId":"42D715F3BCEF47598A874340836A161C","userName":"geshenghua","userRealName":"葛生华"},{"phone":"17773069332","qq":"317268372","userId":"7788347362204F2690274CBA94891771","userName":"lishuang","userRealName":"李双"},{"phone":"17773069330","qq":"1161097955","userId":"2642A854E83140DC9E9C18B2F579FFF4","userName":"liuya","userRealName":"刘涯"},{"phone":"未知","userId":"87AE4788E1E74C5AA7BF214194DAAFEE","userName":"majiao","userRealName":"马姣"},{"phone":"13575009113","userId":"D85D37778F5744C7823D9DE75C2C77F9","userName":"xuzhixia","userRealName":"许志霞"},{"phone":"17773069329","qq":"124912358","userId":"721DB6536501485198F033D371D12A5E","userName":"yangtao","userRealName":"杨涛"},{"phone":"17773069328","qq":"42426382","userId":"851D39149D294A598BE778C30D33CD06","userName":"youxianyi","userRealName":"游险夷"}]}};
数据是已经排过序的,这样做起来会方便很多。
封装成函数

function wrapContactList(data){  
    var html = "",oldChar = "";  
    mui.each(data,function(index,result){  
        var c = result.userName.substr(0,1);  
        if(c != oldChar){  
            //当前userName与上一个不同则另加一个索引  
            html += '<li data-group="'+c.toUpperCase()+'" class="mui-table-view-divider mui-indexed-list-group">'+c.toUpperCase()+'</li>';  
            oldChar = c;  
        }  
        html += '<li data-userid="'+result.userId+'" data-username="'+result.userName+'" data-phone="'+result.phone+'" class="mui-table-view-cell mui-indexed-list-item">'+result.userRealName+ (result.phone.length>7?"("+result.phone+")":"") +'</li>';  
    });  
    //遍历结束插入数据  
    document.getElementById("contactList").innerHTML = html;  
    //重新实例化索引列表,不然快速查找不生效  
    mui("#list").indexedList();  
}  
继续阅读 »

最近做项目用到索引列表,这里跟大家分享下我的经验,效率不是最好,大家一起讨论
接口返回数据格式:
{"result":{"code":"200","data":[{"phone":"未知","userId":"D63E526F8BF64FB6AF82E662B997F8FE","userName":"ceshi01","userRealName":"测试账号"},{"phone":"13256441111","userId":"8773107092004B40BB9FD8C159888D96","userName":"dongsheng_ys","userRealName":"dongsheng_ys"},{"phone":"17773069705","qq":"252231478","userId":"C542D82CE9AE4E0CA67E097008E5EC9C","userName":"fangjing6066","userRealName":"方静"},{"phone":"未知","userId":"5EB7CE0783774147A54E69A7E77A8B8A","userName":"fyw1","userRealName":"fyw1"},{"phone":"17773069331","qq":"645708253","userId":"F59788ADA12D42F38955749B10183FD2","userName":"gaomingzhu","userRealName":"高明珠"},{"phone":"17773069730","userId":"42D715F3BCEF47598A874340836A161C","userName":"geshenghua","userRealName":"葛生华"},{"phone":"17773069332","qq":"317268372","userId":"7788347362204F2690274CBA94891771","userName":"lishuang","userRealName":"李双"},{"phone":"17773069330","qq":"1161097955","userId":"2642A854E83140DC9E9C18B2F579FFF4","userName":"liuya","userRealName":"刘涯"},{"phone":"未知","userId":"87AE4788E1E74C5AA7BF214194DAAFEE","userName":"majiao","userRealName":"马姣"},{"phone":"13575009113","userId":"D85D37778F5744C7823D9DE75C2C77F9","userName":"xuzhixia","userRealName":"许志霞"},{"phone":"17773069329","qq":"124912358","userId":"721DB6536501485198F033D371D12A5E","userName":"yangtao","userRealName":"杨涛"},{"phone":"17773069328","qq":"42426382","userId":"851D39149D294A598BE778C30D33CD06","userName":"youxianyi","userRealName":"游险夷"}]}};
数据是已经排过序的,这样做起来会方便很多。
封装成函数

function wrapContactList(data){  
    var html = "",oldChar = "";  
    mui.each(data,function(index,result){  
        var c = result.userName.substr(0,1);  
        if(c != oldChar){  
            //当前userName与上一个不同则另加一个索引  
            html += '<li data-group="'+c.toUpperCase()+'" class="mui-table-view-divider mui-indexed-list-group">'+c.toUpperCase()+'</li>';  
            oldChar = c;  
        }  
        html += '<li data-userid="'+result.userId+'" data-username="'+result.userName+'" data-phone="'+result.phone+'" class="mui-table-view-cell mui-indexed-list-item">'+result.userRealName+ (result.phone.length>7?"("+result.phone+")":"") +'</li>';  
    });  
    //遍历结束插入数据  
    document.getElementById("contactList").innerHTML = html;  
    //重新实例化索引列表,不然快速查找不生效  
    mui("#list").indexedList();  
}  
收起阅读 »

【交流分享】Dcloud开发APP心得系列之轻松调试解决问题篇

调试 技术分享

经常遇到群里或社区有人问些无从答起的问题:为什么我这块的数据没有显示出来?为什么我这个页面滚动不了?为什么控制台报错了,然后各种截图丢出来。。。
一脸懵逼,完全不知道从何答起。

对于这些摸不着头脑的问题真的不如自己调试下来的快。

1、当然是Hbuilder编辑器自带的控制台输出调试,console.log输出字符串,json对象JSON.stringify()后输出,object对象使用Object.keys()循环输出属性、键值,配合输出就能轻松判断问题在哪里,哪里出错。

2、作为一名前端首选的调试工具当然是chrome谷歌浏览自带的调试工具,APP如何使用谷歌调试?
真机或模拟器(限安卓系统)运行APP,谷歌浏览器访问chrome://inspect,就会列出当前设备运行的页面,inspect就可以打开谷歌控制台,就跟web一样打断点,审查元素、css等,点击左上角手机图标开启手机预览,部分手机可能左侧不会显示当前页面的视图,就无法用点击查看元素,得手动展开元素结构。
注:有时可能需要翻墙inspect才能加载出控制台,不过Hbuilder最新alpha版本已支持完全不需要翻墙。
补充:ios系统 请求使用Safari浏览器调试,用法类似,参考:http://ask.dcloud.net.cn/article/143

3、FE助手,一款谷歌插件,自动格式化访问接口返回的json对象,以及前端开发常用工具

有了这些不愁问题找不到根源,轻松解决各种莫名其妙的问题。当然所有的前提是你的代码整洁规范,不然谁都救不了你。

飞☞☞☞ 初始篇

继续阅读 »

经常遇到群里或社区有人问些无从答起的问题:为什么我这块的数据没有显示出来?为什么我这个页面滚动不了?为什么控制台报错了,然后各种截图丢出来。。。
一脸懵逼,完全不知道从何答起。

对于这些摸不着头脑的问题真的不如自己调试下来的快。

1、当然是Hbuilder编辑器自带的控制台输出调试,console.log输出字符串,json对象JSON.stringify()后输出,object对象使用Object.keys()循环输出属性、键值,配合输出就能轻松判断问题在哪里,哪里出错。

2、作为一名前端首选的调试工具当然是chrome谷歌浏览自带的调试工具,APP如何使用谷歌调试?
真机或模拟器(限安卓系统)运行APP,谷歌浏览器访问chrome://inspect,就会列出当前设备运行的页面,inspect就可以打开谷歌控制台,就跟web一样打断点,审查元素、css等,点击左上角手机图标开启手机预览,部分手机可能左侧不会显示当前页面的视图,就无法用点击查看元素,得手动展开元素结构。
注:有时可能需要翻墙inspect才能加载出控制台,不过Hbuilder最新alpha版本已支持完全不需要翻墙。
补充:ios系统 请求使用Safari浏览器调试,用法类似,参考:http://ask.dcloud.net.cn/article/143

3、FE助手,一款谷歌插件,自动格式化访问接口返回的json对象,以及前端开发常用工具

有了这些不愁问题找不到根源,轻松解决各种莫名其妙的问题。当然所有的前提是你的代码整洁规范,不然谁都救不了你。

飞☞☞☞ 初始篇

收起阅读 »

【交流分享】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();
});

收起阅读 »