form配合iframe实现文件异步上传

简述

使用form和iframe实现异步上传文件的功能

原理

form表单可以上传文件,但是提交form表单会打开新页面或刷新页面,为了不刷新页面,将form指向一个隐藏的iframe,form提交后它所指向的iframe会重新加载,iframe的内容即为服务器返回的内容。

优点

兼容各种浏览器各个版本的文件异步上传

缺点

如果要上传的文件需要校验(类型,大小),对于无法获取files属性的浏览器而言,用户体验性不好。

文件需要校验(类型,大小)的处理

能够获取到files就在前台校验,否则后台校验。

示例代码

HTML
<input type="file" name="file" />
JavaScript
$(function(){
    /* 监听change事件 */
    $('input[name=file]').on('change', function(e){
        
        var $target = $(e.target);
        
        /* 获取/创建 iframe */
        var $frame = $('iframe[name=upload_frame]');
        if(!$frame.length){
            $frame = $('<iframe>', {name: 'upload_frame', style: 'display: none;'});
        }
        
        /* 创建form */
        var $form = $('<form>', {
            method: 'POST',                 // post方式提交
            action: 'sever_address',        // 文件上传的服务器地址
            target: 'upload_frame',         // 指向iframe
            enctype:'multipart/form-data'   // 向服务器发送二进制数据
        });
        
        /* 嵌套form */
        $target.wrap($form)
        
        /* 提交表单 */
        $target.parent().submit();
        
        /* 处理回调 */
        $frame.on('load', function(){
            /* 去除嵌套的form */
            $target.parent().replaceWith($target);
            
            /* 接收服务器返回的数据 */
            var resp = $frame.contents().text();
            try {
                resp = JSON.parse(resp);
                /* 提示上传成功?按理说接下的代码才是真正的回调 */
            } catch (error) {
                console.error(error);
            }
        });
    });
});

完整的示例

HTML
<input type="file" name="file" />
JavaScript
$(function(){
    var utils = new this.Utils();
    $('input[type=file]').on('change', function(e){
        utils.uploadQiNiu({
            $target: $(e.target),       # 文件选择器
            prefix: 'subjective/',      # 前缀
            limitSize: 5,               # 限制文件最大为5M
            limitType: ['png', 'jpg'],  # 限制文件类型,
            callback: function(uri){
                utils.showMsg('上传成功: ' + uri);
            }
        });
    });  
})

后台代码 (Python方式实现)
def upload_file(request):

    # 返回信息
    def render(msg_='上传失败', uri_='', name_='', type_=''):
        return HttpResponse(
            content=json.dumps({
                'uri': uri_,
                'msg': msg_,
                'name': name_,
                'type': type_,
            }),
            content_type="text/html;charset=utf-8",
            status=200
        )
        
    # 获取文件
    file_obj = request.FILES.get('file', None)

    # 无文件对象的处理
    if not file_obj:
        return render("请选择文件!")

    post_vars = request.POST

    file_type = file_obj.content_type
    # 判断是否需要验证(类型,大小)
    if post_vars.get('validate', False):
        
        # 验证文件大小
        limit_size = int(post_vars.get('limitSize', 0))
        if limit_size > 0 and file_obj.size > limit_size * 1024 ** 2:
            return render('文件大小不能超过{}M!'.format(limit_size))

        # 验证文件类型
        limit_type = post_vars.get('limitType', '')
        reg = re.compile(file_type, re.I)
        if limit_type and not reg.match(limit_type.replace(',', '|')):
            return render('类型不符, 请上传{}格式的文件!'.format(limit_type))

        # 验证文件后缀
        suffix_type = post_vars.get('suffixType', '')
        if suffix_type:
            file_type = file_obj.name.split('.').pop()
            reg = re.compile(file_type, re.I)
            if not reg.match(suffix_type.replace(',', '|')):
                return render('类型不符, 请上传{}格式的文件!'.format(suffix_type))

    filename = file_obj.name
    prefix = post_vars.get('prefix', '')
    timestamp = int(time.time())
    key = '{}{}{}'.format(prefix, timestamp, filename)

    # 获取配置信息
    access_key = settings.QINIU_ACCESS_KEY
    secret_key = settings.QINIU_SECRET_KEY
    bucket = settings.QINIU_CMS_RESOURCE_BUCKET
    host = settings.QINIU_CMS_RESOURCE_URL

    # 获取上传凭证
    token = Auth(access_key, secret_key).upload_token(bucket, key, 7200)

    # 上传到七牛
    try:
        ret, info = put_data(
            token, key, file_obj.read(), mime_type=file_obj.content_type
        )
        uri = '{}/{}'.format(host, ret['key'])
        msg = '上传成功'
    except Exception as ex:
        logging.error(ex)
        uri = ''
        msg = '上传失败'

    return render(msg, uri, filename, file_type)
