HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

HBuilderX:代码块说明及自定义代码块教程

自定义代码块 HBuilderX

本帖文档已集成到: hx产品文档

代码块是快速开发的利器。简单的敲几个字母,回车,就能生成大段代码。

比如我们经常会敲if...else结构,在HBuilderX中,只需敲ife回车,就能直接生成相应的代码结构。

  1. 敲ife
  2. 回车后生成if结构体

HBuilderX已经内置了大量常用的代码块,熟悉这些代码块,对于提高编程效率有重要帮助。

查看内建的代码块,点菜单-工具-代码块设置,选择你要查看的语言的代码块。

打开的界面中,左侧即是预置的代码块,右侧是开发者可以自己扩展代码块的地方。

常用代码块列表

通用js代码块

  • iff :简单if
  • forr :for循环结构体
  • fori :for循环结构体并包含i
  • funn:函数
  • funa:匿名函数
  • clog:打印日志
  • clogvar:打印变量命名和值

    dom代码块

  • dg :document.getElementById
  • dl :$("")

    vue代码块

    敲v,即可拉出各种vue代码块

    uni-app代码块

    敲u,即可拉出各种uni-app代码块
    还有ifios、ifandroid,这2个平台判断代码块(HBuilderX 1.9.10+)

模板示例

自定义代码块都是配置json文件中的,直接来一个js例子吧,上述ife代码块的配置如下:

{  
"if ... else": {  
    "body": [  
        "if ($1) {",  
        "\t$0",  
        "} else{",  
        "\t",  
        "}"  
    ],  
    "prefix": "ife",  
    "scope": "source.js"  
}  
}

代码块配置格式说明

HBuilderX使用json定义代码块的格式,兼容vscode的代码块格式,也就是你可以把vscode里已经配置的自定义代码块方便的挪到HBuilderX中使用。

每个配置项的说明如下:

key

"key" :代码块显示名称,显示在代码助手列表中的名字。key是不能重复的。
上面例子中"if ... else"就是一个key

prefix

"prefix" :代码块的触发字符,就是敲什么字母可以激活这个代码块。

body

"body" :代码块的内容。内容中有如下特殊格式

$1 表示代码块输入后光标的所在位置。如需要多光标,就在多个地方配置$1;如该位置有预置数据且需要选中,则写法是${1:selectedtext};这里还支持下拉候选菜单,多选项即下拉候选列表使用${1:foo1/foo2/foo3}
$2 表示代码块输入后再次按tab后光标的切换位置tabstops(代码块展开后按tab可以跳到下一个tabstop,在HBuilderX中看到类似绿色光标的不闪的竖线,就可以按tab或回车跳转光标过去)
$0代表代码块输入后最终光标的所在位置(也可以按回车直接跳过去)。

双引号使用\"转义
换行使用多个数组表示,每个行一个数组,用双引号包围,并用逗号分隔
缩进需要用\t表示,不能直接输入缩进或空格!

triggerAssist

"triggerAssist" :为true表示该代码块输入到文档后立即在第一个tabstop上触发代码提示,拉出代码助手,默认为false

project

project: 将代码块控制在指定项目类型下生效。可取值有:uni-appWebAppWap2App

比如:"project": "uni-app",代表这个代码块仅在uni-app项目下生效

如果不设置,则该代码块在所有项目类型下均生效。

Web指普通项目,App指5+App项目。

如需设置多种项目类型,用逗号分隔。比如:"project": "uni-app,App"

注意事项

  1. 每个代码块以key为主键,多个代码块需要逗号分隔。
  2. 如果json语法不合法,编辑器状态栏会弹出错误信息,json中会画出红波浪线,请注意修正。
继续阅读 »

本帖文档已集成到: hx产品文档

代码块是快速开发的利器。简单的敲几个字母,回车,就能生成大段代码。

比如我们经常会敲if...else结构,在HBuilderX中,只需敲ife回车,就能直接生成相应的代码结构。

  1. 敲ife
  2. 回车后生成if结构体

HBuilderX已经内置了大量常用的代码块,熟悉这些代码块,对于提高编程效率有重要帮助。

查看内建的代码块,点菜单-工具-代码块设置,选择你要查看的语言的代码块。

打开的界面中,左侧即是预置的代码块,右侧是开发者可以自己扩展代码块的地方。

常用代码块列表

通用js代码块

  • iff :简单if
  • forr :for循环结构体
  • fori :for循环结构体并包含i
  • funn:函数
  • funa:匿名函数
  • clog:打印日志
  • clogvar:打印变量命名和值

    dom代码块

  • dg :document.getElementById
  • dl :$("")

    vue代码块

    敲v,即可拉出各种vue代码块

    uni-app代码块

    敲u,即可拉出各种uni-app代码块
    还有ifios、ifandroid,这2个平台判断代码块(HBuilderX 1.9.10+)

模板示例

自定义代码块都是配置json文件中的,直接来一个js例子吧,上述ife代码块的配置如下:

{  
"if ... else": {  
    "body": [  
        "if ($1) {",  
        "\t$0",  
        "} else{",  
        "\t",  
        "}"  
    ],  
    "prefix": "ife",  
    "scope": "source.js"  
}  
}

代码块配置格式说明

HBuilderX使用json定义代码块的格式,兼容vscode的代码块格式,也就是你可以把vscode里已经配置的自定义代码块方便的挪到HBuilderX中使用。

每个配置项的说明如下:

key

"key" :代码块显示名称,显示在代码助手列表中的名字。key是不能重复的。
上面例子中"if ... else"就是一个key

prefix

"prefix" :代码块的触发字符,就是敲什么字母可以激活这个代码块。

body

"body" :代码块的内容。内容中有如下特殊格式

$1 表示代码块输入后光标的所在位置。如需要多光标,就在多个地方配置$1;如该位置有预置数据且需要选中,则写法是${1:selectedtext};这里还支持下拉候选菜单,多选项即下拉候选列表使用${1:foo1/foo2/foo3}
$2 表示代码块输入后再次按tab后光标的切换位置tabstops(代码块展开后按tab可以跳到下一个tabstop,在HBuilderX中看到类似绿色光标的不闪的竖线,就可以按tab或回车跳转光标过去)
$0代表代码块输入后最终光标的所在位置(也可以按回车直接跳过去)。

双引号使用\"转义
换行使用多个数组表示,每个行一个数组,用双引号包围,并用逗号分隔
缩进需要用\t表示,不能直接输入缩进或空格!

triggerAssist

"triggerAssist" :为true表示该代码块输入到文档后立即在第一个tabstop上触发代码提示,拉出代码助手,默认为false

project

project: 将代码块控制在指定项目类型下生效。可取值有:uni-appWebAppWap2App

比如:"project": "uni-app",代表这个代码块仅在uni-app项目下生效

如果不设置,则该代码块在所有项目类型下均生效。

Web指普通项目,App指5+App项目。

如需设置多种项目类型,用逗号分隔。比如:"project": "uni-app,App"

注意事项

  1. 每个代码块以key为主键,多个代码块需要逗号分隔。
  2. 如果json语法不合法,编辑器状态栏会弹出错误信息,json中会画出红波浪线,请注意修正。
收起阅读 »

分享一个Andorid手机插入一个日程事件

Native.JS 日历 mui

实现功能:

  1. 建立自己的账户,每次插入日历都使用自己的账户,区分第三方日历账户与系统日历账户
  2. 实现建立的日历删除
  3. 实现2次闹钟提醒,(addcalendar中的参数 fr, tr的值必须为分钟数字,比如:你的闹钟设置是1个小时提醒,输入的参数必须是60)

