如何下载fetch响应作为文件

时间:2016-02-04 16:38:37

标签: javascript reactjs flux reactjs-flux

以下是actions.js

中的代码
export function exportRecordToExcel(record) {
    return ({fetch}) => ({
        type: EXPORT_RECORD_TO_EXCEL,
        payload: {
            promise: fetch('/records/export', {
                credentials: 'same-origin',
                method: 'post',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify(data)
            }).then(function(response) {
                return response;
            })
        }
    });
}

返回的响应是.xlsx文件。我希望用户能够将其保存为文件,但没有任何反应。我假设服务器正在返回正确类型的响应,因为在控制台中它说

Content-Disposition:attachment; filename="report.xlsx"

我错过了什么?我应该在减速机中做些什么?

6 个答案:

答案 0 :(得分:45)

浏览器技术目前不支持直接从Ajax请求下载文件。解决方法是添加隐藏的表单并在幕后提交,以使浏览器触发“保存”对话框。

我正在运行标准的Flux实现,因此我不确定Redux(Reducer)代码应该是什么,但我刚为文件下载创建的工作流程就像这样...

  1. 我有一个名为FileDownload的React组件。所有这些组件都会呈现一个隐藏的表单,然后在componentDidMount内,立即提交表单并将其命名为onDownloadComplete prop。
  2. 我有另一个React组件,我们将其称为Widget,带有下载按钮/图标(实际上很多......表中的每个项目都有一个)。 Widget具有相应的操作和存储文件。 Widget导入FileDownload
  3. Widget有两种与下载相关的方法:handleDownloadhandleDownloadComplete
  4. Widget商店有一个名为downloadPath的媒体资源。默认设置为null。如果将其值设置为null,则表示正在进行文件下载,Widget组件不会呈现FileDownload组件。
  5. 点击Widget中的按钮/图标会调出触发handleDownload操作的downloadFile方法。 downloadFile操作不会发出Ajax请求。它会向商店发送DOWNLOAD_FILE事件,并随之发送downloadPath文件以供下载。商店会保存downloadPath并发出更改事件。
  6. 由于现在有downloadPathWidget将呈现FileDownload传递必要的道具,包括downloadPath以及handleDownloadComplete方法作为值onDownloadComplete
  7. 呈现FileDownload并且表单随method="GET"提交(POST也应该有效)和action={downloadPath},服务器响应现在将触发浏览器的保存对话框目标下载文件(在IE 9/10中测试,最新的Firefox和Chrome)。
  8. 在表单提交后,立即调用onDownloadComplete / handleDownloadComplete。这会触发另一个调度DOWNLOAD_FILE事件的操作。但是,此时downloadPath设置为null。商店将downloadPath保存为null并发出更改事件。
  9. 由于不再downloadPath FileDownload组件未在Widget呈现,而且世界是一个幸福的地方。
  10. Widget.js - 仅限部分代码

    import FileDownload from './FileDownload';
    
    export default class Widget extends Component {
        constructor(props) {
            super(props);
            this.state = widgetStore.getState().toJS();
        }
    
        handleDownload(data) {
            widgetActions.downloadFile(data);
        }
    
        handleDownloadComplete() {
            widgetActions.downloadFile();
        }
    
        render() {
            const downloadPath = this.state.downloadPath;
    
            return (
    
                // button/icon with click bound to this.handleDownload goes here
    
                {downloadPath &&
                    <FileDownload
                        actionPath={downloadPath}
                        onDownloadComplete={this.handleDownloadComplete}
                    />
                }
            );
        }
    

    widgetActions.js - 仅限部分代码

    export function downloadFile(data) {
        let downloadPath = null;
    
        if (data) {
            downloadPath = `${apiResource}/${data.fileName}`;
        }
    
        appDispatcher.dispatch({
            actionType: actionTypes.DOWNLOAD_FILE,
            downloadPath
        });
    }
    

    widgetStore.js - 仅限部分代码

    let store = Map({
        downloadPath: null,
        isLoading: false,
        // other store properties
    });
    
    class WidgetStore extends Store {
        constructor() {
            super();
            this.dispatchToken = appDispatcher.register(action => {
                switch (action.actionType) {
                    case actionTypes.DOWNLOAD_FILE:
                        store = store.merge({
                            downloadPath: action.downloadPath,
                            isLoading: !!action.downloadPath
                        });
                        this.emitChange();
                        break;
    

    FileDownload.js
    - 完整,功能齐全的代码,可以复制和粘贴 - 与Babel 6.x反应0.14.7 [&#34; es2015&#34;,&#34;反应&#34;,&#34;阶段0&#34;]
    - 表格需要display: none这就是&#34;隐藏&#34; <{1}}用于

    className

答案 1 :(得分:30)

您可以使用这两个库来下载文件http://danml.com/download.html https://github.com/eligrey/FileSaver.js/#filesaverjs

例如

//  for FileSaver
import FileSaver from 'file-saver';
export function exportRecordToExcel(record) {
      return ({fetch}) => ({
        type: EXPORT_RECORD_TO_EXCEL,
        payload: {
          promise: fetch('/records/export', {
            credentials: 'same-origin',
            method: 'post',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify(data)
          }).then(function(response) {
            return response.blob();
          }).then(function(blob) {
            FileSaver.saveAs(blob, 'nameFile.zip');
          })
        }
      });

//  for download 
let download = require('./download.min');
export function exportRecordToExcel(record) {
      return ({fetch}) => ({
        type: EXPORT_RECORD_TO_EXCEL,
        payload: {
          promise: fetch('/records/export', {
            credentials: 'same-origin',
            method: 'post',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify(data)
          }).then(function(response) {
            return response.blob();
          }).then(function(blob) {
            download (blob);
          })
        }
      });

答案 2 :(得分:6)

这对我有用。

const requestOptions = {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
};

fetch(`${url}`, requestOptions)
.then((res) => {
    return res.blob();
})
.then((blob) => {
    const href = window.URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = href;
    link.setAttribute('download', 'config.json'); //or any other extension
    document.body.appendChild(link);
    link.click();
})
.catch((err) => {
    return Promise.reject({ Error: 'Something Went Wrong', err });
})

答案 3 :(得分:3)

通过这种代码,我可以轻松下载由其余API URL生成的文件,这种代码在我的本地计算机上工作得很好:

    import React, {Component} from "react";
    import {saveAs} from "file-saver";

    class MyForm extends Component {

    constructor(props) {
        super(props);
        this.handleSubmit = this.handleSubmit.bind(this);
    }

    handleSubmit(event) {
        event.preventDefault();
        const form = event.target;
        let queryParam = buildQueryParams(form.elements);

        let url = 'http://localhost:8080/...whatever?' + queryParam;

        fetch(url, {
            method: 'GET',
            headers: {
                // whatever
            },
        })
            .then(function (response) {
                    return response.blob();
                }
            )
            .then(function(blob) {
                saveAs(blob, "yourFilename.xlsx");
            })
            .catch(error => {
                //whatever
            })
    }

    render() {
        return (
            <form onSubmit={this.handleSubmit} id="whateverFormId">
                <table>
                    <tbody>
                    <tr>
                        <td>
                            <input type="text" key="myText" name="myText" id="myText"/>
                        </td>
                        <td><input key="startDate" name="from" id="startDate" type="date"/></td>
                        <td><input key="endDate" name="to" id="endDate" type="date"/></td>
                    </tr>
                    <tr>
                        <td colSpan="3" align="right">
                            <button>Export</button>
                        </td>
                    </tr>

                    </tbody>
                </table>
            </form>
        );
    }
}

function buildQueryParams(formElements) {
    let queryParam = "";

    //do code here
    
    return queryParam;
}

export default MyForm;

答案 4 :(得分:0)

我也曾经遇到过同样的问题。 我通过在空链接上创建引用来解决它,如下所示:

linkRef = React.createRef();
render() {
    return (
        <a ref={this.linkRef}/>
    );
}

在获取函数中,我做了这样的事情:

fetch(/*your params*/)
    }).then(res => {
        return res.blob();
    }).then(blob => {
        const href = window.URL.createObjectURL(blob);
        const a = this.linkRef.current;
        a.download = 'Lebenslauf.pdf';
        a.href = href;
        a.click();
        a.href = '';
    }).catch(err => console.error(err));

基本上,我已经将blob url(href)分配给了链接,设置了下载属性,并在链接上单击了一下。 据我了解,这是@Nate提供的答案的“基本”思想。 我不知道这样做是否是个好主意...我做到了。

答案 5 :(得分:0)

我只需要下载一个onClick文件,但是我需要运行一些逻辑来获取或计算文件所在的实际URL。我也不想使用任何反反应命令式模式,例如设置ref并在拥有资源URL时手动单击它。我使用的声明性模式是

Map<String, String> storeConfig = new HashMap<>();
storeConfig.put(TopicConfig.RETENTION_MS_CONFIG, TimeUnit.DAYS.toMillis(30));
storeConfig.put(TopicConfig.CLEANUP_POLICY_CONFIG, "compact,delete");

StoreBuilder store1 = Stores.keyValueStoreBuilder(
   Stores.persistentKeyValueStore("STORE1"),
   Serdes.String(),
   Serdes.String()
);

streamsBuilder.addStateStore(store1.withLoggingEnabled(storeConfig));