HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

完美的mui图片裁切上传(含服务器端、相册选择、拍照,可直接运行)

图片裁剪

附件有含有源码下载

开篇背景:
在网上找了许多资料,汇总之后写了这么一个示例出来,现将完整代码整理处理,包括前端完整的Hbuilder工程代码和后端的处理上传Java实现。图片裁切使用到了jquery.cropper插件,所以这个图片裁切的功能模块是需要使用到jquery的,也尝试自己写了一点实现了基本的图片放大缩小、图片移动后感觉脑袋太晕了,还是找一款插件更加合适,经详细了解了cropper插件后觉得太强大了,兼容pc、安卓、ios等,一些特色功能满足来了我个人对于图片裁切所需要的所有,插件下载地址为:https://github.com/fengyuanchen/cropper ,网上也能找到需要关于此插件的在线运行,也可以将附件源码下载下来,在源码的docs/index.html文件打开可以直接运行代码示例。特色功能有:图片拖动大小缩放、背景图片移动、裁切区域移动、选区大小调整、选区移动、照片左右上下变换、旋转等,这些功能也是我本次示例中使用到了的。

后端处理:
无论用什么语言,它的目的是将用户裁切完成后的数据进行存储,由于前端裁切使用了canvas裁切图片,所以提交到后端的是裁切后的区域图片字符串编码,而非图片文件,后端需要将此字符串采用base64解码转换成一张图片文件存储。。。也就是说这里的后端处理文件上传并非传统的处理图片上传,而是解码base64字符串,如果还不清楚base64字符串编码与图片的转换,可以使用word验证一下。这里临时写了一个简单的Java的servlet程序来处理文件上传,参考代码如下:

package com;  

import java.io.File;  
import java.io.FileOutputStream;  
import java.io.IOException;  
import java.io.OutputStream;  
import java.io.PrintWriter;  
import java.util.UUID;  

import javax.servlet.ServletException;  
import javax.servlet.annotation.WebServlet;  
import javax.servlet.http.HttpServlet;  
import javax.servlet.http.HttpServletRequest;  
import javax.servlet.http.HttpServletResponse;  

import com.alibaba.fastjson.JSONObject;  
import com.alibaba.fastjson.util.Base64;  
import com.frame.base.utils.properties.PropertiesUtils;  
import com.frame.base.utils.properties.enums.EnumProperties;  

@WebServlet("/testaa.servlet")  
public class Test extends HttpServlet {  

    private static final long serialVersionUID = 1L;  

    @Override  
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)  
            throws ServletException, IOException {  
        String imageBase64 = req.getParameter("imageBase64");  
        //将base64的图片编码存储至服务器,并将路径修改至数据库  
        String type = "data:image/png;base64,";//前台是以png的格式存储的  
        imageBase64 = imageBase64.substring(type.length());  
        String realPath = "sysuser" + File.separator + UUID.randomUUID().toString() + ".png";  
        JSONObject json = new JSONObject();  
        try {  
            this.uploadFileByBase64(realPath , imageBase64);  
            json.put("result", "success");  
            json.put("message", realPath);  
        } catch (Exception e) {  
            json.put("result", "error");  
            json.put("message", "上传图片失败!");  
            e.printStackTrace();  
        }  
        //将参数输出至浏览器  
        PrintWriter out = resp.getWriter();  
        out.write(json.toJSONString());  
        out.flush();  
        out.close();  

    }  

    @Override  
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)  
            throws ServletException, IOException {  
        this.doGet(req, resp);  
    }  

    public File uploadFileByBase64(String destRealPath , String imageBase64) throws Exception{  
        OutputStream out = null;  
        try {  
            byte bytes[] = Base64.decodeFast(imageBase64);  
            //这里是我的获取图片文件服务器的路径,可以是随便一个地址,如d:\\temp\\之类的  
            String imageServer = PropertiesUtils.getInstance().getProperty(EnumProperties.APPLICATION, "system.fileupload.information");  
            String destFilePath = imageServer + File.separator + destRealPath;  
            File destFile = new File(destFilePath);  
            if(! destFile.getParentFile().exists()){  
                destFile.getParentFile().mkdirs();  
            }  
            for(int i=0 , lens = bytes.length ; i < lens ; i++){  
                if(bytes[i] < 0){  
                    bytes[i] += 256;  
                }  
            }  
            out = new FileOutputStream(destFile);  
            out.write(bytes);  
            return destFile;  
        } catch (Exception e) {  
            throw new Exception(e);  
        } finally {  
            if(out != null){  
                try {  
                    out.flush();  
                    out.close();  
                } catch (IOException e) {  
                    e.printStackTrace();  
                }  
            }  
        }  
    }  

}  

上传至服务器的图片下载

服务器端是我临时写的代码,如果示例提示上传失败,有可能是我的服务器关闭了无法正常访问,运行示例中提示的上传成功可将相关地址复制到浏览器访问,我的服务器会输出这个裁切后上传到服务器的图片,图片结尾地址为.image,即xxxxx.png.image

其他说明
前台代码中用到了一些windows.xxx没有别的意思,就是为了不想定义全部变量而已。

继续阅读 »

附件有含有源码下载

开篇背景:
在网上找了许多资料,汇总之后写了这么一个示例出来,现将完整代码整理处理,包括前端完整的Hbuilder工程代码和后端的处理上传Java实现。图片裁切使用到了jquery.cropper插件,所以这个图片裁切的功能模块是需要使用到jquery的,也尝试自己写了一点实现了基本的图片放大缩小、图片移动后感觉脑袋太晕了,还是找一款插件更加合适,经详细了解了cropper插件后觉得太强大了,兼容pc、安卓、ios等,一些特色功能满足来了我个人对于图片裁切所需要的所有,插件下载地址为:https://github.com/fengyuanchen/cropper ,网上也能找到需要关于此插件的在线运行,也可以将附件源码下载下来,在源码的docs/index.html文件打开可以直接运行代码示例。特色功能有:图片拖动大小缩放、背景图片移动、裁切区域移动、选区大小调整、选区移动、照片左右上下变换、旋转等,这些功能也是我本次示例中使用到了的。

后端处理:
无论用什么语言,它的目的是将用户裁切完成后的数据进行存储,由于前端裁切使用了canvas裁切图片,所以提交到后端的是裁切后的区域图片字符串编码,而非图片文件,后端需要将此字符串采用base64解码转换成一张图片文件存储。。。也就是说这里的后端处理文件上传并非传统的处理图片上传,而是解码base64字符串,如果还不清楚base64字符串编码与图片的转换,可以使用word验证一下。这里临时写了一个简单的Java的servlet程序来处理文件上传,参考代码如下:

package com;  

import java.io.File;  
import java.io.FileOutputStream;  
import java.io.IOException;  
import java.io.OutputStream;  
import java.io.PrintWriter;  
import java.util.UUID;  

import javax.servlet.ServletException;  
import javax.servlet.annotation.WebServlet;  
import javax.servlet.http.HttpServlet;  
import javax.servlet.http.HttpServletRequest;  
import javax.servlet.http.HttpServletResponse;  

import com.alibaba.fastjson.JSONObject;  
import com.alibaba.fastjson.util.Base64;  
import com.frame.base.utils.properties.PropertiesUtils;  
import com.frame.base.utils.properties.enums.EnumProperties;  

@WebServlet("/testaa.servlet")  
public class Test extends HttpServlet {  

    private static final long serialVersionUID = 1L;  

    @Override  
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)  
            throws ServletException, IOException {  
        String imageBase64 = req.getParameter("imageBase64");  
        //将base64的图片编码存储至服务器,并将路径修改至数据库  
        String type = "data:image/png;base64,";//前台是以png的格式存储的  
        imageBase64 = imageBase64.substring(type.length());  
        String realPath = "sysuser" + File.separator + UUID.randomUUID().toString() + ".png";  
        JSONObject json = new JSONObject();  
        try {  
            this.uploadFileByBase64(realPath , imageBase64);  
            json.put("result", "success");  
            json.put("message", realPath);  
        } catch (Exception e) {  
            json.put("result", "error");  
            json.put("message", "上传图片失败!");  
            e.printStackTrace();  
        }  
        //将参数输出至浏览器  
        PrintWriter out = resp.getWriter();  
        out.write(json.toJSONString());  
        out.flush();  
        out.close();  

    }  

    @Override  
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)  
            throws ServletException, IOException {  
        this.doGet(req, resp);  
    }  

    public File uploadFileByBase64(String destRealPath , String imageBase64) throws Exception{  
        OutputStream out = null;  
        try {  
            byte bytes[] = Base64.decodeFast(imageBase64);  
            //这里是我的获取图片文件服务器的路径,可以是随便一个地址,如d:\\temp\\之类的  
            String imageServer = PropertiesUtils.getInstance().getProperty(EnumProperties.APPLICATION, "system.fileupload.information");  
            String destFilePath = imageServer + File.separator + destRealPath;  
            File destFile = new File(destFilePath);  
            if(! destFile.getParentFile().exists()){  
                destFile.getParentFile().mkdirs();  
            }  
            for(int i=0 , lens = bytes.length ; i < lens ; i++){  
                if(bytes[i] < 0){  
                    bytes[i] += 256;  
                }  
            }  
            out = new FileOutputStream(destFile);  
            out.write(bytes);  
            return destFile;  
        } catch (Exception e) {  
            throw new Exception(e);  
        } finally {  
            if(out != null){  
                try {  
                    out.flush();  
                    out.close();  
                } catch (IOException e) {  
                    e.printStackTrace();  
                }  
            }  
        }  
    }  

}  

上传至服务器的图片下载

服务器端是我临时写的代码,如果示例提示上传失败,有可能是我的服务器关闭了无法正常访问,运行示例中提示的上传成功可将相关地址复制到浏览器访问,我的服务器会输出这个裁切后上传到服务器的图片,图片结尾地址为.image,即xxxxx.png.image

其他说明
前台代码中用到了一些windows.xxx没有别的意思,就是为了不想定义全部变量而已。

收起阅读 »

MUI+VUE2.0:开发一个actionsheet组件

Vue mui
  1. 开发工具:HBuilder
  2. 开发目标:使用vue2.0+mui.min.css开发一个与mui官方功能一致的actionsheet组件
  3. 实现功能
    • actionsheet组件可接收一个items参数来显示具体子项
    • actionsheet组件可接收一个onItemClick回调来处理子项点击事件

备注:简单起见,不使用vue的单文件组件开发模式

开发步骤

  1. 使用HBuilder,创建一个移动App(mui项目)
  2. 引入mui.min.css及vue.js
  3. 搭建一个页面框架,代码如下:

    
    <!DOCTYPE html>  
    <html>  
    
    <head>  
        <meta charset="utf-8">  
        <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />  
        <title></title>  
        <link rel="stylesheet" href="css/mui.min.css" />  
    </head>  
    
    <body>  
        <div id="app"></div>  
        <script type="text/javascript" src="js/vue.js"></script>  
    </body>  

</html>

4.开发actionsheet组件,代码如下  
组件动画样式

/**

  • 重写mui.min.css中的样式
    */

.mui-popover.mui-popover-action {
display: block;
opacity: 1;
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
-webkit-transition: none;
transition: none;
}
/**

  • 定义actionsheet的backdrop进入动画效果
    */

.mui-actionsheet-enter {
opacity: 1;
}
/**

  • 定义actionsheet的backdrop离开动画效果
    */

.mui-actionsheet-leave-to {
opacity: 0;
}
/**

  • 定义actionsheet动画持续时间
    */

.mui-actionsheet-enter-active,
.mui-actionsheet-leave-active,
.mui-actionsheet-enter-active .mui-popover.mui-popover-action,
.mui-actionsheet-leave-active .mui-popover.mui-popover-action {
-webkit-transition: -webkit-transform .3s, opacity .3s;
transition: transform .3s, opacity .3s;
}
/**

  • 定义actionsheet进入时,离开时动画效果
    */

.mui-actionsheet-enter .mui-popover.mui-popover-action,
.mui-actionsheet-leave-to .mui-popover.mui-popover-action {
-webkit-transform: translate3d(0, 100%, 0);
transform: translate3d(0, 100%, 0);
}

组件模板代码

