HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

【分享】省市区三级联动

/* 调用方法  
 * selectAddr.init({  
 *   sId:[...], 下拉框id数组  
 *   deVal:'130102' 下拉框默认区值  
 * })  
 * 省市区选择插件  
 * varstion 1.0.0  
 * by Allen-Fei  
 * tipefi@126.com  
 * 基于js/region.js城市Json文件------手机 and PC端  
 */  

var selectAddr = {  
    init: function(o, callback) {  
        this.end = callback;  
        this.options = o;  
        this.addChange();  
        this.getJson();  
        this.data = {  
            dProvince: [],  
            dCity: [],  
            dCounty: []  
        };  
        this.addr = [];  
    },  
    //获取JSON城市数据  
    getJson: function() {  
        var that = this;  
        var val = this.options.deVal;  
        var sId = this.options.sId;  
        mui.getJSON('js/region.json', function(data) {  
            for(var i = 0; i < data.length; i++) {  
                var len = data[i].qybm.length;  
                if(len == 2) that.data.dProvince.push(data[i])  
                if(len == 4) that.data.dCity.push(data[i])  
                if(len == 6) that.data.dCounty.push(data[i])  
            }  
            that.addOption(that.data.dProvince, 0)  
            if(val) {  
                that.setSelect(that.data.dProvince, val.substring(0, 2), 0);  
                that.sProvince(val.substring(0, 2), true);  
                that.sCity(val.substring(0, 4), true);  
                that.sCounty(val, true);  
            }  
        });  
    },  
    //给select追加事件  
    addChange: function() {  
        var sId = this.options.sId;  
        for(var i = 0; i < sId.length; i++) {  
            if(i == 0) document.getElementById(sId[i]).setAttribute('onchange', 'selectAddr.sProvince(this.options[this.options.selectedIndex])')  
            if(i == 1) document.getElementById(sId[i]).setAttribute('onchange', 'selectAddr.sCity(this.options[this.options.selectedIndex])')  
            if(i == 2) document.getElementById(sId[i]).setAttribute('onchange', 'selectAddr.sCounty(this.options[this.options.selectedIndex])')  
        }  
    },  
    // 给下拉添加列表元素  
    addOption: function(d, n) {  
        var sId = this.options.sId;  
        if(n != 0) document.getElementById(sId[n]).innerHTML = '';  
        for(var i = 0; i < d.length; i++) {  
            var hoption = document.createElement('option');  
            var htext = document.createTextNode(d[i].qyjc);  
            hoption.appendChild(htext);  
            hoption.setAttribute('value', d[i].qybm)  
            document.getElementById(sId[n]).appendChild(hoption);  
        }  
    },  
    // 设置选中的值  
    setSelect: function(d, v, n) {  
        var sId = this.options.sId;  
        for(var i = 0; i < d.length; i++) {  
            if(v == d[i].qybm) {  
                if(n == 0) {  
                    document.getElementById(sId[n])[i + 1].selected = true;  
                } else {  
                    document.getElementById(sId[n])[i].selected = true;  
                }  
            }  
        }  
    },  
    // 选择省后运行(筛选出市列表)  
    sProvince: function(op, isdefault) {  
        var v = op instanceof Object ? op.value : op;  
        var d = this.data.dCity,  
            aCity = [],  
            sId = this.options.sId,  
            $s1 = document.getElementById(sId[1]);  

        this.addr = [];  

        for(var i = 0; i < d.length; i++) {  
            if(d[i].sjqybm == v) {  
                aCity.push(d[i]);  
            }  
        }  
        if(aCity.length > 0) {  
            this.addOption(aCity, 1)  
        } else { //当没有市级时,显示区县  
            var dt = this.data.dCounty;  
            for(var i = 0; i < dt.length; i++) {  
                if(dt[i].sjqybm == v) {  
                    aCity.push(dt[i]);  
                }  
            }  
            this.addOption(aCity, 1)  
        }  

        this.setReturn(0);  
        if(isdefault) {  
            this.setSelect(aCity, this.options.deVal.substring(0, 4), 1);  
            return false;  
        }  

        if(sId.length == 3) this.sCity($s1.options[$s1.options.selectedIndex].value);  
    },  
    // 选择市后运行(筛选出区列表)  
    sCity: function(op, isdefault) {  
        var v = op instanceof Object ? op.value : op;  
        var d = this.data.dCounty,  
            aCounty = [],  
            sId = this.options.sId,  
            $s2 = document.getElementById(sId[2]);  
        for(var i = 0; i < d.length; i++) {  
            if(d[i].sjqybm == v) {  
                aCounty.push(d[i]);  
            }  
        }  
        this.addOption(aCounty, 2)  
        this.setReturn(1)  

        if(isdefault) { //如果值为字符串,则为默认值  
            this.setSelect(aCounty, this.options.deVal, 2);  
            return false;  
        }  

        if(v.length != 6) {  
            this.sCounty($s2.options[$s2.options.selectedIndex].value);  
        } else {  
            this.sCounty('');  
        }  
    },  
    // 选择区后运行  
    sCounty: function(op) {  
        if(op) {  
            var v = op instanceof Object ? op.value : op;  
            this.setReturn(2);  
        }  

        this.end(this.addr);  
    },  
    // 设置返回值  
    setReturn: function(n) {  
        var o = {},  
            sId = this.options.sId,  
            $s0 = document.getElementById(sId[0]),  
            $s1 = document.getElementById(sId[1]),  
            $s2 = document.getElementById(sId[2]);  
        switch(n) {  
            case 0:  
                o.text = $s0.options[$s0.options.selectedIndex].text;  
                o.value = $s0.options[$s0.options.selectedIndex].value;  
                break;  
            case 1:  
                o.text = $s1.options[$s1.options.selectedIndex].text;  
                o.value = $s1.options[$s1.options.selectedIndex].value;  
                break;  
            case 2:  
                o.text = $s2.options[$s2.options.selectedIndex].text;  
                o.value = $s2.options[$s2.options.selectedIndex].value;  
                break;  
            default:  
                break;  
        }  
        this.addr[n] = o;  
    }  
}

城市JSON在附件,仅供参考

继续阅读 »
/* 调用方法  
 * selectAddr.init({  
 *   sId:[...], 下拉框id数组  
 *   deVal:'130102' 下拉框默认区值  
 * })  
 * 省市区选择插件  
 * varstion 1.0.0  
 * by Allen-Fei  
 * tipefi@126.com  
 * 基于js/region.js城市Json文件------手机 and PC端  
 */  