JavaScript 提取的公共方法 utils.js
// Generated by CoffeeScript 1.12.4
(function() {
  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };

  this.Utils = (function() {
    function Utils() {
      this.bodyLock = bind(this.bodyLock, this);
      this.showLoading = bind(this.showLoading, this);
      this.confirmMsg = bind(this.confirmMsg, this);
      this.showMsg = bind(this.showMsg, this);
      this.uploadQiNiu = bind(this.uploadQiNiu, this);
    }

    Utils.prototype.uploadQiNiu = function(options) {
      var $form, $frame, $target, file, fileType, files, limitSize, limitType, settings, suffixType;
      settings = {
        $target: null,
        prefix: 'project/',
        limitSize: 0,
        limitType: [],
        suffixType: [],
        callback: (function(_this) {
          return function() {};
        })(this)
      };
      $.extend(settings, options);
      $target = settings.$target;
      if (!$target) {
        console.error('Missing "$target" in settings');
        return false;
      }
      files = $target[0].files;
      if (files) {
        file = files[0];
        if (file == null) {
          this.showMsg('请选择文件!');
          return false;
        }
        limitSize = parseFloat(settings.limitSize);
        if (limitSize > 0 && file.size > limitSize * 1024 * 1024) {
          this.showMsg("文件大小不能超过" + limitSize + "M!");
          $target.val('');
          return false;
        }
        limitType = settings.limitType;
        fileType = file.type.toLowerCase();
        if (limitType.length && !fileType.match(eval("/" + (limitType.join('|')) + "/"))) {
          this.showMsg("类型不符, 请上传" + limitType + "格式的文件!");
          $target.val('');
          return false;
        }
        suffixType = settings.suffixType;
        if (suffixType.length) {
          fileType = file.name.split('.').pop().toLowerCase();
          if (!fileType.match(eval("/" + (suffixType.join('|')) + "/"))) {
            this.showMsg("类型不符, 请上传" + suffixType + "格式的文件!");
            $target.val('');
            return false;
          }
        }
      }
      this.showLoading('正在上传...');
      $frame = $('iframe[name=upload_frame]');
      if (!$frame.length) {
        $frame = $('<iframe>', {
          name: 'upload_frame',
          style: 'display: none'
        });
        $(document.body).append($frame);
      }
      $frame.on('load', (function(_this) {
        return function() {
          var resp;
          _this.bodyLock(false);
          $target.parent().replaceWith($target);
          resp = $frame.contents().text();
          if (resp.indexOf('413 Request Entity Too Large') !== -1) {
            _this.showMsg('上传文件过大!');
            console.error('413 Request Entity Too Large');
            return;
          }
          resp = JSON.parse(resp);
          _this.showMsg(resp.msg);
          return settings.callback(resp.uri, resp.name, (files ? fileType : resp.type), resp.msg);
        };
      })(this));
      $target.attr('name', 'file');
      $form = $('<form>', {
        method: "post",
        name: "upload_form",
        target: "upload_frame",
        action: "/qi_niu_upload/",
        enctype: "multipart/form-data",
        style: 'display:none'
      });
      $target.wrap($form);
      $target.after("<input type='hidden' name='prefix' value='" + settings.prefix + "'>");
      if (!files) {
        $target.after("<input type='hidden' name='validate' value='True'>");
        $target.after("<input type='hidden' name='limitSize' value='" + settings.limitSize + "'>");
        $target.after("<input type='hidden' name='limitType' value='" + settings.limitType + "'>");
        $target.after("<input type='hidden' name='suffixType' value='" + settings.suffixType + "'>");
      }
      $target.parent().submit();
    };

    Utils.prototype.showMsg = function(msg, timeout) {
      if (timeout == null) {
        timeout = 1000;
      }
      return $.jBox.tip(msg, '', {
        timeout: timeout
      });
    };

    Utils.prototype.confirmMsg = function(msg, fun) {
      return $.jBox.confirm(msg, '提示', (function(_this) {
        return function(v) {
          if (v === 'ok') {
            return fun();
          }
        };
      })(this));
    };

    Utils.prototype.showLoading = function(msg) {
      $.jBox.tip(msg, 'loading');
      $('.jbox-fade').css('height', ($(document).height()) + "px");
      return this.bodyLock(true);
    };

    Utils.prototype.bodyLock = function(lock) {
      var body, flag;
      body = $('body');
      flag = lock ? 'hidden' : 'auto';
      return body.css('overflow', flag);
    };

    return Utils;

  })();

}).call(this);