<script id="actionSheetTpl" type="text/html">
<transition name="mui-actionsheet">
<div class="mui-backdrop" v-show="isOpen" v-on:click="hide">
<div class="mui-popover mui-popover-action mui-popover-bottom">
<ul class="mui-table-view">
<li v-for="(item,index) in items" v-on:click="handleItemClick(item,index+1)" class="mui-table-view-cell">
<a>{{item}}</a>
</li>
</ul>
<ul class="mui-table-view">
<li v-on:click="handleItemClick('取消',0)" class="mui-table-view-cell">
<a><b>取消</b></a>
</li>
</ul>
</div>
</div>
</transition>
</script>

组件实现代码

/**

  • 注册action-sheet组件
    */
    Vue.component('action-sheet', {
    props: ['items', 'shown'],
    data: function() {
    return {
    isOpen: !!this.shown
    }
    },
    watch: {
    shown: function() {
    this.isOpen = !!this.shown;
    }
    },
    methods: {
    show: function() {
    this.isOpen = true; //显示actionsheet
    },
    hide: function() {
    this.isOpen = false; //关闭actionsheet
    },
    toggle: function() {
    this.isOpen = !this.isOpen; //切换显示状态
    },
    handleItemClick: function(item, index) {
    this.$emit('onItemClick', item, index); //触发onItemClick事件
    }
    },
    template: document.getElementById("actionSheetTpl").innerText,
    });

    5.渲染示例(仿官网actionsheet示例)  
    组件使用示例

    <script id="demoTpl" type="text/html">
    <div>
    <header class="mui-bar mui-bar-nav">
    <a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a>
    <h1 class="mui-title">H5模式actionsheet</h1>
    </header>
    <nav class="mui-bar mui-bar-tab">
    <a v-on:click="toggleActionSheet('delete')" class="mui-tab-item">
    <span class="mui-icon mui-icon-trash"></span>
    </a>
    <a class="mui-tab-item" href="#">
    </a>
    <a class="mui-tab-item" href="#">
    </a>
    <a v-on:click="toggleActionSheet('forward')" class="mui-tab-item">
    <span class="mui-icon mui-icon-undo"></span>
    </a>
    </nav>
    <div class="mui-content">
    <div class="mui-content-padded">
    <p>actionsheet一般从底部弹出,显示一系列可选择的操作按钮,供用户选择; actionSheet可从任意位置触发, 点击本页面左下角
    <span class="mui-icon mui-icon-trash"></span>会弹出删除信息确认框; 点击本页面右下角
    <span class="mui-icon mui-icon-undo"></span>会弹出转发确认框; 你也可点击如下按钮,打开照片选择框:
    </p>
    <p>
    <a v-on:click="toggleActionSheet('picture')" class="mui-btn mui-btn-primary mui-btn-block mui-btn-outlined" style="padding: 5px 20px;">打开actionsheet</a>
    </p>
    <p>本页面为H5模式的actionsheet演示示例,该模式具有如下优点:</p>
    <ul class="des">
    <li>可通过css自由定制展现样式</li>
    </ul>

        <p>另一方面,H5模式的actionsheet也具备如下缺点:</p>  
        <ul class="des">  
            <li>不支持覆盖顶部状态栏</li>  
            <li>不支持跨webview的遮罩</li>  
            <li>在有map等原生组件时,容易被遮挡</li>  
        </ul>  
        <p id="info"></p>  
        <action-sheet ref="picture" :items="pictureItems" v-on:onItemClick="handleItemClick"></action-sheet>  
        <action-sheet ref="forward" :items="forwardItems" v-on:onItemClick="handleItemClick"></action-sheet>  
        <action-sheet ref="delete" :items="deleteItems" v-on:onItemClick="handleItemClick"></action-sheet>  
    </div>  

    </div>
    </div>
    </script>

    渲染示例代码

    //渲染示例页面
    var app = new Vue({
    el: '#app',
    data: function() {
    return {
    pictureItems: ['拍照或录像', '选取现有的'],
    forwardItems: ['回复', '转发', '打印'],
    deleteItems: ['删除']
    }
    },
    methods: {
    toggleActionSheet: function(actionsheet) {
    this.$refs[actionsheet]['toggle']();
    },
    handleItemClick: function(item, index) {
    console.log(item, index);
    }
    },
    template: document.getElementById("demoTpl").innerText
    });

完整代码,见附件

继续阅读 »
  1. 开发工具:HBuilder
  2. 开发目标:使用vue2.0+mui.min.css开发一个与mui官方功能一致的actionsheet组件
  3. 实现功能
    • actionsheet组件可接收一个items参数来显示具体子项
    • actionsheet组件可接收一个onItemClick回调来处理子项点击事件

备注:简单起见,不使用vue的单文件组件开发模式

开发步骤

  1. 使用HBuilder,创建一个移动App(mui项目)
  2. 引入mui.min.css及vue.js
  3. 搭建一个页面框架,代码如下:

    
    <!DOCTYPE html>  
    <html>  
    
    <head>  
        <meta charset="utf-8">  
        <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />  
        <title></title>  
        <link rel="stylesheet" href="css/mui.min.css" />  
    </head>  
    
    <body>  
        <div id="app"></div>  
        <script type="text/javascript" src="js/vue.js"></script>  
    </body>  

</html>

4.开发actionsheet组件,代码如下  
组件动画样式

/**

  • 重写mui.min.css中的样式
    */

.mui-popover.mui-popover-action {
display: block;
opacity: 1;
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
-webkit-transition: none;
transition: none;
}
/**

  • 定义actionsheet的backdrop进入动画效果
    */

.mui-actionsheet-enter {
opacity: 1;
}
/**

  • 定义actionsheet的backdrop离开动画效果
    */

.mui-actionsheet-leave-to {
opacity: 0;
}
/**

  • 定义actionsheet动画持续时间
    */

.mui-actionsheet-enter-active,
.mui-actionsheet-leave-active,
.mui-actionsheet-enter-active .mui-popover.mui-popover-action,
.mui-actionsheet-leave-active .mui-popover.mui-popover-action {
-webkit-transition: -webkit-transform .3s, opacity .3s;
transition: transform .3s, opacity .3s;
}
/**

  • 定义actionsheet进入时,离开时动画效果
    */

.mui-actionsheet-enter .mui-popover.mui-popover-action,
.mui-actionsheet-leave-to .mui-popover.mui-popover-action {
-webkit-transform: translate3d(0, 100%, 0);
transform: translate3d(0, 100%, 0);
}

组件模板代码

<script id="actionSheetTpl" type="text/html">
<transition name="mui-actionsheet">
<div class="mui-backdrop" v-show="isOpen" v-on:click="hide">
<div class="mui-popover mui-popover-action mui-popover-bottom">
<ul class="mui-table-view">
<li v-for="(item,index) in items" v-on:click="handleItemClick(item,index+1)" class="mui-table-view-cell">
<a>{{item}}</a>
</li>
</ul>
<ul class="mui-table-view">
<li v-on:click="handleItemClick('取消',0)" class="mui-table-view-cell">
<a><b>取消</b></a>
</li>
</ul>
</div>
</div>
</transition>
</script>

组件实现代码

/**

  • 注册action-sheet组件
    */
    Vue.component('action-sheet', {
    props: ['items', 'shown'],
    data: function() {
    return {
    isOpen: !!this.shown
    }
    },
    watch: {
    shown: function() {
    this.isOpen = !!this.shown;
    }
    },
    methods: {
    show: function() {
    this.isOpen = true; //显示actionsheet
    },
    hide: function() {
    this.isOpen = false; //关闭actionsheet
    },
    toggle: function() {
    this.isOpen = !this.isOpen; //切换显示状态
    },
    handleItemClick: function(item, index) {
    this.$emit('onItemClick', item, index); //触发onItemClick事件
    }
    },
    template: document.getElementById("actionSheetTpl").innerText,
    });

    5.渲染示例(仿官网actionsheet示例)  
    组件使用示例

    <script id="demoTpl" type="text/html">
    <div>
    <header class="mui-bar mui-bar-nav">
    <a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a>
    <h1 class="mui-title">H5模式actionsheet</h1>
    </header>
    <nav class="mui-bar mui-bar-tab">
    <a v-on:click="toggleActionSheet('delete')" class="mui-tab-item">
    <span class="mui-icon mui-icon-trash"></span>
    </a>
    <a class="mui-tab-item" href="#">
    </a>
    <a class="mui-tab-item" href="#">
    </a>
    <a v-on:click="toggleActionSheet('forward')" class="mui-tab-item">
    <span class="mui-icon mui-icon-undo"></span>
    </a>
    </nav>
    <div class="mui-content">
    <div class="mui-content-padded">
    <p>actionsheet一般从底部弹出,显示一系列可选择的操作按钮,供用户选择; actionSheet可从任意位置触发, 点击本页面左下角
    <span class="mui-icon mui-icon-trash"></span>会弹出删除信息确认框; 点击本页面右下角
    <span class="mui-icon mui-icon-undo"></span>会弹出转发确认框; 你也可点击如下按钮,打开照片选择框:
    </p>
    <p>
    <a v-on:click="toggleActionSheet('picture')" class="mui-btn mui-btn-primary mui-btn-block mui-btn-outlined" style="padding: 5px 20px;">打开actionsheet</a>
    </p>
    <p>本页面为H5模式的actionsheet演示示例,该模式具有如下优点:</p>
    <ul class="des">
    <li>可通过css自由定制展现样式</li>
    </ul>

        <p>另一方面,H5模式的actionsheet也具备如下缺点:</p>  
        <ul class="des">  
            <li>不支持覆盖顶部状态栏</li>  
            <li>不支持跨webview的遮罩</li>  
            <li>在有map等原生组件时,容易被遮挡</li>  
        </ul>  
        <p id="info"></p>  
        <action-sheet ref="picture" :items="pictureItems" v-on:onItemClick="handleItemClick"></action-sheet>  
        <action-sheet ref="forward" :items="forwardItems" v-on:onItemClick="handleItemClick"></action-sheet>  
        <action-sheet ref="delete" :items="deleteItems" v-on:onItemClick="handleItemClick"></action-sheet>  
    </div>  

    </div>
    </div>
    </script>

    渲染示例代码

    //渲染示例页面
    var app = new Vue({
    el: '#app',
    data: function() {
    return {
    pictureItems: ['拍照或录像', '选取现有的'],
    forwardItems: ['回复', '转发', '打印'],
    deleteItems: ['删除']
    }
    },
    methods: {
    toggleActionSheet: function(actionsheet) {
    this.$refs[actionsheet]['toggle']();
    },
    handleItemClick: function(item, index) {
    console.log(item, index);
    }
    },
    template: document.getElementById("demoTpl").innerText
    });

完整代码,见附件

收起阅读 »

详解COOKIE和SESSION关系和区别

Cookie session

在技术面试中,经常被问到“说说Cookie和Session的区别”,大家都知道,Session是存储在服务器端的,Cookie是存储在客户端的,然而如果让你更详细地说明,你能说出几点?今天个推君就和大家谈谈“Cookie和Session”的那些事儿。

Cookie是什么?

从它的词语本身含义来看:

Cookie:

 n. 饼干;小甜点  

N-COUNT A cookie is a piece of computer software which enables a website you have visited to recognize you if you visit it again. 再次访问某一网站时,能令网站识别访问人的计算机软件。  

Cookie是客户端保存用户信息的一种机制,用来记录用户的一些信息。如何识别特定的客户呢?cookie就可以做到。每次HTTP请求时,客户端都会发送相应的Cookie信息到服务端。它的过期时间可以任意设置,如果你不主动清除它,在很长一段时间里面都可以保留着,即便这之间你把电脑关机了。

既然它是存储在客户端的,换句话说通过某些手法我就可以篡改本地存储的信息来欺骗服务端的某些策略,那该怎么办呢?我们先按下不表,来看看另外一位朋友 —— Session。

Session是什么?

同样,我们先来看看释义:

Session:

普通释义:n. 会议;(法庭的)开庭;(议会等的)开会;学期;讲习会  

计算机释义:会话  

Session是在无状态的HTTP协议下,服务端记录用户状态时用于标识具体用户的机制。它是在服务端保存的用来跟踪用户的状态的数据结构,可以保存在文件、数据库或者集群中。在浏览器关闭后这次的Session就消失了,下次打开就不再拥有这个Session。其实并不是Session消失了,而是Session ID变了,服务器端可能还是存着你上次的Session ID及其Session 信息,只是他们是无主状态,也许一段时间后会被删除。