mui.plusReady(function() {
if(mui.os.ios) {

            } else {  
                var calanderURL = 'content://com.android.calendar/calendars',  
                    eventsURL = 'content://com.android.calendar/events',  
                    ContentValues = plus.android.importClass("android.content.ContentValues"),  
                    Uri = plus.android.importClass('android.net.Uri'),  
                    ContentUris = plus.android.importClass('android.content.ContentUris'),  
                    Calendar = plus.android.importClass('java.util.Calendar'),  
                    main = plus.android.runtimeMainActivity(),  
                    userCursor = plus.android.invoke(main.getContentResolver(), 'query', Uri.parse(calanderURL), null, null, null, null),  
                    userCursor_count = plus.android.invoke(userCursor, 'getCount'),  
                    TimeZone = plus.android.importClass('java.util.TimeZone'),  
                    TimeZone_str = plus.android.invoke(TimeZone.getDefault(), 'getID');  
            }  
                             //Nid参数为数据库中的一个id,可以不用,我用这个Nid实现与手机系统的日历事件id进行关联  
            addcalendar = function(Nid, title, description, date_str, etime, fr, tr) {  
                if(userCursor_count <= 0) { //如果没有日历账户   
                    addcalendaraccount();   
                    userCursor = plus.android.invoke(main.getContentResolver(), 'query', Uri.parse(calanderURL), null, null, null, null);  
                    userCursor_count++;  
                }    
                plus.android.invoke(userCursor, 'moveToFirst');//第一个  
                //plus.android.invoke(userCursor, 'moveToLast');//最后一个  
                //plus.android.invoke(userCursor, 'moveToNext');//下一个  
                for(var i = 0; i < userCursor_count; i++) {  
                    var calName = plus.android.invoke(userCursor, 'getString', plus.android.invoke(userCursor, 'getColumnIndex', 'calendar_displayName'));  
                    //获得日历的描述名称  
                    if(calName == "OA日历") {  
                        break;  
                    }  
                    if(i == (userCursor_count - 1)) {  
                        addcalendaraccount();   
                        userCursor = plus.android.invoke(main.getContentResolver(), 'query', Uri.parse(calanderURL), null, null, null, null);  
                        userCursor_count++;  
                    }  
                    plus.android.invoke(userCursor, 'moveToNext');  
                }  

                try {  
                    var calId = plus.android.invoke(userCursor, 'getString', plus.android.invoke(userCursor, 'getColumnIndex', '_id')),  
                        events = new ContentValues(),  
                        mCalendar = Calendar.getInstance(),  
                        date = date_str.split(/\s{1}|:|-/g);  
                    plus.android.invoke(mCalendar, 'set', Calendar.YEAR, ~~date[0]);  
                    plus.android.invoke(mCalendar, 'set', Calendar.MONTH, ((~~date[1]) - 1));  
                    plus.android.invoke(mCalendar, 'set', Calendar.DATE, ~~date[2]);  
                    plus.android.invoke(mCalendar, 'set', Calendar.HOUR_OF_DAY, ~~date[3]);  
                    plus.android.invoke(mCalendar, 'set', Calendar.MINUTE, ~~date[4]);  
                    var start = plus.android.invoke(plus.android.invoke(mCalendar, 'getTime'), 'getTime');  
                    //设置日历事件   
                    var etimestamp = new Date(etime.replace(/-/g, "\/")).getTime();//时间可以用传入参数的模式  
                    events.put('title', title);  
                    events.put('description', description);  
                    events.put('calendar_id', calId);  
                    events.put('dtstart', start);  
                    events.put('dtend', etimestamp);  
                    //events.put('ALL_DAY', 0); //值为 1 表示此事件占用一整天(按照本地时区的定义)。 值为 0 表示它是常规事件,可在一天内的任何时间开始和结束。  
                    //events.put('RRULE')  
                    events.put('hasAlarm', 1);  
                    events.put('eventTimezone', TimeZone_str);  
                    var newEvent = plus.android.invoke(main.getContentResolver(), 'insert', Uri.parse('content://com.android.calendar/events'), events);  
                    var id = plus.android.invoke(newEvent, 'getLastPathSegment');  
                    if(fr.length > 0) {  
                        var values = new ContentValues();  
                        values.put('event_id', id);  
                        values.put('minutes', fr);  
                        values.put('method', '1');  
                        plus.android.invoke(main.getContentResolver(), 'insert', Uri.parse('content://com.android.calendar/reminders'), values);  
                    }  
                    if(tr.length > 0) {  
                        var values2 = new ContentValues();  
                        values2.put('event_id', id);  
                        values2.put('minutes', tr);  
                        values2.put('method', '1');  
                        plus.android.invoke(main.getContentResolver(), 'insert', Uri.parse('content://com.android.calendar/reminders'), values2);  
                    }   
                    //SaveCalendID(id, Nid, callSetProRmData); 把插入日历的id保存起来,便于删除调用  
                    //也可以用于更新(先删后插入);  
                } catch(e) {   
                        mui.alert("您没有允许APP访问日历的权限");   
                }  
            }  

            deletecalendar = function(eventID) {  
                if(userCursor_count <= 0) { //如果没有日历账户  
                    addcalendaraccount();  
                }  
                var id = Number(eventID);  
                //alert(id);  
                var deleteUri = ContentUris.withAppendedId(Uri.parse(eventsURL), id);  
                var rows = plus.android.invoke(main.getContentResolver(), "delete", deleteUri, null, null);  
                if(rows == -1) {   
                    return;  
                }   
            }  

            addcalendaraccount = function() {  
                var account = new ContentValues(),  
                    buildUpon = plus.android.invoke(Uri.parse(calanderURL), 'buildUpon'),  
                    CalendarContract = plus.android.importClass('android.provider.CalendarContract');  
                plus.android.invoke(buildUpon, 'appendQueryParameter', CalendarContract.CALLER_IS_SYNCADAPTER, 'true');  
                plus.android.invoke(buildUpon, 'appendQueryParameter', 'account_name', 'OA@xxx.com.com');  
                plus.android.invoke(buildUpon, 'appendQueryParameter', 'account_type', 'com.android.exchange');  
                //设置账户信息  
                account.put('name', 'OAAppAdmin');  
                account.put('account_name', 'OA@xxx.com.com');  
                account.put('account_type', 'com.android.exchange');  
                account.put('calendar_displayName', 'OA日历');  
                account.put('visible', 1);  
                account.put('calendar_color', '-9206951');  
                account.put('calendar_access_level', '700');  
                account.put('sync_events', 1);  
                account.put('calendar_timezone', TimeZone_str);  
                account.put('ownerAccount', 'OA@xxx.com.com');  
                account.put('canOrganizerRespond', 0);  
                //保存账户信息  
                plus.android.invoke(main.getContentResolver(), 'insert', plus.android.invoke(buildUpon, 'build'), account);  
            }   
        });
继续阅读 »

实现功能:

  1. 建立自己的账户,每次插入日历都使用自己的账户,区分第三方日历账户与系统日历账户
  2. 实现建立的日历删除
  3. 实现2次闹钟提醒,(addcalendar中的参数 fr, tr的值必须为分钟数字,比如:你的闹钟设置是1个小时提醒,输入的参数必须是60)

