react-native 使用react-native-image-crop-picker上传图片、视频到服务端

博主主要卡在了上传数据这一步

情景是这样的:

每一次只允许选择一张图片,每次从相册中选择一图片点击右上角确定后,立即发送请求,上传该图片,并且下次再点击时,重复这个动作。

(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,
  },
});

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章