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

 

热门文章

暂无图片
编程学习 ·

oracle创建索引语句

oracle : 单索引 create index 索引名称 on table(column)删除索引 drop index 索引名称复合索引 create index WBSINDEX ON project_info(wbs,is_delete)查询某张表中所有索引 select * from ALL_INDEXS where table_name = project_info查询某张表加了索引的列 select * from…
暂无图片
编程学习 ·

vue+element-ui JYAdmin后台管理系统模板-集成方案【项目搭建篇1】

项目搭建时间:2020-06-29 本章节:讲述基于vue/cli, 项目的基础搭建。 本主题讲述了vue+element-ui JYAdmin 后台管理系统模板-集成方案,从零到一的手写搭建全过程。 该项目不仅是一个持续完善、高效简洁的后台管理系统模板,还是一套企业级后台系统开发集成方案,致力…
暂无图片
编程学习 ·

命令模式

菜鸟教程中代理模式总结)1.定义:将请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令 的合适的对象,并把该命令传给相应的对象,该对象执行命令。 2.主要解决:行为请求者与行为实现者通常是一种紧耦合的关系,为了消除这种耦合关系 3.何时使用:命…
暂无图片
编程学习 ·

2020上半年为你留下了什么?

今天是2020年6月30号,2020年上半年的最后一天.蓦然回首,发现时光过得飞快,记忆中那憋在家里,数着日子,焦虑不安的情景还历历在目.那时候最大的愿望就是像电视中那样:一觉醒来,半年过去了,大家又都按部就班没羞没躁的生活着.好多文人墨客都会鼓吹:每种经历都是人生的财富,人生没…
暂无图片
中恒嘉业 ·

关于主从复制的超详细解析(全)

目录前言1. 主从复制1.1 方式2. Mysql的主从复制2.1 一主一从2.1.1 window和linux通讯2.1.2 linux和linux的通讯2.2 双主双从3. Redis的主从复制3.1 哨兵模式3.2 java代码结合前言 主要介绍mysql的主从复制以及redis的主从复制 能由浅入深的明白原理以及如何操作 再者&#xf…
暂无图片
郑州普通话 ·

android直播框架,Android事件分发机制及设计思路

大厂面试的时候,都会问哪些技术? 最常问的就是四大组件、Binder、网络编程等基本知识点,也会问热修复、换肤、自定义动画等项目实战操作,因此你得要做好方方面面的准备才行。而且BATJ的面试官特别喜欢抓着一个知识点一直问,问到你不会为止。所以精通一个或多个知识点,比博…
暂无图片
郑州普通话 ·

android热更新流程,kotlin高阶函数使用场景

工作2-5年的Android程序员该何去何从?方向:深入学习Android现在流行技术;浴火重生 Android,在占比80%市场为代表的智能手机的普及和发展,互联网行业如火如荼的进入了“移动”时代。但是近几年随着市场的逐渐成熟,整个移动互联网行业正处于增量下降丶存量厮杀的阶段。面对技…
暂无图片
代理记账 ·

在web应用中发送和接收Jakarta消息

Running the websimplemessage Example To Package and Deploy websimplemessage Using Maven _1、Make sure that GlassFish Server has been started (see Starting and Stopping GlassFish Server). _2、In a terminal window, go to: tut-install/examples/jms/websimp…
暂无图片
cgfy ·

C++学习日记2——函数、封装、对象特性

一、函数 1.1 函数默认参数 1.1.1 简介 在C中&#xff0c;函数的形参列表中的形参是可以有默认值的 1.1.2 语法 返回值类型 函数名 (参数 默认值) {} 1.1.3 代码 #include <iostream> using namespace std;// 函数的默认参数 int func(int a, int b 20, int c 30…
暂无图片
coreui ·

视频水印怎么去除?超简单 千万不要错过

小编在知乎看到很多大佬分享的视频去水印的方法&#xff0c;但是感觉都有点太复杂了&#xff0c;今天就来分享一下小编自己私藏的几个针对于视频去水印的软件和网站~建议大家收藏哦~ 1、爱给网-视频去水印小工具&#xff08;免费 在线&#xff09; 推荐点 1、在线操作&#…
暂无图片
coreui ·

