Jak pobrać fetch response w react as file

Oto kod w 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;
            })
        }
    });
}

Zwróconą odpowiedzią jest plik .xlsx. Chcę, aby użytkownik mógł zapisać go jako plik, ale nic się nie dzieje. Zakładam, że serwer zwraca właściwy typ odpowiedzi, ponieważ w konsoli jest napisane

Content-Disposition:attachment; filename="report.xlsx"
Co mi umyka? Co należy zrobić w reduktorze?
Author: lambda, 2016-02-04

2 answers

Technologia przeglądarki obecnie nie obsługuje pobierania pliku bezpośrednio z żądania Ajax. Obejście polega na dodaniu ukrytego formularza i przesłaniu go za kulisami, aby przeglądarka uruchomiła okno dialogowe zapisu.

Uruchamiam standardową implementację Flux, więc nie jestem pewien, jaki powinien być dokładny kod Redux (reduktor), ale workflow, który stworzyłem do pobrania pliku, wygląda tak...

  1. mam komponent Reactowy o nazwie FileDownload. Wszystko co robi ten komponent to Renderuj Ukryty formularz, a następnie, wewnątrz componentDidMount, natychmiast Prześlij formularz i wywołaj go onDownloadComplete prop.
  2. mam inny komponent Reactowy, nazwiemy go Widget, z przyciskiem/ikoną pobierania (wiele właściwie... po jednym dla każdej pozycji w tabeli). Widget posiada odpowiednią akcję i przechowuje pliki. Widget IMPORT FileDownload.
  3. Widget ma dwie metody związane z pobieraniem: handleDownload i handleDownloadComplete.
  4. Widget sklep posiada właściwość o nazwie downloadPath. Domyślnie ustawiona jest na null. Gdy jego wartość jest ustawiona do null nie trwa pobieranie pliku, a komponent Widget nie renderuje komponentu FileDownload.
  5. kliknięcie przycisku / ikony w Widget wywołuje metodę handleDownload, która uruchamia akcję downloadFile. Akcja {[22] } nie wysyła żądania Ajax. Wysyła Zdarzenie DOWNLOAD_FILE do sklepu wysyłając wraz z nim downloadPath do pobrania pliku. Sklep zapisuje downloadPath i emituje Zdarzenie change.
  6. ponieważ istnieje teraz downloadPath, Widget będzie renderować FileDownload przechodząc w niezbędne właściwości, w tym downloadPath oraz metoda handleDownloadComplete jako wartość dla onDownloadComplete.
  7. Kiedy FileDownload jest renderowany, a Formularz jest przesyłany z method="GET" (POST też powinien działać) i action={downloadPath}, odpowiedź serwera uruchomi teraz okno dialogowe zapisywania docelowego pliku do pobrania (testowane w IE 9/10, najnowszych Firefoksach i Chrome).
  8. bezpośrednio po złożeniu formularza, onDownloadComplete/handleDownloadComplete nazywa się. To uruchamia kolejną akcję, która wywołuje zdarzenie DOWNLOAD_FILE. Jednak tym razem downloadPath jest Ustaw na null. Sklep zapisuje downloadPath jako null i emituje Zdarzenie change.
  9. ponieważ nie ma już downloadPath komponent FileDownload nie jest renderowany w Widget i świat jest szczęśliwym miejscem.

Widget.js-tylko częściowy kod

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-tylko częściowy kod

export function downloadFile(data) {
    let downloadPath = null;

    if (data) {
        downloadPath = `${apiResource}/${data.fileName}`;
    }

    appDispatcher.dispatch({
        actionType: actionTypes.DOWNLOAD_FILE,
        downloadPath
    });
}

WidgetStore.js-tylko częściowy kod

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
- kompletny, w pełni funkcjonalny kod gotowy do kopiowania i wklejania
- React 0.14.7 with Babel 6.x ["es2015", "react", "stage-0"]
- forma musi być display: none, czyli to, co "ukryte" className jest dla

import React, {Component, PropTypes} from 'react';
import ReactDOM from 'react-dom';

function getFormInputs() {
    const {queryParams} = this.props;

    if (queryParams === undefined) {
        return null;
    }

    return Object.keys(queryParams).map((name, index) => {
        return (
            <input
                key={index}
                name={name}
                type="hidden"
                value={queryParams[name]}
            />
        );
    });
}

export default class FileDownload extends Component {

    static propTypes = {
        actionPath: PropTypes.string.isRequired,
        method: PropTypes.string,
        onDownloadComplete: PropTypes.func.isRequired,
        queryParams: PropTypes.object
    };

    static defaultProps = {
        method: 'GET'
    };

    componentDidMount() {
        ReactDOM.findDOMNode(this).submit();
        this.props.onDownloadComplete();
    }

    render() {
        const {actionPath, method} = this.props;

        return (
            <form
                action={actionPath}
                className="hidden"
                method={method}
            >
                {getFormInputs.call(this)}
            </form>
        );
    }
}
 40
Author: Nate,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-31 09:46:30

Możesz użyć tych dwóch bibliotek do pobrania plików http://danml.com/download.html https://github.com/eligrey/FileSaver.js/#filesaverjs

Przykład

//  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);
          })
        }
      });
 25
Author: Anton Philin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-16 06:38:43