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

 

展开阅读全文

Python数据分析与挖掘

01-08
92讲视频课+16大项目实战+源码+¥800元课程礼包+讲师社群1V1答疑+社群闭门分享会=99元   为什么学习数据分析?       人工智能、大数据时代有什么技能是可以运用在各种行业的?数据分析就是。       从海量数据中获得别人看不见的信息,创业者可以通过数据分析来优化产品,营销人员可以通过数据分析改进营销策略,产品经理可以通过数据分析洞察用户习惯,金融从业者可以通过数据分析规避投资风险,程序员可以通过数据分析进一步挖掘出数据价值,它和编程一样,本质上也是一个工具,通过数据来对现实事物进行分析和识别的能力。不管你从事什么行业,掌握了数据分析能力,往往在其岗位上更有竞争力。    本课程共包含五大模块: 一、先导篇: 通过分析数据分析师的一天,让学员了解全面了解成为一个数据分析师的所有必修功法,对数据分析师不在迷惑。   二、基础篇: 围绕Python基础语法介绍、数据预处理、数据可视化以及数据分析与挖掘......这些核心技能模块展开,帮助你快速而全面的掌握和了解成为一个数据分析师的所有必修功法。   三、数据采集篇: 通过网络爬虫实战解决数据分析的必经之路:数据从何来的问题,讲解常见的爬虫套路并利用三大实战帮助学员扎实数据采集能力,避免没有数据可分析的尴尬。   四、分析工具篇: 讲解数据分析避不开的科学计算库Numpy、数据分析工具Pandas及常见可视化工具Matplotlib。   五、算法篇: 算法是数据分析的精华,课程精选10大算法,包括分类、聚类、预测3大类型,每个算法都从原理和案例两个角度学习,让你不仅能用起来,了解原理,还能知道为什么这么做。
©️2020 CSDN 皮肤主题: 大白 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值