使用 iframe 上传是很传统的异步上传的方式。我之前从来没有用过。
最近遇到一个问题是,有用户抱怨说图片上传不了。当然我测试是没有问题的。
之前的代码是同事基于 七牛的异步上传示例的代码修改而来的,我接着用了。
由于上传跟后台交互并不多,用户也没有提供更多的信息。为了快速简单的解决这个问题,我干脆使用比较传统的上传方式算了。
我们知道如果直接提交一个表单,当请求结果返回的时候,当前页面的内容会被响应内容替换掉。
简单来说,就是页面刷新了。
我们希望对此进行改进,希望上传操作在后台进行,然后上传完成之后通知我们。
基于 iframe 的异步上传,我认为关键在于设置 target
属性,
将 <form>
的请求响应结果显示到指定的 <iframe>
中。
查看 <form>
中 target
属性的文档 有如下说明:
A name or keyword indicating where to display the response that is received after submitting the form.
iframename: The response is displayed in a named <iframe>.
我们可以通过设置 <form>
的 target
属性。将其提交结果显示到指定的 iframe
中。而不会刷新当前页面。
也就给我们感觉是在后台进行。(当然 target
属性还有更多其他属性,特别是在 HTML 5 中。)
上面解决了后台上传的问题,另一个问题就是上传完成的通知了。
我们什么时候获得上传完成的响应结果?
一般的做法是,上传完成之后,响应的内容返回一段 JS ,然后其中的 JS 回调指定的处理函数 。
于是得到如下相关 前端 代码
<form method="post" id="tr-upload-form" action="/awards/api/upload_file" enctype="multipart/form-data" target='tr_upload_frame'>
<input type="file" name="file" accept="image/jpeg,image/gif,image/png" class="form-control">
<input type="submit" class="btn btn-primary form-control" value='上传'>
</form>
<iframe style="display:none" name="tr_upload_frame"></iframe>
下面的处理表单提交的 JS ,当用户点击上传时,动态替换 <form>
的 action
属性,加上回调参数 。
var $_tr_upload_form = $('#tr-upload-form');
$_tr_upload_form.on('submit',function(){
var seed = Math.floor(Math.random() * 1000);
var callback = 'upload_cb_'+seed;
var url = "/awards/api/upload_file";
$_tr_upload_form.attr('action', url+'?callback='+callback);
window[callback] = function(data){
console.log('received callback:',data);
$_tr_upload_form.attr('action',url);
delete window[callback];
if(data.ok){
form.avatar = data.key;
alert("上传成功")
}else{
alert("上传失败");
}
};
});
后台处理上传,我是用 Flask 和 七牛 Python-SDK-6
Flask 的 file 属性的 FileStorage 的 stream 实例可以直接用于上传这点很方便。
响应的结果就是一段带 JS 回调代码的脚本。
@app.route('/api/upload_file',methods=['POST'])
def api_upload_file():
from qiniu.io import PutExtra,put
import uuid
callback = request.args.get('callback')
file = request.files['file']
uptoken = _create_uptoken()
key = uuid.uuid4().hex
extra = PutExtra()
extra.mime_type = file.mimetype
ret,err = put(uptoken,key,file.stream,extra)
ok = True
if err is not None:
logger.error("uploa_file error "+str(err))
ok = False
data = { "key":key, "ok":ok }
text = '''
<script>
window.top.window['{callback}']({data});
</script>
'''.format(callback=callback,data=json.dumps(data))
return text
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.