mui.plusReady(function() {
if(mui.os.ios) {

            } else {  
                var calanderURL = 'content://com.android.calendar/calendars',  
                    eventsURL = 'content://com.android.calendar/events',  
                    ContentValues = plus.android.importClass("android.content.ContentValues"),  
                    Uri = plus.android.importClass('android.net.Uri'),  
                    ContentUris = plus.android.importClass('android.content.ContentUris'),  
                    Calendar = plus.android.importClass('java.util.Calendar'),  
                    main = plus.android.runtimeMainActivity(),  
                    userCursor = plus.android.invoke(main.getContentResolver(), 'query', Uri.parse(calanderURL), null, null, null, null),  
                    userCursor_count = plus.android.invoke(userCursor, 'getCount'),  
                    TimeZone = plus.android.importClass('java.util.TimeZone'),  
                    TimeZone_str = plus.android.invoke(TimeZone.getDefault(), 'getID');  
            }  
                             //Nid参数为数据库中的一个id,可以不用,我用这个Nid实现与手机系统的日历事件id进行关联  
            addcalendar = function(Nid, title, description, date_str, etime, fr, tr) {  
                if(userCursor_count <= 0) { //如果没有日历账户   
                    addcalendaraccount();   
                    userCursor = plus.android.invoke(main.getContentResolver(), 'query', Uri.parse(calanderURL), null, null, null, null);  
                    userCursor_count++;  
                }    
                plus.android.invoke(userCursor, 'moveToFirst');//第一个  
                //plus.android.invoke(userCursor, 'moveToLast');//最后一个  
                //plus.android.invoke(userCursor, 'moveToNext');//下一个  
                for(var i = 0; i < userCursor_count; i++) {  
                    var calName = plus.android.invoke(userCursor, 'getString', plus.android.invoke(userCursor, 'getColumnIndex', 'calendar_displayName'));  
                    //获得日历的描述名称  
                    if(calName == "OA日历") {  
                        break;  
                    }  
                    if(i == (userCursor_count - 1)) {  
                        addcalendaraccount();   
                        userCursor = plus.android.invoke(main.getContentResolver(), 'query', Uri.parse(calanderURL), null, null, null, null);  
                        userCursor_count++;  
                    }  
                    plus.android.invoke(userCursor, 'moveToNext');  
                }  

                try {  
                    var calId = plus.android.invoke(userCursor, 'getString', plus.android.invoke(userCursor, 'getColumnIndex', '_id')),  
                        events = new ContentValues(),  
                        mCalendar = Calendar.getInstance(),  
                        date = date_str.split(/\s{1}|:|-/g);  
                    plus.android.invoke(mCalendar, 'set', Calendar.YEAR, ~~date[0]);  
                    plus.android.invoke(mCalendar, 'set', Calendar.MONTH, ((~~date[1]) - 1));  
                    plus.android.invoke(mCalendar, 'set', Calendar.DATE, ~~date[2]);  
                    plus.android.invoke(mCalendar, 'set', Calendar.HOUR_OF_DAY, ~~date[3]);  
                    plus.android.invoke(mCalendar, 'set', Calendar.MINUTE, ~~date[4]);  
                    var start = plus.android.invoke(plus.android.invoke(mCalendar, 'getTime'), 'getTime');  
                    //设置日历事件   
                    var etimestamp = new Date(etime.replace(/-/g, "\/")).getTime();//时间可以用传入参数的模式  
                    events.put('title', title);  
                    events.put('description', description);  
                    events.put('calendar_id', calId);  
                    events.put('dtstart', start);  
                    events.put('dtend', etimestamp);  
                    //events.put('ALL_DAY', 0); //值为 1 表示此事件占用一整天(按照本地时区的定义)。 值为 0 表示它是常规事件,可在一天内的任何时间开始和结束。  
                    //events.put('RRULE')  
                    events.put('hasAlarm', 1);  
                    events.put('eventTimezone', TimeZone_str);  
                    var newEvent = plus.android.invoke(main.getContentResolver(), 'insert', Uri.parse('content://com.android.calendar/events'), events);  
                    var id = plus.android.invoke(newEvent, 'getLastPathSegment');  
                    if(fr.length > 0) {  
                        var values = new ContentValues();  
                        values.put('event_id', id);  
                        values.put('minutes', fr);  
                        values.put('method', '1');  
                        plus.android.invoke(main.getContentResolver(), 'insert', Uri.parse('content://com.android.calendar/reminders'), values);  
                    }  
                    if(tr.length > 0) {  
                        var values2 = new ContentValues();  
                        values2.put('event_id', id);  
                        values2.put('minutes', tr);  
                        values2.put('method', '1');  
                        plus.android.invoke(main.getContentResolver(), 'insert', Uri.parse('content://com.android.calendar/reminders'), values2);  
                    }   
                    //SaveCalendID(id, Nid, callSetProRmData); 把插入日历的id保存起来,便于删除调用  
                    //也可以用于更新(先删后插入);  
                } catch(e) {   
                        mui.alert("您没有允许APP访问日历的权限");   
                }  
            }  

            deletecalendar = function(eventID) {  
                if(userCursor_count <= 0) { //如果没有日历账户  
                    addcalendaraccount();  
                }  
                var id = Number(eventID);  
                //alert(id);  
                var deleteUri = ContentUris.withAppendedId(Uri.parse(eventsURL), id);  
                var rows = plus.android.invoke(main.getContentResolver(), "delete", deleteUri, null, null);  
                if(rows == -1) {   
                    return;  
                }   
            }  

            addcalendaraccount = function() {  
                var account = new ContentValues(),  
                    buildUpon = plus.android.invoke(Uri.parse(calanderURL), 'buildUpon'),  
                    CalendarContract = plus.android.importClass('android.provider.CalendarContract');  
                plus.android.invoke(buildUpon, 'appendQueryParameter', CalendarContract.CALLER_IS_SYNCADAPTER, 'true');  
                plus.android.invoke(buildUpon, 'appendQueryParameter', 'account_name', 'OA@xxx.com.com');  
                plus.android.invoke(buildUpon, 'appendQueryParameter', 'account_type', 'com.android.exchange');  
                //设置账户信息  
                account.put('name', 'OAAppAdmin');  
                account.put('account_name', 'OA@xxx.com.com');  
                account.put('account_type', 'com.android.exchange');  
                account.put('calendar_displayName', 'OA日历');  
                account.put('visible', 1);  
                account.put('calendar_color', '-9206951');  
                account.put('calendar_access_level', '700');  
                account.put('sync_events', 1);  
                account.put('calendar_timezone', TimeZone_str);  
                account.put('ownerAccount', 'OA@xxx.com.com');  
                account.put('canOrganizerRespond', 0);  
                //保存账户信息  
                plus.android.invoke(main.getContentResolver(), 'insert', plus.android.invoke(buildUpon, 'build'), account);  
            }   
        });
收起阅读 »

分享一款mui开发的一款在线教育app

直播

印趣云课堂APP截图1
印趣云课堂APP截图2
印趣云课堂APP截图3
印趣云课堂APP截图4
印趣云课堂APP截图5
印趣云课堂APP截图6
印趣云课堂APP截图7
印趣云课堂APP截图8
印趣云课堂APP截图9
印趣云课堂APP截图10
印趣云课堂APP截图11
印趣云课堂APP截图12
印趣云课堂APP截图13

印趣云课堂微信公众号

演示地址 https://0x9.me/OaVLT

继续阅读 »

印趣云课堂APP截图1
印趣云课堂APP截图2
印趣云课堂APP截图3
印趣云课堂APP截图4
印趣云课堂APP截图5
印趣云课堂APP截图6
印趣云课堂APP截图7
印趣云课堂APP截图8
印趣云课堂APP截图9
印趣云课堂APP截图10
印趣云课堂APP截图11
印趣云课堂APP截图12
印趣云课堂APP截图13

印趣云课堂微信公众号

演示地址 https://0x9.me/OaVLT

收起阅读 »

关于uniapp中getphonenumber的问题

昨天遇到了uniapp在开发的时候,使用wx.getPhoneNumber报错的问题。在社区里找了很多帖子也没有具体解决办法,下面是今天我解决后的一些经验:

  1. 首先确保你在调用wx.getPhoneNumber这个之前,是已经做了wx.login()这个方法,在这里笔者是在onLoad的时候调用的。
  2. 其次一定是以button组件去调用<button open-type="getPhoneNumber" @getphonenumber="getPhoneNumber">
  3. 第三method中已写好getPhoneNumber函数。
  4. 最后确定你的小程序是否有权限调用getPhoneNumber函数,微信只对认证的小程序开放此类接口。