var selectAddr = {  
    init: function(o, callback) {  
        this.end = callback;  
        this.options = o;  
        this.addChange();  
        this.getJson();  
        this.data = {  
            dProvince: [],  
            dCity: [],  
            dCounty: []  
        };  
        this.addr = [];  
    },  
    //获取JSON城市数据  
    getJson: function() {  
        var that = this;  
        var val = this.options.deVal;  
        var sId = this.options.sId;  
        mui.getJSON('js/region.json', function(data) {  
            for(var i = 0; i < data.length; i++) {  
                var len = data[i].qybm.length;  
                if(len == 2) that.data.dProvince.push(data[i])  
                if(len == 4) that.data.dCity.push(data[i])  
                if(len == 6) that.data.dCounty.push(data[i])  
            }  
            that.addOption(that.data.dProvince, 0)  
            if(val) {  
                that.setSelect(that.data.dProvince, val.substring(0, 2), 0);  
                that.sProvince(val.substring(0, 2), true);  
                that.sCity(val.substring(0, 4), true);  
                that.sCounty(val, true);  
            }  
        });  
    },  
    //给select追加事件  
    addChange: function() {  
        var sId = this.options.sId;  
        for(var i = 0; i < sId.length; i++) {  
            if(i == 0) document.getElementById(sId[i]).setAttribute('onchange', 'selectAddr.sProvince(this.options[this.options.selectedIndex])')  
            if(i == 1) document.getElementById(sId[i]).setAttribute('onchange', 'selectAddr.sCity(this.options[this.options.selectedIndex])')  
            if(i == 2) document.getElementById(sId[i]).setAttribute('onchange', 'selectAddr.sCounty(this.options[this.options.selectedIndex])')  
        }  
    },  
    // 给下拉添加列表元素  
    addOption: function(d, n) {  
        var sId = this.options.sId;  
        if(n != 0) document.getElementById(sId[n]).innerHTML = '';  
        for(var i = 0; i < d.length; i++) {  
            var hoption = document.createElement('option');  
            var htext = document.createTextNode(d[i].qyjc);  
            hoption.appendChild(htext);  
            hoption.setAttribute('value', d[i].qybm)  
            document.getElementById(sId[n]).appendChild(hoption);  
        }  
    },  
    // 设置选中的值  
    setSelect: function(d, v, n) {  
        var sId = this.options.sId;  
        for(var i = 0; i < d.length; i++) {  
            if(v == d[i].qybm) {  
                if(n == 0) {  
                    document.getElementById(sId[n])[i + 1].selected = true;  
                } else {  
                    document.getElementById(sId[n])[i].selected = true;  
                }  
            }  
        }  
    },  
    // 选择省后运行(筛选出市列表)  
    sProvince: function(op, isdefault) {  
        var v = op instanceof Object ? op.value : op;  
        var d = this.data.dCity,  
            aCity = [],  
            sId = this.options.sId,  
            $s1 = document.getElementById(sId[1]);  

        this.addr = [];  

        for(var i = 0; i < d.length; i++) {  
            if(d[i].sjqybm == v) {  
                aCity.push(d[i]);  
            }  
        }  
        if(aCity.length > 0) {  
            this.addOption(aCity, 1)  
        } else { //当没有市级时,显示区县  
            var dt = this.data.dCounty;  
            for(var i = 0; i < dt.length; i++) {  
                if(dt[i].sjqybm == v) {  
                    aCity.push(dt[i]);  
                }  
            }  
            this.addOption(aCity, 1)  
        }  

        this.setReturn(0);  
        if(isdefault) {  
            this.setSelect(aCity, this.options.deVal.substring(0, 4), 1);  
            return false;  
        }  

        if(sId.length == 3) this.sCity($s1.options[$s1.options.selectedIndex].value);  
    },  
    // 选择市后运行(筛选出区列表)  
    sCity: function(op, isdefault) {  
        var v = op instanceof Object ? op.value : op;  
        var d = this.data.dCounty,  
            aCounty = [],  
            sId = this.options.sId,  
            $s2 = document.getElementById(sId[2]);  
        for(var i = 0; i < d.length; i++) {  
            if(d[i].sjqybm == v) {  
                aCounty.push(d[i]);  
            }  
        }  
        this.addOption(aCounty, 2)  
        this.setReturn(1)  

        if(isdefault) { //如果值为字符串,则为默认值  
            this.setSelect(aCounty, this.options.deVal, 2);  
            return false;  
        }  

        if(v.length != 6) {  
            this.sCounty($s2.options[$s2.options.selectedIndex].value);  
        } else {  
            this.sCounty('');  
        }  
    },  
    // 选择区后运行  
    sCounty: function(op) {  
        if(op) {  
            var v = op instanceof Object ? op.value : op;  
            this.setReturn(2);  
        }  

        this.end(this.addr);  
    },  
    // 设置返回值  
    setReturn: function(n) {  
        var o = {},  
            sId = this.options.sId,  
            $s0 = document.getElementById(sId[0]),  
            $s1 = document.getElementById(sId[1]),  
            $s2 = document.getElementById(sId[2]);  
        switch(n) {  
            case 0:  
                o.text = $s0.options[$s0.options.selectedIndex].text;  
                o.value = $s0.options[$s0.options.selectedIndex].value;  
                break;  
            case 1:  
                o.text = $s1.options[$s1.options.selectedIndex].text;  
                o.value = $s1.options[$s1.options.selectedIndex].value;  
                break;  
            case 2:  
                o.text = $s2.options[$s2.options.selectedIndex].text;  
                o.value = $s2.options[$s2.options.selectedIndex].value;  
                break;  
            default:  
                break;  
        }  
        this.addr[n] = o;  
    }  
}

城市JSON在附件,仅供参考

收起阅读 »

【分享】关于ajax的几个方法(方便自己下次用,包括错误处理、日志处理、加密处理)

/* 参数列表  
 * url 请求的地址后缀(必填)  
 * params 请求的参数(必填:如果没有参数,则传个空对象)  
 * onSuccess 请求成功的回调方法(选填)  
 * noData 请求成功但无数据的回调方法(选填)  
 * retry 请求自动重试的次数(选填)  
 * describe 请求的描述,用来提示错误信息(选填)  
 */  
function bpAjax(url, params, onSuccess, noData, retry, describe) {  
    plus.nativeUI.showWaiting();  
    var address = arguments[0];  
    var url = USERINFO.DL_HOST + arguments[0];  
    var onSuccess = arguments[2] ? arguments[2] : function() {};  
    var noData = arguments[3] ? arguments[3] : function() {};  
    var retry = arguments[4] ? arguments[4] : 3;  
    var params = params;  
    console.log(describe + '参数' + JSON.stringify(params));  
    if(!params.sign) params.sign = getRSA(params, describe) //当sign为空时,自动给键值排序生成sign  
    mui.ajax(url, {  
        data: params,  
        dataType: 'json', //服务器返回json格式数据  
        type: 'post', //HTTP请求类型  
        timeout: 10000, //超时时间设置为10秒;  
        success: function(data) {  
            plus.nativeUI.closeWaiting();  
            console.log(describe + '——' + data);  
            var d = JSON.parse(data);  
            if(d.msg == '1') {  
                onSuccess(d);  
            } else if(d.msg == '3') {  
                //localStorage.clear();  
                mui.toast(d.info);  
                onError('INVALID_TOKEN');  
            } else if(d.msg == '99'){  
                mui.toast(d.info);  
            }else {  
                noData(d);  
            }  
        },  
        error: function(xhr, type, errorThrown) {  
            plus.nativeUI.closeWaiting();  
            console.log(describe + '__' + errorThrown);  
            retry--;  
            if(retry > 0) return bpAjax(address, params, onSuccess, noData, retry, describe);  
            onError('FAILED_NETWORK', describe);  
        }  
    });  
}

========================华丽的分隔线========================

/* 参数列表  
 * errcode 错误编码(必填)  
 * 可选值:'FAILED_NETWORK' 重连多次不成功网络不佳  
 * 可选值:'INVALID_TOKEN' 无效的token  
 */  
function onError(errcode, describe) {  
    switch(errcode) {  
        case 'FAILED_NETWORK':  
            mui.toast('当前网络不佳');  
            break;  
        case 'INVALID_TOKEN':  
            openWV('login.html');  
            break;  
        default:  
            console.log(describe + '——' + errcode);  
    }  
}

========================华丽的分隔线========================

/* 需要RSA加密的对象,按对象键值排序加密 参数列表  
 * o参数为对象,  
 * 逻辑:需要RSA加密的对象,按对象键值排序加密,并返回  
 * DL_RED_PACKET 字符串是跟后台协定的  
 */  
function getRSA(o, describe) {  
    var encrypt = new JSEncrypt();  
    encrypt.setPublicKey(USERINFO.DL_PUBLIC_KEY); //设置公有key  
    var keys = Object.keys(o).sort();  
    if(arguments.length <= 0) return false;  
    var str = '';  
    for(var i = 0; i < keys.length; i++) {  
        if(keys[i] != 'sign') {  
            str += o[keys[i]];  
        }  
    }  
    var sign = '';  
    str = encodeURI(str + 'DL_RED_PACKET');  
    for(var i = 0; i <= parseInt(str.length / 117); i++) {  
        sign += encrypt.encrypt(str.substr(i * 117, 117))  
    }  
    return sign;  
}
继续阅读 »
/* 参数列表  
 * url 请求的地址后缀(必填)  
 * params 请求的参数(必填:如果没有参数,则传个空对象)  
 * onSuccess 请求成功的回调方法(选填)  
 * noData 请求成功但无数据的回调方法(选填)  
 * retry 请求自动重试的次数(选填)  
 * describe 请求的描述,用来提示错误信息(选填)  
 */  
function bpAjax(url, params, onSuccess, noData, retry, describe) {  
    plus.nativeUI.showWaiting();  
    var address = arguments[0];  
    var url = USERINFO.DL_HOST + arguments[0];  
    var onSuccess = arguments[2] ? arguments[2] : function() {};  
    var noData = arguments[3] ? arguments[3] : function() {};  
    var retry = arguments[4] ? arguments[4] : 3;  
    var params = params;  
    console.log(describe + '参数' + JSON.stringify(params));  
    if(!params.sign) params.sign = getRSA(params, describe) //当sign为空时,自动给键值排序生成sign  
    mui.ajax(url, {  
        data: params,  
        dataType: 'json', //服务器返回json格式数据  
        type: 'post', //HTTP请求类型  
        timeout: 10000, //超时时间设置为10秒;  
        success: function(data) {  
            plus.nativeUI.closeWaiting();  
            console.log(describe + '——' + data);  
            var d = JSON.parse(data);  
            if(d.msg == '1') {  
                onSuccess(d);  
            } else if(d.msg == '3') {  
                //localStorage.clear();  
                mui.toast(d.info);  
                onError('INVALID_TOKEN');  
            } else if(d.msg == '99'){  
                mui.toast(d.info);  
            }else {  
                noData(d);  
            }  
        },  
        error: function(xhr, type, errorThrown) {  
            plus.nativeUI.closeWaiting();  
            console.log(describe + '__' + errorThrown);  
            retry--;  
            if(retry > 0) return bpAjax(address, params, onSuccess, noData, retry, describe);  
            onError('FAILED_NETWORK', describe);  
        }  
    });  
}