实际上Cookie与Session都是会话的一种方式。它们的典型使用场景比如“购物车”,当你点击下单按钮时,服务端并不清楚具体用户的具体操作,为了标识并跟踪该用户,了解购物车中有几样物品,服务端通过为该用户创建Cookie/Session来获取这些信息。

如果你的站点是多节点部署,使用Nginx做负载均衡,那么有可能会出现Session丢失的情况(比如,忽然就处于未登录状态)。这时可以使用IP负载均衡(IP绑定 ip_hash,每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决Session的问题),或者将Session信息存储在集群中。在大型的网站中,一般会有专门的Session服务器集群,用来保存用户会话,这时可以使用缓存服务比如Memcached或者Redis之类的来存放Session。

目前大多数的应用都是用 Cookie 实现Session跟踪的。第一次创建Session时,服务端会通过在HTTP协议中反馈到客户端,需要在 Cookie 中记录一个Session ID,以便今后每次请求时都可分辨你是谁。有人问,如果客户端的浏览器禁用了 Cookie 怎么办?建议使用URL重写技术进行会话跟踪,即每次HTTP交互,URL后面都被附加上诸如 sid=xxxxx 的参数,以便服务端依此识别用户。

换个姿势~

客户端和服务端之间的通信交流,可以这样简单理解:

比如当你在个推技术分享沙龙上觉得某位讲师讲得很好,在会后问了他几个问题,他对你这些问题进行了回答,这就是一个会话。但这个讲师太受欢迎,于是工作人员收集问题,并给每个提问者一个号码牌,讲师按照号码牌依次给出相应解答并告诉相应的人。这就是Session。一段时间后,当你再次遇见这位讲师,他发现你身上有上次回复你的答案,知晓你是那个好学的程序猿。于是你欣喜若狂,哇塞,讲师居然认出我了,这就是Cookie,你的小甜点。客户端好比听课的技术爱好者,服务端就是这位讲师。

Cookie还可以在一些方便用户的场景下使用。比如你某次登陆过一个网站,下次登录的时候不想再次输入账号了,怎么办?这个信息可以被写到Cookie里面,当访问网站时,网站页面的脚本可以读取这个信息,自动填写用户名,方便用户使用,给用户一点甜头。

总结语:

1、Cookie 在客户端(浏览器),Session 在服务器端。

2、Cookie的安全性一般,他人可通过分析存放在本地的Cookie并进行Cookie欺骗。在安全性第一的前提下,选择Session更优。重要交互信息比如权限等就要放在Session中,一般的信息记录放Cookie就好了。

3、单个Cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个Cookie。

4、Session 可以放在 文件、数据库或内存中,比如在使用Node时将Session保存在redis中。由于一定时间内它是保存在服务器上的,当访问增多时,会较大地占用服务器的性能。考虑到减轻服务器性能方面,应当适时使用Cookie。

5、Session 的运行依赖Session ID,而 Session ID 是存在 Cookie 中的,也就是说,如果浏览器禁用了 Cookie,Session 也会失效(但是可以通过其它方式实现,比如在 url 中传递 Session ID)。

6、用户验证这种场合一般会用 Session。因此,维持一个会话的核心就是客户端的唯一标识,即Session ID。

题外话,那么话说Session Cookie能被篡改么?

理论上可以,只要改变了连接时的Session ID 就可以了~

继续阅读 »

在技术面试中,经常被问到“说说Cookie和Session的区别”,大家都知道,Session是存储在服务器端的,Cookie是存储在客户端的,然而如果让你更详细地说明,你能说出几点?今天个推君就和大家谈谈“Cookie和Session”的那些事儿。

Cookie是什么?

从它的词语本身含义来看:

Cookie:

 n. 饼干;小甜点  

N-COUNT A cookie is a piece of computer software which enables a website you have visited to recognize you if you visit it again. 再次访问某一网站时,能令网站识别访问人的计算机软件。  

Cookie是客户端保存用户信息的一种机制,用来记录用户的一些信息。如何识别特定的客户呢?cookie就可以做到。每次HTTP请求时,客户端都会发送相应的Cookie信息到服务端。它的过期时间可以任意设置,如果你不主动清除它,在很长一段时间里面都可以保留着,即便这之间你把电脑关机了。

既然它是存储在客户端的,换句话说通过某些手法我就可以篡改本地存储的信息来欺骗服务端的某些策略,那该怎么办呢?我们先按下不表,来看看另外一位朋友 —— Session。

Session是什么?

同样,我们先来看看释义:

Session:

普通释义:n. 会议;(法庭的)开庭;(议会等的)开会;学期;讲习会  

计算机释义:会话  

Session是在无状态的HTTP协议下,服务端记录用户状态时用于标识具体用户的机制。它是在服务端保存的用来跟踪用户的状态的数据结构,可以保存在文件、数据库或者集群中。在浏览器关闭后这次的Session就消失了,下次打开就不再拥有这个Session。其实并不是Session消失了,而是Session ID变了,服务器端可能还是存着你上次的Session ID及其Session 信息,只是他们是无主状态,也许一段时间后会被删除。

实际上Cookie与Session都是会话的一种方式。它们的典型使用场景比如“购物车”,当你点击下单按钮时,服务端并不清楚具体用户的具体操作,为了标识并跟踪该用户,了解购物车中有几样物品,服务端通过为该用户创建Cookie/Session来获取这些信息。

如果你的站点是多节点部署,使用Nginx做负载均衡,那么有可能会出现Session丢失的情况(比如,忽然就处于未登录状态)。这时可以使用IP负载均衡(IP绑定 ip_hash,每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决Session的问题),或者将Session信息存储在集群中。在大型的网站中,一般会有专门的Session服务器集群,用来保存用户会话,这时可以使用缓存服务比如Memcached或者Redis之类的来存放Session。

目前大多数的应用都是用 Cookie 实现Session跟踪的。第一次创建Session时,服务端会通过在HTTP协议中反馈到客户端,需要在 Cookie 中记录一个Session ID,以便今后每次请求时都可分辨你是谁。有人问,如果客户端的浏览器禁用了 Cookie 怎么办?建议使用URL重写技术进行会话跟踪,即每次HTTP交互,URL后面都被附加上诸如 sid=xxxxx 的参数,以便服务端依此识别用户。

换个姿势~

客户端和服务端之间的通信交流,可以这样简单理解:

比如当你在个推技术分享沙龙上觉得某位讲师讲得很好,在会后问了他几个问题,他对你这些问题进行了回答,这就是一个会话。但这个讲师太受欢迎,于是工作人员收集问题,并给每个提问者一个号码牌,讲师按照号码牌依次给出相应解答并告诉相应的人。这就是Session。一段时间后,当你再次遇见这位讲师,他发现你身上有上次回复你的答案,知晓你是那个好学的程序猿。于是你欣喜若狂,哇塞,讲师居然认出我了,这就是Cookie,你的小甜点。客户端好比听课的技术爱好者,服务端就是这位讲师。

Cookie还可以在一些方便用户的场景下使用。比如你某次登陆过一个网站,下次登录的时候不想再次输入账号了,怎么办?这个信息可以被写到Cookie里面,当访问网站时,网站页面的脚本可以读取这个信息,自动填写用户名,方便用户使用,给用户一点甜头。

总结语:

1、Cookie 在客户端(浏览器),Session 在服务器端。

2、Cookie的安全性一般,他人可通过分析存放在本地的Cookie并进行Cookie欺骗。在安全性第一的前提下,选择Session更优。重要交互信息比如权限等就要放在Session中,一般的信息记录放Cookie就好了。

3、单个Cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个Cookie。

4、Session 可以放在 文件、数据库或内存中,比如在使用Node时将Session保存在redis中。由于一定时间内它是保存在服务器上的,当访问增多时,会较大地占用服务器的性能。考虑到减轻服务器性能方面,应当适时使用Cookie。

5、Session 的运行依赖Session ID,而 Session ID 是存在 Cookie 中的,也就是说,如果浏览器禁用了 Cookie,Session 也会失效(但是可以通过其它方式实现,比如在 url 中传递 Session ID)。

6、用户验证这种场合一般会用 Session。因此,维持一个会话的核心就是客户端的唯一标识,即Session ID。

题外话,那么话说Session Cookie能被篡改么?

理论上可以,只要改变了连接时的Session ID 就可以了~

收起阅读 »

MUI+VUE2.0:开发一个accordion组件

Vue mui
  1. 开发工具:HBuilder
  2. 开发目标:使用vue2.0+mui.min.css开发一个与mui官方功能一致的accordion组件
  3. 实现功能
    • accordion组件可接收一个index参数来控制显示哪个子项
    • accordion组件内同时仅能展开一个子项
    • accordion组件可同时使用其他非collapse的mui-table-view-cell
    • accordion-item组件可单独使用,可接收一个title(子项标题)和collapse(子项是否展开)

备注:简单起见,不使用vue的单文件组件开发模式

开发步骤

  1. 使用HBuilder,创建一个移动App(mui项目)
  2. 引入mui.min.css及vue.js
  3. 搭建一个页面框架,代码如下:

    
    <!DOCTYPE html>  
    <html>  
    
    <head>  
        <meta charset="utf-8">  
        <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />  
        <title></title>  
        <link rel="stylesheet" href="css/mui.min.css" />  
    </head>  
    
    <body>  
        <header class="mui-bar mui-bar-nav">  
            <a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a>  
            <h1 class="mui-title">折叠面板</h1>  
        </header>  
        <div class="mui-content">  
            <div class="mui-card">  
    
            </div>  
        </div>  
        <script type="text/javascript" src="js/vue.js"></script>  
    </body>  

</html>

4.开发accordion-item组件,代码如下  
组件模板代码

<script id="itemTpl" type="text/html">
<li v-bind:class="['mui-table-view-cell','mui-collapse',{'mui-active':isOpen}]">
<a v-bind:class="['mui-navigate-right',{'mui-active':isOpen}]" v-on:click="isOpen=!isOpen">{{title}}</a>
<div class="mui-collapse-content">
<slot></slot>
</div>
</li>
</script>

组件实现代码

/**

  • 注册accordion-item组件
    */
    Vue.component('accordion-item', {
    props: ['index', 'title', 'collapse'],//title,collapse对外开放,(index是与accordion同时使用时内部索引值)
    data: function() {
    return {
    isOpen: !this.collapse//将props的collapse转化为data中的isOpen
    }
    },
    watch: {//vue2.0移除了props的双向绑定,故使用watch来实现accordion-item的状态同步
    collapse: function() {
    this.isOpen = !this.collapse; //父组件修改了collapse
    },
    isOpen: function() {
    this.$emit('onCollapseChange', this.index, this.isOpen); //子组件修改了collapse通知父组件
    }
    },
    template: document.getElementById("itemTpl").innerText,
    });
    5.开发accordion组件,代码如下

    /**

  • 注册accordion组件
    */
    Vue.component('accordion', {
    props: ['index'], //可指定一个索引值来控制哪个子项处于显示状态
    data: function() {
    return {
    showIndex: parseInt(this.index) || 0
    }
    },
    methods: {
    onCollapseChange: function(itemIndex, isOpen) {
    isOpen && (this.showIndex = itemIndex); //接收到子组件发生变化,修改当前showIndex
    }
    },
    render: function(createElement) { //自定义渲染,主要为了实现accordion-item的双向绑定
    var vnodes = this.$slots.default;
    if (vnodes && vnodes.length) {
    var itemIndex = 0;
    for (var i = 0; i < vnodes.length; i++) {
    var vnode = vnodes[i];
    if (!vnode) {
    continue;
    }
    var componentOptions = vnode.componentOptions;
    if (!componentOptions) {
    continue;
    }
    if (componentOptions.tag !== 'accordion-item') { //识别是accordion-item组件
    continue;
    }
    componentOptions.propsData.index = itemIndex; //当前子组件索引值
    componentOptions.propsData.collapse = itemIndex !== this.showIndex; //当前子组件收缩状态
    if (!componentOptions.listeners) {
    componentOptions.listeners = {};
    }
    componentOptions.listeners.onCollapseChange = this.onCollapseChange; //监听子组件收缩状态变化,实现双向绑定
    itemIndex++;
    }
    }
    return createElement('ul', {
    'class': {
    'mui-table-view': true,
    }
    }, this.$slots.default);
    }
    });

    6.使用accordion渲染示例(仿官网accordion示例)  
    组件使用示例

    <script id="tableTpl" type="text/html">
    <accordion>
    <accordion-item title="表单">
    <form class="mui-input-group">
    <div class="mui-input-row">
    <label>Input</label>
    <input type="text" placeholder="普通输入框">
    </div>
    <div class="mui-input-row">
    <label>Input</label>
    <input type="text" class="mui-input-clear" placeholder="带清除按钮的输入框" data-input-clear="3"><span class="mui-icon mui-icon-clear mui-hidden"></span>
    </div>

        <div class="mui-input-row mui-plus-hidden">  
            <label>Input</label>  
            <input type="text" class="mui-input-speech mui-input-clear" placeholder="语音输入" data-input-clear="4" data-input-speech="4"><span class="mui-icon mui-icon-clear mui-hidden"></span><span class="mui-icon mui-icon-speech"></span>  
        </div>  
        <div class="mui-button-row">  
            <button class="mui-btn mui-btn-primary" type="button" onclick="return false;">确认</button>&nbsp;&nbsp;  
            <button class="mui-btn mui-btn-primary" type="button" onclick="return false;">取消</button>  
        </div>  
    </form>  

    </accordion-item>
    <accordion-item title="文字排版">
    <h1>h1. Heading</h1>
    <h2>h2. Heading</h2>
    <h3>h3. Heading</h3>
    <h4>h4. Heading</h4>
    <h5>h5. Heading</h5>
    <h6>h6. Heading</h6>
    <p>
    p. 目前最接近原生App效果的框架。
    </p>
    </accordion-item>
    </accordion>
    </script>

    渲染示例代码

    //渲染页面
    var app = new Vue({
    el: '.mui-card',
    template: document.getElementById("tableTpl").innerText
    });

