博主主要卡在了上传数据这一步
情景是这样的:
每一次只允许选择一张图片,每次从相册中选择一图片点击右上角确定后,立即发送请求,上传该图片,并且下次再点击时,重复这个动作。
(1)点击下图的上传资料
(2)点击红框内的按钮
(3)选择图片
(4)选择完毕的同时,上传图片到服务器(这边展示的图片是本地的,不是服务器那请求回来的)
上传图片的回调返回的Image信息:
{
creationDate: "1344408930"
cropRect: null
data: "/9j/4AAQSkZJRgABAQAASABIAAD/4QBYRXhpZgAATU0AKgAAAA"
exif: null
filename: "IMG_0005.JPG"
height: 2002
localIdentifier: "ED7AC36B-A150-4C38-BB8C-B6D696F4F2ED/L0/001"
mime: "image/jpeg"
modificationDate: "1552363036"
path: "/Users/ng/Library/Developer/CoreSimulator/Devices/CC28FB0A-09AA-4DEB-9633-F570FD1EDDE5/data/Containers/Data/Application/03FA20A9-374E-44E0-BACB-14FE9833296F/tmp/react-native-image-crop-picker/B0CD309A-4004-4B06-ADA6-92521584328F.jpg"
size: 4752033
sourceURL: "file:///Users/ng/Library/Developer/CoreSimulator/Devices/CC28FB0A-09AA-4DEB-9633-F570FD1EDDE5/data/Media/DCIM/100APPLE/IMG_0005.JPG"
width: 3000
}
我们可以看到,提供给我们的是本地的图片路径,还有base64,这边我们需要的自然是path的属性值啦,不过,IOS是不需要file:///的,android才需要,因此,这边需要做个代码适配
请求头上传类型Content-Type:multipart/form-data
我们可以看到如下的请求结构(Request Payload):
这是multipart/form-data类型的请求体数据,Content-Disposition是用来备注,提示我们的,而底下的[object Object]则是form-data数据啦,也就是我们真正要上传的图片、视频数据
上面的就是我们要上传的formData数据了,那我们打印出来会是什么样的呢?
成功发送请求后,返回一个fileId给我们:
完整代码
/**
* @flow
* @author
* @description 上传图片
*/
import React, { PureComponent } from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
Image,
TextInput,
ActivityIndicator,
} from 'react-native';
import ImagePicker from 'react-native-image-crop-picker';
import CommonModalView from '../../widget/CommonModalView';
import OASize, { Ratio } from '../../constants/OASize';
import OAColor from '../../theme/OAColor';
import OAStyles from '../../theme/OAStyles';
import { ButtonBase } from '../../components';
import API from '../../api';
import NetworkHandler from '../../utils/NetworkHandler';
import OAConstants from '../../constants/OAConstants';
import { system } from '../../utils';
import MOALog from '../../utils/MOALog';
import Toast from '../../widget/Toast';
type ITProps = {
contentId: number,
};
export default class ImageMaterialUploadContainer extends PureComponent<ITProps> {
constructor(props: Props) {
super(props);
this.state = {
imgList: [],
inputText: '',
isLoading: false,
};
}
componentDidMount() {
// const fetchHandler = new NetworkHandler(
// { api: '/partyAppDev/task/fileDownload' },
// { fileId: '06b258e1d802471c85a53e14c6fa7e3a' }
// );
// fetchHandler.get((res: any, error) => {
// MOALog.info('文件下载res', res, 'error', error);
// });
}
_handleUpload = () => {
// global.FilePicker.pick((files, type, other) => {
// console.log('FilePicker files:', files);
// this._getImgList([files[0]]);
// // [{}] 数组对象
// // 视频
// // {
// // height: 2232
// // mime: "video/mp4"
// // modificationDate: "1592807013000"
// // path: "file:///data/user/0/com.cmschina.partydevelopdev/cache/react-native-image-crop-picker/S00620-15080390.mp4"
// // size: 37277904
// // width: 1080
// // }
// // 相片
// // {
// // height: 2232
// // mime: "image/png"
// // modificationDate: "1592807163000"
// // path: "file:///data/user/0/com.cmschina.partydevelopdev/cache/react-native-image-crop-picker/S00621-14103571.png"
// // size: 373418
// // width: 1080
// // }
// });
const imgPickProps = {
loadingLabelText: '正在处理中...',
multipleChooseText: '完成',
multipleCancelText: '取消',
includeBase64: true,
};
CommonModalView.showActionsTextModal(
'',
[
{
id: 'photo',
name: '从相册选择',
},
{
id: 'material',
name: '从素材库选择',
},
{
id: 'camera',
name: '拍照',
},
{
id: 'video',
name: '选择视频',
},
],
(item, index) => {
switch (index) {
case 0:
setTimeout(() => {
ImagePicker.openPicker({
multiple: false,
mediaType: 'photo', //选择的类型
...imgPickProps,
})
.then((image) => {
console.log('image:', image);
this._handlePictureRes(image);
})
.catch((error) => {
console.log('error:', error);
// Toast.info(`您的图片无权限读取`);
});
}, 350);
break;
case 1:
mb.getNavigator().push('MaterialLibraryScene');
break;
case 2:
setTimeout(() => {
ImagePicker.openCamera({
cropping: false,
mediaType: 'photo', //选择的类型
...imgPickProps,
}).then((image) => {
// this._getImgList([image]);
this._handlePictureRes(image);
});
}, 350);
break;
case 3:
setTimeout(() => {
ImagePicker.openPicker({
mediaType: 'video', //选择的类型
// multiple: true,
// ...imgPickProps,
})
.then((image) => {
// this._getImgList([image]);
this._handlePictureRes(image);
})
.catch((error) => {
Toast.info(`您的视频无权限读取`, error);
});
}, 350);
break;
default:
}
}
);
};
_handlePictureRes = (image) => {
MOALog.info('_handlePictureRes image:', image);
this.setState({ isLoading: true });
let _fileName = image.filename || image.name;
let _path = image.path || image.sourceURL;
// [修复] android上传文件file路径需要`file://`
if (system.isIOS && /^file:\/\//i.test(_path)) {
_path = _path.replace('file://', '');
} else if (system.isAndroid && !/^file:\/\//i.test(_path)) {
_path = 'file://' + _path;
}
if (!_fileName) {
_fileName = _path.match(/[^\/]+$/)[0];
}
if (image.size > OAConstants.MAX_ATTACHMENT_SIZE) {
Toast.info(
`附件“${image.filename}”无法添加:\n单个附件的大小不能超过10M`
);
this.setState({ isLoading: false });
return false;
}
// 上传附件
console.log('fetchHandler url:', _path);
const fetchHandler = new NetworkHandler({
api: API.home.uploadFile,
// api: 'https://oams.newone.com.cn/api/email/attachment/upload',
});
fetchHandler.upload({ uri: _path, name: _fileName }, (res: any, error) => {
MOALog.info('res, error===>', res, error);
if (error) {
this.setState({ isLoading: false });
Toast.info(error);
return;
}
this._getImgList([image], res);
this.setState({ isLoading: false });
});
};
_getImgList = (image, res) => {
console.log('FilePicker image:', image);
let { imgList } = this.state;
if (image.length > 6) {
return Toast.info('添加的图片不超过6张');
}
const list = image.map((item) => {
let list = {
path: item.path,
creationDate: item.creationDate,
data: item.data ? `data:${item.mime};base64,${item.data}` : item.path,
height: item.height,
width: item.width,
imgData: item.data,
imgName: item.mime,
fileIds: res, // 关键,成功上传后获取到的图片的唯一标志
};
return list;
});
imgList = [...imgList, ...list];
this.setState({
imgList,
});
};
_uploadImg = () => {
const { imgList, inputText } = this.state;
const { contentId } = this.props;
const arr = [];
imgList &&
imgList.length &&
imgList.forEach((img) => {
arr.push(img.fileIds);
});
MOALog.info(
'replyTask imgList',
imgList,
'replyTask files',
arr,
'contentId',
contentId
);
// 附件上传后,一次性提交
const fetchHandler = new NetworkHandler(
{ api: API.home.replyTask },
{ replyExplain: inputText, files: arr, contentId }
);
fetchHandler.post((res: any, error) => {
MOALog.info('item, index', res, 'replyTask error', error);
if (error) {
return;
}
mb.getNavigator().pop();
});
};
_removeImgItem = (item, index) => {
this.setState({ isLoading: true });
MOALog.info('_removeImgItem item, index', item, index);
// 附件删除
const fetchHandler = new NetworkHandler(
{ api: API.home.delFile },
{ fileId: item.fileIds.fileId }
);
fetchHandler.get((res: any, error) => {
// 真正删除成功时,才删除对应数组元素
this.state.imgList.splice(index, 1);
this.setState({
list: this.state.imgList,
isLoading: false,
});
MOALog.info('res', res, 'error', error);
});
};
_onChangeText = (v) => {
this.setState({ inputText: v });
};
render() {
const { imgList, isLoading } = this.state;
return (
<View style={styles.imgPick}>
<Text
style={{
...OAStyles.font,
marginBottom: OASize(5),
fontSize: OASize(16),
fontWeight: 'bold',
}}
>
说明:
</Text>
<TextInput
onChangeText={this._onChangeText}
multiline
// autoFocus
maxLength={100}
numberOfLines={3}
placeholder="请输入说明内容..."
style={{
height: OASize(80),
marginBottom: OASize(30),
backgroundColor: 'rgba(0, 0, 0, 0.05)',
}}
/>
<View style={{ flexDirection: 'row' }}>
<Text style={styles.text_label}>资料上传</Text>
<Text style={{ color: '#666', fontSize: 15 * Ratio }}>(选填)</Text>
</View>
<View style={{ flexDirection: 'row', flexWrap: 'wrap' }}>
<TouchableOpacity onPress={this._handleUpload}>
<Image
resizeMode="contain"
source={require('../../assets/app/pic_add.png')}
style={styles.itemImg}
/>
</TouchableOpacity>
{imgList.length
? imgList.map((item, index) => {
return (
<View key={index}>
<TouchableOpacity
style={{
position: 'absolute',
right: 4,
top: 0,
zIndex: 100,
}}
activeOpacity={0.88}
onPress={() => this._removeImgItem(item, index)}
>
<Image
resizeMode="contain"
source={require('../../assets/app/pic_del.png')}
style={{
width: 16,
height: 16,
}}
/>
</TouchableOpacity>
<Image
// resizeMode="contain"
source={{ uri: item.data }}
style={styles.itemImg}
// style={{
// width: 100,
// height: 100
// }}
/>
</View>
);
})
: null}
{isLoading ? (
<View
style={[
{
justifyContent: 'center',
alignItems: 'center',
},
styles.itemImg,
]}
>
<ActivityIndicator />
</View>
) : null}
</View>
<ButtonBase
textStyle={{ color: OAColor.white }}
outline={OAColor.primary}
style={{
minWidth: OASize(80),
marginTop: OASize(15),
backgroundColor: '#499ad0',
borderWidth: 0,
}}
onPress={(_) => {
// mb.getNavigator().push('ImageMaterialUploadScene', {
// listTitle: '三会一课(第一部分)',
// });
this._uploadImg();
}}
>
提交
</ButtonBase>
</View>
);
}
}
const styles = StyleSheet.create({
imgPick: {
paddingVertical: 10 * Ratio,
paddingHorizontal: OASize(15),
},
text_label: { color: '#333', fontSize: 15 * Ratio },
itemImg: {
width: 70 * Ratio,
height: 70 * Ratio,
marginVertical: 10 * Ratio,
marginRight: 12 * Ratio,
},
});