========================华丽的分隔线========================

/* 参数列表  
 * errcode 错误编码(必填)  
 * 可选值:'FAILED_NETWORK' 重连多次不成功网络不佳  
 * 可选值:'INVALID_TOKEN' 无效的token  
 */  
function onError(errcode, describe) {  
    switch(errcode) {  
        case 'FAILED_NETWORK':  
            mui.toast('当前网络不佳');  
            break;  
        case 'INVALID_TOKEN':  
            openWV('login.html');  
            break;  
        default:  
            console.log(describe + '——' + errcode);  
    }  
}

========================华丽的分隔线========================

/* 需要RSA加密的对象,按对象键值排序加密 参数列表  
 * o参数为对象,  
 * 逻辑:需要RSA加密的对象,按对象键值排序加密,并返回  
 * DL_RED_PACKET 字符串是跟后台协定的  
 */  
function getRSA(o, describe) {  
    var encrypt = new JSEncrypt();  
    encrypt.setPublicKey(USERINFO.DL_PUBLIC_KEY); //设置公有key  
    var keys = Object.keys(o).sort();  
    if(arguments.length <= 0) return false;  
    var str = '';  
    for(var i = 0; i < keys.length; i++) {  
        if(keys[i] != 'sign') {  
            str += o[keys[i]];  
        }  
    }  
    var sign = '';  
    str = encodeURI(str + 'DL_RED_PACKET');  
    for(var i = 0; i <= parseInt(str.length / 117); i++) {  
        sign += encrypt.encrypt(str.substr(i * 117, 117))  
    }  
    return sign;  
}
收起阅读 »

新浪微博分享链接

微博分享

新浪微博的分享链接不是单独出来的,并与图片产生冲突。
所以不要将分享的链接写到msg.href中,而是拼接在content中,如果链接经过新浪官方验证,则会显示概览信息(此信息与图片冲突)。
发此文还有个原因是告诉大家,新浪分享本来就是这样的,这个不是一个折衷的方案,好使,不用找别的了。
错怪官方这么久没修复这个东西 =。=

ps.回调地址没有的可以用默认 https://api.weibo.com/oauth2/default.html

站内相关问题参考:
http://ask.dcloud.net.cn/article/707
http://ask.dcloud.net.cn/question/8353 (此文基本囊括遇到问题)

继续阅读 »

新浪微博的分享链接不是单独出来的,并与图片产生冲突。
所以不要将分享的链接写到msg.href中,而是拼接在content中,如果链接经过新浪官方验证,则会显示概览信息(此信息与图片冲突)。
发此文还有个原因是告诉大家,新浪分享本来就是这样的,这个不是一个折衷的方案,好使,不用找别的了。
错怪官方这么久没修复这个东西 =。=

ps.回调地址没有的可以用默认 https://api.weibo.com/oauth2/default.html

站内相关问题参考:
http://ask.dcloud.net.cn/article/707
http://ask.dcloud.net.cn/question/8353 (此文基本囊括遇到问题)

收起阅读 »

查看流应用统计数据

统计 流应用

开发者发布流应用后,可以登录DCloud开发者中心查看流应用的统计数据。

开发者登录后,会在首页展现所有已创建的应用列表,包括5+App、流应用、快应用等;

从列表中点击某个应用,可以查看该应用不同版本的的日活趋势、新增来源等统计数据。

应用趋势

开发者可以从多个维度查看具体应用的趋势数据,比如当日新增下载、新增激活、当日日活、启动次数等维度。

应用来源

流应用可以从多个场景触发,比如浏览器、应用市场、扫码等,开发者可以查看流应用的启动来源,分析各渠道推广效果。

继续阅读 »

开发者发布流应用后,可以登录DCloud开发者中心查看流应用的统计数据。

开发者登录后,会在首页展现所有已创建的应用列表,包括5+App、流应用、快应用等;

从列表中点击某个应用,可以查看该应用不同版本的的日活趋势、新增来源等统计数据。

应用趋势

开发者可以从多个维度查看具体应用的趋势数据,比如当日新增下载、新增激活、当日日活、启动次数等维度。

应用来源

流应用可以从多个场景触发,比如浏览器、应用市场、扫码等,开发者可以查看流应用的启动来源,分析各渠道推广效果。

收起阅读 »

ios蓝牙调戏一代小米手环

蓝牙 iOS

先上效果图

手环是小米一代光感版的,二代手环没有试过,地址有改变,应该调戏不了。
为什么说是调戏呢?
因为这一代小米手环有一些设置没有验证措施,随便一个手机都能读取手环的一些简单数据。
比如电量,步数,控制震动,其中很重要的控制震动竟然都不需要验证,希望二代手环有修复这个问题。
其他功能小米有限制,没有权限读取,要向小米申请,我就不折腾了。

我写得比较急,代码太难看了,就放两个截图吧。


Xcode上写的JS,这个乱真不怪我,Xcode会自动把js给缩进,开始时候还耐心的改一下,后面的又开始夸张的缩进,我就索性不管了。

插件引用了BabyBluetooth,放一下这个项目地址:https://github.com/coolnameismy/BabyBluetooth
BabyBluetooth还真好用。不用像原生CoreBluetooth那样凌乱了。

其实插件完全的按照官方文档还是能做出来的,虽然是门槛高一点,但是代码可控,用起来舒心。

继续阅读 »

先上效果图

手环是小米一代光感版的,二代手环没有试过,地址有改变,应该调戏不了。
为什么说是调戏呢?
因为这一代小米手环有一些设置没有验证措施,随便一个手机都能读取手环的一些简单数据。
比如电量,步数,控制震动,其中很重要的控制震动竟然都不需要验证,希望二代手环有修复这个问题。
其他功能小米有限制,没有权限读取,要向小米申请,我就不折腾了。

我写得比较急,代码太难看了,就放两个截图吧。


Xcode上写的JS,这个乱真不怪我,Xcode会自动把js给缩进,开始时候还耐心的改一下,后面的又开始夸张的缩进,我就索性不管了。

插件引用了BabyBluetooth,放一下这个项目地址:https://github.com/coolnameismy/BabyBluetooth
BabyBluetooth还真好用。不用像原生CoreBluetooth那样凌乱了。

其实插件完全的按照官方文档还是能做出来的,虽然是门槛高一点,但是代码可控,用起来舒心。

收起阅读 »

解决:最近升级后安卓手机无法自动更新的问题

最近升级了HBuilder版本,一直用的好好的自动在线升级的功能,在苹果手机上使用正常,但是在安卓手机上提示:找不到文件路径;

经过反复调试,后来终于发现并解决问题。