完整代码,见附件

继续阅读 »
  1. 开发工具:HBuilder
  2. 开发目标:使用vue2.0+mui.min.css开发一个与mui官方功能一致的accordion组件
  3. 实现功能
    • accordion组件可接收一个index参数来控制显示哪个子项
    • accordion组件内同时仅能展开一个子项
    • accordion组件可同时使用其他非collapse的mui-table-view-cell
    • accordion-item组件可单独使用,可接收一个title(子项标题)和collapse(子项是否展开)

备注:简单起见,不使用vue的单文件组件开发模式

开发步骤

  1. 使用HBuilder,创建一个移动App(mui项目)
  2. 引入mui.min.css及vue.js
  3. 搭建一个页面框架,代码如下:

    
    <!DOCTYPE html>  
    <html>  
    
    <head>  
        <meta charset="utf-8">  
        <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />  
        <title></title>  
        <link rel="stylesheet" href="css/mui.min.css" />  
    </head>  
    
    <body>  
        <header class="mui-bar mui-bar-nav">  
            <a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a>  
            <h1 class="mui-title">折叠面板</h1>  
        </header>  
        <div class="mui-content">  
            <div class="mui-card">  
    
            </div>  
        </div>  
        <script type="text/javascript" src="js/vue.js"></script>  
    </body>  

</html>

4.开发accordion-item组件,代码如下  
组件模板代码

<script id="itemTpl" type="text/html">
<li v-bind:class="['mui-table-view-cell','mui-collapse',{'mui-active':isOpen}]">
<a v-bind:class="['mui-navigate-right',{'mui-active':isOpen}]" v-on:click="isOpen=!isOpen">{{title}}</a>
<div class="mui-collapse-content">
<slot></slot>
</div>
</li>
</script>

组件实现代码

/**

  • 注册accordion-item组件
    */
    Vue.component('accordion-item', {
    props: ['index', 'title', 'collapse'],//title,collapse对外开放,(index是与accordion同时使用时内部索引值)
    data: function() {
    return {
    isOpen: !this.collapse//将props的collapse转化为data中的isOpen
    }
    },
    watch: {//vue2.0移除了props的双向绑定,故使用watch来实现accordion-item的状态同步
    collapse: function() {
    this.isOpen = !this.collapse; //父组件修改了collapse
    },
    isOpen: function() {
    this.$emit('onCollapseChange', this.index, this.isOpen); //子组件修改了collapse通知父组件
    }
    },
    template: document.getElementById("itemTpl").innerText,
    });
    5.开发accordion组件,代码如下

    /**

  • 注册accordion组件
    */
    Vue.component('accordion', {
    props: ['index'], //可指定一个索引值来控制哪个子项处于显示状态
    data: function() {
    return {
    showIndex: parseInt(this.index) || 0
    }
    },
    methods: {
    onCollapseChange: function(itemIndex, isOpen) {
    isOpen && (this.showIndex = itemIndex); //接收到子组件发生变化,修改当前showIndex
    }
    },
    render: function(createElement) { //自定义渲染,主要为了实现accordion-item的双向绑定
    var vnodes = this.$slots.default;
    if (vnodes && vnodes.length) {
    var itemIndex = 0;
    for (var i = 0; i < vnodes.length; i++) {
    var vnode = vnodes[i];
    if (!vnode) {
    continue;
    }
    var componentOptions = vnode.componentOptions;
    if (!componentOptions) {
    continue;
    }
    if (componentOptions.tag !== 'accordion-item') { //识别是accordion-item组件
    continue;
    }
    componentOptions.propsData.index = itemIndex; //当前子组件索引值
    componentOptions.propsData.collapse = itemIndex !== this.showIndex; //当前子组件收缩状态
    if (!componentOptions.listeners) {
    componentOptions.listeners = {};
    }
    componentOptions.listeners.onCollapseChange = this.onCollapseChange; //监听子组件收缩状态变化,实现双向绑定
    itemIndex++;
    }
    }
    return createElement('ul', {
    'class': {
    'mui-table-view': true,
    }
    }, this.$slots.default);
    }
    });

    6.使用accordion渲染示例(仿官网accordion示例)  
    组件使用示例

    <script id="tableTpl" type="text/html">
    <accordion>
    <accordion-item title="表单">
    <form class="mui-input-group">
    <div class="mui-input-row">
    <label>Input</label>
    <input type="text" placeholder="普通输入框">
    </div>
    <div class="mui-input-row">
    <label>Input</label>
    <input type="text" class="mui-input-clear" placeholder="带清除按钮的输入框" data-input-clear="3"><span class="mui-icon mui-icon-clear mui-hidden"></span>
    </div>

        <div class="mui-input-row mui-plus-hidden">  
            <label>Input</label>  
            <input type="text" class="mui-input-speech mui-input-clear" placeholder="语音输入" data-input-clear="4" data-input-speech="4"><span class="mui-icon mui-icon-clear mui-hidden"></span><span class="mui-icon mui-icon-speech"></span>  
        </div>  
        <div class="mui-button-row">  
            <button class="mui-btn mui-btn-primary" type="button" onclick="return false;">确认</button>&nbsp;&nbsp;  
            <button class="mui-btn mui-btn-primary" type="button" onclick="return false;">取消</button>  
        </div>  
    </form>  

    </accordion-item>
    <accordion-item title="文字排版">
    <h1>h1. Heading</h1>
    <h2>h2. Heading</h2>
    <h3>h3. Heading</h3>
    <h4>h4. Heading</h4>
    <h5>h5. Heading</h5>
    <h6>h6. Heading</h6>
    <p>
    p. 目前最接近原生App效果的框架。
    </p>
    </accordion-item>
    </accordion>
    </script>

    渲染示例代码

    //渲染页面
    var app = new Vue({
    el: '.mui-card',
    template: document.getElementById("tableTpl").innerText
    });

完整代码,见附件

收起阅读 »

Dcloud中mui 微信支付和支付宝支付接口完美实现付款代码(PHPdemo)

应用内支付 支付 支付宝 微信支付

演示下载你可以参考这里:http://www.erdangjiade.com/php/2750.html
演示下载你可以参考这里:http://www.erdangjiade.com/php/2475.html
完整代码及教程:
最近项目里用到Dcloud、官网上给的Demo是各种坑啊,于是自己整理了mui 微信支付和支付宝支付接口完美实现付款代码(PHP支付宝demo)希望用到的同学沙走些弯路。

1.先上图片,由于mui自己集成了支付宝,所以不需要配置sdk和获取appid,微信配置有些小细节,不注意就会出错,在这里微信支付只能调用一次,详情看下去在特别注意里
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>支付</title>
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<link rel="stylesheet" href="css/mui.min.css" />
<script type="text/javascript" src="js/mui.min.js"></script>
<style type="text/css">
.top {
margin-top: 40px;
}
.weixin {
width: 200px;
height: 50px;
margin-left: 50px;
background: url(../images/icon-weixin.png);
}
.zhifubao {
width: 200px;
height: 50px;

           margin-left: 50px;    
            background: url(../images/alipay.jpg);      
        }    

        #jine{   
            -webkit-user-select:text;   
            text-align:right;   
            padding:0 1em;   
            border: 0px;   
            border-bottom:1px solid #ECB100;   
            border-radius: 0;   
            font-size:16px;   
            width:30%;   
            outline:none;   
            text-align:center;   
        }   

    </style>   
</head>   
<body>   
     <hrader class="mui-bar mui-bar-nav">   
         <a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a>   
         <h1 class="mui-title">第三方支付</h1>   
     </hrader>   

     <div class="mui-content">   

            捐赠金额:<input id="jine" type="number" value="1" /> 元   

            <div class="top" id="testLogin" >   
                <input type="button" class="weixin" id="weixin1" value="微信支付" />   
                <input type="button" class="zhifubao" id="zhifubao" value="支付宝支付" />   

            </div>   

     </div>   
       <script>   
           var wxChannel = null; // 微信支付    
        var aliChannel = null; // 支付宝支付    
        var channel = null;   //支付通道   
        mui.init({    
            swipeBack:true //启用右滑关闭功能    
        });    

         mui.plusReady(function() {      
        // 获取支付通道    
            plus.payment.getChannels(function(channels){    
            for (var i in channels) {   
                    if (channels[i].id == "wxpay") {   
                         wxChannel=channels[i];    
                    }else{   
                        aliChannel=channels[i];    
                    }   
                }       
            },function(e){    
             alert("获取支付通道失败:"+e.message);    
            });    
    })    

    document.getElementById('weixin1').addEventListener('tap',function() {    
        console.log("微信");    
        pay('wxpay');    
    })    
    document.getElementById('zhifubao').addEventListener('tap',function() {    
        console.log("zhifubao");    
        pay('alipay');     
    })    

    var ALIPAYSERVER='http://demo.dcloud.net.cn/helloh5/payment/alipay.php?total=';    
    var WXPAYSERVER='http://demo.dcloud.net.cn/helloh5/payment/wxpay.php?total=';    

    // 2. 发起支付请求    
    function pay(id){    
            // 从服务器请求支付订单    
            var PAYSERVER='';    
            if(id=='alipay'){    
            PAYSERVER=ALIPAYSERVER;    
            channel = aliChannel;    
        }else if(id=='wxpay'){    
                PAYSERVER=WXPAYSERVER;    
                channel = wxChannel;    
            }else{    
                plus.nativeUI.alert("不支持此支付通道!",null,"捐赠");    
                return;    
         }    
            var xhr=new XMLHttpRequest();    
             var amount = document.getElementById('jine').value;   

            xhr.onreadystatechange=function(){    
                switch(xhr.readyState){    
                    case 4:    
                    if(xhr.status==200){    
                        plus.payment.request(channel,xhr.responseText,function(result){    
                            plus.nativeUI.alert("支付成功!",function(){    
                            back();    
                        });    
                        },function(error){    
                            plus.nativeUI.alert("支付失败:" + error.code);    
                        });    
                    }else{    
                        alert("获取订单信息失败!");    
                    }    
                    break;    
                default:    
                break;    
            }    
     }    
        xhr.open('GET',PAYSERVER+amount);    
        xhr.send();    

}    

       </script>     
 <script type="text/javascript" src="js/immersed.js" ></script>   
</body>   

</html>
3.重点看这里关于配置和质疑问题
如下图

点击manifest.json文件的“代码视图”,在permissions节点下添加Payment节点:
如下图

在plus -> distribute -> plugins 节点下添加payment节点:
如下图

4.特别注意
1.由于mui集成了支付宝插件,所以支付宝支付不需要配置就可以,

2,。注意微信weixin节点下配置微信支付相关信息

appid值为在微信开放平台申请应用的AppID值。(微信开放平台不是微信公众号平台申请的appid)

因为我在微信公众号申请的也不知到什么原因只成功调取一次,其余失败。