Mac 安装 tomcat10

Mac 安装 tomcat10 1、下载tomcat tomcat官网&#xff1a;https://tomcat.apache.org/ 点击我下载的tomcat10&#xff1a; 2、下载解压,给bin下的*.sh文件添加可执行权限 3、修改webapps下的ROOT中的index文件查看效果
暂无图片
未来博客 ·

视频水印怎么去除?超简单 千万不要错过

小编在知乎看到很多大佬分享的视频去水印的方法&#xff0c;但是感觉都有点太复杂了&#xff0c;今天就来分享一下小编自己私藏的几个针对于视频去水印的软件和网站~建议大家收藏哦~ 1、爱给网-视频去水印小工具&#xff08;免费 在线&#xff09; 推荐点 1、在线操作&#…
暂无图片
未来博客 ·

Mac 安装 tomcat10

Mac 安装 tomcat10 1、下载tomcat tomcat官网&#xff1a;https://tomcat.apache.org/ 点击我下载的tomcat10&#xff1a; 2、下载解压,给bin下的*.sh文件添加可执行权限 3、修改webapps下的ROOT中的index文件查看效果
暂无图片
建站日记 ·

惠州实验室建设选址、勘察事项

惠州实验室建设选址、勘察事项&#xff0c;SICOLAB技术员带您从实验室建设启动前思考问题考虑如下&#xff1a;一、不同实验室建设选址要求 1.化学实验室 &#xff08;1&#xff09;清洁安静环境 &#xff08;2&#xff09;远离住宅、生活区 &#xff08;3&#xff09;锅炉房与…
暂无图片
建站日记 ·

NLP聊天机器人原理(seq2seq模型)

一、seq2seq模型 1.概念 seq2seq是一个Encoder-Decoder结构的网络&#xff0c;它的输入是一个序列&#xff0c;输出也是一个序列。Encoder中将一个可变长度的信号序列变为固定长度的向量表达&#xff0c;Decoder将这个固定长度的向量变成可变长度的目标的信号序列。这个结构最…
暂无图片
mfbz ·

惠州实验室建设选址、勘察事项

惠州实验室建设选址、勘察事项&#xff0c;SICOLAB技术员带您从实验室建设启动前思考问题考虑如下&#xff1a;一、不同实验室建设选址要求 1.化学实验室 &#xff08;1&#xff09;清洁安静环境 &#xff08;2&#xff09;远离住宅、生活区 &#xff08;3&#xff09;锅炉房与…
暂无图片
mfbz ·

全渠道会员通-天猫会员通3: 会员运营内容准备

在天猫会员通技术对接开发过程中&#xff0c;为了通知存量会员的通知工作&#xff0c;发挥会员通的优势&#xff0c;品牌需要做好以下事宜&#xff1a; 会员体系暂停公告&#xff1a;因会员通技术升级期间&#xff0c;会员服务将被暂停&#xff0c;店铺tab中会员入口将被下线&…
暂无图片
珊珊日记 ·

C# 执行Javascript脚本

c#教程https://www.xin3721.com/eschool/CSharpxin3721/ 前一阵子使用C#编写SCXML状态机&#xff0c;需要解析EMCScript表达式&#xff0c;使用了Jint库&#xff08;https://github.com/sebastienros/jint/)&#xff0c;当时感觉与C#之间的数据转换不是很方便。这两天有时间又关…
暂无图片
珊珊日记 ·

第九届“图灵杯”NEUQ-ACM程序设计竞赛个人赛

A.大学期末现状 题目描述 作为一名大学生的你&#xff0c;现在又到了期末查成绩的时候&#xff0c;当你的成绩大于等于60时请输出“jige,haoye!”,否则输出"laoshi,caicai,laolao"。 输入描述: 一行&#xff0c;一个整数x代表你的成绩&#xff08;0<x<100&a…