質問編集履歴
5
title fix
test
CHANGED
@@ -1 +1 @@
|
|
1
|
-
Laravelでmultipart/form-data(画像)のリクエストが
|
1
|
+
Laravelでmultipart/form-data(画像)のリクエストがnull
|
test
CHANGED
File without changes
|
4
不要な文言削除
test
CHANGED
File without changes
|
test
CHANGED
@@ -7,8 +7,7 @@
|
|
7
7
|
|
8
8
|
### 実現したいこと
|
9
9
|
|
10
|
-
ここに実現したいことを箇条書きで書いてください。
|
11
|
-
- [ ] ▲▲リクエストを受け取れるようにする
|
10
|
+
- [ ] ▲▲リクエストパラメータを受け取れるようにする
|
12
11
|
|
13
12
|
### 発生している問題・エラーメッセージ
|
14
13
|
|
3
ImageSettings.js の上方修正
test
CHANGED
File without changes
|
test
CHANGED
@@ -268,7 +268,7 @@
|
|
268
268
|
┃ ┗CropUtils.js (画像生成処理など)
|
269
269
|
┠ pages
|
270
270
|
┃ ┗accountSettings
|
271
|
-
┃ ┗ImageSettings.js (
|
271
|
+
┃ ┗ImageSettings.js (モーダルの呼び出し元)
|
272
272
|
|
273
273
|
|
274
274
|
### 試したこと
|
2
view側のソース追記、setProfileIconを呼び出してる箇所の追記
test
CHANGED
File without changes
|
test
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
### 前提
|
2
2
|
|
3
3
|
Reactでaxiosを使ってLaravel側に画像とそれに関連するデータをリクエストしようとしています。
|
4
|
+
|
5
|
+
https://codesandbox.io/s/react-uploady-crop-and-upload-with-react-easy-crop-5g7vw?file=/src/App.js
|
6
|
+
↑を参考にトリミングした画像をサーバー側に送ろうとしています。
|
4
7
|
|
5
8
|
### 実現したいこと
|
6
9
|
|
@@ -17,23 +20,256 @@
|
|
17
20
|
リクエストパラメータ
|
18
21
|

|
19
22
|
|
23
|
+
components/modules/crop/CropImage.js
|
20
24
|
```javascript
|
25
|
+
import React, { useState, useCallback } from "react";
|
26
|
+
import styled from "styled-components";
|
27
|
+
import Cropper from "react-easy-crop";
|
28
|
+
import getCroppedImg from "./CropUtils";
|
29
|
+
import "./Crop.css";
|
30
|
+
import { withTokenRequest, formData } from '../../../http';
|
31
|
+
import { PREVIEW_TYPES } from "@rpldy/upload-preview";
|
32
|
+
import {
|
33
|
+
withRequestPreSendUpdate,
|
34
|
+
useItemFinalizeListener,
|
35
|
+
useItemProgressListener
|
36
|
+
} from "@rpldy/uploady";
|
37
|
+
|
38
|
+
const PreviewButtons = ({
|
39
|
+
finished,
|
40
|
+
crop,
|
41
|
+
updateRequest,
|
42
|
+
onUploadCancel,
|
43
|
+
onUploadCrop
|
44
|
+
}) => {
|
45
|
+
return (
|
46
|
+
<ButtonsWrapper>
|
47
|
+
<button
|
48
|
+
style={{
|
49
|
+
display: !finished && updateRequest && crop ? "block" : "none"
|
50
|
+
}}
|
51
|
+
onClick={onUploadCrop}
|
52
|
+
>
|
53
|
+
Upload Cropped
|
54
|
+
</button>
|
55
|
+
<button
|
56
|
+
style={{
|
57
|
+
display: !finished && updateRequest && crop ? "block" : "none"
|
58
|
+
}}
|
59
|
+
onClick={onUploadCancel}
|
60
|
+
>
|
61
|
+
Cancel
|
62
|
+
</button>
|
63
|
+
</ButtonsWrapper>
|
64
|
+
);
|
65
|
+
};
|
66
|
+
|
67
|
+
/* API REQUEST */
|
21
|
-
function setProfileIcon(item) {
|
68
|
+
function setProfileIcon(item, formDataParam) {
|
22
69
|
const submitData = new FormData();
|
23
|
-
submitData.append("requestParameters", JSON.stringify({user_id:
|
70
|
+
submitData.append("requestParameters", JSON.stringify({user_id: localStorage.getItem('user_id')}));
|
24
71
|
submitData.append("image", item);
|
25
|
-
|
72
|
+
withTokenRequest.post('/setProfileIcon', submitData,
|
26
73
|
{
|
27
|
-
headers: {
|
28
|
-
|
74
|
+
headers: formDataParam // http.jsの定数
|
29
|
-
"Authorization": `トークン情報`,
|
30
|
-
}
|
31
75
|
})
|
32
76
|
.then((result) => {
|
33
77
|
console.log(result);
|
34
78
|
});
|
79
|
+
}
|
80
|
+
|
81
|
+
export const ItemPreviewWithCrop = withRequestPreSendUpdate((props) => {
|
82
|
+
const {
|
83
|
+
id,
|
84
|
+
url,
|
85
|
+
isFallback,
|
86
|
+
type,
|
87
|
+
updateRequest,
|
88
|
+
requestData,
|
89
|
+
previewMethods,
|
90
|
+
aspectProps,
|
91
|
+
api,
|
92
|
+
aspectControllButtonsVisible,
|
93
|
+
inputTextVisible
|
94
|
+
} = props;
|
95
|
+
|
96
|
+
formData.Authorization = `${localStorage.getItem('token_type')} ${localStorage.getItem('access_token')}`;
|
97
|
+
const [uploadState, setUploadState] = useState(UPLOAD_STATES.NONE);
|
98
|
+
const [croppedImg, setCroppedImg] = useState(null);
|
99
|
+
const [values, setValues] = useState(null);
|
100
|
+
const [aspect, setAspect] = useState(aspectProps);
|
101
|
+
|
102
|
+
//data for react-easy-crop
|
103
|
+
const [crop, setCrop] = useState({ x: 0, y: 0 });
|
104
|
+
const [zoom, setZoom] = useState(1);
|
105
|
+
const [croppedAreaPixels, setCroppedAreaPixels] = useState(null);
|
106
|
+
|
107
|
+
const onCropComplete = useCallback((croppedArea, croppedAreaPixels) => {
|
108
|
+
// console.log(croppedArea, croppedAreaPixels);
|
109
|
+
setCroppedAreaPixels(croppedAreaPixels);
|
110
|
+
}, []);
|
111
|
+
|
112
|
+
function handleChange(e) {
|
113
|
+
const target = e.target;
|
114
|
+
const value = target.value;
|
115
|
+
const name = target.name;
|
116
|
+
setValues({ ...values, [name]: value });
|
117
|
+
}
|
118
|
+
|
119
|
+
const isFinished = uploadState === UPLOAD_STATES.FINISHED;
|
120
|
+
|
121
|
+
useItemProgressListener(() => setUploadState(UPLOAD_STATES.UPLOADING), id);
|
122
|
+
useItemFinalizeListener(() => setUploadState(UPLOAD_STATES.FINISHED), id);
|
123
|
+
|
124
|
+
// Upload Cropped ボタン押下時イベント
|
125
|
+
const onUploadCrop = useCallback(async () => {
|
126
|
+
if (updateRequest && croppedAreaPixels) {
|
127
|
+
const [croppedBlob, croppedUri] = await getCroppedImg(
|
128
|
+
url,
|
129
|
+
croppedAreaPixels
|
130
|
+
);
|
131
|
+
|
132
|
+
requestData.items[0].file = croppedBlob;
|
133
|
+
|
134
|
+
updateRequest({ items: requestData.items });
|
135
|
+
switch (api) {
|
136
|
+
case 'setProfileIcon':
|
137
|
+
setProfileIcon(requestData.items[0].file, formData);
|
138
|
+
break;
|
139
|
+
default:
|
140
|
+
break;
|
141
|
+
}
|
142
|
+
}
|
143
|
+
}, [url, requestData, updateRequest, croppedAreaPixels]);
|
144
|
+
|
145
|
+
const onUploadCancel = useCallback(() => {
|
146
|
+
updateRequest(false);
|
147
|
+
if (previewMethods.current?.clear) {
|
148
|
+
previewMethods.current.clear();
|
149
|
+
}
|
150
|
+
}, [updateRequest, previewMethods]);
|
151
|
+
|
152
|
+
return isFallback || type !== PREVIEW_TYPES.IMAGE ? (
|
153
|
+
null
|
154
|
+
) : (
|
155
|
+
<>
|
156
|
+
{requestData && uploadState === UPLOAD_STATES.NONE ? (
|
157
|
+
<div className="crop-view">
|
158
|
+
<div className="crop-container">
|
159
|
+
<Cropper
|
160
|
+
image={url}
|
161
|
+
crop={crop}
|
162
|
+
zoom={zoom}
|
163
|
+
aspect={aspect}
|
164
|
+
onCropChange={setCrop}
|
165
|
+
onCropComplete={onCropComplete}
|
166
|
+
onZoomChange={setZoom}
|
167
|
+
/>
|
168
|
+
</div>
|
169
|
+
<div className="aspectControll" style={{ position: 'absolute', display: aspectControllButtonsVisible ? '' : 'none' }}>
|
170
|
+
<button onClick={() => setAspect(1/1)}>Square</button>
|
171
|
+
<button onClick={() => setAspect(5/4)}>Portrait</button>
|
172
|
+
<button onClick={() => setAspect(1/1.91)}>Landscape</button>
|
173
|
+
</div>
|
174
|
+
<div className="inputText" style={{ position: 'absolute', display: inputTextVisible ? '' : 'none' }}>
|
175
|
+
<textarea onChange={handleChange}></textarea>
|
176
|
+
</div>
|
177
|
+
<div className="controls">
|
178
|
+
<input
|
179
|
+
type="range"
|
180
|
+
value={zoom}
|
181
|
+
min={1}
|
182
|
+
max={3}
|
183
|
+
step={0.1}
|
184
|
+
aria-labelledby="Zoom"
|
185
|
+
onChange={(e) => {
|
186
|
+
setZoom(e.target.value);
|
187
|
+
}}
|
188
|
+
className="zoom-range"
|
189
|
+
/>
|
190
|
+
</div>
|
191
|
+
</div>
|
192
|
+
) : (
|
193
|
+
null
|
194
|
+
)}
|
195
|
+
<PreviewButtons
|
196
|
+
finished={isFinished}
|
197
|
+
crop={crop}
|
198
|
+
updateRequest={updateRequest}
|
199
|
+
onUploadCancel={onUploadCancel}
|
200
|
+
onUploadCrop={onUploadCrop}
|
201
|
+
/>
|
202
|
+
</>
|
203
|
+
);
|
204
|
+
});
|
205
|
+
```
|
206
|
+
|
207
|
+
http.js
|
208
|
+
```javascript
|
209
|
+
import axios from 'axios';
|
210
|
+
const apiUrl = "http://127.0.0.1:80/api";
|
211
|
+
export const formData = {
|
212
|
+
"Content-type": "multipart/form-data",
|
213
|
+
"Authorization": '',
|
35
214
|
}
|
215
|
+
export const withTokenRequest = axios.create({
|
216
|
+
baseURL:apiUrl,
|
217
|
+
})
|
36
|
-
```
|
218
|
+
```
|
219
|
+
|
220
|
+
comoponents/pages/accountsettings/ImageSettings.js
|
221
|
+
```javascript
|
222
|
+
import React, { useRef } from "react";
|
223
|
+
import Uploady from "@rpldy/uploady";
|
224
|
+
import UploadButton from "@rpldy/upload-button";
|
225
|
+
import UploadPreview from "@rpldy/upload-preview";
|
226
|
+
import "../../modules/crop/Crop.css";
|
227
|
+
import "../../modules/crop/CropImage";
|
228
|
+
import noImage from "../../../assets/img/no image/noimage.png"
|
229
|
+
import { ItemPreviewWithCrop, mockSenderEnhancer } from "../../modules/crop/CropImage";
|
230
|
+
|
231
|
+
const ImageSettings = () => {
|
232
|
+
return (
|
233
|
+
<>
|
234
|
+
<div style={mainContents}>
|
235
|
+
<Uploady
|
236
|
+
multiple={false}
|
237
|
+
destination={{ url: "[upload-url]" }}
|
238
|
+
>
|
239
|
+
<div className="IconSettings" style={iconSettings}>
|
240
|
+
Icon<br></br>
|
241
|
+
<img src={noImage} alt="picture" style={iconStyle}></img><br></br>
|
242
|
+
<UploadButton>SETTINGS</UploadButton>
|
243
|
+
<br />
|
244
|
+
<UploadPreview
|
245
|
+
PreviewComponent={ItemPreviewWithCrop} // CropImage.js の ItemPreviewWithCrop を呼び出します
|
246
|
+
previewComponentProps={{
|
247
|
+
previewMethods: previewMethodsRef,
|
248
|
+
aspect: 1 / 1,
|
249
|
+
api: 'setProfileIcon',
|
250
|
+
aspectControllButtonsVisible: false,
|
251
|
+
inputTextVisible: false }}
|
252
|
+
previewMethodsRef={previewMethodsRef}
|
253
|
+
/>
|
254
|
+
</div>
|
255
|
+
</Uploady>
|
256
|
+
</div>
|
257
|
+
</>
|
258
|
+
);
|
259
|
+
};
|
260
|
+
```
|
261
|
+
|
262
|
+
ディレクトリ構成 (見づらくてすみません)
|
263
|
+
components
|
264
|
+
┠ modules
|
265
|
+
┃ ┗crop
|
266
|
+
┃ ┠Crop.css
|
267
|
+
┃ ┠CropImage.js (実際にリクエストするところ)
|
268
|
+
┃ ┗CropUtils.js (画像生成処理など)
|
269
|
+
┠ pages
|
270
|
+
┃ ┗accountSettings
|
271
|
+
┃ ┗ImageSettings.js (setProfileIconの呼び出し元)
|
272
|
+
|
37
273
|
|
38
274
|
### 試したこと
|
39
275
|
|
1
タイトル修正
test
CHANGED
@@ -1 +1 @@
|
|
1
|
-
Laravelでmultipart/form-dataのリクエストが受け取れない
|
1
|
+
Laravelでmultipart/form-data(画像)のリクエストが受け取れない
|
test
CHANGED
File without changes
|