5.由于项目需要我会等后台完善后,在总结一份

继续阅读 »

演示下载你可以参考这里:http://www.erdangjiade.com/php/2750.html
演示下载你可以参考这里:http://www.erdangjiade.com/php/2475.html
完整代码及教程:
最近项目里用到Dcloud、官网上给的Demo是各种坑啊,于是自己整理了mui 微信支付和支付宝支付接口完美实现付款代码(PHP支付宝demo)希望用到的同学沙走些弯路。

1.先上图片,由于mui自己集成了支付宝,所以不需要配置sdk和获取appid,微信配置有些小细节,不注意就会出错,在这里微信支付只能调用一次,详情看下去在特别注意里
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>支付</title>
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<link rel="stylesheet" href="css/mui.min.css" />
<script type="text/javascript" src="js/mui.min.js"></script>
<style type="text/css">
.top {
margin-top: 40px;
}
.weixin {
width: 200px;
height: 50px;
margin-left: 50px;
background: url(../images/icon-weixin.png);
}
.zhifubao {
width: 200px;
height: 50px;

           margin-left: 50px;    
            background: url(../images/alipay.jpg);      
        }    

        #jine{   
            -webkit-user-select:text;   
            text-align:right;   
            padding:0 1em;   
            border: 0px;   
            border-bottom:1px solid #ECB100;   
            border-radius: 0;   
            font-size:16px;   
            width:30%;   
            outline:none;   
            text-align:center;   
        }   

    </style>   
</head>   
<body>   
     <hrader class="mui-bar mui-bar-nav">   
         <a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a>   
         <h1 class="mui-title">第三方支付</h1>   
     </hrader>   

     <div class="mui-content">   

            捐赠金额:<input id="jine" type="number" value="1" /> 元   

            <div class="top" id="testLogin" >   
                <input type="button" class="weixin" id="weixin1" value="微信支付" />   
                <input type="button" class="zhifubao" id="zhifubao" value="支付宝支付" />   

            </div>   

     </div>   
       <script>   
           var wxChannel = null; // 微信支付    
        var aliChannel = null; // 支付宝支付    
        var channel = null;   //支付通道   
        mui.init({    
            swipeBack:true //启用右滑关闭功能    
        });    

         mui.plusReady(function() {      
        // 获取支付通道    
            plus.payment.getChannels(function(channels){    
            for (var i in channels) {   
                    if (channels[i].id == "wxpay") {   
                         wxChannel=channels[i];    
                    }else{   
                        aliChannel=channels[i];    
                    }   
                }       
            },function(e){    
             alert("获取支付通道失败:"+e.message);    
            });    
    })    

    document.getElementById('weixin1').addEventListener('tap',function() {    
        console.log("微信");    
        pay('wxpay');    
    })    
    document.getElementById('zhifubao').addEventListener('tap',function() {    
        console.log("zhifubao");    
        pay('alipay');     
    })    

    var ALIPAYSERVER='http://demo.dcloud.net.cn/helloh5/payment/alipay.php?total=';    
    var WXPAYSERVER='http://demo.dcloud.net.cn/helloh5/payment/wxpay.php?total=';    

    // 2. 发起支付请求    
    function pay(id){    
            // 从服务器请求支付订单    
            var PAYSERVER='';    
            if(id=='alipay'){    
            PAYSERVER=ALIPAYSERVER;    
            channel = aliChannel;    
        }else if(id=='wxpay'){    
                PAYSERVER=WXPAYSERVER;    
                channel = wxChannel;    
            }else{    
                plus.nativeUI.alert("不支持此支付通道!",null,"捐赠");    
                return;    
         }    
            var xhr=new XMLHttpRequest();    
             var amount = document.getElementById('jine').value;   

            xhr.onreadystatechange=function(){    
                switch(xhr.readyState){    
                    case 4:    
                    if(xhr.status==200){    
                        plus.payment.request(channel,xhr.responseText,function(result){    
                            plus.nativeUI.alert("支付成功!",function(){    
                            back();    
                        });    
                        },function(error){    
                            plus.nativeUI.alert("支付失败:" + error.code);    
                        });    
                    }else{    
                        alert("获取订单信息失败!");    
                    }    
                    break;    
                default:    
                break;    
            }    
     }    
        xhr.open('GET',PAYSERVER+amount);    
        xhr.send();    

}    

       </script>     
 <script type="text/javascript" src="js/immersed.js" ></script>   
</body>   

</html>
3.重点看这里关于配置和质疑问题
如下图

点击manifest.json文件的“代码视图”,在permissions节点下添加Payment节点:
如下图

在plus -> distribute -> plugins 节点下添加payment节点:
如下图

4.特别注意
1.由于mui集成了支付宝插件,所以支付宝支付不需要配置就可以,

2,。注意微信weixin节点下配置微信支付相关信息

appid值为在微信开放平台申请应用的AppID值。(微信开放平台不是微信公众号平台申请的appid)

因为我在微信公众号申请的也不知到什么原因只成功调取一次,其余失败。

5.由于项目需要我会等后台完善后,在总结一份

收起阅读 »

android系统设置角标建议增加主流系统支持,方法参考开源工程ShortcutBadger

plus.runtime.setBadgeNumber( number );
设置程序快捷方式上显示的提示数字
平台支持:
Android - 2.2 (支持): 目前仅支持小米(MIUI v5),其它设备调用后无任何效果。
iOS - 4.3 (支持): 应用需开启“Push Notifications”服务才生效

最近再用贵公司的dcloud,确实很强大。 就是设置角标只支持小米系统有点郁闷。建议增加主流andoid系统的支持。

该android开源项目有各个 android系统角标设置的方法
https://github.com/leolin310148/ShortcutBadger
.

继续阅读 »

plus.runtime.setBadgeNumber( number );
设置程序快捷方式上显示的提示数字
平台支持:
Android - 2.2 (支持): 目前仅支持小米(MIUI v5),其它设备调用后无任何效果。
iOS - 4.3 (支持): 应用需开启“Push Notifications”服务才生效

最近再用贵公司的dcloud,确实很强大。 就是设置角标只支持小米系统有点郁闷。建议增加主流andoid系统的支持。

该android开源项目有各个 android系统角标设置的方法
https://github.com/leolin310148/ShortcutBadger
.

收起阅读 »

通过修改sass,自定义mui控件样式

控件样式 mui

mui为大家提供了好用好看的ui控件,但实际开发中,可能需要修改mui的控件样式。此时,我们需要自定义mui相关类的样式,之前也有文章做出分享,文章地址。这篇文章是通过写新的css样式覆盖mui原来样式,由于mui是开源的,我们也可以通过修改sass重编译来实现自己的需求。

第一步,将mui源码从github上clone到本地,mui在github链接,clone下来初始的结构如下图:


第二步,在mui放置的目录执行npm install 下载相关依赖,然后你的目录就会多了一个node_modules目录,如下图:

第三步,安装相关环境,安装grunt,grunt官网,然后在你的mui的本地目录执行grunt,若出现下图所示的现象,你需要去下载安装Ruby,sass

下载Ruby,然后根据提示安装,安装完ruby之后,在开始菜单中,找到刚才我们安装的ruby,打开Start Command Prompt with Ruby

然后在命令行中输入命令,安装sass

gem install sass

接着,进入mui的本地目录执行grunt,若出现下图则说明我们重编译成功了,生成的文件在dist目录里(下载下来时里面是有文件的,我们可以先删除它们做测试)
注意:如果出现GBK报错,可以在 C:\Ruby24-x64\lib\ruby\gems\2.4.0\gems\sass-3.5.5\lib\sass 的engine.rb文件里的require的结束位置添加Encoding.default_external = Encoding.find('utf-8')就不会报错了。


第四步,修改sass,实现自定义控件样式;这次我们修改body的默认颜色,原始body颜色为#efeff4,如下图所示

修改base.scss的background-color: #efeff4;为background-color: red;然后重新grunt,得到下图

经过这些步骤,我们就完成了通过修改sass自定义mui控件样式,若还有问题欢迎大家在本帖或ask中提出。

继续阅读 »

mui为大家提供了好用好看的ui控件,但实际开发中,可能需要修改mui的控件样式。此时,我们需要自定义mui相关类的样式,之前也有文章做出分享,文章地址。这篇文章是通过写新的css样式覆盖mui原来样式,由于mui是开源的,我们也可以通过修改sass重编译来实现自己的需求。

第一步,将mui源码从github上clone到本地,mui在github链接,clone下来初始的结构如下图:


第二步,在mui放置的目录执行npm install 下载相关依赖,然后你的目录就会多了一个node_modules目录,如下图:

第三步,安装相关环境,安装grunt,grunt官网,然后在你的mui的本地目录执行grunt,若出现下图所示的现象,你需要去下载安装Ruby,sass

下载Ruby,然后根据提示安装,安装完ruby之后,在开始菜单中,找到刚才我们安装的ruby,打开Start Command Prompt with Ruby

然后在命令行中输入命令,安装sass

gem install sass

接着,进入mui的本地目录执行grunt,若出现下图则说明我们重编译成功了,生成的文件在dist目录里(下载下来时里面是有文件的,我们可以先删除它们做测试)
注意:如果出现GBK报错,可以在 C:\Ruby24-x64\lib\ruby\gems\2.4.0\gems\sass-3.5.5\lib\sass 的engine.rb文件里的require的结束位置添加Encoding.default_external = Encoding.find('utf-8')就不会报错了。


第四步,修改sass,实现自定义控件样式;这次我们修改body的默认颜色,原始body颜色为#efeff4,如下图所示

修改base.scss的background-color: #efeff4;为background-color: red;然后重新grunt,得到下图

经过这些步骤,我们就完成了通过修改sass自定义mui控件样式,若还有问题欢迎大家在本帖或ask中提出。

收起阅读 »

tab选项卡示例教程-基于subnview模式的原生tab(含底部凸起大图标)

原生view控件 nativeObj模式底部选项卡 底部选项卡 HTML5+ nativeObj

方案比较说明

关于tab bar选项卡,目前已经有多种实现方案,最常见的有div方案的和双webview方案的。

div方案
div方案的选项卡是普通的实现方案,所有都放在一个页面中执行,页面压力可想而知,在手机上会出现卡顿现象。SPA仅适用于非常简单的dom和非常少的页面;

双webview方案
双webview方案的实现思路就是一个包含tab bar 的父webview + n个对应tab的子webview。在5+App的manifest可以配置双Webview首页,可以让tab父页和第一个子tab的子页同时启动。
但Webview的渲染速度不如nview快,Webview太多对内存吃的太多,崩溃和白屏的概率就会增加。此模式也不是最优。
而下面我们介绍的这种方案,让显示速度,占用内存,性能消耗都达到了一种更优的状态!

基于subnview的原生tab选项卡方案
HBuilder8.8起提供了subnview,subnview顾名思义就是子原生view,也就是把一个全屏Webview的一部分区域切出来,渲染工作交给原生引擎渲染,不走Webview渲染。
此方案设计思路简单来说就是把首页的底部区域变成subnview,交给5+的原生引擎渲染,也就是说是要nview来绘制底部tab。
manifest里配置的首页作为第一个tab选项卡窗口,同时为这个Webview配置subnview,在这个subnview里编写nview的描述,就可以创建一个原生的选项卡,这个选项卡的绘制由5+引擎原生完成,在首页Webview渲染之前就由原生层渲染好了。
其余tab项子窗口在首页plus ready后创建,并同时监听tab点击。
原生view控件制作的tab,还可以轻松实现tab中部凸起大图标的效果,这种效果在双Webview模式下不好实现。

可见基于subnview的原生tab选项卡方案渲染更快、内存更少,并且自定义效果也更丰富。
当然也有一些缺点就是有点复杂,对于学习纯HTML5的程序员而言,首先需要学习subnview这些HTML5+独有的技术。
但nview真的是非常值得学习的技术,掌握nview后,你做出来的App体验绝对和原生一样好。

最新方案优化说明

经过一段时间的实践,发现原生tab选项卡方案存在部分问题,已修复如下问题:

  1. 添加输入框并点击之后webview窗口发生变化,出现白条
  2. 添加titleNView会造成某些tab点击失效
  3. 添加titleNView安卓上会出现遮盖子webview的现象
  4. 设置应用为沉浸式,安卓上中间图标会有下移现象