原来关键代码:plus.downloader.createDownload( wgtUrl, {filename:"temp/update/"}, function(d,status){

修改后代码 :plus.downloader.createDownload( wgtUrl, {filename:"_doc/update/"}, function(d,status){

修改代码后自动在线升级功能恢复正常。

经过反复实验,发现在安卓手机上,"_doc/update/" 这个文件路径不能够自定义,只有改成这个路径才能恢复正常。

继续阅读 »

最近升级了HBuilder版本,一直用的好好的自动在线升级的功能,在苹果手机上使用正常,但是在安卓手机上提示:找不到文件路径;

经过反复调试,后来终于发现并解决问题。

原来关键代码:plus.downloader.createDownload( wgtUrl, {filename:"temp/update/"}, function(d,status){

修改后代码 :plus.downloader.createDownload( wgtUrl, {filename:"_doc/update/"}, function(d,status){

修改代码后自动在线升级功能恢复正常。

经过反复实验,发现在安卓手机上,"_doc/update/" 这个文件路径不能够自定义,只有改成这个路径才能恢复正常。

收起阅读 »

【分享】静默更新

/*  
* 静默更新插件  
* 直接appUpadata.init()去调用此插件  
 */  
! function(window, undefined) {  
    var appUpdata = window.appUpdata = {  
        init: function() {  
            var that = this;  
            if(plus) {  
                plus.runtime.getProperty(plus.runtime.appid, function(inf) {  
                    var wgtVer = inf.version;  
                    bpAjax("此处为利用ajax请求服务器的静默更新版本,后台返回一个下载地址", {  
                        version: plus.runtime.version,  
                        resVersion: wgtVer  
                    }, function(d) {  
                        that.downFn(d.data.url);  
                    }, function(d) {  
                        //mui.toast(d.info)  
                    }, 1, "版本")  
                });  

            }  
        },  
        // 下载文件  
        downFn: function(wgtUrl) {  
            var that = this;  
            if(plus) {  
                plus.downloader.createDownload(wgtUrl, {  
                    filename: "_doc/update/"  
                }, function(d, status) {  
                    if(status == 200) {  
                        that.installWgt(d.filename);  
                    } else {  
                        console.log("下载版本失败!");  
                    }  
                }).start();  
            }  
        },  
        // 安装文件  
        installWgt: function(path) {  
            if(plus) {  
                plus.runtime.install(path, {}, function() {  
                    console.log("安装版本成功!");  
                }, function(e) {  
                    console.log("安装版本失败[" + e.code + "]:" + e.message);  
                });  
            }  
        }  
    }  
}(window)
继续阅读 »
/*  
* 静默更新插件  
* 直接appUpadata.init()去调用此插件  
 */  
! function(window, undefined) {  
    var appUpdata = window.appUpdata = {  
        init: function() {  
            var that = this;  
            if(plus) {  
                plus.runtime.getProperty(plus.runtime.appid, function(inf) {  
                    var wgtVer = inf.version;  
                    bpAjax("此处为利用ajax请求服务器的静默更新版本,后台返回一个下载地址", {  
                        version: plus.runtime.version,  
                        resVersion: wgtVer  
                    }, function(d) {  
                        that.downFn(d.data.url);  
                    }, function(d) {  
                        //mui.toast(d.info)  
                    }, 1, "版本")  
                });  

            }  
        },  
        // 下载文件  
        downFn: function(wgtUrl) {  
            var that = this;  
            if(plus) {  
                plus.downloader.createDownload(wgtUrl, {  
                    filename: "_doc/update/"  
                }, function(d, status) {  
                    if(status == 200) {  
                        that.installWgt(d.filename);  
                    } else {  
                        console.log("下载版本失败!");  
                    }  
                }).start();  
            }  
        },  
        // 安装文件  
        installWgt: function(path) {  
            if(plus) {  
                plus.runtime.install(path, {}, function() {  
                    console.log("安装版本成功!");  
                }, function(e) {  
                    console.log("安装版本失败[" + e.code + "]:" + e.message);  
                });  
            }  
        }  
    }  
}(window)
收起阅读 »

关于mui的两个上拉加载下拉刷新遇到的几个问题

第一种方式,无需插件,但是页面不能通过url传参,会起冲突。
冲突效果:页面不能滑动,且在日志里报错。如图所示
代码结构
解决的方式
1、可以将传参的内容通过mui.openwindow()跳转页面传参
2、可以用localStorage进行存储,跳转后取出使用后并再销毁
html
<div id="pullrefresh" class="mui-content mui-scroll-wrapper" >
<div class="mui-scroll">

            <ul class="mui-table-view">   

            </ul>                                                                                                               
        </div>  
    </div>  

js
mui.init({
pullRefresh: {
container: '#pullrefresh' ,
up: {
contentrefresh : "正在加载中...",
callback: ajax_allAddress //自定义加载方法
}
}
});
第二种方式,需要mui的上拉下拉组件,但是那中刷新方式会激活mui.back返回上一页的刷新页面代码
冲突效果:滑动时会导致refresh的自定义代码运行导致页面闪烁
上拉下拉组件
<script src="../js/mui.pullToRefresh.js"></script>
<script src="../js/mui.pullToRefresh.material.js"></script>
监听父页面js
mui.init({
beforeback: function() {
     //获得父页面的webview
var list = plus.webview.currentWebview().opener(); //返回的页面
     //触发父页面的自定义事件(refresh),从而进行刷新
mui.fire(list, 'refresh');
//返回true,继续页面关闭逻辑
return true;
}
});
父页面刷新js
//监听页面刷新
window.addEventListener("refresh",function(e){
function1();//自定义部分
});
父页面上拉加载html结构
<div class="mui-content">
<div id="slider" class="mui-slider mui-fullscreen">
<div id="sliderSegmentedControl" class="mui-scroll-wrapper mui-slider-indicator mui-segmented-control mui-segmented-control-inverted">
<div id="my_order_top" class="mui-scroll">
<a class="mui-control-item mui-active" href="#item1mobile" data-value="1">
已保存
</a>
<a class="mui-control-item" href="#item2mobile" data-value="2">
已提交
</a>
<a class="mui-control-item" href="#item3mobile" data-value="3">
已完成
</a>
</div>
</div>
<div class="mui-slider-group">
<div id="item1mobile" class="mui-slider-item mui-control-content mui-active">
<div class="mui-scroll-wrapper">
<div class="mui-scroll">
<ul class="orderSheet_list" id="orderSheet_list1">

                            </ul>  
                        </div>  
                    </div>  
                </div>  
                <div id="item2mobile" class="mui-slider-item mui-control-content">  
                    <div class="mui-scroll-wrapper">  
                        <div class="mui-scroll">  
                            <ul class="orderSheet_list" id="orderSheet_list2">  
                            </ul>  
                        </div>  
                    </div>  
                </div>  
                <div id="item3mobile" class="mui-slider-item mui-control-content">  
                    <div class="mui-scroll-wrapper">  
                        <div class="mui-scroll">  
                            <ul class="orderSheet_list" id="orderSheet_list3">  
                            </ul>  
                        </div>  
                    </div>  
                </div>          
            </div>  
        </div>  
    </div>  

父页面上拉加载js结构
//阻尼系数
var deceleration = mui.os.ios?0.003:0.0009;
mui('.mui-scroll-wrapper').scroll({
bounce: false,
indicators: true, //是否显示滚动条
deceleration:deceleration
});
//循环初始化所有下拉刷新,上拉加载。
mui.each(document.querySelectorAll('.mui-slider-group .mui-scroll'), function(index, pullRefreshEl) {
mui(pullRefreshEl).pullToRefresh({
up: {
callback: function() {
var self = this;
setTimeout(function() {
self.endPullUpToRefresh();
}, 1000);
}
}
});
});
解决的方式
1、不使用refresh监听事件
2、选用第一种刷新方式
3、修改组件源码

继续阅读 »

第一种方式,无需插件,但是页面不能通过url传参,会起冲突。
冲突效果:页面不能滑动,且在日志里报错。如图所示
代码结构
解决的方式
1、可以将传参的内容通过mui.openwindow()跳转页面传参
2、可以用localStorage进行存储,跳转后取出使用后并再销毁
html
<div id="pullrefresh" class="mui-content mui-scroll-wrapper" >
<div class="mui-scroll">

            <ul class="mui-table-view">   

            </ul>                                                                                                               
        </div>  
    </div>  

js
mui.init({
pullRefresh: {
container: '#pullrefresh' ,
up: {
contentrefresh : "正在加载中...",
callback: ajax_allAddress //自定义加载方法
}
}
});
第二种方式,需要mui的上拉下拉组件,但是那中刷新方式会激活mui.back返回上一页的刷新页面代码
冲突效果:滑动时会导致refresh的自定义代码运行导致页面闪烁
上拉下拉组件
<script src="../js/mui.pullToRefresh.js"></script>
<script src="../js/mui.pullToRefresh.material.js"></script>
监听父页面js
mui.init({
beforeback: function() {
     //获得父页面的webview
var list = plus.webview.currentWebview().opener(); //返回的页面
     //触发父页面的自定义事件(refresh),从而进行刷新
mui.fire(list, 'refresh');
//返回true,继续页面关闭逻辑
return true;
}
});
父页面刷新js
//监听页面刷新
window.addEventListener("refresh",function(e){
function1();//自定义部分
});
父页面上拉加载html结构
<div class="mui-content">
<div id="slider" class="mui-slider mui-fullscreen">
<div id="sliderSegmentedControl" class="mui-scroll-wrapper mui-slider-indicator mui-segmented-control mui-segmented-control-inverted">
<div id="my_order_top" class="mui-scroll">
<a class="mui-control-item mui-active" href="#item1mobile" data-value="1">
已保存
</a>
<a class="mui-control-item" href="#item2mobile" data-value="2">
已提交
</a>
<a class="mui-control-item" href="#item3mobile" data-value="3">
已完成
</a>
</div>
</div>
<div class="mui-slider-group">
<div id="item1mobile" class="mui-slider-item mui-control-content mui-active">
<div class="mui-scroll-wrapper">
<div class="mui-scroll">
<ul class="orderSheet_list" id="orderSheet_list1">

                            </ul>  
                        </div>  
                    </div>  
                </div>  
                <div id="item2mobile" class="mui-slider-item mui-control-content">  
                    <div class="mui-scroll-wrapper">  
                        <div class="mui-scroll">  
                            <ul class="orderSheet_list" id="orderSheet_list2">  
                            </ul>  
                        </div>  
                    </div>  
                </div>  
                <div id="item3mobile" class="mui-slider-item mui-control-content">  
                    <div class="mui-scroll-wrapper">  
                        <div class="mui-scroll">  
                            <ul class="orderSheet_list" id="orderSheet_list3">  
                            </ul>  
                        </div>  
                    </div>  
                </div>          
            </div>  
        </div>  
    </div>  

父页面上拉加载js结构
//阻尼系数
var deceleration = mui.os.ios?0.003:0.0009;
mui('.mui-scroll-wrapper').scroll({
bounce: false,
indicators: true, //是否显示滚动条
deceleration:deceleration
});
//循环初始化所有下拉刷新,上拉加载。
mui.each(document.querySelectorAll('.mui-slider-group .mui-scroll'), function(index, pullRefreshEl) {
mui(pullRefreshEl).pullToRefresh({
up: {
callback: function() {
var self = this;
setTimeout(function() {
self.endPullUpToRefresh();
}, 1000);
}
}
});
});
解决的方式
1、不使用refresh监听事件
2、选用第一种刷新方式
3、修改组件源码

收起阅读 »

利用叮铛制做一个app的原理逻辑

小程序 移动APP

听起来有点理想主义,更甚至有点荒唐?没错,零代码开发正是一种理想的应用生产方式,我们回顾一下应用开发的历史就会发现,开发一个应用,需要写的代码越来越少,到最理想的状态就是不需要写代码。

叮铛应用(www.ddapp.com),是这条道路上唯一一个执着且坚定的实践者,并通过大量的实例反复验证了这条道路的可行性。这一点都不荒唐,而是历史的趋势。叮铛应用是一款集iOS、安卓Android、小程序、H5四端于一体的零代码移动应用生产运营平台,为全行业多领域提供应用解决方案—电商、区域社区、知识付费,本地社群,量身定制服务,高效制作纯原生代码封装的移动app应用

一、应用开发的发展路径
首先回顾一下应用开发的发展历史,我们会发现应用开发的代码越来越少的规律。

刀耕火种期
这个时期,应用开发生态刚萌牙,应用的每一个功能,从服务端到客户端都要开发者一行一行代码的敲,如果需要用到服务器,通常也是开发者购买物理主机,托管到电信机房里去,装机维护全都需要自己搞定。

模块复用期
随着代码的积累,有重用意识的开发者开始把通用的功能剥离出来,形成为一个个独立的功能模块。他们还通过内部共享或开源的方式,让越来越多人基于此可以快速实现一个功能。

云服务期
云计算的兴旺发达,让应用开发有了质的改变。

一是表现在机器的托管上,开发者慢慢的从购买主机,租用机购托管服务器,变成了直接购买IAAS服务商的云主机。
二是应用的快速开发上。
PAAS(平台即服务)的百花绽放,让开发者几行代码就可以实现推送、分享、文件存储甚至部署等原本繁琐、复杂的功能,而且不需要额外的维护成本。开发者只需要关注核心业务,因此代码也更少了。
BAAS(后端即服务)的出现,一下子解放了后端程序员,这下应用开发者只需要有客户端或前端开发者就够,开发成本一下子又砍掉一大半;要写的代码也只剩下客户端与前端了。

AAAS
AAAS(应用即服务,不同于SAAS)是叮铛应用提出的一个新名词,在BAAS的解放后端程序员的基础上,AAAS顺手把客户端和前端程序员也解放了。到此,真正达到零代码应用开发。

二、叮铛应用是怎么做到的
简单来说,叮铛把应用服务端和客户端的技术进行了高度的抽象,让组成应用的零件都变得通用且具备高度可配置性。可以这么说,我们通过叮铛把一个应用配置出来。

下面我们通过解构一个手机app应用,来建立起叮铛应用的世界观。

一个app的构成
一个手机app主要由两大部分的程序运行支撑起来的:客户端和服务端。

客户端,运行在用户的手机上,即用户直接使用到的app,例如微信、微博app,客户端由各种各样的功能界面组合而成,用于展示应用的数据并和用户产生交互。
服务端,运行在云端的某一台服务器上,通过网络与客户端连接起来,用于保存应用和用户的数据。

服务端
先讲服务端,因为服务端是存储应用业务数据的地方,而业务数据是一个应用的核心灵魂。我们要开发一个应用,通常要先梳理出业务数据,其次才是考虑如何设计它的界面和交互。

服务端有两个重要的角色:数据表与触发器。

数据表,用于存储应用的业务数据,表达业务数据之间的逻辑关系。
触发器,用户行为触发服务端执行其他任务的调度机制。
两者紧密结合,可以完成服务端的大部分需求场景。

下面分别认识一下。

  1. 数据表
    上面讲过,服务端的主要职责是存储应用的业务数据,数据表正是用于实现此目的的。

没学过数据库或没有开发经验的同学可能会担心自己不能理解数据表的概念,在此,我用一个大家常见的场景来类比一下,大家就会明白了。

大家都使用过Excel这个表格软件,下面用Excel来做一张班级的学生花名册的表格。

第一步:设计表结构
首先,我们要确定一下,这张表格要记录学生的什么资料,如:姓名、性别、籍贯、民族、学号、手机号码、生日等,确定完毕后,我们先创建一行表头,用于表示这个表格每一列表示学生的哪种信息,如下图:

表头创建完之后,我们算是设计好了学生资料的结构——一个学生的资料,由姓名、性别等信息(或称为列)构成。

第二步:填充表
接下来,我们向设计好的表格里面填数据,连续填进去多条学生的数据。

在叮铛,一张数据表就像一张Excel表类似,你要先设计好表结构,才能去填数据,让用户在客户端访问它们。

在叮铛,我们把表格中的每一列叫做字段,即叮铛的数据表结构是由多个字段组成的。如上面示例中的学生表的结构,由姓名、性别等多个字段组成。每个字段都有它对应的数据类型,如姓名是文本、手机号码是数字、生日是日期等,和Excel里单元格的格式是同样的道理。

数据表定义好,数据也准备好了,用户怎么通过客户端访问呢?在客户端一节中将有详细说明。
更多关于数据表的内容请参考【数据表】一节。

  1. 触发器
    请想象以下的场景:
    当一个客户在app下了一个订单,想要给商家推送一条消息。
    当一篇文章被点赞超过200次时,将该文章设置为热门文章。
    在叮铛,这些场景都需要通过触发器来实现。

触发器,顾名思义,发生了某件事情后,触发另一件事情的发生。所以,一个触发器是由【触发事件】和【响应动作】两个环节构成,当A事件发生时,触发B动向响应。

触发事件
叮铛内置了一系列的事件,供用户选择使用,例如:用户登录、用户购买商品成功、数据表发生改表等。用户选择某一种关心的事件后,需要设定其触发条件,当触发条件成立,叮铛会去执行相应的响应动作。

响应动作
叮铛同样内置了一系列的响应动作,供用户选择使用,例如:推送消息、添加用户到某个分组、修改数据、发送邮件等等。用户选择需要响应的动作后,设置对应的参数即可。创建一个触发器时,可以添加多个响应动作,所有响应动作都会被放进执行队列中异步执行。

以上面说的场景1为例,添加一个触发器:
触发事件,选择用户购买成功事件,不修改配置的情况默认用户购买所有商品都会触发该事件,如果你只关注其中某一种商品,那配置一下过滤条件即可。
响应动作,选择推送消息,消息接收人填上商家的用户标识,内容中填写“你有一条新订单”。
保存即完成想要的功能,当有人在你的app下单时,你(商家)的app会收到一条推送消息。

未来,随着业务能力的丰富,叮铛会添加更多触发事件及响应动作,满足用户的不同需求。
更多触发器的内容请参考【触发器】一节。

客户端
叮铛对客户端程序进行了解构,重组,抽象出一套通用的界面构建规则,满足大部份app的数据展示和交互需求。
app由多个页面组成。
一个页面由多个组件构成。
布局组件(允许自定义)由多个控件构成。

由此可见,客户端界面,有三种主要的组成元素:页面、组件、控件。

  1. 页面
    app是由多个页面组成的,在叮铛,我们把页面划分为四个类型:
    通用页
    详情页
    组合页
    功能页

通用页
能直接使用,无须额外提供数据即可使用的页面,通常一个app的顶级页面,都是此类页面,实例如首页、推荐图书列表等。

详情页
与通用页相比,详情页需要指定一种数据类型,代表该详情页用于展示该类型数据内容的。此类页面为展示某条数据和该数据相关联的其他数据用的,脱离指定的数据无法直接使用,通常用于点击某条数据后打开的页面,不会用于app的顶级页面。实例如图书内容展示和相关评论页面。

组合页
顾名思义,由多个通用页或多个详情页组合起来的页面集,叫组合页。组合页的组合规则是:
一个组合页里的页面要么全是通用页,要么全是详情页。
如果组合页里的页面是详情页,这些详情页的指定数据必须是相同的。

功能页
这类页面不需要用户制作,是由叮铛开发好,提供给用户直接使用的。

  1. 组件
    组件是构成页面的基本单元,拿到一个页面的设计图后,我们会把页面分拆成多个组件,在叮铛上创建每个组件,拼成一个完整页面。

叮铛提供了丰定的组件类型,每种类型又提供了大量的模板,让应用界面的搭建变得轻松。

常见的组件为列表组件(又细分多种风格的列表)、栏目栏、幻灯、导航器、布局组件,每一个组件都有自己完整的界面显示、数据读取方法和与用户的交互方式,使用者可以通过修改组件的相关属性,达到自定义其外观、数据源、交互方式的目的。

设计组件
叮铛用户可以通过设置组件开放出来的属性来达到定制组件样式的目的。叮铛的组件抽象度很高,一个组件通过不同的属性组合呈现出来的样式千变万化。
在页面的管理后台,选择一个页面,点击设计组件可以看到该组件所有开放的属性。修改它们,通过【叮铛应用助手】这个app可以实时看到页面的效果。

数据
组件需要显示数据,但有两个问题需要解决:
组件需要显示什么数据?
怎么显示这些数据?

解决第一个问题的方法,是给组件设定一个数据源,告诉组件要显示哪个数据表的数据。当我们需要有选择性地显示数据表的数据时,可以对数据源设定一些筛选条件。

在制作后台 -> 页面管理 -> 组件管理 页面中,选定一个组件,多数组件会有数据来源设定的选项。

组件设定了数据源属性,在应用运行的时候会到服务器去获取相关的数据。

第二个问题,给组件指定了数据来源,数据有了,该如何显示?答案是建立数据表字段与组件内控件的绑定关系。

组件由更小的控件组成,组件内的每一个控件,都承担着展示某个字段信息展现和交互的责任。

在制作后台 -> 页面管理 -> 组件管理页面中,选定一个组件,都会有一个显示内容设定选项,用于设置控件(显示字段)与数据表字段(内容)字段之前的关系。

设置控件与数据表字段的绑定关系的同时,我们还可以进一步对显示的格式进行设置,在客户端内置的【客户端函数】帮助下达到各种格式化的目的。

行为管理
用户在组件上的一个点击,会引发客户端多种多样的响应,例如跳转到另一个页面、拨打一个电话号码等。在叮铛,这些行为可以让我们自由配置。
在制作后台 -> 页面管理 -> 组件管理页面中,选定一个组件,都会有一个点击事件的选项,用于设置组件里某个部位被点击,所以做出的响应。

  1. 控件
    控件是组件的组成单元。
    叮铛很多组件的结构都是固定的,允许你去修改它们的样式,给里面的控件绑定显示的数据表字段和响应的事件,但无法修改其内部结构,例如要给组件增加一个按钮控件、修改组件内元素的排列等。
    为了满足应用界面多样化的需求,叮铛提供了布局组件(DIY组件),允许用户使用控件任意设计出组件的内容和样式。
    布局组件在叮铛内部广泛使用于其他列表组件的扩展区域。
    控件是叮铛界面体系里,组成界面的最细粒度的元素,通常表现为一段文字,一个按钮,一张图片,一段视频等。布局组件是一块画布,你可以在上面任意摆放各种控件,设计出你需要的界面。

  2. 界面与数据
    客户端还承担起数据展示的作用,那么数据又是如何与界面的元素结合到一起的呢?上一节介绍组件时,说了组件数据来源和展示方式。本节再详细讲一讲数据在客户端的组织和使用。

应用的业务数据一开始都存储在服务器端,当应用在手机上被打开时,叮铛会根据所显示的页面及其包含组件所配置的数据源,从服务器上把数据取下来,再按数据字段与控件绑定的关系显示出来。

在客户端,数据按有效范围被分为四种:

app全局数据。指在整个app范围内均可被使用的数据,如用户数据、app基本信息、地理位置等。目前仅有固定几种数据,稍晚会开放给用户自己定义app的全局数据。
页面数据(详情页的指定数据)。该数据只存在于详情页,可用范围是整个详情页。页面内所有组件均可使用该数据。
组件数据(当前数据)。即组件所配置的数据源,这些数据只能被所属的组件使用。
页面参数。数据可以在页面之间传递,传递方法,就是通过页面参数的方式实现,参数的有效范围是接受参数的整个页面。(关于页面参数更多内容,见【页面参数】一节)

所以,你在制作后台会经常看到弹出的数据选择器当中,有上面几组数据供你选择。

  1. URI
    叮铛应用的每个页面都有自己的URI。叮铛通过URI打开页面,调用系统的功能。
    URI除了用于打开页面,还广泛用于调用一些特殊的操作,例如打电话、发短信。叮铛为大家提供了较为丰富的URI,可以满足多数场景的需求,未来还会持续增加。
    给有开发经验的小伙伴另一种视角:把URI当作客户端内置函数的一种调用方式。

内置功能集
叮铛提供了许多通用的功能,让大家开箱即用,例如会员系统、权限系统、注册、登录、评论、支付、推送等。这些功能在各个应用中的表现大同小异,因此,叮铛把它们做成固化的功能,方便开发者使用。

叮铛提供的通用功能均保留有一定的可扩展能力,让大家根据实际需求来做更个性化的调整,例如:会员字段允许增加自定义字段、任何数据都可以摇身一变变成商品等等。

部份内置功能默认开启,其余功能需要在叮铛应用制作后台的功能菜单中打开。更详尽的功能介绍与使用方式,见【内置功能】一节。

三、叮铛应用的二次开发

零代码开发是叮铛的终极目标,但现阶段仍然会有一些需求在叮铛上是暂时做不出来,为解决这个问题,叮铛成立了专门的开放平台组。

如果叮铛提供的能力仍然不能满足需要,如果你恰好具备一定的开发能力,叮铛应用开发平台能让二次开发。开放平台提供以下几种二次开发的途径:

H5接入,叮铛提供了JSSDK,部分在叮铛应用暂不能实现的app页面,可使用H5来开发,嵌入到制作的app中。
双向SDK:a.叮铛提供插件开发套件,让开发者开发符合叮铛标准的原生插件,上传到开发者自己的插件库,与制作的app进行打包。b.叮铛还提供把制作好的app页面导出为SDK,被开发者整合进他们原有的自主研发的app当中
第三方数据的引入。如果开发者有自己的业务服务器,可以使用叮铛的数据引入机制,既能让你的数据保存在自己的服务器上,又能让你享受叮铛制作app的便利。
生成可交付的代码。遇到需要交付代码的客户?我们可以搞定,叮铛可以为你生成具备交付能力的代码。

继续阅读 »

听起来有点理想主义,更甚至有点荒唐?没错,零代码开发正是一种理想的应用生产方式,我们回顾一下应用开发的历史就会发现,开发一个应用,需要写的代码越来越少,到最理想的状态就是不需要写代码。

叮铛应用(www.ddapp.com),是这条道路上唯一一个执着且坚定的实践者,并通过大量的实例反复验证了这条道路的可行性。这一点都不荒唐,而是历史的趋势。叮铛应用是一款集iOS、安卓Android、小程序、H5四端于一体的零代码移动应用生产运营平台,为全行业多领域提供应用解决方案—电商、区域社区、知识付费,本地社群,量身定制服务,高效制作纯原生代码封装的移动app应用

一、应用开发的发展路径
首先回顾一下应用开发的发展历史,我们会发现应用开发的代码越来越少的规律。

刀耕火种期
这个时期,应用开发生态刚萌牙,应用的每一个功能,从服务端到客户端都要开发者一行一行代码的敲,如果需要用到服务器,通常也是开发者购买物理主机,托管到电信机房里去,装机维护全都需要自己搞定。

模块复用期
随着代码的积累,有重用意识的开发者开始把通用的功能剥离出来,形成为一个个独立的功能模块。他们还通过内部共享或开源的方式,让越来越多人基于此可以快速实现一个功能。

云服务期
云计算的兴旺发达,让应用开发有了质的改变。

一是表现在机器的托管上,开发者慢慢的从购买主机,租用机购托管服务器,变成了直接购买IAAS服务商的云主机。
二是应用的快速开发上。
PAAS(平台即服务)的百花绽放,让开发者几行代码就可以实现推送、分享、文件存储甚至部署等原本繁琐、复杂的功能,而且不需要额外的维护成本。开发者只需要关注核心业务,因此代码也更少了。
BAAS(后端即服务)的出现,一下子解放了后端程序员,这下应用开发者只需要有客户端或前端开发者就够,开发成本一下子又砍掉一大半;要写的代码也只剩下客户端与前端了。

AAAS
AAAS(应用即服务,不同于SAAS)是叮铛应用提出的一个新名词,在BAAS的解放后端程序员的基础上,AAAS顺手把客户端和前端程序员也解放了。到此,真正达到零代码应用开发。

二、叮铛应用是怎么做到的
简单来说,叮铛把应用服务端和客户端的技术进行了高度的抽象,让组成应用的零件都变得通用且具备高度可配置性。可以这么说,我们通过叮铛把一个应用配置出来。

下面我们通过解构一个手机app应用,来建立起叮铛应用的世界观。

一个app的构成
一个手机app主要由两大部分的程序运行支撑起来的:客户端和服务端。

客户端,运行在用户的手机上,即用户直接使用到的app,例如微信、微博app,客户端由各种各样的功能界面组合而成,用于展示应用的数据并和用户产生交互。
服务端,运行在云端的某一台服务器上,通过网络与客户端连接起来,用于保存应用和用户的数据。

服务端
先讲服务端,因为服务端是存储应用业务数据的地方,而业务数据是一个应用的核心灵魂。我们要开发一个应用,通常要先梳理出业务数据,其次才是考虑如何设计它的界面和交互。

服务端有两个重要的角色:数据表与触发器。

数据表,用于存储应用的业务数据,表达业务数据之间的逻辑关系。
触发器,用户行为触发服务端执行其他任务的调度机制。
两者紧密结合,可以完成服务端的大部分需求场景。

下面分别认识一下。

  1. 数据表
    上面讲过,服务端的主要职责是存储应用的业务数据,数据表正是用于实现此目的的。

没学过数据库或没有开发经验的同学可能会担心自己不能理解数据表的概念,在此,我用一个大家常见的场景来类比一下,大家就会明白了。

大家都使用过Excel这个表格软件,下面用Excel来做一张班级的学生花名册的表格。

第一步:设计表结构
首先,我们要确定一下,这张表格要记录学生的什么资料,如:姓名、性别、籍贯、民族、学号、手机号码、生日等,确定完毕后,我们先创建一行表头,用于表示这个表格每一列表示学生的哪种信息,如下图:

表头创建完之后,我们算是设计好了学生资料的结构——一个学生的资料,由姓名、性别等信息(或称为列)构成。

第二步:填充表
接下来,我们向设计好的表格里面填数据,连续填进去多条学生的数据。

在叮铛,一张数据表就像一张Excel表类似,你要先设计好表结构,才能去填数据,让用户在客户端访问它们。

在叮铛,我们把表格中的每一列叫做字段,即叮铛的数据表结构是由多个字段组成的。如上面示例中的学生表的结构,由姓名、性别等多个字段组成。每个字段都有它对应的数据类型,如姓名是文本、手机号码是数字、生日是日期等,和Excel里单元格的格式是同样的道理。

数据表定义好,数据也准备好了,用户怎么通过客户端访问呢?在客户端一节中将有详细说明。
更多关于数据表的内容请参考【数据表】一节。

  1. 触发器
    请想象以下的场景:
    当一个客户在app下了一个订单,想要给商家推送一条消息。
    当一篇文章被点赞超过200次时,将该文章设置为热门文章。
    在叮铛,这些场景都需要通过触发器来实现。

触发器,顾名思义,发生了某件事情后,触发另一件事情的发生。所以,一个触发器是由【触发事件】和【响应动作】两个环节构成,当A事件发生时,触发B动向响应。

触发事件
叮铛内置了一系列的事件,供用户选择使用,例如:用户登录、用户购买商品成功、数据表发生改表等。用户选择某一种关心的事件后,需要设定其触发条件,当触发条件成立,叮铛会去执行相应的响应动作。

响应动作
叮铛同样内置了一系列的响应动作,供用户选择使用,例如:推送消息、添加用户到某个分组、修改数据、发送邮件等等。用户选择需要响应的动作后,设置对应的参数即可。创建一个触发器时,可以添加多个响应动作,所有响应动作都会被放进执行队列中异步执行。

以上面说的场景1为例,添加一个触发器:
触发事件,选择用户购买成功事件,不修改配置的情况默认用户购买所有商品都会触发该事件,如果你只关注其中某一种商品,那配置一下过滤条件即可。
响应动作,选择推送消息,消息接收人填上商家的用户标识,内容中填写“你有一条新订单”。
保存即完成想要的功能,当有人在你的app下单时,你(商家)的app会收到一条推送消息。

未来,随着业务能力的丰富,叮铛会添加更多触发事件及响应动作,满足用户的不同需求。
更多触发器的内容请参考【触发器】一节。

客户端
叮铛对客户端程序进行了解构,重组,抽象出一套通用的界面构建规则,满足大部份app的数据展示和交互需求。
app由多个页面组成。
一个页面由多个组件构成。
布局组件(允许自定义)由多个控件构成。

由此可见,客户端界面,有三种主要的组成元素:页面、组件、控件。

  1. 页面
    app是由多个页面组成的,在叮铛,我们把页面划分为四个类型:
    通用页
    详情页
    组合页
    功能页

通用页
能直接使用,无须额外提供数据即可使用的页面,通常一个app的顶级页面,都是此类页面,实例如首页、推荐图书列表等。

详情页
与通用页相比,详情页需要指定一种数据类型,代表该详情页用于展示该类型数据内容的。此类页面为展示某条数据和该数据相关联的其他数据用的,脱离指定的数据无法直接使用,通常用于点击某条数据后打开的页面,不会用于app的顶级页面。实例如图书内容展示和相关评论页面。

组合页
顾名思义,由多个通用页或多个详情页组合起来的页面集,叫组合页。组合页的组合规则是:
一个组合页里的页面要么全是通用页,要么全是详情页。
如果组合页里的页面是详情页,这些详情页的指定数据必须是相同的。

功能页
这类页面不需要用户制作,是由叮铛开发好,提供给用户直接使用的。

  1. 组件
    组件是构成页面的基本单元,拿到一个页面的设计图后,我们会把页面分拆成多个组件,在叮铛上创建每个组件,拼成一个完整页面。

叮铛提供了丰定的组件类型,每种类型又提供了大量的模板,让应用界面的搭建变得轻松。

常见的组件为列表组件(又细分多种风格的列表)、栏目栏、幻灯、导航器、布局组件,每一个组件都有自己完整的界面显示、数据读取方法和与用户的交互方式,使用者可以通过修改组件的相关属性,达到自定义其外观、数据源、交互方式的目的。

设计组件
叮铛用户可以通过设置组件开放出来的属性来达到定制组件样式的目的。叮铛的组件抽象度很高,一个组件通过不同的属性组合呈现出来的样式千变万化。
在页面的管理后台,选择一个页面,点击设计组件可以看到该组件所有开放的属性。修改它们,通过【叮铛应用助手】这个app可以实时看到页面的效果。

数据
组件需要显示数据,但有两个问题需要解决:
组件需要显示什么数据?
怎么显示这些数据?

解决第一个问题的方法,是给组件设定一个数据源,告诉组件要显示哪个数据表的数据。当我们需要有选择性地显示数据表的数据时,可以对数据源设定一些筛选条件。

在制作后台 -> 页面管理 -> 组件管理 页面中,选定一个组件,多数组件会有数据来源设定的选项。

组件设定了数据源属性,在应用运行的时候会到服务器去获取相关的数据。

第二个问题,给组件指定了数据来源,数据有了,该如何显示?答案是建立数据表字段与组件内控件的绑定关系。

组件由更小的控件组成,组件内的每一个控件,都承担着展示某个字段信息展现和交互的责任。

在制作后台 -> 页面管理 -> 组件管理页面中,选定一个组件,都会有一个显示内容设定选项,用于设置控件(显示字段)与数据表字段(内容)字段之前的关系。

设置控件与数据表字段的绑定关系的同时,我们还可以进一步对显示的格式进行设置,在客户端内置的【客户端函数】帮助下达到各种格式化的目的。

行为管理
用户在组件上的一个点击,会引发客户端多种多样的响应,例如跳转到另一个页面、拨打一个电话号码等。在叮铛,这些行为可以让我们自由配置。
在制作后台 -> 页面管理 -> 组件管理页面中,选定一个组件,都会有一个点击事件的选项,用于设置组件里某个部位被点击,所以做出的响应。

  1. 控件
    控件是组件的组成单元。
    叮铛很多组件的结构都是固定的,允许你去修改它们的样式,给里面的控件绑定显示的数据表字段和响应的事件,但无法修改其内部结构,例如要给组件增加一个按钮控件、修改组件内元素的排列等。
    为了满足应用界面多样化的需求,叮铛提供了布局组件(DIY组件),允许用户使用控件任意设计出组件的内容和样式。
    布局组件在叮铛内部广泛使用于其他列表组件的扩展区域。
    控件是叮铛界面体系里,组成界面的最细粒度的元素,通常表现为一段文字,一个按钮,一张图片,一段视频等。布局组件是一块画布,你可以在上面任意摆放各种控件,设计出你需要的界面。

  2. 界面与数据
    客户端还承担起数据展示的作用,那么数据又是如何与界面的元素结合到一起的呢?上一节介绍组件时,说了组件数据来源和展示方式。本节再详细讲一讲数据在客户端的组织和使用。

应用的业务数据一开始都存储在服务器端,当应用在手机上被打开时,叮铛会根据所显示的页面及其包含组件所配置的数据源,从服务器上把数据取下来,再按数据字段与控件绑定的关系显示出来。

在客户端,数据按有效范围被分为四种:

app全局数据。指在整个app范围内均可被使用的数据,如用户数据、app基本信息、地理位置等。目前仅有固定几种数据,稍晚会开放给用户自己定义app的全局数据。
页面数据(详情页的指定数据)。该数据只存在于详情页,可用范围是整个详情页。页面内所有组件均可使用该数据。
组件数据(当前数据)。即组件所配置的数据源,这些数据只能被所属的组件使用。
页面参数。数据可以在页面之间传递,传递方法,就是通过页面参数的方式实现,参数的有效范围是接受参数的整个页面。(关于页面参数更多内容,见【页面参数】一节)

所以,你在制作后台会经常看到弹出的数据选择器当中,有上面几组数据供你选择。

  1. URI
    叮铛应用的每个页面都有自己的URI。叮铛通过URI打开页面,调用系统的功能。
    URI除了用于打开页面,还广泛用于调用一些特殊的操作,例如打电话、发短信。叮铛为大家提供了较为丰富的URI,可以满足多数场景的需求,未来还会持续增加。
    给有开发经验的小伙伴另一种视角:把URI当作客户端内置函数的一种调用方式。

内置功能集
叮铛提供了许多通用的功能,让大家开箱即用,例如会员系统、权限系统、注册、登录、评论、支付、推送等。这些功能在各个应用中的表现大同小异,因此,叮铛把它们做成固化的功能,方便开发者使用。

叮铛提供的通用功能均保留有一定的可扩展能力,让大家根据实际需求来做更个性化的调整,例如:会员字段允许增加自定义字段、任何数据都可以摇身一变变成商品等等。

部份内置功能默认开启,其余功能需要在叮铛应用制作后台的功能菜单中打开。更详尽的功能介绍与使用方式,见【内置功能】一节。

三、叮铛应用的二次开发

零代码开发是叮铛的终极目标,但现阶段仍然会有一些需求在叮铛上是暂时做不出来,为解决这个问题,叮铛成立了专门的开放平台组。

如果叮铛提供的能力仍然不能满足需要,如果你恰好具备一定的开发能力,叮铛应用开发平台能让二次开发。开放平台提供以下几种二次开发的途径:

H5接入,叮铛提供了JSSDK,部分在叮铛应用暂不能实现的app页面,可使用H5来开发,嵌入到制作的app中。
双向SDK:a.叮铛提供插件开发套件,让开发者开发符合叮铛标准的原生插件,上传到开发者自己的插件库,与制作的app进行打包。b.叮铛还提供把制作好的app页面导出为SDK,被开发者整合进他们原有的自主研发的app当中
第三方数据的引入。如果开发者有自己的业务服务器,可以使用叮铛的数据引入机制,既能让你的数据保存在自己的服务器上,又能让你享受叮铛制作app的便利。
生成可交付的代码。遇到需要交付代码的客户?我们可以搞定,叮铛可以为你生成具备交付能力的代码。

收起阅读 »

mui APP开发的几点心得

mui

这几天潜心研究MUI,并做了一个不大不小的项目,一些经验和各位分享。
前端:MUI + JQ
后端:PHP+MYSQL

  1. 能用JQ还是尽量用JQ吧,毕竟现在手机性能越来越好,空间也越来越大,JQ封装了很多方法用起来很方便,建议各位自行权衡。MUI选择器 方法也不错,与用户交互的地方都做了很多美化,两者结合用。

  1. 手机的界面,一般是显示两个webview,我的主页面就是一个index.html 只做页头和页脚,其它webview在中间切来切去。所以我的项目,显示的webview永远只有两个。所以,每一次切换webview,我都把两个之外的webview全部销毁掉。理由是,大多的webview是占用资源的,特别是有些webview页面上,有很多实时项目,需要不断从服务器读取数据,如果不销毁,手机和服务器压力都很大。

  1. 引入JQ后,一般的写法如下:
    mui.plusReady(function() {
    $(function() {
    todo....
    })
    })
    4.尽量自定义属性,特别是对于URL,我习惯用page='index.html',因为你搞不清楚哪个href是可以跳转,哪个是不跳转的
  2. 全程都没用preload,webview开启、切换,转场都很流畅,手机是华为900块钱一台的。
  3. mui自带的字体库 图标太少了点,可以对外开放,叫更多的人来补充。
  4. mui的教程,特别是入门 提高的系统性教程,官方应该录制一些。
  5. 官方的QQ群貌似也没有,建议建几个这样的群,好东西用的人越多越好。


继续阅读 »

这几天潜心研究MUI,并做了一个不大不小的项目,一些经验和各位分享。
前端:MUI + JQ
后端:PHP+MYSQL

  1. 能用JQ还是尽量用JQ吧,毕竟现在手机性能越来越好,空间也越来越大,JQ封装了很多方法用起来很方便,建议各位自行权衡。MUI选择器 方法也不错,与用户交互的地方都做了很多美化,两者结合用。

  1. 手机的界面,一般是显示两个webview,我的主页面就是一个index.html 只做页头和页脚,其它webview在中间切来切去。所以我的项目,显示的webview永远只有两个。所以,每一次切换webview,我都把两个之外的webview全部销毁掉。理由是,大多的webview是占用资源的,特别是有些webview页面上,有很多实时项目,需要不断从服务器读取数据,如果不销毁,手机和服务器压力都很大。

  1. 引入JQ后,一般的写法如下:
    mui.plusReady(function() {
    $(function() {
    todo....
    })
    })
    4.尽量自定义属性,特别是对于URL,我习惯用page='index.html',因为你搞不清楚哪个href是可以跳转,哪个是不跳转的
  2. 全程都没用preload,webview开启、切换,转场都很流畅,手机是华为900块钱一台的。
  3. mui自带的字体库 图标太少了点,可以对外开放,叫更多的人来补充。
  4. mui的教程,特别是入门 提高的系统性教程,官方应该录制一些。
  5. 官方的QQ群貌似也没有,建议建几个这样的群,好东西用的人越多越好。


收起阅读 »

前端老司机 带队接单

多年开发经验,国内主流混合开发框架熟练运用,有需求联系 646676665

多年开发经验,国内主流混合开发框架熟练运用,有需求联系 646676665

如何转让应用

转让项目 转让应用 开发者中心

> 文档已迁移至新链接:https://uniapp.dcloud.net.cn/dev/app/transfer.html
> 如有疑问,可以单独发贴咨询。

继续阅读 »

> 文档已迁移至新链接:https://uniapp.dcloud.net.cn/dev/app/transfer.html
> 如有疑问,可以单独发贴咨询。

收起阅读 »