较友好的Web文件下载用户体验实例

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.总结

上面应该算是不依赖任何插件下,较简单的一种增加浏览器下载文件友好度的实现。因整体项目毕竟为工作项目,所以就不贴真实源码了,以后在自己其他业余时间做的项目中会用到这块逻辑,到时候再贴上博客。只希望本文的一些思路能对大家有帮助。

原文地址:https://www.cnblogs.com/yaoh/p/5090431.html