另外上文提到过webview太多对内存会吃太多,nview创建太多同样存在内存吃紧的问题,原来的方案中为了层次分明和便于监听点击事件总共创建了有6个nview,这与本示例的初衷相悖,所以现在缩减为两个nview。分别是:1.中间凸起图标nview 2.底部选项卡nview。

下面来详细说明此方案(最新方案)的实现方法。

示例效果说明

本示例实现的效果是 使用nativeObj绘制原生view控件类型的底部选项卡 + 底部中央悬浮球图标 + webview模式的选项卡窗口,适用于5+app 和流应用环境,通常应用于app 首页;

效果图展示

实现步骤

1.配置底部选项卡

应用首页配置底部选项卡可以直接在manifest.json 中找到 plus 节点,在里面创建如下示例:
以下给出一个tab的示例,完整示例请下载附件中demo查看。

"launchwebview": {   //首页webview窗口  
    "bottom": "0px",  
    "background": "#fff",  
    "subNViews": [  //subNViews节点下配置你的tab (此处只给出一个tab配置示例)  
            {  
                "id": "tabBar",  
                "styles": {  
                    "bottom": "0px",  
                    "left": "0",  
                    "height": "50px",  
                    "width": "100%",  
                    "backgroundColor": "#fff"  
                },  
                "tags": [  
                    {  
                        "tag": "font",  
                        "id": "indexIcon",  
                        "text": "\ue500",  
                        "position": {  
                            "top": "4px",  
                            "left": "0",  
                            "width": "25%",  
                            "height": "24px"  
                        },  
                        "textStyles": {  
                            "fontSrc": "_www/fonts/mui.ttf",  
                            "align": "center",  
                            "size": "24px"  
                        }  
                    }, {  
                        "tag": "font",  
                        "id": "indexText",  
                        "text": "首页",  
                        "position": {  
                            "top": "23px",  
                            "left": "0",  
                            "width": "25%",  
                            "height": "24px"  
                        },  
                        "textStyles": {  
                            "align": "center",  
                            "size": "10px"  
                        }  
                    }  
            ]  
        }  
    ]  
}

注意:
1.此处配置tab建议直接配置n个tab 对应n个tag而不是nview控件,尽量少的为webview添加nview控件,nview控件在提供原生渲染能力的同时也会消耗内存。
2.配置需要自己计算图标和文字的显示位置,以上只给出一个示例,具体详情方法可参考附件文件或者HTML5+ API 窗口原生子View控件样式WebviewSubNViewStyles

2.定义tab窗口

将入口页也就是首页作为第一个tab窗口页面,这样做就只需创建3个子webview,相比把tab窗口都作为子webview,占用内存更少,消耗性能也优化更多,而且创建子webview需要等待plusReady事件触发执行,在一些低端机上,子窗口出现会有明显延迟,所以很明显,相比之下直接将首页作为第一个tab窗口页面的方法更优!

首页显示了,接下来是在首页plusReady事件中创建其余tab选项卡对应的窗口,通过webviewObj.append()方法,添加为当前 webview 的 子 webview,方式类似 Hello MUI 实例中 tab bar选项卡 -> 底部选项卡-webview模式

var self = plus.webview.currentWebview();  
// 初始化首页tab窗口为首次显示  
var temp = {};  
temp[self.id] = "true";  
mui.extend(aniShow, temp);  

// 初始化首个tab 按钮,(此方法为自定义方法,详情见上传的附件中)  
toggleNview(self.getStyle().subNViews[0], 0);  

// 初始化其余tab窗口  
for(var i = 0, len = subpages.length; i < len; i++) {  
if(!plus.webview.getWebviewById(subpages[i])) {  
       // 创建子webview  
    var sub = plus.webview.create(subpages[i], subpages[i], subpage_style);  
    // 隐藏子窗口  
        sub.hide();     

    // 添加为当前webview 的子 webview ------此步骤很重要  
    self.append(sub);  
    }  
}

3.底部选项卡添加事件监听

目前底部已经生成四个选项卡,鉴于最新方案已更改为以subnview中创建tag的方式生成选项卡的原因,实现方法为在view控件上添加点击事件监听,然后通过判断点击位置实现tab切换。
部分代码如下:

/**  
 * 根据判断view控件点击位置判断切换的tab  
 */  
nview.addEventListener('click', function(e) {  
    var clientX = e.clientX;  

    if(clientX > 0 && clientX <= parseInt(pageW * 0.25)) {  
        currIndex = 0;  
    } else if(clientX > parseInt(pageW * 0.25) && clientX <= parseInt(pageW * 0.45)) {  
        currIndex = 1;  
    } else if(clientX > parseInt(pageW * 0.45) && clientX <= parseInt(pageW * 0.8)) {  
        currIndex = 2;  
    } else {  
        currIndex = 3;  
    }  
    // 匹配对应tab窗口      
    if(currIndex > 0) {  
        targetPage = plus.webview.getWebviewById(subpages[currIndex - 1]);  
    } else {  
        targetPage = plus.webview.currentWebview();  
    }  

    if(targetPage == activePage) {  
        return;  
    }  

    if(currIndex !== 3) {   
        //底部选项卡切换  
        util.toggleNview(currIndex);  
        // 子页面切换  
        util.changeSubpage(targetPage, activePage, aniShow);  
        //更新当前活跃的页面  
        activePage = targetPage;  
    } else {  
        //第四个tab 打开新窗口  
        plus.webview.open('html/new-webview.html', 'new', {}, 'slide-in-right', 200);  
    }  
});

说明:点击事件中包含tab选项卡以及tab窗口切换,详情见附件

到这里其实绘制底部选项卡和webview模式的子页面,并且给选项卡添加了监听事件,已经初步完成。
但是小伙伴们还想在这样的模式下给底部中央一个凸起的悬浮大图标,可以怎么做呢,其实原理是一样的,也可以在manifest.json中配置,但是考虑到水平中央的图标涉及屏幕分辨率动态计算目前放在首页plusReady事件中配置,也给开发者一种可以手动绘制view控件的方法,如果你想在非首页采用这种方法呢。

4.利用nativeObj绘制凸起图标参考 HTML 5+ API plus.nativeObj.View
实现步骤如下:
1.首页plusReady事件中创建实例对象,最新方案中将背景nview和图标nview整合到了一起。
示例如下:

// 设置水平居中位置  
var leftPos = Math.ceil((window.innerWidth - 60) / 2);  

/**  
 * 简单封装了绘制原生view控件方法 drawNative()  
 * 绘制内容支持font(文本,字体图标),图片img , 矩形区域rect  
 *  
 */  
function drawNative(id, styles, tags) {  
    var view = new plus.nativeObj.View(id, styles, tags);  
    return view  
}  

/**   
 * drawNativeIcon 绘制带边框的半圆,  
 * 实现原理:  
 *   id为bg的tag 创建带边框的圆  
 *   id为bg2的tag 创建白色矩形遮住圆下半部分,只显示凸起带边框部分  
 *   id为iconBg的红色背景图  
 *   id为icon的字体图标  
 *   注意创建先后顺序,创建越晚的层级越高  
 */  
var drawNativeIcon = util.drawNative('icon', {  
    bottom: '5px',  
    left: leftPos + 'px',  
    width: '60px',  
    height: '60px'  
}, [{  
    tag: 'rect',  
    id: 'bg',  
    position: {  
        top: '1px',  
        left: '0px',  
        width: '100%',  
        height: '100%'  
    },  
    rectStyles: {  
        color: '#fff',  
        radius: '50%',  
        borderColor: '#ccc',  
        borderWidth: '1px'  
    }  
}, {  
    tag: 'rect',  
    id: 'bg2',  
    position: {  
        bottom: '-0.5px',  
        left: '0px',  
        width: '100%',  
        height: '45px'  
    },  
    rectStyles: {  
        color: '#fff'  
    }  
}, {  
    tag: 'rect',  
    id: 'iconBg',  
    position: {  
        top: '5px',  
        left: '5px',  
        width: '50px',  
        height: '50px'  
    },  
    rectStyles: {  
        color: '#d74b28',  
        radius: '50%'  
    }  
}, {  
    tag: 'font',  
    id: 'icon',  
    text: '\ue600', //此为字体图标Unicode码'\e600'转换为'\ue600'  
    position: {  
        top: '0px',  
        left: '5px',  
        width: '50px',  
        height: '100%'  
    },  
    textStyles: {  
        fontSrc: '_www/fonts/iconfont.ttf',  
        align: 'center',  
        color: '#fff',  
        size: '30px'  
    }  
}]);  
// 添加到当前父webview中,------此步骤很重要  
self.append(drawNativeIcon);

2.view控件添加监听事件

//自定义监听图标点击事件  
drawNativeIcon.addEventListener('click', function(e) {  
    mui.alert('你点击了图标,你在此可以打开摄像头或者新窗口等自定义点击事件。','悬浮球点击事件');  
});

原生view控件使用说明

  1. 以上描述创建并绘制原生view控件有两种方法:
    a.如果是首页应用,可以在manifest.json文件中相应节点配置,渲染快,配置方法简单
    b.通过new plus.nativeObj.View(),创建View实例对象,然后将该实例对象append到当前窗口(plus.webview.currentWebview)
  2. 这两种方法都需要开发者在首页plusReady事件中去针对view控件添加监听事件,比如切换对应窗口这种。
  3. view控件支持绘制类型:图片、矩形区域、文本(包括字体图标)
  4. 字体图标比如该悬浮球字体,来自iconfont.cn 阿里巴巴字体库,字体一般为Unicode(16进制)码:'\e600',但是配置的时候,需要填写为"\ue600"才能被原生层正确解析
  5. 参考 HTML 5+ API plus.nativeObj.View

该示例补充说明

  1. 中央悬浮大图标都是通过字体加纯色背景实现
  2. 其背后带边线半球区域也是通过两个矩形区域的view控件叠加拼成的,具体实现方法可以在附件中查看
  3. 本示例字体文件分别采用mui字体文件和阿里巴巴字体库
  4. 第四个选项卡点击打开新窗口,为示例教程,开发者可自行定义
  5. 屏幕旋转后,需手动重绘nview,参考nview的api。虽复杂也可以实现。也可以在manifest里禁用掉旋转。

扩展链接

字体图标绘制原生view控件
如何更新通过nativeObj.View创建的view控件教程

下载附件中的文件,直接解压真机运行即可体验

继续阅读 »

方案比较说明

关于tab bar选项卡,目前已经有多种实现方案,最常见的有div方案的和双webview方案的。

div方案
div方案的选项卡是普通的实现方案,所有都放在一个页面中执行,页面压力可想而知,在手机上会出现卡顿现象。SPA仅适用于非常简单的dom和非常少的页面;

双webview方案
双webview方案的实现思路就是一个包含tab bar 的父webview + n个对应tab的子webview。在5+App的manifest可以配置双Webview首页,可以让tab父页和第一个子tab的子页同时启动。
但Webview的渲染速度不如nview快,Webview太多对内存吃的太多,崩溃和白屏的概率就会增加。此模式也不是最优。
而下面我们介绍的这种方案,让显示速度,占用内存,性能消耗都达到了一种更优的状态!

基于subnview的原生tab选项卡方案
HBuilder8.8起提供了subnview,subnview顾名思义就是子原生view,也就是把一个全屏Webview的一部分区域切出来,渲染工作交给原生引擎渲染,不走Webview渲染。
此方案设计思路简单来说就是把首页的底部区域变成subnview,交给5+的原生引擎渲染,也就是说是要nview来绘制底部tab。
manifest里配置的首页作为第一个tab选项卡窗口,同时为这个Webview配置subnview,在这个subnview里编写nview的描述,就可以创建一个原生的选项卡,这个选项卡的绘制由5+引擎原生完成,在首页Webview渲染之前就由原生层渲染好了。
其余tab项子窗口在首页plus ready后创建,并同时监听tab点击。
原生view控件制作的tab,还可以轻松实现tab中部凸起大图标的效果,这种效果在双Webview模式下不好实现。

可见基于subnview的原生tab选项卡方案渲染更快、内存更少,并且自定义效果也更丰富。
当然也有一些缺点就是有点复杂,对于学习纯HTML5的程序员而言,首先需要学习subnview这些HTML5+独有的技术。
但nview真的是非常值得学习的技术,掌握nview后,你做出来的App体验绝对和原生一样好。

最新方案优化说明

