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"
私は何が欠けていますか?レデューサーで何をすべきですか?
現在、ブラウザテクノロジーは、Ajaxリクエストからのファイルの直接ダウンロードをサポートしていません。回避策は、非表示のフォームを追加し、バックグラウンドで送信して、ブラウザーが「保存」ダイアログをトリガーするようにすることです。
標準のFlux実装を実行しているため、正確なRedux(Reducer)コードがどうあるべきかわかりませんが、ファイルダウンロード用に作成したワークフローは次のようになります...
FileDownload
というReactコンポーネントがあります。このコンポーネントは、非表示のフォームをレンダリングし、componentDidMount
の内部ですぐにフォームを送信し、そのonDownloadComplete
propを呼び出します。Widget
と呼び、ダウンロードボタン/アイコン(実際には...テーブルの各アイテムに1つ)を使用します。 Widget
には、対応するアクションファイルとストアファイルがあります。 Widget
はFileDownload
をインポートします。Widget
には、ダウンロードに関連するhandleDownload
とhandleDownloadComplete
の2つのメソッドがあります。Widget
storeには、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}
で送信されると、サーバーの応答により、ターゲットダウンロードファイルのブラウザーの[保存]ダイアログがトリガーされます(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
-完全な機能を備えた完全なコードをコピーアンドペーストできる
-React Babel 6.xで0.14.7 ["es2015"、 "react"、 "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.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);
})
}
});
私も同じ問題に一度直面しました。私はそれを参照して空のリンクを作成して解決しました:
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)を割り当て、ダウンロード属性を設定し、リンクを1回クリックします。私が理解している限り、これは@Nateが提供する答えの「基本的な」アイデアです。この方法でこれを行うのが良いかどうかはわかりません...