1.实际需求整理与分析
该问题起源于为公司做的一个B/S架构的游戏静态数据管理工具,其中有一个需求是点击页面上的一些按钮要下载文件,可能根据按钮类型的不同需要转换下载.json、.zip、.xlsx等文件格式,为了好的体验,当时考虑这个功能应做到以下几点:
1.1.页面跳转
下载文件时,页面不会刷新、跳转、添加页面(单页面且所有数据交互都通过ajax,所以要做到这点)
1.2.错误处理
在遇到意外情况,如:文件不存在、服务器异常,同样不允许跳转或者刷新
1.3.错误提示
在1.2的基础上,应能根据错误对用户做出提示。
1.4.需求分析
做到第1点很简单,现代浏览器对于响应头信息Content-Type为application/octet-stream都会提示下载,而不会跳转或者刷新页面。但是直接这样做会带来2,3点的问题,如果文件不存在或者服务器处理出现了异常,则会跳转到404、5**的错误界面。
查了一些方法,综合以后准备利用iframe实现第2点,利用iframe与父页面的交互实现第2点。
2.实现抽象示例
这里以最基本的文件来演示需求的实现。(这里只写了前端的实现,完全实现需要后端处理,对后端的要求这里只做简单介绍)
2.1.功能及文件抽象
这里我根据需求抽象了4个文件:
index.html 提供给用户点击交互的页面(主页)
iframe.html 主页的iframe中加载的页面,该页面body内容默认只有需要加载的js和一个id为json_data,内容为空的div节点(错误提示的时候会有值,后面会介绍),主页面的iframe元素应全程保持隐藏状态
index.js 主页加载的逻辑js文件
iframe.js iframe页面加载的js文件。
2.2.代码实现
下面介绍实例代码,(注:依赖jquery,且只有符合上面需求的功能):
2.2.1.index.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Index</title> </head> <body> <!-- 这里还至少应该有个下载的按钮或者其他能够提供下载交互的组件 --> <script type="text/javascript" src="jquery.js"></script> <script type="text/javascript" src="index.js"></script> </body> </html>
2.2.2.index.js
/** * 文件下载方法 * * @param string strUrl 下载文件的请求地址 */ function DownLoad(strUrl) { var download_iframe = $('#download_iframe'); // 没有iframe就添加一个 if (download_iframe.length === 0) { download_iframe = $('<iframe></iframe>'); download_iframe.attr('id', 'download_iframe'); download_iframe.attr('src', 'iframe.html'); download_iframe.css('display', 'none'); $('body').append(download_iframe); } else { // 有的话直接重新载入页面 download_iframe.attr('src','iframe.html?' + Math.random()); } // 在该iframe加载完成后执行iframe.js内部的Download方法。 var content = download_iframe[0].contentWindow; download_iframe.one('load', function () { content.DownLoad(strUrl); }); } /** * 处理服务器下载文件返回异常信息 * * @param object jsonTxt 服务器返回的json信息序列化后的对象 * */ function processDownloadErr(jsonTxt) { alert(jsonTxt.Msg); // 这里只做最简单的弹出处理,实际使用的时候应根据自己项目中有的提示插件进行提示 }
上面代码中会在第一次调用Download时创建一个iframe加载iframe.html;以后再次调用Download则会直接重新加载iframe的内容。
processDownloadErr方法用于处理iframe传过来的错误消息。
2.2.3.iframe.html
一般情况下iframe内容:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>iframe</title> </head> <body> <div id="json_data">
</div> <script type="text/javascript" src="jquery.js"></script> <script type="text/javascript" src="iframe.js"></script> </body> </html>
有错误或者自定义提示时的内容:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>iframe</title> </head> <body> <div id="json_data"> {"Msg":"该ID对应的文件不存在!"} </div> <script type="text/javascript" src="jquery.js"></script> <script type="text/javascript" src="iframe.js"></script> </body> </html>
2.2.4.iframe.js
/** * 实际下载文件的方法 * * @param string strUrl 文件的下载地址 * 该函数可以根据实际需要扩充,比如需要传递参数则再加一个{}类型的形参。 */ function DownLoad(strUrl) { // 查找页面上的form表单(因为前面规定,iframe.html中的body默认是空的,所以如果能找到的话必定是下载的form) var form = $('form'); // 如果找不到,则添加一个 if (form.length == 0) { form = $("<form>"); // 定义一个form表单 form.attr('style', 'display:none'); // 在form表单中添加查询参数 form.attr('target', ''); // 这里默认给提交自己, form.attr('method', 'post'); // 这里我设定为post,如果需要自定义的话可以再给Download方法再添加一个参数 } form.attr('action', strUrl); $('body').append(form); // 将表单放置在页面body中 form.submit(); } // 默认#json_data内容为空,只有在有错误信息的时候才会获得内容且提交给iframe的顶级节点处理 (function() { var json_text = $.trim($('#json_data').text()); if (json_text !== '') { top.processDownloadErr($.parseJSON(json_text).Data); } })();
iframe的Download方法被调用时会向body添加一个form表单,并按传入的url提交请求。因为该页面是在iframe中的,所以如果服务器返回404或者5xx等错误也不会引起主页面的跳转。
服务器发现下载文件出现问题的时候,仍返回iframe.html,在其#json_data中设置错误信息(如2.2.3中的内容)。这个时候json_text会获取到值,交由顶层页面处理。
2.2.5.服务器逻辑要求
这里对于服务器逻辑的要求,也就是能够满足在检测文件状态或者参数有误的时候,仍能返回iframe.html,且设置错误信息。
3.总结
上面应该算是不依赖任何插件下,较简单的一种增加浏览器下载文件友好度的实现。因整体项目毕竟为工作项目,所以就不贴真实源码了,以后在自己其他业余时间做的项目中会用到这块逻辑,到时候再贴上博客。只希望本文的一些思路能对大家有帮助。