Подтвердить что ты не робот

Как загрузить ответ извлечения в реакцию как файл

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

Чем я скучаю? Что я должен делать в редукторе?

4b9b3361

Ответ 1

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

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

  • У меня есть компонент React, называемый FileDownload. Вся эта составляющая делает визуализацию скрытой формы, а затем внутри componentDidMount, немедленно отправьте форму и назовите ее onDownloadComplete prop.
  • У меня есть еще один компонент React, мы будем называть его Widget, с кнопкой загрузки/значком (многие фактически... по одному для каждого элемента в таблице). Widget имеет соответствующие действия и хранит файлы. Widget импортирует FileDownload.
  • Widget имеет два метода, связанных с загрузкой: handleDownload и handleDownloadComplete.
  • Widget У магазина есть свойство downloadPath. По умолчанию установлено значение null. Когда значение установлено на null, процесс загрузки файла не выполняется, а компонент Widget не отображает компонент FileDownload.
  • Нажатие кнопки/значка в Widget вызывает метод handleDownload, который запускает действие downloadFile. Действие downloadFile НЕ делает запрос Ajax. Он отправляет событие DOWNLOAD_FILE в хранилище, отправляя вместе с ним downloadPath для загружаемого файла. Магазин сохраняет downloadPath и испускает событие изменения.
  • Поскольку теперь существует downloadPath, Widget будет передавать FileDownload в необходимые реквизиты, включая downloadPath, а также метод handleDownloadComplete в качестве значения для onDownloadComplete.
  • Когда FileDownload отображается и форма отправляется с method="GET" (POST тоже должен работать) и action={downloadPath}, ответ сервера теперь вызывает диалог браузера Save для целевого файла загрузки (проверенный в IE 9/10, последние версии Firefox и Chrome).
  • Сразу после отправки формы вызывается onDownloadComplete/handleDownloadComplete. Это вызывает другое действие, которое отправляет событие DOWNLOAD_FILE. Однако на этот раз downloadPath установлено значение null. Магазин сохраняет downloadPath как null и испускает событие изменения.
  • Поскольку не существует 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", "реагировать", "stage-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>
        );
    }
}

Ответ 2

Вы можете использовать эти две библиотеки для загрузки файлов 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);
          })
        }
      });

Ответ 3

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

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

в основном я назначил URL-адрес blob (href) ссылке, установил атрибут загрузки и ввел один щелчок по ссылке. Насколько я понимаю, это "основная" идея ответа, предоставленного @Nate. Я не знаю, если это хорошая идея сделать это таким образом... Я сделал.

Ответ 4

Мне нужно было просто скачать файл на клике, но мне нужно было запустить некоторую логику, чтобы либо извлечь, либо вычислить фактический URL, где существовал файл. Я также не хотел использовать какие-либо императивные антиреактивные шаблоны, такие как установка ссылки и ручное нажатие на нее, когда у меня был URL ресурса. Я использовал декларативный шаблон

onClick = () => {
  // do something to compute or go fetch
  // the url we need from the server
  const url = goComputeOrFetchURL();

  // window.location forces the browser to prompt the user if they want to download it
  window.location = url
}

render() {
  return (
    <Button onClick={ this.onClick } />
  );
}