经过一段时间的实践,发现原生tab选项卡方案存在部分问题,已修复如下问题:

  1. 添加输入框并点击之后webview窗口发生变化,出现白条
  2. 添加titleNView会造成某些tab点击失效
  3. 添加titleNView安卓上会出现遮盖子webview的现象
  4. 设置应用为沉浸式,安卓上中间图标会有下移现象

另外上文提到过webview太多对内存会吃太多,nview创建太多同样存在内存吃紧的问题,原来的方案中为了层次分明和便于监听点击事件总共创建了有6个nview,这与本示例的初衷相悖,所以现在缩减为两个nview。分别是:1.中间凸起图标nview 2.底部选项卡nview。

下面来详细说明此方案(最新方案)的实现方法。

示例效果说明

本示例实现的效果是 使用nativeObj绘制原生view控件类型的底部选项卡 + 底部中央悬浮球图标 + webview模式的选项卡窗口,适用于5+app 和流应用环境,通常应用于app 首页;

效果图展示

实现步骤

1.配置底部选项卡

应用首页配置底部选项卡可以直接在manifest.json 中找到 plus 节点,在里面创建如下示例:
以下给出一个tab的示例,完整示例请下载附件中demo查看。

"launchwebview": {   //首页webview窗口  
    "bottom": "0px",  
    "background": "#fff",  
    "subNViews": [  //subNViews节点下配置你的tab (此处只给出一个tab配置示例)  
            {  
                "id": "tabBar",  
                "styles": {  
                    "bottom": "0px",  
                    "left": "0",  
                    "height": "50px",  
                    "width": "100%",  
                    "backgroundColor": "#fff"  
                },  
                "tags": [  
                    {  
                        "tag": "font",  
                        "id": "indexIcon",  
                        "text": "\ue500",  
                        "position": {  
                            "top": "4px",  
                            "left": "0",  
                            "width": "25%",  
                            "height": "24px"  
                        },  
                        "textStyles": {  
                            "fontSrc": "_www/fonts/mui.ttf",  
                            "align": "center",  
                            "size": "24px"  
                        }  
                    }, {  
                        "tag": "font",  
                        "id": "indexText",  
                        "text": "首页",  
                        "position": {  
                            "top": "23px",  
                            "left": "0",  
                            "width": "25%",  
                            "height": "24px"  
                        },  
                        "textStyles": {  
                            "align": "center",  
                            "size": "10px"  
                        }  
                    }  
            ]  
        }  
    ]  
}

注意:
1.此处配置tab建议直接配置n个tab 对应n个tag而不是nview控件,尽量少的为webview添加nview控件,nview控件在提供原生渲染能力的同时也会消耗内存。
2.配置需要自己计算图标和文字的显示位置,以上只给出一个示例,具体详情方法可参考附件文件或者HTML5+ API 窗口原生子View控件样式WebviewSubNViewStyles

2.定义tab窗口

将入口页也就是首页作为第一个tab窗口页面,这样做就只需创建3个子webview,相比把tab窗口都作为子webview,占用内存更少,消耗性能也优化更多,而且创建子webview需要等待plusReady事件触发执行,在一些低端机上,子窗口出现会有明显延迟,所以很明显,相比之下直接将首页作为第一个tab窗口页面的方法更优!

首页显示了,接下来是在首页plusReady事件中创建其余tab选项卡对应的窗口,通过webviewObj.append()方法,添加为当前 webview 的 子 webview,方式类似 Hello MUI 实例中 tab bar选项卡 -> 底部选项卡-webview模式

var self = plus.webview.currentWebview();  
// 初始化首页tab窗口为首次显示  
var temp = {};  
temp[self.id] = "true";  
mui.extend(aniShow, temp);  

// 初始化首个tab 按钮,(此方法为自定义方法,详情见上传的附件中)  
toggleNview(self.getStyle().subNViews[0], 0);  

// 初始化其余tab窗口  
for(var i = 0, len = subpages.length; i < len; i++) {  
if(!plus.webview.getWebviewById(subpages[i])) {  
       // 创建子webview  
    var sub = plus.webview.create(subpages[i], subpages[i], subpage_style);  
    // 隐藏子窗口  
        sub.hide();     

    // 添加为当前webview 的子 webview ------此步骤很重要  
    self.append(sub);  
    }  
}

3.底部选项卡添加事件监听

目前底部已经生成四个选项卡,鉴于最新方案已更改为以subnview中创建tag的方式生成选项卡的原因,实现方法为在view控件上添加点击事件监听,然后通过判断点击位置实现tab切换。
部分代码如下:

/**  
 * 根据判断view控件点击位置判断切换的tab  
 */  
nview.addEventListener('click', function(e) {  
    var clientX = e.clientX;  

    if(clientX > 0 && clientX <= parseInt(pageW * 0.25)) {  
        currIndex = 0;  
    } else if(clientX > parseInt(pageW * 0.25) && clientX <= parseInt(pageW * 0.45)) {  
        currIndex = 1;  
    } else if(clientX > parseInt(pageW * 0.45) && clientX <= parseInt(pageW * 0.8)) {  
        currIndex = 2;  
    } else {  
        currIndex = 3;  
    }  
    // 匹配对应tab窗口      
    if(currIndex > 0) {  
        targetPage = plus.webview.getWebviewById(subpages[currIndex - 1]);  
    } else {  
        targetPage = plus.webview.currentWebview();  
    }  

    if(targetPage == activePage) {  
        return;  
    }  

    if(currIndex !== 3) {   
        //底部选项卡切换  
        util.toggleNview(currIndex);  
        // 子页面切换  
        util.changeSubpage(targetPage, activePage, aniShow);  
        //更新当前活跃的页面  
        activePage = targetPage;  
    } else {  
        //第四个tab 打开新窗口  
        plus.webview.open('html/new-webview.html', 'new', {}, 'slide-in-right', 200);  
    }  
});

说明:点击事件中包含tab选项卡以及tab窗口切换,详情见附件

到这里其实绘制底部选项卡和webview模式的子页面,并且给选项卡添加了监听事件,已经初步完成。
但是小伙伴们还想在这样的模式下给底部中央一个凸起的悬浮大图标,可以怎么做呢,其实原理是一样的,也可以在manifest.json中配置,但是考虑到水平中央的图标涉及屏幕分辨率动态计算目前放在首页plusReady事件中配置,也给开发者一种可以手动绘制view控件的方法,如果你想在非首页采用这种方法呢。

4.利用nativeObj绘制凸起图标参考 HTML 5+ API plus.nativeObj.View
实现步骤如下:
1.首页plusReady事件中创建实例对象,最新方案中将背景nview和图标nview整合到了一起。
示例如下:

// 设置水平居中位置  
var leftPos = Math.ceil((window.innerWidth - 60) / 2);  

/**  
 * 简单封装了绘制原生view控件方法 drawNative()  
 * 绘制内容支持font(文本,字体图标),图片img , 矩形区域rect  
 *  
 */  
function drawNative(id, styles, tags) {  
    var view = new plus.nativeObj.View(id, styles, tags);  
    return view  
}  

/**   
 * drawNativeIcon 绘制带边框的半圆,  
 * 实现原理:  
 *   id为bg的tag 创建带边框的圆  
 *   id为bg2的tag 创建白色矩形遮住圆下半部分,只显示凸起带边框部分  
 *   id为iconBg的红色背景图  
 *   id为icon的字体图标  
 *   注意创建先后顺序,创建越晚的层级越高  
 */  
var drawNativeIcon = util.drawNative('icon', {  
    bottom: '5px',  
    left: leftPos + 'px',  
    width: '60px',  
    height: '60px'  
}, [{  
    tag: 'rect',  
    id: 'bg',  
    position: {  
        top: '1px',  
        left: '0px',  
        width: '100%',  
        height: '100%'  
    },  
    rectStyles: {  
        color: '#fff',  
        radius: '50%',  
        borderColor: '#ccc',  
        borderWidth: '1px'  
    }  
}, {  
    tag: 'rect',  
    id: 'bg2',  
    position: {  
        bottom: '-0.5px',  
        left: '0px',  
        width: '100%',  
        height: '45px'  
    },  
    rectStyles: {  
        color: '#fff'  
    }  
}, {  
    tag: 'rect',  
    id: 'iconBg',  
    position: {  
        top: '5px',  
        left: '5px',  
        width: '50px',  
        height: '50px'  
    },  
    rectStyles: {  
        color: '#d74b28',  
        radius: '50%'  
    }  
}, {  
    tag: 'font',  
    id: 'icon',  
    text: '\ue600', //此为字体图标Unicode码'\e600'转换为'\ue600'  
    position: {  
        top: '0px',  
        left: '5px',  
        width: '50px',  
        height: '100%'  
    },  
    textStyles: {  
        fontSrc: '_www/fonts/iconfont.ttf',  
        align: 'center',  
        color: '#fff',  
        size: '30px'  
    }  
}]);  
// 添加到当前父webview中,------此步骤很重要  
self.append(drawNativeIcon);

2.view控件添加监听事件

//自定义监听图标点击事件  
drawNativeIcon.addEventListener('click', function(e) {  
    mui.alert('你点击了图标,你在此可以打开摄像头或者新窗口等自定义点击事件。','悬浮球点击事件');  
});

原生view控件使用说明

  1. 以上描述创建并绘制原生view控件有两种方法:
    a.如果是首页应用,可以在manifest.json文件中相应节点配置,渲染快,配置方法简单
    b.通过new plus.nativeObj.View(),创建View实例对象,然后将该实例对象append到当前窗口(plus.webview.currentWebview)
  2. 这两种方法都需要开发者在首页plusReady事件中去针对view控件添加监听事件,比如切换对应窗口这种。
  3. view控件支持绘制类型:图片、矩形区域、文本(包括字体图标)
  4. 字体图标比如该悬浮球字体,来自iconfont.cn 阿里巴巴字体库,字体一般为Unicode(16进制)码:'\e600',但是配置的时候,需要填写为"\ue600"才能被原生层正确解析
  5. 参考 HTML 5+ API plus.nativeObj.View

该示例补充说明

  1. 中央悬浮大图标都是通过字体加纯色背景实现
  2. 其背后带边线半球区域也是通过两个矩形区域的view控件叠加拼成的,具体实现方法可以在附件中查看
  3. 本示例字体文件分别采用mui字体文件和阿里巴巴字体库
  4. 第四个选项卡点击打开新窗口,为示例教程,开发者可自行定义
  5. 屏幕旋转后,需手动重绘nview,参考nview的api。虽复杂也可以实现。也可以在manifest里禁用掉旋转。

扩展链接

字体图标绘制原生view控件
如何更新通过nativeObj.View创建的view控件教程

下载附件中的文件,直接解压真机运行即可体验

收起阅读 »

IOS提交时报ITMS-90168错和Missing Info.plist key 的解决

Appstore

IOS提交时,先是报90168错
后来查了下,说要换APPLICATION LOADER 3.6版本,但朋友那边用3.0提交他的APP,一点问题没有
用朋友的3.0提交,也还是报ITMS-90168错
估计还是自己的APP问题,后来发现有IP3和IP4的引导图大小不太对,把这俩图换了,再让朋友提交就OK了。(我这边用APPLICATION LOADER 3.0提交还是不行,可能是系统原因吧)
猜想ITMS-90168的错是个笼统的,没有详细的出错说明,这点做的太不到位

提交完成后,过了一会收到邮件,说Missing Info.plist key - This app attempts to access privacy-sensitive data without a usage description. The app's Info.plist must contain an NSContactsUsageDescription key with a string value explaining to the user how the app uses this data.

在坛子里查了一下,也有几个说法,最后综合了一下,改为下面这样

然后就过了初审了

继续阅读 »

IOS提交时,先是报90168错
后来查了下,说要换APPLICATION LOADER 3.6版本,但朋友那边用3.0提交他的APP,一点问题没有
用朋友的3.0提交,也还是报ITMS-90168错
估计还是自己的APP问题,后来发现有IP3和IP4的引导图大小不太对,把这俩图换了,再让朋友提交就OK了。(我这边用APPLICATION LOADER 3.0提交还是不行,可能是系统原因吧)
猜想ITMS-90168的错是个笼统的,没有详细的出错说明,这点做的太不到位

提交完成后,过了一会收到邮件,说Missing Info.plist key - This app attempts to access privacy-sensitive data without a usage description. The app's Info.plist must contain an NSContactsUsageDescription key with a string value explaining to the user how the app uses this data.

