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

 

热门文章

暂无图片
编程学习 ·

从入门到删库跑路的过程

数据库简介数据库的发展史萌芽阶段:文件系统使用磁盘文件来存储数据初级阶段:第一代数据库出现了网状模型、层次模型的数据库中级阶段:第二代数据库关系型数据库和结构化查询语言高级阶段:新一代数据库关系-对象 型数据库NoSQL非关系数据库:Not Only SQL 数据库管理系统 数…
暂无图片
编程学习 ·

树莓派4B介绍及其系统安装 入门教程(一)

树莓派4B介绍及其系统安装 入门教程(一)树莓派介绍系统下载安装连接外设启动后续计划入门进阶扩展参考资料 树莓派介绍 树莓派介绍可以参考链接: 树莓派介绍。里面介绍的很详细了,这里就不重复讲了,也可以去树莓派官方网站下载它的参数资料,里面也有很多利用树莓派设计制作…
暂无图片
编程学习 ·

NC6 基于元数据的持久化服务接口实现类

基于元数据的持久化服务接口实现类: package nc.md.persist.framework.imp;import java.util.Collection;import nc.md.data.access.NCObject; import nc.md.data.criterion.QueryCondition; import nc.md.model.MetaDataException; import nc.md.persist.framework.IMDPersis…
暂无图片
编程学习 ·

云原生已来,只是分布不均

作者 | 右京 阿里云交付专家 **导读:**云原生是什么?相信不同的人有不同的认识和解读。本文结合大家的各种讨论及项目实践经验,从交付的角度,分享阿里交付专家对云原生的理解,阐述如何构建云原生应用,云原生有哪些关键技术,以及关于云原生落地的思考。 前言 Internet 改…
暂无图片
编程学习 ·

制药业中的自然语言处理(NLP)

文章目录NLP 用于发现新药物化合物NLP 用于将参与者纳入临床试验药品营销的 NLP参考资料 转载来源:https://zhuanlan.zhihu.com/p/140044281自然语言处理(NLP)在制药业的使用似乎少于机器视觉和预测分析等 AI 方法,但尽管如此,NLP 在制药业仍有一些应用。该行业主要处理结…
暂无图片
编程学习 ·

LeetCode题解(0762):二进制表示中质数个计算置位(Python)

题目:原题链接(简单)解法 时间复杂度 空间复杂度 执行用时Ans 1 (Python) O(N)O(N)O(N) O(1)O(1)O(1) 200ms (99.05%)Ans 2 (Python)Ans 3 (Python)LeetCode的Python执行用时随缘,只要时间复杂度没有明显差异,执行用时一般都在同一个量级,仅作参考意义。解法一:【思路】…
暂无图片
编程学习 ·

springboot 整合xcf 发布 webservice

Spring Boot集成webService在pom添加依赖<!--WerbService CXF依赖 start--> <dependency><groupId>org.apache.cxf</groupId><artifactId>cxf-rt-frontend-jaxws</artifactId> </dependency> <dependency><groupId>org.…
暂无图片
编程学习 ·

vue中实现一个搜索框的组件

在前端开发中有些东西就会经常反复使用,这样的东西抽取成组件比较合适,最近工作中遇到一个搜索楼盘的页面需要反复多次使用,抽取成了组件,现在记录一下1.创建一个searchcom.vue文件2.文件中填入一下代码,具体内容在代码后边进行解释<template> <div><heade…
暂无图片
编程学习 ·

MyBatis 结构拆解

MyBatis 的执行流程大概可以拆分为如下几个部分:初始化配置解析 mybatis-config.xml 文件 根据 mybatis-config.xml 文件中的配置,依次解析 Mapper.xml 文件 将 Mapper.xml 与 接口 通过 xml 文件的 namespace 属性来进行绑定**【重点】**;该篇有介绍 XML 文件和 接口进行绑…
暂无图片
编程学习 ·

MySQL配置文件

MySQL配置文件 1.配置环境变量 新建MYSQL_HOME变量,变量值是包的路径。 2.然后再path中添加:%MYSQL_HOME%\bin 3.执行mysqld install命令当出现Service successfully installed时表示mysql服务安装完成 4.MySQL初始化 :输入: mysqld --initialize --console 执行完成后,会…
暂无图片
编程学习 ·

人工智能常用数据预处理

人工智能常用数据预处理一级目录正态化、标准化、归一化、正则化区别和作用 一级目录 1.读数据 2.合并训练和测试 2.填充空白数据 4.改变非数字为数字 5.去除无关数据 6.降为(合并相关数据) 7.正态化数据(碗圆) 正态化、标准化、归一化、正则化区别和作用 1.正态化归一化是…
暂无图片
编程学习 ·

mxnet安装环境配置

一、安装Miniconda 官方网址:https://conda.io/en/latest/miniconda.html 本人选择python3.7版本Windows64位 安装完成后打开Anaconda Prompt创建虚拟环境conda create –n env python=3.7 这里的env为自定义环境名激活环境 conda activate env 退出环境: conda deactivate查…
暂无图片
编程学习 ·

vue 插件大全

vue 插件大全 Vue是一个构建数据驱动的 web 界面的渐进式框架。Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件特别整理了常用的vue插件,来了个大汇总,方便查找使用,便于工作和学习。很全的vue插件汇总,赶紧收藏下吧! 一、UI组件及框架element …
暂无图片
编程学习 ·

Java调起手机电脑摄像头

一、直接上代码 要导入的maven<!-- java调用摄像头 --><!-- https://mvnrepository.com/artifact/org.bytedeco/javacv-platform --><dependency><groupId>org.bytedeco</groupId><artifactId>javacv-platform</artifactId><vers…
暂无图片
编程学习 ·

谷粒商城-elasticsearch

1. elasticsearch基本操作 1.1. 基本概念 Elasticsearch也是基于Lucene的全文检索库,本质也是存储数据,很多概念与MySQL类似的。 对比关系: 索引(indices)----------------------Databases 数据库类型(type)--------------------------Table 数据表文档(Document)--…
暂无图片
编程学习 ·

python的PIL库的使用

①缩放图像 from PIL import Image //要引入anaconda库im = Image.open(7.jpg) w, h = im.size //获取图像的宽度、高度 print(image size is %sx%s %(w, h)) im.thumbnail((w//2, h//2)) //缩小图像为原来的1/2 im.save(good.jpg, jpeg) //以jpeg的形式保存图像②模糊图像 …
暂无图片
编程学习 ·

大数据在金融业有哪些应用?

金融行业会运用到很多大数据,但是大数据也会有很多方面的应用。下面来看看大数据在金融行业的应用都是什么。 根据数据显示,中国大数据IT应用投资规模以五大行业最高,其中以互联网行业占比最高,占大数据IT应用投资规模的28.9%,其次是电信领域(19.9%),第三为金融领域(17.5…
暂无图片
编程学习 ·

Java使用APT定义注解处理器

文章目录自定义编译时注解处理器APT简介自定义注解处理器创建一个注解实现注解处理器添加tools.jar添加SPImaven打包使用自定义的注解处理器新建项目IDEA设置遇到的问题 自定义编译时注解处理器 使用IDEA构建的maven项目。 项目github地址 注解的保留类型 @Retention(Retention…