utils.js 对应的源文件 CoffeeScript
class @Utils

    # 上传文件
    uploadQiNiu: (options) =>
        settings =
            $target: null       # 文件选择器
            prefix: 'project/'  # 七牛空间前缀
            limitSize: 0        # 限制大小
            limitType: []       # 文件类型
            suffixType: []      # 后缀类型
            callback: () =>     # 回调函数

        # 配置信息
        $.extend settings, options

        # 判断文件选择器是否存在
        $target = settings.$target
        if not $target
            console.error 'Missing "$target" in settings'
            return false

        files = $target[0].files
        # 能获取到files属性
        if files
            file = files[0]

            # 判断是否选中文件
            if not file?
                @showMsg '请选择文件!'
                return false

            # 判断是否需要限制文件大小
            limitSize = parseFloat settings.limitSize
            if limitSize > 0 and file.size >  limitSize * 1024 * 1024
                @showMsg "文件大小不能超过#{limitSize}M!"
                $target.val ''
                return false

            # 判断是否需要限制文件类型
            limitType = settings.limitType
            fileType = file.type.toLowerCase()
            if limitType.length and not fileType.match eval "/#{limitType.join '|'}/"
                @showMsg "类型不符, 请上传#{limitType}格式的文件!"
                $target.val ''
                return false

            # 判断是否需要限制文件后缀
            suffixType = settings.suffixType
            if suffixType.length
                fileType = file.name.split('.').pop().toLowerCase()
                if not fileType.match eval "/#{suffixType.join '|'}/"
                    @showMsg "类型不符, 请上传#{suffixType}格式的文件!"
                    $target.val ''
                    return false

        @showLoading '正在上传...'
        $frame = $ 'iframe[name=upload_frame]'
        if not $frame.length
            $frame = $ '<iframe>',
                name: 'upload_frame'
                style: 'display: none'
            $(document.body).append $frame


        $frame.on 'load', () =>
            @bodyLock false
            $target.parent().replaceWith($target)

            resp = $frame.contents().text()
            if resp.indexOf('413 Request Entity Too Large') != -1
                @showMsg '上传文件过大!'
                console.error('413 Request Entity Too Large')
                return

            resp = JSON.parse resp
            @showMsg resp.msg
            settings.callback resp.uri, resp.name, (if files then fileType else resp.type), resp.msg

        # 后台获取文件的key为file
        $target.attr 'name', 'file'

        $form = $ '<form>',
            method: "post"
            name: "upload_form"
            target: "upload_frame"
            action: "/qi_niu_upload/"
            enctype: "multipart/form-data"
            style: 'display:none'

        # 外层包装form
        $target.wrap($form)
        $target.after("<input type='hidden' name='prefix' value='#{settings.prefix}'>")
        if not files
            $target.after("<input type='hidden' name='validate' value='True'>")
            $target.after("<input type='hidden' name='limitSize' value='#{settings.limitSize}'>")
            $target.after("<input type='hidden' name='limitType' value='#{settings.limitType}'>")
            $target.after("<input type='hidden' name='suffixType' value='#{settings.suffixType}'>")
        $target.parent().submit()
        return

    # 提示信息
    showMsg: (msg, timeout=1000) =>
        $.jBox.tip msg, '', {timeout: timeout}

    # 确认窗口
    confirmMsg: (msg, fun) =>
        $.jBox.confirm msg, '提示', (v) =>
            if v is 'ok'
                fun()

    # 加载中
    showLoading: (msg) =>
        $.jBox.tip msg, 'loading'
        $('.jbox-fade').css 'height', "#{$(document).height()}px"
        @bodyLock true

    # 设置body是否可以滚动
    bodyLock: (lock) =>
        body = $ 'body'
        flag = if lock then 'hidden' else 'auto'
        body.css 'overflow', flag

转载于:https://my.oschina.net/tianshl/blog/1519445

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值