在坛子里查了一下,也有几个说法,最后综合了一下,改为下面这样

然后就过了初审了

收起阅读 »

利用mui toast制作下拉刷新成功后的顶部提醒

toast 下拉刷新

在很多资讯类App中,下拉刷新后,会在顶部提示获取多少条更新内容,如今日头条:

mui要实现类似效果很简单,重写div模式的toast控件样式即可,先看基于Hello MUI中单webview下拉刷新示例改造后的结果:

代码实现

1、重写div模式toast控件样式

这部分实现主要包括:

  • toast顶部显示
  • 宽度修改为100%宽度
  • 定制颜色

示例代码如下:

.mui-toast-container {  
    top: 0px; //顶部显示  
    left: 0;  
    width: 100%;//宽度修改为100%宽度  
    -webkit-transform:none;  
    transform: none;  
}  

.mui-toast-container.mui-active {  
    opacity: .7;  
}  

.mui-toast-message {  
    padding: 8px 15px;  
    background-color: #007AFF;//定制颜色  
    color: #fff;  
    border-radius: 0;  
    font-size: 13px;  
}

2、下拉刷新结束后,显示toast控件

/**  
 * 下拉刷新具体业务实现  
 */  
function pulldownRefresh() {  
    setTimeout(function() {  
        addData();//这里需要修改为具体业务代码,比如ajax请求  
        mui('#pullrefresh').pullRefresh().endPulldown();  
        mui.toast("Hello MUI推荐引擎有5条更新",{type:'div'});  
    }, 1500);  
}
继续阅读 »

在很多资讯类App中,下拉刷新后,会在顶部提示获取多少条更新内容,如今日头条:

mui要实现类似效果很简单,重写div模式的toast控件样式即可,先看基于Hello MUI中单webview下拉刷新示例改造后的结果:

代码实现

1、重写div模式toast控件样式

这部分实现主要包括:

  • toast顶部显示
  • 宽度修改为100%宽度
  • 定制颜色

示例代码如下:

.mui-toast-container {  
    top: 0px; //顶部显示  
    left: 0;  
    width: 100%;//宽度修改为100%宽度  
    -webkit-transform:none;  
    transform: none;  
}  

.mui-toast-container.mui-active {  
    opacity: .7;  
}  

.mui-toast-message {  
    padding: 8px 15px;  
    background-color: #007AFF;//定制颜色  
    color: #fff;  
    border-radius: 0;  
    font-size: 13px;  
}

2、下拉刷新结束后,显示toast控件

/**  
 * 下拉刷新具体业务实现  
 */  
function pulldownRefresh() {  
    setTimeout(function() {  
        addData();//这里需要修改为具体业务代码,比如ajax请求  
        mui('#pullrefresh').pullRefresh().endPulldown();  
        mui.toast("Hello MUI推荐引擎有5条更新",{type:'div'});  
    }, 1500);  
}
收起阅读 »

获取屏幕滚动状态

swipe scroll

应用场景:在屏幕滚动的时候触发相应事件。例如:侧边按钮的隐藏显示、回到顶部按钮的隐藏显示。
iOS:
var pullrefresh = document.getElementById("pullrefresh");
pullrefresh.addEventListener("scrollstart", function(e) {
//滚动开始的方法
});
document.getElementById("pullrefresh").addEventListener("scrollend", function(e) {
//滚动停止的方法
});
Android:
理论上可以监听到window.onscroll,但还不知道如何监听滚动开始和滚动停止的方法,这个方法在滚动的过程中一直触发。Android上的问题还没有解决,如有大神已经解决,还望指教。
相关文章:http://ask.dcloud.net.cn/question/901
http://ask.dcloud.net.cn/article/788

继续阅读 »

应用场景:在屏幕滚动的时候触发相应事件。例如:侧边按钮的隐藏显示、回到顶部按钮的隐藏显示。
iOS:
var pullrefresh = document.getElementById("pullrefresh");
pullrefresh.addEventListener("scrollstart", function(e) {
//滚动开始的方法
});
document.getElementById("pullrefresh").addEventListener("scrollend", function(e) {
//滚动停止的方法
});
Android:
理论上可以监听到window.onscroll,但还不知道如何监听滚动开始和滚动停止的方法,这个方法在滚动的过程中一直触发。Android上的问题还没有解决,如有大神已经解决,还望指教。
相关文章:http://ask.dcloud.net.cn/question/901
http://ask.dcloud.net.cn/article/788

收起阅读 »

【示例】Barcod二维码扫描及其结果在webview中的信息的传递

Webview evalJS Barcode

应用场景

1.A页面到B页面,B页面扫描得到结果,返回给A页面,A页面做相关处理;
2.A页面到B页面,B页面扫描得到结果,打开C页面,B页面的结果在C页面处理;根据需求是否关闭B页面,若不关闭,C页面back到B页面,重新调用barcode扫描二维码;
3.扫描页,可以添加本机图片进行扫描、也可以调用闪光灯;

具体实现

1.核心

调用扫描二维码Barcode的方法,Barcode参考文档

var scan = new plus.barcode.Barcode('mask');//创建  
scan.start();//执行  
scan.onmarked = function(type, result){//成功回调  
    console.log(result);  
};  
scan.onerror = function ( error ) {//失败回调  
     console.log(error);       
}

2.A→B→A

首先仍是调用Barcode,得到结果,使用evalJ传递数据给指定页面,evalJ参考文档

scan.onmarked = function(type, result){  
    if(result) {  
        alert(result);  
        scan.close();  
        plus.storage.setItem('result',result);//这里使用storage来保存数据  
        var wn = plus.webview.getLaunchWebview();//传递消息返回的webview  
        wn && wn.evalJS("var result = plus.storage.getItem('result');document.querySelector('#a').innerText = result;");//引号里为指定窗口要执行的代码  
        plus.webview.currentWebview().close();//扫码成功,关闭当前webview  
    }  
};

3.A→B→C

在B页面得到数据,传给C页面,这里采用的是 将数据存放在Webview窗口的额外扩展参数里面,参考文档
当然也可以用本地储存、mui自定义事件 mui.fire()等方法,本地储存文档mui.fire()文档

scan.onmarked = function(type, result) {  
    if(result) {  
         alert(result);  
         scan.close();  
         var w = plus.webview.create('Barcode_B2C.html', 'id', {  
             'titleNView': {  
                 'backgroundColor': '#262930',  
                 'titleText': '这里是C页面,返回应该关闭B',  
                 'titleColor': '#FFFFFF',  
                 autoBackButton: true  
             }  
         }, {//额外参数  
             barcode_data: result  
         });  
         w.show('pop-in','300ms');  
    }  
};

C页面返回时,关闭B页面,直接回到A页面,两种方法实现:

//1.在当前页面back时处理事件  
plus.key.addEventListener('backbutton',function(){  
    plus.webview.getWebviewById('carcode2').close();  
    plus.webview.currentWebview().close();  
});  
//2.在页面加载完成后直接关闭B页面  
plus.webview.getWebviewById('carcode2').close();

C页面返回时不关闭B页面,同时还要让B页面可以继续扫码:

plus.key.addEventListener('backbutton',function(){  
    var wn = plus.webview.getWebviewById('carcode3');  
//  wn && wn.reload();//刷新B页面  
    wn && wn.evalJS("scan = new plus.barcode.Barcode('mask');scan.start();scan.onmarked = function(type, result) {alert(result)}");//重新让B页面执行扫码操作  
    plus.webview.currentWebview().close();  
})

4.调用闪光灯、本地图片

调用闪光灯的方法,参考文档,相关代码如下:

//闪光灯  
document.querySelector('#scan2 img').addEventListener('click', function() {  
    if(/scan_off.png/.test(this.src)) {//实际中不建议用正则判断,声明个变量就行,这里偷了个懒  
         this.src = "../img/scan_on.png";  
         scan.setFlash(true);  
    } else {  
         this.src = "../img/scan_off.png";  
         scan.setFlash(false);  
    }  
})

调用本地图片的方法,调用了 Gallery模块的,参考文档

//闪光灯  
//添加本地图片  
document.querySelector('#scan1 img').addEventListener('click', function() {  
    plus.gallery.pick(function(path){//调用本机图片  
         console.log(path);  
        plus.barcode.scan(path,onmarked,function(error){//识别图片  
             plus.nativeUI.alert(JSON.stringify(error));  
         });  
    }, function(err){  
        plus.nativeUI.alert('Failed: '+err.message);  
    });  
})

最后,附上demo源码

继续阅读 »

应用场景

1.A页面到B页面,B页面扫描得到结果,返回给A页面,A页面做相关处理;
2.A页面到B页面,B页面扫描得到结果,打开C页面,B页面的结果在C页面处理;根据需求是否关闭B页面,若不关闭,C页面back到B页面,重新调用barcode扫描二维码;
3.扫描页,可以添加本机图片进行扫描、也可以调用闪光灯;

具体实现

1.核心

调用扫描二维码Barcode的方法,Barcode参考文档

var scan = new plus.barcode.Barcode('mask');//创建  
scan.start();//执行  
scan.onmarked = function(type, result){//成功回调  
    console.log(result);  
};  
scan.onerror = function ( error ) {//失败回调  
     console.log(error);       
}

2.A→B→A

首先仍是调用Barcode,得到结果,使用evalJ传递数据给指定页面,evalJ参考文档

scan.onmarked = function(type, result){  
    if(result) {  
        alert(result);  
        scan.close();  
        plus.storage.setItem('result',result);//这里使用storage来保存数据  
        var wn = plus.webview.getLaunchWebview();//传递消息返回的webview  
        wn && wn.evalJS("var result = plus.storage.getItem('result');document.querySelector('#a').innerText = result;");//引号里为指定窗口要执行的代码  
        plus.webview.currentWebview().close();//扫码成功,关闭当前webview  
    }  
};

3.A→B→C

在B页面得到数据,传给C页面,这里采用的是 将数据存放在Webview窗口的额外扩展参数里面,参考文档
当然也可以用本地储存、mui自定义事件 mui.fire()等方法,本地储存文档mui.fire()文档

scan.onmarked = function(type, result) {  
    if(result) {  
         alert(result);  
         scan.close();  
         var w = plus.webview.create('Barcode_B2C.html', 'id', {  
             'titleNView': {  
                 'backgroundColor': '#262930',  
                 'titleText': '这里是C页面,返回应该关闭B',  
                 'titleColor': '#FFFFFF',  
                 autoBackButton: true  
             }  
         }, {//额外参数  
             barcode_data: result  
         });  
         w.show('pop-in','300ms');  
    }  
};

C页面返回时,关闭B页面,直接回到A页面,两种方法实现:

//1.在当前页面back时处理事件  
plus.key.addEventListener('backbutton',function(){  
    plus.webview.getWebviewById('carcode2').close();  
    plus.webview.currentWebview().close();  
});  
//2.在页面加载完成后直接关闭B页面  
plus.webview.getWebviewById('carcode2').close();

C页面返回时不关闭B页面,同时还要让B页面可以继续扫码:

plus.key.addEventListener('backbutton',function(){  
    var wn = plus.webview.getWebviewById('carcode3');  
//  wn && wn.reload();//刷新B页面  
    wn && wn.evalJS("scan = new plus.barcode.Barcode('mask');scan.start();scan.onmarked = function(type, result) {alert(result)}");//重新让B页面执行扫码操作  
    plus.webview.currentWebview().close();  
})

4.调用闪光灯、本地图片

调用闪光灯的方法,参考文档,相关代码如下:

//闪光灯  
document.querySelector('#scan2 img').addEventListener('click', function() {  
    if(/scan_off.png/.test(this.src)) {//实际中不建议用正则判断,声明个变量就行,这里偷了个懒  
         this.src = "../img/scan_on.png";  
         scan.setFlash(true);  
    } else {  
         this.src = "../img/scan_off.png";  
         scan.setFlash(false);  
    }  
})

调用本地图片的方法,调用了 Gallery模块的,参考文档

//闪光灯  
//添加本地图片  
document.querySelector('#scan1 img').addEventListener('click', function() {  
    plus.gallery.pick(function(path){//调用本机图片  
         console.log(path);  
        plus.barcode.scan(path,onmarked,function(error){//识别图片  
             plus.nativeUI.alert(JSON.stringify(error));  
         });  
    }, function(err){  
        plus.nativeUI.alert('Failed: '+err.message);  
    });  
})

最后,附上demo源码

收起阅读 »