it-swarm.com.ru

Как скачать ответный ответ в файле

Вот код в 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"

Что мне не хватает? Что мне делать в редукторе?

35
Rafael K.

В настоящее время технология браузера не поддерживает загрузку файла непосредственно из запроса Ajax. Обходной путь - добавить скрытую форму и отправить ее за сцену, чтобы браузер вызвал диалоговое окно «Сохранить».

Я использую стандартную реализацию Flux, поэтому я не уверен, каким должен быть точный код Redux (Reducer), но рабочий процесс, который я только что создал для загрузки файла, выглядит следующим образом ...

  1. У меня есть компонент React, называемый FileDownload. Все, что делает этот компонент - это отображает скрытую форму, а затем внутри componentDidMount немедленно отправляет форму и вызывает ее onDownloadComplete prop.
  2. У меня есть другой компонент React, назовем его Widget, с кнопкой/значком загрузки (на самом деле, многие ... по одному для каждого элемента в таблице). Widget имеет соответствующее действие и хранит файлы. Widget импорт FileDownload.
  3. Widget имеет два метода, связанных с загрузкой: handleDownload и handleDownloadComplete.
  4. У Widget store есть свойство с именем downloadPath. По умолчанию установлено значение null. Если для него установлено значение null, загрузка файла не выполняется, и компонент Widget не отображает компонент FileDownload.
  5. Нажатие кнопки/значка в Widget вызывает метод handleDownload, который запускает действие downloadFile. Действие downloadFile НЕ выполняет Ajax-запрос. Он отправляет событие DOWNLOAD_FILE в хранилище, отправляя вместе с ним downloadPath для файла для загрузки. Магазин сохраняет downloadPath и генерирует событие change.
  6. Поскольку теперь существует downloadPath, Widget отобразит FileDownload, передав необходимые реквизиты, включая downloadPath, а также метод handleDownloadComplete в качестве значения для onDownloadComplete.
  7. Когда отображается FileDownload и форма отправляется с method="GET" (POST также должен работать) и action={downloadPath}, ответ сервера теперь будет вызывать диалоговое окно браузера Save для целевого загрузочного файла (протестировано в IE 9/10, последней версии Firefox и Хром).
  8. Сразу после отправки формы вызывается onDownloadComplete/handleDownloadComplete. Это вызывает другое действие, которое отправляет событие DOWNLOAD_FILE. Однако на этот раз для downloadPath установлено значение null. Хранилище сохраняет downloadPath как null и генерирует событие изменения.
  9. Поскольку downloadPath больше не существует, компонент FileDownload не отображается в Widget, и мир становится счастливым местом.

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
- полный, полностью функциональный код, готовый для копирования и вставки
- Реагировать 0.14.7 с Babel 6.x ["es2015", "реагировать", "стадия-0"]
- форма должна быть display: none, для чего и предназначена «скрытая» 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

Вы можете использовать эти две библиотеки для загрузки файлов http://danml.com/download.htmlhttps://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);
          })
        }
      });
28
Anton Philin

Я тоже однажды столкнулся с той же проблемой . Я решил ее, создав пустую ссылку с ссылкой на нее, например, так:

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 (href) на ссылку, установил атрибут загрузки и ввел один клик по ссылке . Насколько я понимаю, это "основная" идея ответа, предоставленного @Nate . Я не знаю, если это хорошая идея, чтобы сделать это таким образом ... Я сделал.

0
Stanislav