web前端之文件上传

前言

最近太忙一直没时间认真的写博客(哈哈哈),最近pm提一个需求,移动端需要一个上传图片的功能,允许多选、删除、预览、点击查看大图并可以滑动。虽然听起来很多,但是这个功能在web上实现过啊,使用webuploader妥妥的,然后就拍着胸口答应了下来,并让B同事做。

开发完成后,后端说同一次上传多张图只能发一次请求,纳尼。。我没仔细看过webuploader的API,不知道为什么需要单独进行上传,看了一下请求信息,在支持FormData的浏览器中使用的FormData来模拟form。那既然这样也可以支持多图一起请求啊(如果webuploader支持或者有其他考虑,欢迎大家指出),算了,自己造吧,也就有了这篇随笔。

1.form表单提交

像我这样的小白都知道form表单提交之后要刷新页面,更别说要实现pm提的那些需求了,但是代码还是要上一下的。

<form action="http://baidu.com" target="" id="uploadForm" enctype="multipart/form-data">
  <input id="file" type="file" name="file"/>
  <input type="submit" name="submit" id="submit" value="upload" />
</form>

因为我们这里要上传文件,所以enctype的值为multipart/form-data。

2.form+iframe

看到上面的代码,form有一个target的属性,规定在何处打开action,可能的值有

  • _blank
  • _self
  • _parent
  • _top
  • framename

就不一一介绍了,我们最关心的是最后一个值,framename,我们将页面放在iframe里处理就不担心刷新的问题了,然后再设置一个回调就可以处理服务端返回的参数

html:

<form action="http://baidu.com"  target="" id="uploadForm" enctype="multipart/form-data">
       <input id="file" type="file" name="file"/>
       <input type="submit" name="submit" id="submit" value="upload" />
</form>

jq:

<script type="text/javascript">
        var form = $("#uploadForm");
            form.on("submit",function(){
                var seed = Math.floor(Math.random()*1000),
                id = "uploader-iframe" + seed,
                callback = "uploader-cb" + seed,
                iframe = $("<iframe id='"+id+"' name='"+id+"' style='display:none'></iframe>"),
                url = form.attr("action");
                form.attr("target",id).append(iframe).attr("action",url+"?iframe="+callback);
                window[callback]=function(data){
                    iframe.remove();
                    form.removeAttr("target");
                    form.attr("action",url);
                    window[callback] = undefined;
                }
            })
        </script>

有没有觉得和jsonp的方式有点像,但是这里不需要动态创建script标签来调用,因为iframe本来就是一个页面,只需要服务端返回调用方法和数据在iframe页面就ok了

服务端返回:

<script type="text/javascript">
window.top.window[callback](data)
</script>

callback是我们事前约定好并传给服务器的参数,data为服务器返回的数据。

还有一种拿数据的方法,不通过后端回调

iframe.on("load",function(){
       var ifr =$(this).contents() //jq对象document
        //ifr = this.contentDocument || this.document//兼容ie
})

跟后端约定返回数据格式,然后进行操作

form+iframe这种伪异步的提交方式对文件的处理还是无力,不能想删就删,预览图片只有先传给后台,后台再返回一个线上的地址

3.HTML5之FormData、FileReader

当当当。。文章的主角出现

利用FormData模拟表单数据,通过ajax进行提交,FileReader的readAsDataURL方法拿到base64地址来预览(完美,注意兼容性)

form表单初始化FormData提交

<form action="http://baidu.com"  target="" id="uploadForm" enctype="multipart/form-data">
            <input id="file" type="file" name="file"/>
            <input type="submit" name="submit" id="submit" value="upload" />
        </form>
$.ajax({
    url: '/upload',
    type: 'POST',
    cache: false,
    data: new FormData($('#uploadForm')[0]),
    processData: false,
    contentType: false
}).done(function(res) {
}).fail(function(res) {});
  • processData设置为false。因为data值是FormData对象,不需要对数据做处理。
  • <form>标签添加enctype="multipart/form-data"属性。
  • cache设置为false,上传文件不需要缓存。
  • contentType设置为false。因为是由<form>表单构造的FormData对象,且已经声明了属性enctype="multipart/form-data",所以这里设置为false。