继续阅读 »

昨天遇到了uniapp在开发的时候,使用wx.getPhoneNumber报错的问题。在社区里找了很多帖子也没有具体解决办法,下面是今天我解决后的一些经验:

  1. 首先确保你在调用wx.getPhoneNumber这个之前,是已经做了wx.login()这个方法,在这里笔者是在onLoad的时候调用的。
  2. 其次一定是以button组件去调用<button open-type="getPhoneNumber" @getphonenumber="getPhoneNumber">
  3. 第三method中已写好getPhoneNumber函数。
  4. 最后确定你的小程序是否有权限调用getPhoneNumber函数,微信只对认证的小程序开放此类接口。
收起阅读 »

Mob短信验证码插件使用说明

欢迎使用Mob短信验证码插件


插件地址:
https://ext.dcloud.net.cn/plugin?id=399
MobSms实现了注册时短信验证,取回密码手机验证功能,使用此模块之前需要先去http://mob.com/ 注册获取MOBAppKey和MOBAppSecret,mob每天可以有10000条免费的短信,基本上可以算是免费的了

使用注意事项: 1、请在云端打包!请在云端打包!请在云端打包!(重要的事说三遍) 2、短信中的掌淘科技可以去除,具体请咨询mob官方 3、老给一个手机号发短信会导致失败率很高,这个是运营商为了防骚扰做的

使用Mob短信验证码插件


1. 配置插件[^code]

将相应的MOBAppKey和MOBAppSecret填写到APP原生插件配置里  

1. 注册插件[^code]

const mobsms = uni.requireNativePlugin('HY-MobSms');

2. 发送验证码[^code]

