web-dev-qa-db-fra.com

Comment télécharger la réponse de récupération dans le fichier de réaction

Voici le code dans 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;
            })
        }
    });
}

La réponse renvoyée est un fichier .xlsx. Je veux que l'utilisateur puisse l'enregistrer en tant que fichier, mais rien ne se passe. Je suppose que le serveur renvoie le bon type de réponse car dans la console, il est indiqué 

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

Qu'est-ce qui me manque? Que dois-je faire dans le réducteur?

35
Rafael K.

La technologie de navigateur ne prend actuellement pas en charge le téléchargement de fichier directement à partir d'une requête Ajax. La solution consiste à ajouter un formulaire masqué et à le soumettre en arrière-plan pour que le navigateur déclenche la boîte de dialogue Enregistrer.

J'utilise une implémentation standard de Flux, donc je ne suis pas sûr de ce que devrait être le code exact de Redux (Réducteur), mais le flux de travail que je viens de créer pour le téléchargement d'un fichier va comme ceci ...

  1. J'ai un composant React appelé FileDownload. Tout ce composant ne fait que restituer une forme masquée puis, à l'intérieur de componentDidMount, soumettez immédiatement la fiche et appelez-la, c'est onDownloadComplete prop.
  2. J'ai un autre composant React, nous l'appellerons Widget, avec un bouton/une icône de téléchargement (plusieurs en fait ... un pour chaque élément d'un tableau). Widget a l'action correspondante et stocke les fichiers. Widget importations FileDownload.
  3. Widget a deux méthodes liées au téléchargement: handleDownload et handleDownloadComplete.
  4. Widget store a une propriété appelée downloadPath. Il est défini sur null par défaut. Lorsque sa valeur est définie sur null, aucun téléchargement de fichier n'est en cours et le composant Widget ne rend pas le composant FileDownload.
  5. Cliquer sur le bouton/l'icône dans Widget appelle la méthode handleDownload qui déclenche une action downloadFile. L'action downloadFile NE fait PAS une demande Ajax. Il envoie un événement DOWNLOAD_FILE au magasin en lui envoyant le downloadPath pour le fichier à télécharger. Le magasin enregistre le downloadPath et émet un événement de modification.
  6. Puisqu'il existe maintenant un downloadPath, Widget rendra FileDownload en passant par les accessoires nécessaires, notamment downloadPath, ainsi que la méthode handleDownloadComplete en tant que valeur pour onDownloadComplete.
  7. Lorsque FileDownload est rendu et que le formulaire est soumis avec method="GET" (POST devrait également fonctionner) et action={downloadPath}, la réponse du serveur va maintenant déclencher la boîte de dialogue Enregistrer du navigateur pour le fichier de téléchargement cible (testée dans IE 9/10, dernière modification). Firefox et Chrome).
  8. Immédiatement après l'envoi du formulaire, onDownloadComplete/handleDownloadComplete est appelé. Cela déclenche une autre action qui distribue un événement DOWNLOAD_FILE. Cependant, cette fois, downloadPath est défini sur null. Le magasin enregistre downloadPath sous le nom null et émet un événement de modification.
  9. Puisqu'il n'y a plus de downloadPath, le composant FileDownload n'est pas rendu en Widget et le monde est un endroit heureux.

Widget.js - code partiel seulement

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 - code partiel uniquement

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

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

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

widgetStore.js - code partiel uniquement

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
- code complet, entièrement fonctionnel, prêt à être copié et collé
- Réagissez 0.14.7 avec Babel 6.x ["es2015", "réagit", "stage-0"]
La forme - doit être display: none, ce à quoi sert le "caché" className 

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>
        );
    }
}
41
Nate

Vous pouvez utiliser ces deux bibliothèques pour télécharger des fichiers http://danml.com/download.htmlhttps://github.com/eligrey/FileSaver.js/#filesaverjs

exemple

//  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);
          })
        }
      });
28
Anton Philin

J'ai fait face au même problème une fois aussi ..__ Je l'ai résolu en créant un lien vide avec une référence comme ceci:

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

et dans ma fonction de recherche, j'ai fait quelque chose comme ceci:

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));

en gros, j’ai assigné l’URL des blobs (href) au lien, défini l’attribut de téléchargement et imposé un clic sur le lien . Pour autant que je sache, c’est l’idée "de base" de la réponse fournie par @Nate . Je ne sais pas si c'est une bonne idée de le faire de cette façon ... c'est ce que j'ai fait.

0
Stanislav