上传后服务端通过file来接收文件流。

 通过FormData对象append方法来添加

<div id="uploadForm">
    <input id="file" type="file"/>
    <button id="upload" type="button">upload</button>
</div>
var formData = new FormData();
formData.append('file', $('#file')[0].files[0]);
$.ajax({
    url: '/upload',
    type: 'POST',
    cache: false,
    data: formData,
    processData: false,
    contentType: false
}).done(function(res) {
}).fail(function(res) {});

FileReader获取DataUrl

<input multiple="multiple" id="file" type="file" name="file"/>
var reader = new FileReader();
reader.onload=function(e){
    //e.target.result为$("#file")[0].files[0]的base64地址
}
reader.readAsDataURL($("#file")[0].files[0])

更多FormData和FileReader方法可以去查看一下API

4.flash实现

flash的实现不在我们讨论的范围,而且浏览器的支持对flash有很大的影响,现在有很多上传组件做了低版本flash的兼容。比如webuploader、uploadify等

需求实现

 说了这么多,最后还是要回归到需求上来,由于这个需求是在移动端上,我们自然而然就选了第三种实现方式,这里讲一下实现的思路(伪代码)。

var formdata = new FormData(), count = 0, mId = '${model.id}', a = [];
    formdata.append("id", mId)
    $("#file").on("change",function() {//file触发change时循环files做相应的处理
                        if ($("#file")[0].files.length > 0) {
                            
                            for (var i = 0, j = $("#file")[0].files.length; i < j; i += 1) {

                                (function(k) {//按顺序插入预览图片,并在数组中保存对应的files
                                    var reader = new FileReader()
                                    reader.onload = function(e) {//保证预览顺序和files数组顺序一致,方便后面删除file
                                        count += 1;
                                       a.push($("#file")[0].files[k]);
                                        $(".filelist").append("<li><p class='imgWrap'><img class='close' src='img/close.png'/><img id='"
                                                                + $("#file")[0].files[k].name
                                                                + "' class='choose-img' src='"
                                                                + e.target.result
                                                                + "'' /></p></li>");//预览相关处理
$(".swiper-wrapper").append("<div class='swiper-slide'><img id='"
                                            + $("#file")[0].files[k].name
                                            + "' class='choose-img' src='"
                                            + e.target.result
                                            + "'' /></div>");//点击查看大图相关处理
                                    }
                                    
                                    
                                    reader.readAsDataURL($("#file")[0].files[i]);
                                })(i);

                            }
                            
                        }
                    });
    $(document).on("click", ".close", function() {//图片删除处理,处理files数组,更新count
        var $this = $(this);
        a.splice($this.parents("li").index(), 1);
        $(".swiper-slide").eq($this.parents("li").index()).remove();//如果考虑复用,这个index可以优化下
        count -= 1;
        $this.parents("li").remove();
        $("#jsUpload").show();
    })
    $("#jsUploadBtn").on("click", function() {//上传时,将files数组循环append进FromData进行ajax提交
        for (var i = 0, j = a.length; i < j; i += 1) {
            formdata.append("file", a[i])
        }
        $.ajax({
            url : '',
            type : 'POST',
            cache : false,
            data : formdata,
            processData : false,
            contentType : false
        }).done(function(data) {
            if (data == 'error') {
                $(".flie-toast").addClass("hide");
                toast("上传失败,请联系管理员!");
            } else if (data == 'no') {
                $(".flie-toast").addClass("hide");
                toast("您没有参与比赛,不允许上传截图");
            } else if (data == 'yes') {
                $(".flie-toast").addClass("hide");
                toast("上传成功");
                setTimeout(function() {
                    window.location.href = "";
                }, 2000);
            } else {
                $(".flie-toast").addClass("hide");
                toast("上传失败,请联系管理员!");
            }

        }).fail(function(res) {
        });

    })

虽然FormData对象有一个delete的方法,但是现在浏览器的支持率堪忧啊,所以只有曲线救国了。

个人知识的宽度和广度毕竟有限,如文章有什么疏漏和错误的地方,欢迎大家留言指出。

posted @ 2016-07-29 11:24  jesse-li  阅读(...)  评论(...编辑  收藏