// 请求验证码,其中country表示国家代码,如“86”;phone表示手机号码,如“13800138000”  
//成功  
mobsms.sendSms({country:'86',phone:this.phone},result=>{  
    console.log(JSON.stringify(result))  
    if(result.code==1){  
        uni.showToast({  
          title: "发送成功",  
          icon: "success"  
        })  
    }else{  
        uni.showToast({  
          title: "发送失败",  
          icon: "none"  
        })  
    //失败会返回错误码                                          //错误码对应查看地址:[http://wiki.mob.com/android-api-%E9%94%99%E8%AF%AF%E7%A0%81%E5%8F%82%E8%80%83/][3]  
    //const status= result.status  
})

1. 验证验证码[^code]

mobsms.verify({country:'86',phone:this.mobile.phone,code:this.mobile.code},result=>{  
    console.log(JSON.stringify(result))  
    if(result.code==1){  
        uni.showToast({  
          title: "验证成功",  
          icon: "success"  
        })  
    }else{  
        uni.showToast({  
          title: "验证失败",  
          icon: "none"  
        })  
        //失败会返回错误码  

        //错误码对应查看地址:[http://wiki.mob.com/android-api-%E9%94%99%E8%AF%AF%E7%A0%81%E5%8F%82%E8%80%83/][4]  
        //const status= result.status  
    }  
})
继续阅读 »

欢迎使用Mob短信验证码插件


插件地址:
https://ext.dcloud.net.cn/plugin?id=399
MobSms实现了注册时短信验证,取回密码手机验证功能,使用此模块之前需要先去http://mob.com/ 注册获取MOBAppKey和MOBAppSecret,mob每天可以有10000条免费的短信,基本上可以算是免费的了

使用注意事项: 1、请在云端打包!请在云端打包!请在云端打包!(重要的事说三遍) 2、短信中的掌淘科技可以去除,具体请咨询mob官方 3、老给一个手机号发短信会导致失败率很高,这个是运营商为了防骚扰做的

使用Mob短信验证码插件


1. 配置插件[^code]

将相应的MOBAppKey和MOBAppSecret填写到APP原生插件配置里  

1. 注册插件[^code]

const mobsms = uni.requireNativePlugin('HY-MobSms');

2. 发送验证码[^code]

// 请求验证码,其中country表示国家代码,如“86”;phone表示手机号码,如“13800138000”  
//成功  
mobsms.sendSms({country:'86',phone:this.phone},result=>{  
    console.log(JSON.stringify(result))  
    if(result.code==1){  
        uni.showToast({  
          title: "发送成功",  
          icon: "success"  
        })  
    }else{  
        uni.showToast({  
          title: "发送失败",  
          icon: "none"  
        })  
    //失败会返回错误码                                          //错误码对应查看地址:[http://wiki.mob.com/android-api-%E9%94%99%E8%AF%AF%E7%A0%81%E5%8F%82%E8%80%83/][3]  
    //const status= result.status  
})

1. 验证验证码[^code]

mobsms.verify({country:'86',phone:this.mobile.phone,code:this.mobile.code},result=>{  
    console.log(JSON.stringify(result))  
    if(result.code==1){  
        uni.showToast({  
          title: "验证成功",  
          icon: "success"  
        })  
    }else{  
        uni.showToast({  
          title: "验证失败",  
          icon: "none"  
        })  
        //失败会返回错误码  

        //错误码对应查看地址:[http://wiki.mob.com/android-api-%E9%94%99%E8%AF%AF%E7%A0%81%E5%8F%82%E8%80%83/][4]  
        //const status= result.status  
    }  
})
收起阅读 »

HBuilderX代码提示系统说明

代码提示 扩展代码提示 扩展语法库

> 本帖文档已集成到: hx产品文档

HBuilderX的代码提示系统很庞大,支持多种语法提示模型。

内置语法库

  • web项目有内置的html、js、css语法库
  • App项目有内置的plus扩展语法库
  • uni-app项目有内置的uni-app语法库
  • 微信小程序、快应用等项目也有对应的内置语法库

js框架语法库(sdocml格式)

HBuilderX中,在可以输入js的文件,比如js、html等文件里,(不含vue、ts),底部状态栏有“语法提示库”,可以加载内置的框架语法库。

其中node.js也是作为一种框架语法而存在的。
勾选相应js框架语法后,js区域即可提示相应语法(初次勾选需要延时几秒后才能使用)

该选择是项目级的,一旦勾选后,整个项目下可以写js的地方都会加载。
如果文件是单独打开,没有在HBuilderX左侧的项目管理器中,则无法使用本功能。

如果HBuilderX能检测到项目下有jquery或mui等常用框架,也会自动给这个项目挂载语法提示库。但有时可能检测不准,需要开发者手动引入。

d.ts

很多框架都内置了d.ts语法提示库。HBuilderX完整支持d.ts的语法提示。
如果项目下有某个框架的d.ts文件,HBuilderX则可以提示这个框架的语法提示。

jsdoc+

jsdoc是以注释方式声明方法、参数、属性,HBuilderX提供了经过扩展的jsdoc+,可实现强大的语法提示,详见:https://ask.dcloud.net.cn/docs/#//ask.dcloud.net.cn/article/129

vue doc

vue组件开发者,如果想给组件使用者提供更好的使用方式,应该给组件写vue doc。
vue doc是一种类似jsdoc的方式,通过在注释里描述组件的方法、参数、属性。
详见:https://ask.dcloud.net.cn/article/35814

兼容vscode vetur插件中的vue规范

一些vue的组件库,已经按照vetur规范制作语法提示库,比如Element UI、Onsen UI、Bootstrap Vue等框架。
这些框架npm安装是在node_module下会自带一个json语法库,或在HBuilderX新建模板中选择element ui模板安装也会包含该库。有了这个语法库,就可以直接代码提示。如下图

代码块

HBuilderX支持自定义代码块,在菜单工具-代码块设置中可自行扩展。
代码块数据格式兼容vscode,并扩展了更多丰富设置。对于提高开发效率帮助很大。

继续阅读 »

> 本帖文档已集成到: hx产品文档

HBuilderX的代码提示系统很庞大,支持多种语法提示模型。

内置语法库

  • web项目有内置的html、js、css语法库
  • App项目有内置的plus扩展语法库
  • uni-app项目有内置的uni-app语法库
  • 微信小程序、快应用等项目也有对应的内置语法库

js框架语法库(sdocml格式)

HBuilderX中,在可以输入js的文件,比如js、html等文件里,(不含vue、ts),底部状态栏有“语法提示库”,可以加载内置的框架语法库。

其中node.js也是作为一种框架语法而存在的。
勾选相应js框架语法后,js区域即可提示相应语法(初次勾选需要延时几秒后才能使用)

该选择是项目级的,一旦勾选后,整个项目下可以写js的地方都会加载。
如果文件是单独打开,没有在HBuilderX左侧的项目管理器中,则无法使用本功能。

如果HBuilderX能检测到项目下有jquery或mui等常用框架,也会自动给这个项目挂载语法提示库。但有时可能检测不准,需要开发者手动引入。

d.ts

很多框架都内置了d.ts语法提示库。HBuilderX完整支持d.ts的语法提示。
如果项目下有某个框架的d.ts文件,HBuilderX则可以提示这个框架的语法提示。

jsdoc+

jsdoc是以注释方式声明方法、参数、属性,HBuilderX提供了经过扩展的jsdoc+,可实现强大的语法提示,详见:https://ask.dcloud.net.cn/docs/#//ask.dcloud.net.cn/article/129

vue doc

vue组件开发者,如果想给组件使用者提供更好的使用方式,应该给组件写vue doc。
vue doc是一种类似jsdoc的方式,通过在注释里描述组件的方法、参数、属性。
详见:https://ask.dcloud.net.cn/article/35814

兼容vscode vetur插件中的vue规范

一些vue的组件库,已经按照vetur规范制作语法提示库,比如Element UI、Onsen UI、Bootstrap Vue等框架。
这些框架npm安装是在node_module下会自带一个json语法库,或在HBuilderX新建模板中选择element ui模板安装也会包含该库。有了这个语法库,就可以直接代码提示。如下图

代码块

HBuilderX支持自定义代码块,在菜单工具-代码块设置中可自行扩展。
代码块数据格式兼容vscode,并扩展了更多丰富设置。对于提高开发效率帮助很大。

收起阅读 »

h5地区选择器解决方案

省市选择

官方的地区选择并不支持h5,官方多级连选案例简直是一阔糊

那就用单选择器吧

                <picker @change="provinceChange" :value="provinceId" :range="provinceJson" range-key="name">  
                    <view class="uni-input">{{provinceJson[provinceId].name}}</view>  
                </picker>  

                <picker @change="regionChange" :value="regionId" :range="regionShowJson" range-key="name">  
                    <view class="uni-input">{{regionShowJson[regionId].name}}</view>  
                </picker>

先定义参数

                provinceId: 0,  
                regionId: 0,  
                regionShowJson: [  
                    {  
                        name:'请选择'  
                    }  
                ]  

//省市json在评论区

定义函数

            regionChange: function(e) {  
                // console.log(e.target)  
                console.log('picker发送选择改变,携带值为:' + e.target.value)  
                this.regionId = e.target.value  
            },  
            provinceChange: function(e) {  
                // console.log(e.target)  
                console.log('picker发送选择改变,携带值为:' + e.target.value)  
                this.provinceId = e.target.value  
                this.regionShowJson = this.regionJson[e.target.value].cities  
            },
继续阅读 »

官方的地区选择并不支持h5,官方多级连选案例简直是一阔糊

那就用单选择器吧

                <picker @change="provinceChange" :value="provinceId" :range="provinceJson" range-key="name">  
                    <view class="uni-input">{{provinceJson[provinceId].name}}</view>  
                </picker>  

                <picker @change="regionChange" :value="regionId" :range="regionShowJson" range-key="name">  
                    <view class="uni-input">{{regionShowJson[regionId].name}}</view>  
                </picker>

先定义参数

                provinceId: 0,  
                regionId: 0,  
                regionShowJson: [  
                    {  
                        name:'请选择'  
                    }  
                ]  

//省市json在评论区

定义函数

            regionChange: function(e) {  
                // console.log(e.target)  
                console.log('picker发送选择改变,携带值为:' + e.target.value)  
                this.regionId = e.target.value  
            },  
            provinceChange: function(e) {  
                // console.log(e.target)  
                console.log('picker发送选择改变,携带值为:' + e.target.value)  
                this.provinceId = e.target.value  
                this.regionShowJson = this.regionJson[e.target.value].cities  
            },
收起阅读 »

各种5+插件 uniapp插件集成 离线打包问题指导 欢迎咨询 qq 37894663

外包

各种5+插件、 uniapp插件集成 、离线打包问题指导 以及各种页面绘制 欢迎咨询 qq 37894663

各种5+插件、 uniapp插件集成 、离线打包问题指导 以及各种页面绘制 欢迎咨询 qq 37894663

iOS平台:用Native.js来写 如何判断系统功能权限是否开启

iOS 权限是否开启 Native.JS 权限判断 权限开启 授权 赋权

更新:插件市场已经提供了封装更完善版本:https://ext.dcloud.net.cn/plugin?id=594

1、推送

判断推送权限是否开启

var UIApplication = plus.ios.import("UIApplication");  
                var app = UIApplication.sharedApplication();  
                var enabledTypes = 0;  
                if (app.currentUserNotificationSettings) {  
                    var settings = app.currentUserNotificationSettings();  
                    enabledTypes = settings.plusGetAttribute("types");  
                    console.log("enabledTypes1:" + enabledTypes);  
                    if (enabledTypes == 0) {  
                        console.log("推送权限没有开启");  
                    }else {  
                        console.log("已经开启推送功能!")  
                    }  
                    plus.ios.deleteObject(settings);  
                } else {  
                    enabledTypes = app.enabledRemoteNotificationTypes();  
                    if (enabledTypes == 0) {  
                        console.log("推送权限没有开启!");  
                    } else {  
                        console.log("已经开启推送功能!")  
                    }  
                    console.log("enabledTypes2:" + enabledTypes);  
                }  
                plus.ios.deleteObject(app);  
                                plus.ios.deleteObject(UIApplication);

2、定位

判断定位权限是否开启

                                var cllocationManger = plus.ios.import("CLLocationManager");  
                var enable = cllocationManger.locationServicesEnabled();  
                var status = cllocationManger.authorizationStatus();  
                console.log("enable:" + enable);  
                console.log("status:" + status);  
                if (enable && status != 2) {  
                    console.log("手机系统的定位已经打开");  
                } else {  
                    console.log("手机系统的定位没有打开");  
                }  
                             plus.ios.deleteObject(cllocationManger);

3、麦克风

判断麦克风权限是否开启

                                var avaudiosession = plus.ios.import("AVAudioSession");  
                var avaudio = avaudiosession.sharedInstance();  
                var permissionStatus = avaudio.recordPermission();  
                console.log("permissionStatus:" + permissionStatus);  
                if (permissionStatus == 1684369017 || permissionStatus == 1970168948) {   
                    console.log("麦克风权限没有开启");  
                } else {  
                    console.log("麦克风权限已经开启");  
                }  
                    plus.ios.deleteObject(avaudiosession);

4、相机

判断相机权限是否开启

                               var AVCaptureDevice = plus.ios.import("AVCaptureDevice");  
                var authStatus = AVCaptureDevice.authorizationStatusForMediaType('vide');  
                console.log("authStatus:" + authStatus);  
                if (authStatus == 3) {  
                    console.log("相机权限已经开启");  
                } else {  
                    console.log("相机权限没有开启");  
                }  
                    plus.ios.deleteObject(AVCaptureDevice);

5、相册

判断相册权限是否开启

                              var PHPhotoLibrary = plus.ios.import("PHPhotoLibrary");  
                var authStatus = PHPhotoLibrary.authorizationStatus();  
                console.log("authStatus:" + authStatus);  
                if (authStatus == 3) {  
                    console.log("相册权限已经开启");  
                } else  {  
                    console.log("相册权限没有开启");  
                }  
                    plus.ios.deleteObject(PHPhotoLibrary);

6、通讯录

判断通讯录权限是否开启

                              var CNContactStore = plus.ios.import("CNContactStore");  
                var cnAuthStatus = CNContactStore.authorizationStatusForEntityType(0);  
                if (cnAuthStatus == 3) {  
                    console.log("通讯录权限已经开启");  
                } else  {  
                    console.log("通讯录权限没有开启");  
                }  
                    plus.ios.deleteObject(CNContactStore);

7、日历

判断日历权限是否开启

                              var EKEventStore = plus.ios.import("EKEventStore");  
                  var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(0);  
                if (ekAuthStatus == 3) {  
                    console.log("日历权限已经开启");  
                } else  {  
                    console.log("日历权限没有开启");  
                }  
                    plus.ios.deleteObject(EKEventStore);

8、备忘录

判断备忘录权限是否开启

                              var EKEventStore = plus.ios.import("EKEventStore");  
                  var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(1);  
                if (ekAuthStatus == 3) {  
                    console.log("备忘录权限已经开启");  
                } else  {  
                    console.log("备忘录权限没有开启");  
                }  
                    plus.ios.deleteObject(EKEventStore);

9、网络

判断网络是否开启

                              var cellularData = plus.ios.newObject("CTCellularData");  
                var state = cellularData.plusGetAttribute("restrictedState");  
                if (state == 2) {  
                    console.log("已经开启了互联网权限:NotRestricted");  
                } else if (state == 0) {  
                    console.log("StateUnknown");  
                } else if (state == 1) {  
                    console.log("Restricted");  
                }  
                    plus.ios.deleteObject(cellularData);

9、蓝牙

判断蓝牙是否开启

判断蓝牙权限是否开启无法用native.js来写,可以用蓝牙模块的API, getBluetoothAdapterState(获取本机蓝牙适配器状态),onBluetoothAdapterStateChange(监听蓝牙适配器状态变化事件)来判断。

提示用户开启权限

跳转到应用的权限页面,下面写了一个提示框让用户去选择

                                      plus.nativeUI.confirm("XX权限没有开启,是否去开启?", function(e) {  
                        if (e.index == 0) {  
                            var UIApplication = plus.ios.import("UIApplication");  
                            var application2 = UIApplication.sharedApplication();  
                            var NSURL2 = plus.ios.import("NSURL");                        
                            var setting2 = NSURL2.URLWithString("app-settings:");  
                            application2.openURL(setting2);  

                            plus.ios.deleteObject(setting2);  
                            plus.ios.deleteObject(NSURL2);  
                            plus.ios.deleteObject(application2);  
                        }  
                    }, {  
                        "buttons": ["Yes", "No"],  
                        "verticalAlign": "center"  
                    });

安卓权限见:https://ask.dcloud.net.cn/article/35861

继续阅读 »

更新:插件市场已经提供了封装更完善版本:https://ext.dcloud.net.cn/plugin?id=594

1、推送

判断推送权限是否开启

var UIApplication = plus.ios.import("UIApplication");  
                var app = UIApplication.sharedApplication();  
                var enabledTypes = 0;  
                if (app.currentUserNotificationSettings) {  
                    var settings = app.currentUserNotificationSettings();  
                    enabledTypes = settings.plusGetAttribute("types");  
                    console.log("enabledTypes1:" + enabledTypes);  
                    if (enabledTypes == 0) {  
                        console.log("推送权限没有开启");  
                    }else {  
                        console.log("已经开启推送功能!")  
                    }  
                    plus.ios.deleteObject(settings);  
                } else {  
                    enabledTypes = app.enabledRemoteNotificationTypes();  
                    if (enabledTypes == 0) {  
                        console.log("推送权限没有开启!");  
                    } else {  
                        console.log("已经开启推送功能!")  
                    }  
                    console.log("enabledTypes2:" + enabledTypes);  
                }  
                plus.ios.deleteObject(app);  
                                plus.ios.deleteObject(UIApplication);

2、定位

判断定位权限是否开启

                                var cllocationManger = plus.ios.import("CLLocationManager");  
                var enable = cllocationManger.locationServicesEnabled();  
                var status = cllocationManger.authorizationStatus();  
                console.log("enable:" + enable);  
                console.log("status:" + status);  
                if (enable && status != 2) {  
                    console.log("手机系统的定位已经打开");  
                } else {  
                    console.log("手机系统的定位没有打开");  
                }  
                             plus.ios.deleteObject(cllocationManger);

3、麦克风

判断麦克风权限是否开启

                                var avaudiosession = plus.ios.import("AVAudioSession");  
                var avaudio = avaudiosession.sharedInstance();  
                var permissionStatus = avaudio.recordPermission();  
                console.log("permissionStatus:" + permissionStatus);  
                if (permissionStatus == 1684369017 || permissionStatus == 1970168948) {   
                    console.log("麦克风权限没有开启");  
                } else {  
                    console.log("麦克风权限已经开启");  
                }  
                    plus.ios.deleteObject(avaudiosession);

4、相机

判断相机权限是否开启

                               var AVCaptureDevice = plus.ios.import("AVCaptureDevice");  
                var authStatus = AVCaptureDevice.authorizationStatusForMediaType('vide');  
                console.log("authStatus:" + authStatus);  
                if (authStatus == 3) {  
                    console.log("相机权限已经开启");  
                } else {  
                    console.log("相机权限没有开启");  
                }  
                    plus.ios.deleteObject(AVCaptureDevice);

5、相册

判断相册权限是否开启

                              var PHPhotoLibrary = plus.ios.import("PHPhotoLibrary");  
                var authStatus = PHPhotoLibrary.authorizationStatus();  
                console.log("authStatus:" + authStatus);  
                if (authStatus == 3) {  
                    console.log("相册权限已经开启");  
                } else  {  
                    console.log("相册权限没有开启");  
                }  
                    plus.ios.deleteObject(PHPhotoLibrary);

6、通讯录

判断通讯录权限是否开启

                              var CNContactStore = plus.ios.import("CNContactStore");  
                var cnAuthStatus = CNContactStore.authorizationStatusForEntityType(0);  
                if (cnAuthStatus == 3) {  
                    console.log("通讯录权限已经开启");  
                } else  {  
                    console.log("通讯录权限没有开启");  
                }  
                    plus.ios.deleteObject(CNContactStore);

7、日历

判断日历权限是否开启

                              var EKEventStore = plus.ios.import("EKEventStore");  
                  var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(0);  
                if (ekAuthStatus == 3) {  
                    console.log("日历权限已经开启");  
                } else  {  
                    console.log("日历权限没有开启");  
                }  
                    plus.ios.deleteObject(EKEventStore);

8、备忘录

判断备忘录权限是否开启

                              var EKEventStore = plus.ios.import("EKEventStore");  
                  var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(1);  
                if (ekAuthStatus == 3) {  
                    console.log("备忘录权限已经开启");  
                } else  {  
                    console.log("备忘录权限没有开启");  
                }  
                    plus.ios.deleteObject(EKEventStore);

9、网络

判断网络是否开启

                              var cellularData = plus.ios.newObject("CTCellularData");  
                var state = cellularData.plusGetAttribute("restrictedState");  
                if (state == 2) {  
                    console.log("已经开启了互联网权限:NotRestricted");  
                } else if (state == 0) {  
                    console.log("StateUnknown");  
                } else if (state == 1) {  
                    console.log("Restricted");  
                }  
                    plus.ios.deleteObject(cellularData);

9、蓝牙

判断蓝牙是否开启

判断蓝牙权限是否开启无法用native.js来写,可以用蓝牙模块的API, getBluetoothAdapterState(获取本机蓝牙适配器状态),onBluetoothAdapterStateChange(监听蓝牙适配器状态变化事件)来判断。

提示用户开启权限

跳转到应用的权限页面,下面写了一个提示框让用户去选择

                                      plus.nativeUI.confirm("XX权限没有开启,是否去开启?", function(e) {  
                        if (e.index == 0) {  
                            var UIApplication = plus.ios.import("UIApplication");  
                            var application2 = UIApplication.sharedApplication();  
                            var NSURL2 = plus.ios.import("NSURL");                        
                            var setting2 = NSURL2.URLWithString("app-settings:");  
                            application2.openURL(setting2);  

                            plus.ios.deleteObject(setting2);  
                            plus.ios.deleteObject(NSURL2);  
                            plus.ios.deleteObject(application2);  
                        }  
                    }, {  
                        "buttons": ["Yes", "No"],  
                        "verticalAlign": "center"  
                    });

安卓权限见:https://ask.dcloud.net.cn/article/35861

收起阅读 »

绿色风格mui微信端用户注册登录界面模板

mui

绿色风格mui微信端用户注册登录界面模板,登录注册验证是用的mui前端框架,包括手机号、密码验证

下载地址:https://www.sucaihuo.com/templates/1582.html

继续阅读 »

绿色风格mui微信端用户注册登录界面模板,登录注册验证是用的mui前端框架,包括手机号、密码验证

下载地址:https://www.sucaihuo.com/templates/1582.html

收起阅读 »

App平台iOS设备上因内存不足导致白屏、闪退的问题解决方案

拍照 iOS WKWebview白屏 白屏

原因

任何手机设备上,当手机内存不足时,os都会回收资源。一般是先回收后台打开的资源。如果当前应用占用的资源过高,当前应用也有可能崩溃。
尤其是在调用摄像头点击拍照时,手机内存占用会达到一个峰值,此时较容易出问题。

iOS上当内存不足时,根据uiwebview和wkwebview的不同,它自身有不同的回收策略。

  • 如果是uiwebview的app(常见于5+app),内存不足时整个app会崩溃,即闪退。
  • 如果是wkwebview的app(uni-app和wap2app在iOS上默认就是wkwebview),内存不足时,单个wkwebview会崩溃。也就是所谓的应用还在,而页面白屏。

这个问题在所有使用wkwebview的应用都会出现,比如微信的公众号网页里也存在。在微信小程序里,它做了一个自动恢复手段,可以让jscore存储数据状态,崩溃的wkwebview自动恢复。所以在遇到问题时,会白一下然后恢复渲染。

解决方案

  • uni-app因为引入了独立的jscore处理数据状态,jscore不会崩溃,所以官方采用了和微信小程序一致的策略,补充自动的白屏恢复能力。
  • uni-app中也可以使用nvue来避免这个问题,nvue页面不会出现内存不足引发的白屏崩溃。
  • 5+app、wap2app,一方面注意前端代码写法,减低内存使用。另外HBuilder2.3.4+开始支持配置WKWebview内核奔溃是重新启动应用还是重新加载页面的配置,详情参考:https://ask.dcloud.net.cn/article/36540。但整体而言,5+app和wap2app在WKWebview下问题很多,还有各种跨域限制,还是建议开发者尽快升级为uni-app。

在前端减少内存使用的注意

最重要的注意,就是图片渲染,尤其是大图片。

在页面上不要渲染多张大图,比如从摄像头或相册选择多张图,并缩放尺寸渲染在页面上,虽然肉眼看起来手机屏幕上是几张小图,但实际上是多张大图只是被缩小,这种情况非常耗费内存。一张图片3m,9张这样的大图同时渲染到屏幕上,什么手机都受不了。
一个缩略图控制在几k或十几k,才是合理的。

详情页面展现多张大图并不受影响。如果图片滚动在屏幕外,os内存不足时也会自动收回这些屏幕外图片占用的渲染资源,最吃资源的就是同屏渲染多张大图。

继续阅读 »

原因

任何手机设备上,当手机内存不足时,os都会回收资源。一般是先回收后台打开的资源。如果当前应用占用的资源过高,当前应用也有可能崩溃。
尤其是在调用摄像头点击拍照时,手机内存占用会达到一个峰值,此时较容易出问题。

iOS上当内存不足时,根据uiwebview和wkwebview的不同,它自身有不同的回收策略。

  • 如果是uiwebview的app(常见于5+app),内存不足时整个app会崩溃,即闪退。
  • 如果是wkwebview的app(uni-app和wap2app在iOS上默认就是wkwebview),内存不足时,单个wkwebview会崩溃。也就是所谓的应用还在,而页面白屏。

这个问题在所有使用wkwebview的应用都会出现,比如微信的公众号网页里也存在。在微信小程序里,它做了一个自动恢复手段,可以让jscore存储数据状态,崩溃的wkwebview自动恢复。所以在遇到问题时,会白一下然后恢复渲染。

解决方案

  • uni-app因为引入了独立的jscore处理数据状态,jscore不会崩溃,所以官方采用了和微信小程序一致的策略,补充自动的白屏恢复能力。
  • uni-app中也可以使用nvue来避免这个问题,nvue页面不会出现内存不足引发的白屏崩溃。
  • 5+app、wap2app,一方面注意前端代码写法,减低内存使用。另外HBuilder2.3.4+开始支持配置WKWebview内核奔溃是重新启动应用还是重新加载页面的配置,详情参考:https://ask.dcloud.net.cn/article/36540。但整体而言,5+app和wap2app在WKWebview下问题很多,还有各种跨域限制,还是建议开发者尽快升级为uni-app。

在前端减少内存使用的注意

最重要的注意,就是图片渲染,尤其是大图片。

在页面上不要渲染多张大图,比如从摄像头或相册选择多张图,并缩放尺寸渲染在页面上,虽然肉眼看起来手机屏幕上是几张小图,但实际上是多张大图只是被缩小,这种情况非常耗费内存。一张图片3m,9张这样的大图同时渲染到屏幕上,什么手机都受不了。
一个缩略图控制在几k或十几k,才是合理的。

详情页面展现多张大图并不受影响。如果图片滚动在屏幕外,os内存不足时也会自动收回这些屏幕外图片占用的渲染资源,最吃资源的就是同屏渲染多张大图。

收起阅读 »

APP+小程序+门店,强劲结合正确打造新流量池

5+App开发

身处全民生活互联网化时代,传统企业走互联网转型已是必然趋势。然而,如何结合自身门店、用户资源,通过开发App、小程序为业务增添加瓦,想必对所有传统行业业主来说,仍是一片未耕耘的土地。今天,就让我们一同借鉴百果园,这位历经十余载,门店超3700家,线上销量突破全年总销量20%以上的水果零售霸主,究竟是如何通过门店+App+小程序的方式,GET到新流量池的正确打造模式吧!

百果园核心战略:线上线下一体化

2016年年底,百果园正式开启了“线上线下一体化战略”布局,并从一二线城市进行扎根,近几年逐步下沉到三四线渠道。在运营环节中,百果园一方面大力开店建仓布局线下,降低冷链配送成本;另一方面搭建开发APP、小程序,并结合微信分别在用户、营销、配送服务等方面取得了可喜的进展,并通过爆款打造,以及小程序拼团也大幅增加了用户二次消费率。那么,微信、小程序、App、门店究竟都各自扮演了哪些角色?又对百果园巨大的销量,起到了怎样的推动作用呢?跟随力谱云的脚步,详细探究一番吧!

微信+小程序+App:逐一击破用户、营销、服务营垒

目前,搭建开发APP、小程序,以及微信生态已基本涵盖主流移动渠道,并且基于每个渠道的消费场景不同,用户重叠度也并不特别高,因而百果园分别通过微信社群、微信公众号,支付宝/微信小程序、百果园App及美团等第三方平台,在线上业务板块做了细分切割,并且这三者的销售关系并非此消彼长,而是在不断一同增长。平台细分如下,企业家们也可参考打造!

支付宝小程序:交易类服务平台。作为新晋入场角色,主要提供会员服务与到家到店服务,目前发展体量较小,但拥有着增速较快的良好势头。
微信社群+小程序+公众号:百果园的核心流量池场景,由门店+社群构成。主要用于辐射门店周边社区用户,与用户建立起积极的沟通渠道,并利用公众号、小程序,进行服务推广,帮助深度触达用户,唤醒消费。
微信小程序:百果园旗下共上线了3款微信小程序。百果园+,主要用于提供核心业务。百果园好礼,以礼品卡形式,为用户提供水果送礼的服务。百果心享,则专为付费会员提供服务。这三者业务分工不同,但可彼此跳转关联,多方打通微信生态圈业务。
App+美团、饿了么第三方平台:借由App平台功能齐全的特性,在开发属于自己企业APP的同时,合作美团、饿了么等第三方平台,主要用于为用户提供配送到家服务,进一步覆盖主流渠道线上用户。
营销战略:爆款与拼团双管齐下

在线上运营环节中,爆款推广和社区拼团都是百果园的一大变现利器。比如针对季节推选时令水果,能更好地刺激消费,提升业绩。在拼团方面,他们通过App进行试水,在取得令人惊喜的成绩后,便试图在App、小程序中都纷纷加入这一拼团要素,并且搭配到店服务,不仅可以提升销量,提升新用户对门店品牌的信赖感,更能加深用户的到店消费习惯,最后还可刺激二次消费,可谓一举多得。正所谓,战术不在乎多,而贵乎精,如果企业能依据自身产品、用户习惯,搭配几项有效的营销策略,必然能让平台更上一层楼。

想打造百果园App+小程序+..?力谱云一次就能Carry全场

如果您的企业正处于传统转型的阵痛之中,如果您寻求门店服务的新突破,如果您渴望收割移动洪荒流量,如果您也想复刻百果园式成功,一站式解决所有平台开发难题,与营销解决方案,就让力谱云来带您Carry全场吧!

力谱云,专注为企业用户提供新颖、热门、高效的场景化移动电商平台。支持一站式搭建App+小程序+微商城+移动网站,并打通了四大主流移动电商解决方案,分别为社交分销、社区团购、服务预订与入住、订货与供应链解决方案。无论商业模式是O2O、B2B、B2C、OMO,还是B2B2C模式,力谱云全都为您HOLD住。另外,在运营营销环节,配备分销、拼团、信息流、秒杀等数十种前沿营销工具,与会员运营工具,帮助企业轻松开展营销活动,并借助大数据实时捕捉平台、用户数据,为每一次的运营获取精准的市场确据。并且,通过力谱云,可省下传统App开发市场的90%成本,高效进行App平台搭建。

移动流量窗口仍在黄金期,而你的企业现在只缺线上平台与工具了!搭乘一站式开发的快速列车,让力谱云为您的移动之旅,一路用技术Carry到底吧!

继续阅读 »

身处全民生活互联网化时代,传统企业走互联网转型已是必然趋势。然而,如何结合自身门店、用户资源,通过开发App、小程序为业务增添加瓦,想必对所有传统行业业主来说,仍是一片未耕耘的土地。今天,就让我们一同借鉴百果园,这位历经十余载,门店超3700家,线上销量突破全年总销量20%以上的水果零售霸主,究竟是如何通过门店+App+小程序的方式,GET到新流量池的正确打造模式吧!

百果园核心战略:线上线下一体化

2016年年底,百果园正式开启了“线上线下一体化战略”布局,并从一二线城市进行扎根,近几年逐步下沉到三四线渠道。在运营环节中,百果园一方面大力开店建仓布局线下,降低冷链配送成本;另一方面搭建开发APP、小程序,并结合微信分别在用户、营销、配送服务等方面取得了可喜的进展,并通过爆款打造,以及小程序拼团也大幅增加了用户二次消费率。那么,微信、小程序、App、门店究竟都各自扮演了哪些角色?又对百果园巨大的销量,起到了怎样的推动作用呢?跟随力谱云的脚步,详细探究一番吧!

微信+小程序+App:逐一击破用户、营销、服务营垒

目前,搭建开发APP、小程序,以及微信生态已基本涵盖主流移动渠道,并且基于每个渠道的消费场景不同,用户重叠度也并不特别高,因而百果园分别通过微信社群、微信公众号,支付宝/微信小程序、百果园App及美团等第三方平台,在线上业务板块做了细分切割,并且这三者的销售关系并非此消彼长,而是在不断一同增长。平台细分如下,企业家们也可参考打造!

支付宝小程序:交易类服务平台。作为新晋入场角色,主要提供会员服务与到家到店服务,目前发展体量较小,但拥有着增速较快的良好势头。
微信社群+小程序+公众号:百果园的核心流量池场景,由门店+社群构成。主要用于辐射门店周边社区用户,与用户建立起积极的沟通渠道,并利用公众号、小程序,进行服务推广,帮助深度触达用户,唤醒消费。
微信小程序:百果园旗下共上线了3款微信小程序。百果园+,主要用于提供核心业务。百果园好礼,以礼品卡形式,为用户提供水果送礼的服务。百果心享,则专为付费会员提供服务。这三者业务分工不同,但可彼此跳转关联,多方打通微信生态圈业务。
App+美团、饿了么第三方平台:借由App平台功能齐全的特性,在开发属于自己企业APP的同时,合作美团、饿了么等第三方平台,主要用于为用户提供配送到家服务,进一步覆盖主流渠道线上用户。
营销战略:爆款与拼团双管齐下

在线上运营环节中,爆款推广和社区拼团都是百果园的一大变现利器。比如针对季节推选时令水果,能更好地刺激消费,提升业绩。在拼团方面,他们通过App进行试水,在取得令人惊喜的成绩后,便试图在App、小程序中都纷纷加入这一拼团要素,并且搭配到店服务,不仅可以提升销量,提升新用户对门店品牌的信赖感,更能加深用户的到店消费习惯,最后还可刺激二次消费,可谓一举多得。正所谓,战术不在乎多,而贵乎精,如果企业能依据自身产品、用户习惯,搭配几项有效的营销策略,必然能让平台更上一层楼。

想打造百果园App+小程序+..?力谱云一次就能Carry全场

如果您的企业正处于传统转型的阵痛之中,如果您寻求门店服务的新突破,如果您渴望收割移动洪荒流量,如果您也想复刻百果园式成功,一站式解决所有平台开发难题,与营销解决方案,就让力谱云来带您Carry全场吧!

力谱云,专注为企业用户提供新颖、热门、高效的场景化移动电商平台。支持一站式搭建App+小程序+微商城+移动网站,并打通了四大主流移动电商解决方案,分别为社交分销、社区团购、服务预订与入住、订货与供应链解决方案。无论商业模式是O2O、B2B、B2C、OMO,还是B2B2C模式,力谱云全都为您HOLD住。另外,在运营营销环节,配备分销、拼团、信息流、秒杀等数十种前沿营销工具,与会员运营工具,帮助企业轻松开展营销活动,并借助大数据实时捕捉平台、用户数据,为每一次的运营获取精准的市场确据。并且,通过力谱云,可省下传统App开发市场的90%成本,高效进行App平台搭建。

移动流量窗口仍在黄金期,而你的企业现在只缺线上平台与工具了!搭乘一站式开发的快速列车,让力谱云为您的移动之旅,一路用技术Carry到底吧!

收起阅读 »