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

 

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