精华内容
下载资源
问答
  • 2022-04-07 16:27:07

    微信小程序实现图片上传

    最近做了个小程序,涉及到了图片上传的功能,今天给大家详细介绍下如何实现小程序图片上传,话不多说先上代码

    首先是静态布局和样式部分

    .wxml代码部分

    <view class='load-img'>
        <view class='load-box'>
          <view class='img-item' wx:for="{{fileList}}" wx:key="index" >
            <image src="{{item.path}}" data-src="{{item}}" mode="aspectFill" data-list="{{fileList}}" bindtap=""></image>
            <icon class='icon' type="clear" size="20" color='#EF4444' catchtap='_onDelTab' data-idx="{{index}}" wx:if="{{!prevent}}"/>
          </view>
          <image class='img-add' bindtap='_addImg' wx:if="{{!prevent}}"></image>
        </view>
      </view>
    

    .wxss代码部分

    /* 上传图片 */
    .load-name {
        height: 80rpx;
        line-height: 80rpx;
        font-size: 30rpx;
      }
      .load-box {
        display: flex;
        flex-direction: row;
        flex-wrap: wrap;
      }
      .img-item, .img-add {
        position: relative;
        width: 140rpx;
        height: 140rpx;
        margin: 20rpx;
      }
      .img-add {
        border: 1px solid #ccc;
      }
      .img-add:after{
        width: 1rpx;
        height: 50rpx;
        content: " ";
        position: absolute;
        top: 50%;
        left: 50%;
        -webkit-transform: translate(-50%, -50%);
        -ms-transform: translate(-50%, -50%);
        transform: translate(-50%, -50%);
        background-color: #ccc;
      }
      .img-add:before{
        position: absolute;
        top: 50%;
        right: 31%;
        width: 50rpx;
        height: 1rpx;
        content: " ";
        display: inline-block;
        background-color: #ccc;  
      }
      
      .img-item {
        margin-right: 20rpx;
      }
      .img-item image {
        width: 100%;
        height: 100%;
        border-radius: 10rpx;
      }
      .icon {
        position: absolute;
        top: 0;
        right: 0;
      }
    

    以上这些基本代码就可以完成图片上传,显示,删除等样式布局

    下面是js的部分,我已详细备注~~~

    先来看下完整的代码

    /**
     * 小程序图片上传
     * 组件接受参数
     * fileList  图片数组
     * prevent 控制是否可新增
     * 方法
     * bindimageChange 选择图片后触发
     * bindimageDel  删除图片后触发
     * 
     */
    const app = getApp();
    Component({
      properties: {
        fileList: {
          type: Array
        },
        prevent: {
          type: Boolean,
          value: false
        }
      },
      data: {
        fileList: []
      },
      ready() {},
      methods: {
        // 点击加号进入手机相册,并进行图片选择
        _addImg() {
          let _this = this;
          // 此方法为微信小程序自带api 详情访问https://developers.weixin.qq.com/miniprogram/dev/api/media/image/wx.chooseImage.html
          wx.chooseImage({
            count: 5,
            success(res) {
              //此处会返回图片暂存路径和文件大小
              const data = res.tempFiles;
              _this.setFile(data)
            }
          })
        },
        setFile (data) {
          // 将wx.chooseImage返回的数据进行扩展
          data.map((item, index) => {
            // 通过路径截取文件后缀名
            const fileFormat = item.path.substring(item.path.lastIndexOf(".") + 1, item.path.length);
            // wx.getFileSystemManager()小程序文件管理器api,可以将通过文件路径将其转换成64编码
            const fileManager = wx.getFileSystemManager();
            const base64 = fileManager.readFileSync(item.path, 'base64');
            item.fileContent = base64;
            item.fileSize = item.size;
            // 通过时间获取随机13位随机数并且拼接文件后缀进行文件命名
            item.fileName = this.getFileName(13) + '.' + fileFormat;
            // 此处操作是用来进行选中图片显示的,只有这样拼接才能显示base64编码的路径
            item.path = `data:image/${fileFormat};base64,${base64}`;;
          })
          this.setData({ 
            fileList: this.data.fileList.concat(data)
          });
          // 此处操作是用来将获取到的文件数据传递给父组件进行文件上传
          this.triggerEvent('imageChange', this.data.fileList)
        },
        // 随机生成文件名
        getFileName (m) {
          m = m > 13 ? 13 : m;
          var num = new Date().getTime();
          return num.toString().substring(13 - m);
        },
        点击进行图片删除
        _onDelTab(e) {
          // 获取图片索引
          let idx = e.currentTarget.dataset.idx;
          let delFile = this.data.fileList[idx];
          console.log(delFile);
          this.data.fileList.splice(idx, 1);
          this.setData({
            fileList: this.data.fileList
          })
          this.triggerEvent('imageDel', delFile);
        }
    })
    

    代码里对代码的备注已经很明确了,大家仔细扒一下,根据的自己的项目进行相应的调整,基本上都是没问题的,~~不要直接直接粘贴不复置,我是直接在我的项目中直接拿过来的代码,直接粘贴复制肯定是不行的!!!~~

    大家需要注意的是这里

    在这里插入图片描述
    通常在真机上点击选中图片后wx.chooseImage方法中返回的文件路径是wxfile:开头的路径,这样的路径想直接转成base64,上面的方式是可以实现的,我也是查了很多资料才找到的解决办法。

    再一个需要注意的是image src属性想显示base64格式的图片要进行字符串拼接才可以正常显示如下图

    在这里插入图片描述
    好啦这些就是我小程序上传图片的操作,这些只是我对日常工作的积累,不喜勿喷 不喜勿喷 不喜勿喷 重要的事情说三遍大家仔细看下代码理解用法,还是很简单的,看都不看想白漂肯定是不行的呦~~~~~~~~~

    更多相关内容
  • html5移动端图片上传本地生成缩略图预览,单图上传
  • PHP+ajax图片上传的简单实现。 使用js实现ajax,PHP上传图片成功以后返回图片的路径(弹窗出来)。 参考文档:http://blog.csdn.net/u014175572/article/details/51062856
  • 一个好用的jquery多图片上传插件

    热门讨论 2012-03-08 14:04:12
    swfupload是比较著名的图片上传工具,可以多图片上传,在一次开发中偶遇uploadify,比swfupload还强大好用,选项也很丰富,功能也很强大,再配合ajax技术,可以把多图片上传发挥到极致,我曾用它做多图片上传,上传...
  • 安卓自定义View实现图片上传进度

    热门讨论 2015-05-12 13:56:28
    安卓自定义View实现图片上传进度,仿qq聊天发送图片时进度显示
  • springboot 实现图片上传功能

    千次阅读 2022-01-18 10:52:43
    springboot 实现图片上传功能

    springboot 实现图片上传功能

       这几天在做重构学校的图书馆项目,用sprinboot重新搭建项目,原项目是使用PHP搭建的,刚开始看着挺懵的,慢慢的就看懂。这个项目中遇到的难题是照片上传功能,弄了挺久才实现出来。

       此功能没有导入如何jar包。

       这里介绍呢,我就按照项目本身的结构来实现这个功能。

       首先是数据库的数据结构,项目先是通过照片名(xxx.jpg)保存到数据库(picurl).
       故我的想法是通过新书的名字,获取到这个新书对应的照片名,进行对上传上的照片进行名字的修改。
    在这里插入图片描述

    1.Mapper层

    	<!--上传图片-->
    	<select id="changeAvatar" resultType="NewBooks">
    	    select * from newbooks where title=#{title}
    	</select>
    

    2.Service层

    @Override
    public NewBooks changePicUrl(String title) {
        //根据参数调用方法
        NewBooks newBooks = newBooksMapper.changeAvatar(title);
        if (newBooks == null){
            throw new NewsBookNotException("书库没有该书数据,请先添加!");
        }
        return newBooks;
    }
    

    3.自定义异常

    3.1service层自定义异常

       为了便于统一管理自定义异常,在service包中创一个ex包用来放自定义异常的基类异常,继承自RuntimeException类,并从父类生成子类的五个构造方法。
    在这里插入图片描述创建NewsBookNotException类,当用户输入到的书名,在数据库中没有找到数据,则会出对应的信息。
    在这里插入图片描述

    3.2 上传文件自定义异常

    处理异常

    1.在处理上传文件的过程中,用户可能会选择错误的文件上传,此时就应该抛出对应的异常并进行处理。所以需要创建文件上传相关异常的基类,即在com.cy.store.controller.ex包下创建FileUploadException类,并继承自RuntimeException类。

    package com.pzlib.service.ex;
    
    /** 文件上传相关异常的基类 */
    public class FileUploadException extends RuntimeException {
        public FileUploadException() {
            super();
        }
    
        public FileUploadException(String message) {
            super(message);
        }
    
        public FileUploadException(String message, Throwable cause) {
            super(message, cause);
        }
    
        public FileUploadException(Throwable cause) {
            super(cause);
        }
    
        protected FileUploadException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
            super(message, cause, enableSuppression, writableStackTrace);
        }
    }
    

    2.在处理上传的文件过程中,经分析可能会产生以下异常。这些异常类都需要继承自FileUploadException类。

    // 上传的文件为空
    controller.ex.FileEmptyException
    // 上传的文件大小超出了限制值
    controller.ex.FileSizeException
    // 上传的文件类型超出了限制
    controller.ex.FileTypeException
    // 上传的文件状态异常
    controller.ex.FileStateException
    // 上传文件时读写异常
    controller.ex.FileUploadIOException
    

    3.创建FileEmptyException异常类,并继承FileUploadException类。

    package com.pzlib.service.ex;
    
    /** 上传的文件为空的异常,例如没有选择上传的文件就提交了表单,或选择的文件是0字节的空文件 */
    public class FileEmptyException extends FileUploadException {
        // Override Methods...
    }
    

    4.创建FileSizeException异常类,并继承FileUploadException类。

    package com.pzlib.service.ex;
    
    /** 上传的文件的大小超出了限制值 */
    
    
    public class FileSizeException extends FileUploadException {
        // Override Methods...
    }
    

    5.创建FileTypeException异常类,并继承FileUploadException类。

    package com.pzlib.service.ex;
    /** 上传的文件类型超出了限制 */
    public class FileTypeException extends FileUploadException {
        // Override Methods...
    }
    

    6.创建FileStateException异常类,并继承FileUploadException类。

    package com.pzlib.service.ex;
    
    /** 上传的文件状态异常 */
    public class FileStateException extends FileUploadException {
        // Override Methods...
    }
    

    7.创建FileUploadIOException异常类,并继承FileUploadException类。

    package com.pzlib.service.ex;
    
    /** 上传文件时读写异常 */
    public class FileUploadIOException extends FileUploadException {
        // Override Methods...
    }
    

    8.然后在BaseController的handleException()的@ExceptionHandler注解中添加FileUploadException.class异常的处理;最后在方法中处理这些异常。

    @ExceptionHandler({ServiceException.class, FileUploadException.class})
    public JsonResult<Void> handleException(Throwable e) {
        JsonResult<Void> result = new JsonResult<Void>(e);
        if (e instanceof UsernameDuplicateException) {
            result.setState(4000);
        } else if (e instanceof UserNotFoundException) {
            result.setState(4001);
        } else if (e instanceof PasswordNotMatchException) {
            result.setState(4002);
        } else if (e instanceof InsertException) {
            result.setState(5000);
        } else if (e instanceof UpdateException) {
            result.setState(5001);
        } else if (e instanceof FileEmptyException) {
            result.setState(6000);
        } else if (e instanceof FileSizeException) {
            result.setState(6001);
        } else if (e instanceof FileTypeException) {
            result.setState(6002);
        } else if (e instanceof FileStateException) {
            result.setState(6003);
        } else if (e instanceof FileUploadIOException) {
            result.setState(6004);
        }
        return result;
    }
    

    4.BaseController层

    package com.pzlib.controller;
    
    
    import com.pzlib.controller.ex.*;
    import com.pzlib.service.ex.*;
    import com.pzlib.util.JsonResult;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    
    /** 控制器类的基类 */
    public class BaseController {
        /** 操作成功的状态码 */
        public static final int OK = 200;
    
        /** @ExceptionHandler用于统一处理方法抛出的异常 */
        @ExceptionHandler({ServiceException.class,FileUploadException.class})
        public JsonResult<Void> handleException(Throwable e) {
            JsonResult<Void> result = new JsonResult<Void>(e);
            if (e instanceof UsernameDuplicateException) {
                result.setState(4000);
            } else if (e instanceof UserNotFoundException) {
                result.setState(4001);
            } else if (e instanceof PasswordNotMatchException) {
                result.setState(4002);
            } else if (e instanceof NewsBookNotException) {
                result.setState(4003);
            }else if (e instanceof UpdateException) {
                result.setState(5001);
             }else if (e instanceof InsertException) {
                result.setState(5002);
            }else if (e instanceof SelectException) {
                result.setState(5003);
            }else if (e instanceof DeleteException) {
                result.setState(5004);
            }else if (e instanceof FileEmptyException) {
                result.setState(6000);
            } else if (e instanceof FileSizeException) {
                result.setState(6001);
            } else if (e instanceof FileTypeException) {
                result.setState(6002);
            } else if (e instanceof FileStateException) {
                result.setState(6003);
            } else if (e instanceof FileUploadIOException) {
                result.setState(6004);
            }
            return result;
        }
    }
    
    5.FileUrlController

        自定义了异常,需要FileUrlController类继承BaseController类,捕获到异常信息。

    package com.pzlib.controller;
    
    import com.pzlib.controller.ex.*;
    import com.pzlib.entity.NewBooks;
    import com.pzlib.service.INewsBootService;
    
    import com.pzlib.util.JsonResult;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.system.ApplicationHome;
    import org.springframework.web.bind.annotation.PostMapping;
    
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.multipart.MultipartFile;
    
    import java.io.File;
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.List;
    
    
    @RestController
    public class FileUrlController extends BaseController {
    
        @Autowired
        private INewsBootService newsBootService;
    
        /** 头像文件大小的上限值(1MB) */
        public static final int AVATAR_MAX_SIZE = 1 * 1024 * 1024;
        /** 允许上传的头像的文件类型 */
        public static final List<String> AVATAR_TYPES = new ArrayList<String>();
    
        /** 初始化允许上传的头像的文件类型 */
        static {
            AVATAR_TYPES.add("image/jpeg");
            AVATAR_TYPES.add("image/jpg");
            AVATAR_TYPES.add("image/png");
            AVATAR_TYPES.add("image/bmp");
            AVATAR_TYPES.add("image/gif");
            AVATAR_TYPES.add("image/pjpeg");
            AVATAR_TYPES.add("image/x-png");
    
        }
    
    
        @PostMapping("/newsImage")
        public JsonResult<String> newsImage(@RequestParam("file") MultipartFile file,String title) {
            // 判断上传的文件是否为空
            if (file.isEmpty()) {
                // 是:抛出异常
                throw new FileEmptyException("上传的头像文件不允许为空");
            }
    
            // 判断上传的文件大小是否超出限制值
            if (file.getSize() > AVATAR_MAX_SIZE) { // getSize():返回文件的大小,以字节为单位
                // 是:抛出异常
                throw new FileSizeException("不允许上传超过" + (AVATAR_MAX_SIZE / 1024) + "KB的头像文件");
            }
    
            // 判断上传的文件类型是否超出限制
            String contentType = file.getContentType();
            // boolean contains(Object o):当前列表若包含某元素,返回结果为true;若不包含该元素,返回结果为false
            if (!AVATAR_TYPES.contains(contentType)) {
                // 是:抛出异常
                throw new FileTypeException("不支持使用该类型的文件作为头像,允许的文件类型:" + AVATAR_TYPES);
            }
    
            //获取jar包所在目录
            ApplicationHome h = new ApplicationHome(getClass());
            File jarF = h.getSource();
            //在jar包所在目录下生成一个upload文件夹用来存储上传的图片
            String parent = jarF.getParentFile().toString()+"/classes/static/images/book/";
            System.out.println(parent);
    
            // 保存头像文件的文件夹
            File dir = new File(parent);
            if (!dir.exists()) {
                dir.mkdirs();
            }
    
            // 保存的头像文件的文件名
            String suffix = "";
            String originalFilename = file.getOriginalFilename();
            int beginIndex = originalFilename.lastIndexOf(".");
            if (beginIndex > 0) {
                suffix = originalFilename.substring(beginIndex);
            }
            NewBooks newBooks = newsBootService.changePicUrl(title);
            String string = newBooks.getPicurl();
            String[] split = string.split("\\.");
            String filename =split[0]+ suffix;
    
            // 创建文件对象,表示保存的头像文件
            File dest = new File(dir, filename);
            // 执行保存头像文件
            try {
                file.transferTo(dest);
            } catch (IllegalStateException e) {
                // 抛出异常
                throw new FileStateException("文件状态异常,可能文件已被移动或删除");
            } catch (IOException e) {
                // 抛出异常
                throw new FileUploadIOException("上传文件时读写错误,请稍后重新尝试");
            }
    
            /*// 头像路径
            String avatar = "/images/book/" + filename;
            // 返回成功头像路径*/
            return new JsonResult<String>(OK);
        }
    

       运行结果

    在这里插入图片描述运行失败:
    在这里插入图片描述

       &图片保存路径是在target/classes/static/images/book/xxx.jpg,
    在这里插入图片描述
       &如果是打包成jar包,则路径和jar路径一样。

    在这里插入图片描述
       如有错误请指正,谢谢!

    展开全文
  • jsp实现图片上传+预览+裁剪

    热门讨论 2013-02-03 09:44:14
    程序实现了jsp图片上传功能,上传图片后可以预览和裁剪图片,生成裁剪好的新图片
  • 移动端H5实现图片上传

    千次阅读 2019-05-27 14:28:45
    效果图 基础知识 FormData 通过FormData对象可以组装一组用 XMLHttpRequest发送请求的键/值对。它可以更灵活方便的发送表单数据,因为可以独立于表单使用。...这是一种常见的移动端上传方式,FormData也是H5新增...

    效果图
    在这里插入图片描述

    基础知识

    FormData

    通过FormData对象可以组装一组用 XMLHttpRequest发送请求的键/值对。它可以更灵活方便的发送表单数据,因为可以独立于表单使用。如果你把表单的编码类型设置为multipart/form-data ,则通过FormData传输的数据格式和表单通过submit() 方法传输的数据格式相同。

    这是一种常见的移动端上传方式,FormData也是H5新增的 兼容性如下:
    在这里插入图片描述

    base64

    Base64是一种基于64个可打印字符来表示二进制数据的表示方法。 由于2的6次方等于64,所以每6个位元为一个单元,对应某个可打印字符。 三个字节有24个位元,对应于4个Base64单元,即3个字节可表示4个可打印字符。

    base64可以说是很出名了,就是用一段字符串来描述一个二进制数据,所以很多时候也可以使用base64方式上传。兼容性如下:
    在这里插入图片描述

    Blob对象

    一个 Blob对象表示一个不可变的, 原始数据的类似文件对象。Blob表示的数据不一定是一个JavaScript原生格式。 File 接口基于Blob,继承 blob功能并将其扩展为支持用户系统上的文件。

    简单说Blob就是一个二进制对象,是原生支持的,兼容性如下:
    在这里插入图片描述

    FileReader对象

    FileReader 对象允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用 File 或 Blob 对象指定要读取的文件或数据。

    FileReader也就是将本地文件转换成base64格式的dataUrl。
    在这里插入图片描述

    图片上传思路

    准备工作都做完了,那怎样用这些材料完成一件事情呢。
    这里要强调的是,考虑到移动端流量很贵,所以有必要对大图片进行下压缩再上传。
    图片压缩很简单,将图片用canvas画出来,再使用canvas.toDataUrl方法将图片转成base64格式。

    所以图片上传思路大致是:

    1.监听一个input(type=‘file’)的onchange事件,这样获取到文件file;
    2.将file转成dataUrl;
    3.然后根据dataUrl利用canvas绘制图片压缩,然后再转成新的dataUrl;
    4.再把dataUrl转成Blob;
    5.把Blob append进FormData中;
    6.xhr实现上传。

    手机兼容性问题

    理想很丰满,现实很骨感。
    实际上由于手机平台兼容性问题,上面这套流程并不能全都支持。
    所以需要根据兼容性判断。

    经过试验发现:

    1.部分安卓微信浏览器无法触发onchange事件(第一步就特么遇到问题)
    这其实安卓微信的一个遗留问题。 查看讨论 解决办法也很简单:input标签 <input type=“file" name=“image” accept="image/gif, image/jpeg, image/png”>要写成就没问题了。
    2.部分安卓微信不支持Blob对象
    3.部分Blob对象append进FormData中出现问题
    4.iOS 8不支持new File Constructor,但是支持input里的file对象。
    5.iOS 上经过压缩后的图片可以上传成功 但是size是0 无法打开。
    6.部分手机出现图片上传转换问题。
    7.安卓手机不支持多选,原因在于multiple属性根本就不支持。
    8.多张图片转base64时候卡顿,因为调用了cpu进行了计算。
    9.上传图片可以使用base64上传或者formData上传

    上传思路修改方案

    经过考虑,我们决定做兼容性处理:
    在这里插入图片描述
    这里边两条路,最后都是File对象append进FormData中实现上传。

    代码实现

    首先有个html

    <input type="file" name="image" accept=“image/*” onchange='handleInputChange'>
    

    然后js如下:

    // 全局对象,不同function使用传递数据
    const imgFile = {};
    
    function handleInputChange (event) {
        // 获取当前选中的文件
        const file = event.target.files[0];
        const imgMasSize = 1024 * 1024 * 10; // 10MB
    
        // 检查文件类型
        if(['jpeg', 'png', 'gif', 'jpg'].indexOf(file.type.split("/")[1]) < 0){
            // 自定义报错方式
            // Toast.error("文件类型仅支持 jpeg/png/gif!", 2000, undefined, false);
            return;
        }
    
        // 文件大小限制
        if(file.size > imgMasSize ) {
            // 文件大小自定义限制
            // Toast.error("文件大小不能超过10MB!", 2000, undefined, false);
            return;
        }
    
        // 判断是否是ios
        if(!!window.navigator.userAgent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/)){
            // iOS
            transformFileToFormData(file);
            return;
        }
    
        // 图片压缩之旅
        transformFileToDataUrl(file);
    }
    // 将File append进 FormData
    function transformFileToFormData (file) {
        const formData = new FormData();
        // 自定义formData中的内容
        // type
        formData.append('type', file.type);
        // size
        formData.append('size', file.size || "image/jpeg");
        // name
        formData.append('name', file.name);
        // lastModifiedDate
        formData.append('lastModifiedDate', file.lastModifiedDate);
        // append 文件
        formData.append('file', file);
        // 上传图片
        uploadImg(formData);
    }
    // 将file转成dataUrl
    function transformFileToDataUrl (file) {
        const imgCompassMaxSize = 200 * 1024; // 超过 200k 就压缩
    
        // 存储文件相关信息
        imgFile.type = file.type || 'image/jpeg'; // 部分安卓出现获取不到type的情况
        imgFile.size = file.size;
        imgFile.name = file.name;
        imgFile.lastModifiedDate = file.lastModifiedDate;
    
        // 封装好的函数
        const reader = new FileReader();
    
        // file转dataUrl是个异步函数,要将代码写在回调里
        reader.onload = function(e) {
            const result = e.target.result;
    
            if(result.length < imgCompassMaxSize) {
                compress(result, processData, false );    // 图片不压缩
            } else {
                compress(result, processData);            // 图片压缩
            }
        };
    
        reader.readAsDataURL(file);
    }
    // 使用canvas绘制图片并压缩
    function compress (dataURL, callback, shouldCompress = true) {
        const img = new window.Image();
    
        img.src = dataURL;
    
        img.onload = function () {
            const canvas = document.createElement('canvas');
            const ctx = canvas.getContext('2d');
    
            canvas.width = img.width;
            canvas.height = img.height;
    
            ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
    
            let compressedDataUrl;
    
            if(shouldCompress){
                compressedDataUrl = canvas.toDataURL(imgFile.type, 0.2);
            } else {
                compressedDataUrl = canvas.toDataURL(imgFile.type, 1);
            }
    
            callback(compressedDataUrl);
        }
    }
    
    function processData (dataURL) {
        // 这里使用二进制方式处理dataUrl
        const binaryString = window.atob(dataUrl.split(',')[1]);
        const arrayBuffer = new ArrayBuffer(binaryString.length);
        const intArray = new Uint8Array(arrayBuffer);
        const imgFile = this.imgFile;
    
        for (let i = 0, j = binaryString.length; i < j; i++) {
            intArray[i] = binaryString.charCodeAt(i);
        }
    
        const data = [intArray];
    
        let blob;
    
        try {
            blob = new Blob(data, { type: imgFile.type });
        } catch (error) {
            window.BlobBuilder = window.BlobBuilder ||
                window.WebKitBlobBuilder ||
                window.MozBlobBuilder ||
                window.MSBlobBuilder;
            if (error.name === 'TypeError' && window.BlobBuilder){
                const builder = new BlobBuilder();
                builder.append(arrayBuffer);
                blob = builder.getBlob(imgFile.type);
            } else {
                // Toast.error("版本过低,不支持上传图片", 2000, undefined, false);
                throw new Error('版本过低,不支持上传图片');
            }
        }
    
        // blob 转file
        const fileOfBlob = new File([blob], imgFile.name);
        const formData = new FormData();
    
        // type
        formData.append('type', imgFile.type);
        // size
        formData.append('size', fileOfBlob.size);
        // name
        formData.append('name', imgFile.name);
        // lastModifiedDate
        formData.append('lastModifiedDate', imgFile.lastModifiedDate);
        // append 文件
        formData.append('file', fileOfBlob);
    
        uploadImg(formData);
    }
    
    // 上传图片
    uploadImg (formData) {
        const xhr = new XMLHttpRequest();
    
        // 进度监听
        xhr.upload.addEventListener('progress', (e)=>{console.log(e.loaded / e.total)}, false);
        // 加载监听
        // xhr.addEventListener('load', ()=>{console.log("加载中");}, false);
        // 错误监听
        xhr.addEventListener('error', ()=>{Toast.error("上传失败!", 2000, undefined, false);}, false);
        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4) {
                const result = JSON.parse(xhr.responseText);
                if (xhr.status === 200) {
                    // 上传成功
                    
    
                } else {
                    // 上传失败
                }
            }
        };
        xhr.open('POST', '/uploadUrl' , true);
        xhr.send(formData);
    }
    

    多图并发上传

    多张图片上传方式有三种:

    图片队列一张一张上传
    图片队列并发全部上传
    图片队列并发上传X个,其中一个返回了结果直接触发下一个上传,保证最多有X个请求。
    这个一张一张上传好解决,但是问题是上传事件太长了,体验不佳;多张图片全部上传事件变短了,但是并发量太大了,很可能出现问题;最后这个并发上传X个,体验最佳,只是需要仔细想想如何实现。

    并发上传实现

    最后我们确定X = 3或者4。比如说上传9张图片,第一次上传个3个,其中一个请求回来了,立即去上传第四个,下一个回来上传第5个,以此类推。
    这里我使用es6的generator函数来实现的,定义一个函数,返回需要上传的数组:

    *uploadGenerator (uploadQueue) {
            /**
             * 多张图片并发上传控制规则
             * 上传1-max数量的图片
             * 设置一个最大上传数量
             * 保证最大只有这个数量的上传请求
             *
             */
    
            // 最多只有三个请求在上传
            const maxUploadSize = 3;
    
            if(uploadQueue.length > maxUploadSize){
    
                const result = [];
    
                for(let i = 0; i < uploadQueue.length; i++){
                    // 第一次return maxUploadSize数量的图片
                    if(i < maxUploadSize){
                        result.push(uploadQueue[i]);
    
                        if(i === maxUploadSize - 1){
                            yield result;
                        }
                    } else {
                        yield [uploadQueue[i]];
                    }
                }
    
            } else {
                yield uploadQueue.map((item)=>(item));
            }
        }
    

    调用的时候:

    // 通过该函数获取每次要上传的数组
            this.uploadGen = this.uploadGenerator(uploadQueue);
            // 第一次要上传的数量
            const firstUpload = this.uploadGen.next();
    
    
            // 真正开始上传流程
            firstUpload.value.map((item)=>{
                /**
                 * 图片上传分成5步
                 * 图片转dataUrl
                 * 压缩
                 * 处理数据格式
                 * 准备数据上传
                 * 上传
                 *
                 * 前两步是回调的形式 后面是同步的形式
                 */
                this.transformFileToDataUrl(item, this.compress, this.processData);
            });
    

    这样将每次上传几张图片的逻辑分离出来。

    单个图片上传函数改进

    然后遇到了下一个问题,图片上传分成5步,

    1.图片转dataUrl
    2.压缩
    3.处理数据格式
    4.准备数据上传
    5.上传

    这里面前两个是回调的形式,最后一个是异步形式。无法写成正常函数一个调用一个;而且各个function之间需要共享一些数据,之前把这个数据挂载到this.imgFile上了,但是这次是并发,一个对象没法满足需求了,改成数组也有很多问题。

    所以这次方案是:第一步创建一个要上传的对象,每次都通过参数交给下一个方法,直到最后一个方法上传。并且通过回调的方式,将各个步骤串联起来。Upload完整的代码如下:

    /**
     * Created by Aus on 2017/7/4.
     */
    import React from 'react'
    import classNames from 'classnames'
    import Touchable from 'rc-touchable'
    import Figure from './Figure'
    import Toast from '../../../Feedback/Toast/components/Toast'
    import '../style/index.scss'
    
    // 统计img总数 防止重复
    let imgNumber = 0;
    
    // 生成唯一的id
    const getUuid = () => {
        return "img-" + new Date().getTime() + "-" + imgNumber++;
    };
    
    class Uploader extends React.Component{
        constructor (props) {
            super(props);
            this.state = {
                imgArray: [] // 图片已上传 显示的数组
            };
            this.handleInputChange = this.handleInputChange.bind(this);
            this.compress = this.compress.bind(this);
            this.processData = this.processData.bind(this);
        }
        componentDidMount () {
            // 判断是否有初始化的数据传入
            const {data} = this.props;
    
            if(data && data.length > 0){
                this.setState({imgArray: data});
            }
        }
        handleDelete(id) {
            this.setState((previousState)=>{
                previousState.imgArray = previousState.imgArray.filter((item)=>(item.id !== id));
                return previousState;
            });
        }
        handleProgress (id, e) {
            // 监听上传进度 操作DOM 显示进度
            const number = Number.parseInt((e.loaded / e.total) * 100) + "%";
            const text = document.querySelector('#text-'+id);
            const progress = document.querySelector('#progress-'+id);
    
            text.innerHTML = number;
            progress.style.width = number;
        }
        handleUploadEnd (data, status) {
            // 准备一条标准数据
            const _this = this;
            const obj = {id: data.uuid, imgKey: '', imgUrl: '', name: data.file.name, dataUrl: data.dataUrl, status: status};
    
            // 更改状态
            this.setState((previousState)=>{
                previousState.imgArray = previousState.imgArray.map((item)=>{
                    if(item.id === data.uuid){
                        item = obj;
                    }
    
                    return item;
                });
                return previousState;
            });
    
            // 上传下一个
            const nextUpload = this.uploadGen.next();
            if(!nextUpload.done){
                nextUpload.value.map((item)=>{
                    _this.transformFileToDataUrl(item, _this.compress, _this.processData);
                });
            }
        }
        handleInputChange (event) {
            const {typeArray, max, maxSize} = this.props;
            const {imgArray} = this.state;
            const uploadedImgArray = []; // 真正在页面显示的图片数组
            const uploadQueue = []; // 图片上传队列 这个队列是在图片选中到上传之间使用的 上传完成则清除
    
            // event.target.files是个类数组对象 需要转成数组方便处理
            const selectedFiles = Array.prototype.slice.call(event.target.files).map((item)=>(item));
    
            // 检查文件个数 页面显示的图片个数不能超过限制
            if(imgArray.length + selectedFiles.length > max){
                Toast.error('文件数量超出最大值', 2000, undefined, false);
                return;
            }
    
            let imgPass = {typeError: false, sizeError: false};
    
            // 循环遍历检查图片 类型、尺寸检查
            selectedFiles.map((item)=>{
                // 图片类型检查
                if(typeArray.indexOf(item.type.split('/')[1]) === -1){
                    imgPass.typeError = true;
                }
                // 图片尺寸检查
                if(item.size > maxSize * 1024){
                    imgPass.sizeError = true;
                }
    
                // 为图片加上位移id
                const uuid = getUuid();
                // 上传队列加入该数据
                uploadQueue.push({uuid: uuid, file: item});
                // 页面显示加入数据
                uploadedImgArray.push({ // 显示在页面的数据的标准格式
                    id: uuid, // 图片唯一id
                    dataUrl: '', // 图片的base64编码
                    imgKey: '', // 图片的key 后端上传保存使用
                    imgUrl: '', // 图片真实路径 后端返回的
                    name: item.name, // 图片的名字
                    status: 1 // status表示这张图片的状态 1:上传中,2上传成功,3:上传失败
                });
            });
    
            // 有错误跳出
            if(imgPass.typeError){
                Toast.error('不支持文件类型', 2000, undefined, false);
                return;
            }
    
            if(imgPass.sizeError){
                Toast.error('文件大小超过限制', 2000, undefined, false);
                return;
            }
    
            // 没错误准备上传
            // 页面先显示一共上传图片个数
            this.setState({imgArray: imgArray.concat(uploadedImgArray)});
    
            // 通过该函数获取每次要上传的数组
            this.uploadGen = this.uploadGenerator(uploadQueue);
            // 第一次要上传的数量
            const firstUpload = this.uploadGen.next();
    
    
            // 真正开始上传流程
            firstUpload.value.map((item)=>{
                /**
                 * 图片上传分成5步
                 * 图片转dataUrl
                 * 压缩
                 * 处理数据格式
                 * 准备数据上传
                 * 上传
                 *
                 * 前两步是回调的形式 后面是同步的形式
                 */
                this.transformFileToDataUrl(item, this.compress, this.processData);
            });
        }
        *uploadGenerator (uploadQueue) {
            /**
             * 多张图片并发上传控制规则
             * 上传1-max数量的图片
             * 设置一个最大上传数量
             * 保证最大只有这个数量的上传请求
             *
             */
    
            // 最多只有三个请求在上传
            const maxUploadSize = 3;
    
            if(uploadQueue.length > maxUploadSize){
    
                const result = [];
    
                for(let i = 0; i < uploadQueue.length; i++){
                    // 第一次return maxUploadSize数量的图片
                    if(i < maxUploadSize){
                        result.push(uploadQueue[i]);
    
                        if(i === maxUploadSize - 1){
                            yield result;
                        }
                    } else {
                        yield [uploadQueue[i]];
                    }
                }
    
            } else {
                yield uploadQueue.map((item)=>(item));
            }
        }
        transformFileToDataUrl (data, callback, compressCallback) {
            /**
             * 图片上传流程的第一步
             * @param data file文件 该数据会一直向下传递
             * @param callback 下一步回调
             * @param compressCallback 回调的回调
             */
            const {compress} = this.props;
            const imgCompassMaxSize = 200 * 1024; // 超过 200k 就压缩
    
            // 封装好的函数
            const reader = new FileReader();
    
            // ⚠️ 这是个回调过程 不是同步的
            reader.onload = function(e) {
                const result = e.target.result;
                data.dataUrl = result;
    
                if(compress && result.length > imgCompassMaxSize){
                    data.compress = true;
    
                    callback(data, compressCallback); // 图片压缩
                } else {
                    data.compress = false;
    
                    callback(data, compressCallback); // 图片不压缩
                }
            };
    
            reader.readAsDataURL(data.file);
        }
        compress (data, callback) {
            /**
             * 压缩图片
             * @param data file文件 数据会一直向下传递
             * @param callback 下一步回调
             */
            const {compressionRatio} = this.props;
            const imgFile = data.file;
            const img = new window.Image();
    
            img.src = data.dataUrl;
    
            img.onload = function () {
                const canvas = document.createElement('canvas');
                const ctx = canvas.getContext('2d');
    
                canvas.width = img.width;
                canvas.height = img.height;
    
                ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
    
                let compressedDataUrl;
    
                if(data.compress){
                    compressedDataUrl = canvas.toDataURL(imgFile.type, (compressionRatio / 100));
                } else {
                    compressedDataUrl = canvas.toDataURL(imgFile.type, 1);
                }
    
                data.compressedDataUrl = compressedDataUrl;
    
                callback(data);
            }
        }
        processData (data) {
            // 为了兼容性 处理数据
            const dataURL = data.compressedDataUrl;
            const imgFile = data.file;
            const binaryString = window.atob(dataURL.split(',')[1]);
            const arrayBuffer = new ArrayBuffer(binaryString.length);
            const intArray = new Uint8Array(arrayBuffer);
    
            for (let i = 0, j = binaryString.length; i < j; i++) {
                intArray[i] = binaryString.charCodeAt(i);
            }
    
            const fileData = [intArray];
    
            let blob;
    
            try {
                blob = new Blob(fileData, { type: imgFile.type });
            } catch (error) {
                window.BlobBuilder = window.BlobBuilder ||
                    window.WebKitBlobBuilder ||
                    window.MozBlobBuilder ||
                    window.MSBlobBuilder;
                if (error.name === 'TypeError' && window.BlobBuilder){
                    const builder = new BlobBuilder();
                    builder.append(arrayBuffer);
                    blob = builder.getBlob(imgFile.type);
                } else {
                    throw new Error('版本过低,不支持上传图片');
                }
            }
    
            data.blob = blob;
            this.processFormData(data);
        }
        processFormData (data) {
            // 准备上传数据
            const formData = new FormData();
            const imgFile = data.file;
            const blob = data.blob;
    
            // type
            formData.append('type', blob.type);
            // size
            formData.append('size', blob.size);
            // append 文件
            formData.append('file', blob, imgFile.name);
    
            this.uploadImg(data, formData);
        }
        uploadImg (data, formData) {
            // 开始发送请求上传
            const _this = this;
            const xhr = new XMLHttpRequest();
            const {uploadUrl} = this.props;
    
            // 进度监听
            xhr.upload.addEventListener('progress', _this.handleProgress.bind(_this, data.uuid), false);
    
            xhr.onreadystatechange = function () {
                if (xhr.readyState === 4) {
                    if (xhr.status === 200 || xhr.status === 201) {
                        // 上传成功
                        _this.handleUploadEnd(data, 2);
                    } else {
                        // 上传失败
                        _this.handleUploadEnd(data, 3);
                    }
                }
            };
    
            xhr.open('POST', uploadUrl , true);
            xhr.send(formData);
        }
        getImagesListDOM () {
            // 处理显示图片的DOM
            const {max} = this.props;
            const _this = this;
            const result = [];
            const uploadingArray = [];
            const imgArray = this.state.imgArray;
    
            imgArray.map((item)=>{
                result.push(
                    <Figure key={item.id} {...item} onDelete={_this.handleDelete.bind(_this)} />
                );
    
                // 正在上传的图片
                if(item.status === 1){
                    uploadingArray.push(item);
                }
            });
    
            // 图片数量达到最大值
            if(result.length >= max ) return result;
    
            let onPress = ()=>{_this.refs.input.click();};
    
            //  或者有正在上传的图片的时候 不可再上传图片
            if(uploadingArray.length > 0) {
                onPress = undefined;
            }
    
            // 简单的显示文案逻辑判断
            let text = '上传图片';
    
            if(uploadingArray.length > 0){
                text = (imgArray.length - uploadingArray.length) + '/' + imgArray.length;
            }
    
            result.push(
                <Touchable
                    key="add"
                    activeClassName={'zby-upload-img-active'}
                    onPress={onPress}
                >
                    <div className="zby-upload-img">
                        <span key="icon" className="fa fa-camera" />
                        <p className="text">{text}</p>
                    </div>
                </Touchable>
            );
    
            return result;
        }
        render () {
            const imagesList = this.getImagesListDOM();
                
            return (
                <div className="zby-uploader-box">
                    {imagesList}
                    <input ref="input" type="file" className="file-input" name="image" accept="image/*" multiple="multiple" onChange={this.handleInputChange} />
                </div>
            )
        }
    }
    
    Uploader.propTypes = {
        uploadUrl: React.PropTypes.string.isRequired, // 图上传路径
        compress: React.PropTypes.bool, // 是否进行图片压缩
        compressionRatio: React.PropTypes.number, // 图片压缩比例 单位:%
        data: React.PropTypes.array, // 初始化数据 其中的每个元素必须是标准化数据格式
        max: React.PropTypes.number, // 最大上传图片数
        maxSize: React.PropTypes.number, // 图片最大体积 单位:KB
        typeArray: React.PropTypes.array, // 支持图片类型数组
    };
    
    Uploader.defaultProps = {
        compress: true,
        compressionRatio: 20,
        data: [],
        max: 9,
        maxSize: 5 * 1024, // 5MB
        typeArray: ['jpeg', 'jpg', 'png', 'gif'],
    };
    
    export default Uploader
    

    原文地址: https://segmentfault.com/a/1190000010034177?utm_source=tag-newest

    展开全文
  • Typora使用技巧之插入图片及图片上传

    万次阅读 多人点赞 2021-01-17 16:20:10
    文章目录如何在 Typora 中插入图片直接写 Markdown拖拽图片从剪贴板中粘贴图片插入图片时做的动作上传图片配置图片上传服务iPicuPicPicGo-Core(命令行应用)安装预编译好的 PicGo-Core 二进制程序通过 Node.js 包...


    阅读本文的前置条件:

    • 掌握基本的 Markdown 语法
    • 用过 Typora,或者至少知道 Typora 是干嘛用的

    如何在 Typora 中插入图片

    在 Typora 中插入图片可以通过下面这几种方式

    直接写 Markdown

    在 Markdown 中,插入图片的语法是:![alt](src)src既可以是本地图片的绝对/相对路径,比如../images/test.png,也可以是 URL,比如:https://octodex.github.com/images/yaktocat.pngalt是图片的注释,可以为空。

    如果你不想手动敲 Markdown 语法,也可以让 Typora 帮你自动生成。

    macOS 用户可以在菜单栏中选择「格式」→「图像」→「插入图片」或者直接按快捷键「⌃⌘I」(macOS)、「Ctrl+Shift+I」(Windows)、「Ctrl+Shift+I」(Linux)。

    出现如下图所示的提示之后,输入图片的本地路径或者 URL 就可以插入图片了。如果不知道本地图片的路径,也可以点击右侧的那个文件夹按钮,通过图形化界面手动选择本地图片。

    插入本地图片还有一种方式,就是在菜单栏中选择「格式」→「图像」→「插入本地图片…」。

    拖拽图片

    将图片拖拽到 Typora 中也可以达到插入图片的目的,一次可以拖拽多个图片。

    从剪贴板中粘贴图片

    Typora 支持从剪贴板中粘贴图片,但是因为 Markdown 文件是纯文本文件,所以用户无法在 Markdown 文件中直接插入图片文件,而是通过在 Markdown 文件中引用文件路径或者 URL 的方式插入图片,所以默认情况下,Typora 会先将文件保存到一个固定的目录,具体什么目录跟操作系统有关。

    在 macOS 上,是保存到~/Library/Application Support/typora-user-images/目录下

    在 Windows 上,是保存到~\AppData\Roaming\Typora\typora-user-images\目录下

    你也可以告诉 Typora,粘贴后的图片要放到什么位置。具体怎么告诉,我们会在下一节「插入图片时做的动作」中介绍。

    Typora 会将图片拷贝到指定的文件夹或者服务器,然后通过引用保存的图片的路径或者 URL 来插入图片。

    小贴士:

    1. 在 macOS 上,你可以在访达中复制图片文件,然后在 Typora 中进行粘贴,这和拖拽文件的效果是一样的。
    2. 在 macOS 上,你可以从 iPhone 中复制图片,然后在 Typora 中进行粘贴,但是要先设置粘贴后的图片存放的位置。

    插入图片时做的动作

    打开 Typora 的偏好设置窗口,点击左侧的「图像」选项卡,在「插入图片时…」下面有一个下拉框,里面有这样几个选项,如下图所示。

    第一个选项「无特殊操作」是默认选项。

    第二个选项「复制图片到当前文件夹(./)」意思是你在插入图片时,将图片复制到你正在编辑的 Markdown 文件所在的目录下。

    第三个选项「复制图片到 ./ f i l e n a m e . a s s e t s 文 件 夹 」 意 思 是 , 在 你 插 入 图 片 时 , 会 在 当 前 目 录 下 创 建 一 个 名 为 ‘ {filename}.assets 文件夹」意思是,在你插入图片时,会在当前目录下创建一个名为` filename.assets{filename}.asset的文件夹,然后把图片保存在这个文件夹下,其中${filename}`指的是当前你正在编辑的 Markdown 文件的文件名。

    第四个选项「上传图片」就是在你插入图片时,使用图片上传服务将图片上传到服务器。这个具体的设置请参考下一节「上传图片」。

    第五个选项「复制到指定路径」,意思很明白,就不再解释。当你选择这个选项后,会出现一个输入框让你输入保存图片的路径,这里也可以使用${filename}来指代当前编辑的 Markdown 文件的文件名。也可以点击右边的文件夹按钮在弹出的对话框中选择一个路径。

    我的配置一般是这样:

    因为如果你在 Markdown 文件中使用了图片,那说明这张图片和你写的东西是有关联的,那最好把图片和 Markdown 文件放在一起,这样也便于以后查找。还要注意的是,如果你选择了「复制图片到当前文件夹(./)」,那么要把下面的「优先使用相对路径」选项勾选上,这样,Typora 在引用图片时就会使用相对路径,以后即使你移动 Markdown 文件到别的目录下,只要图片随着 Markdown 文件一起移动, Typora 就永远都能找到这张图片。再配合接下来要讲的「上传图片」,就能使 Markdown 文件的维护成本最小化。

    上传图片

    当你在 Typora 中插入本地图片时,前面提到的插入图片的语法![alt](src)中的src部分在 Markdown 中是以本地路径的方式保存的,如果你要把你的 Markdown 文件分享给别人,或者发布到网上,那么要把图片一起分享或者发布,这样就比较麻烦。而如果src是 URL 的话,你在分享的时候只要分享一个 Markdown 文件就可以了,只要能联网,Markdown 的渲染引擎就能根据 URL 找到你引用的图片。

    要想把本地的图片路径转换成 URL,就得把图片上传到网络上某个位置,然后拿到这个图片的 URL。使用「Typora + 第三方图片上传服务」就可以自动完成这一操作。

    之前我在 CSDN 上发布文章时,一般都是现在本地用 Typora 编辑好,然后复制到 CSDN 上,但是文章中的图片没法直接复制,只能再一张张地上传,如果你要把文章发布到多个平台,那这样的工作还要重复好几次,这对于一个程序员来说简直难以忍受。

    自从我用了 Typora 的上传图片的功能后,就像找到了救星一般,写文章的意愿比以前更大了。下面就介绍一下这个功能怎么用。

    ⚠️警告:

    在介绍上传图片的功能之前,你需要了解的是,Typora 是通过调用第三方的软件实现图片上传的,它本身并不会上传图片,提供存储图片服务的也是第三方的云存储服务,它们 并不属于 Typora。所以你在使用上传图片功能的时候,要注意这些第三方服务的许可证书、用户协议、服务稳定性以及可靠性。

    当你在偏好设置中开启上传图片的功能后,Typora 会将要上传的图片发送给第三方图片上传软件,然后他们会把你的图片上传到第三方(甚至是第四方)图床,Typora 不会控制这个过程,也无法保证这个过程的顺利,所以一定要选择一个可靠的服务来保证数据的安全。

    除了隐私和可靠性问题,你还需注意,有些服务在保存图片时可能会设置过期时间;有些服务没有提供「删除」的功能,即使你不小心上传了敏感图片;有些服务遵守的不是你所在国家的法律,可能会封禁一些特定国家的特定类型的图片或者用户等等。

    总而言之,在选择某个图片上传应用及服务之前,一定要仔细阅读他们的用户手册。

    配置

    在偏好设置面板中可以开启并配置图片上传服务,Typora 支持以下图片上传服务:

    • iPic (macOS,免费增值)
    • uPic (macOS,开源)
    • PicGo.app (macOS / Windows / Linux, 只有简体中文版)
    • PicGo (命令行应用) (Windows / Linux)
    • 自定义 (macOS / Windows / Linux)

    在选择了其中一个上传服务后,你需要下载并安装相应的应用,做一些简单的配置,比如你想使用哪个云存储服务来保存上传的图片,然后点击「验证图片上传选项」,验证 Typora 使用当前服务和配置是否能上传本地图片。

    点击「验证图片上传选项」按钮后,Typora 会尝试使用当前服务和配置上传图片,如果出现「验证成功」的对话框,说明图片上传服务可以用了。如果显示的是「验证失败」的对话框,有可能是你电脑上还没有安装相应的服务或者网络问题,可以根据对话框中的提示进行检查。

    图片上传服务

    iPic

    iPic 仅支持 macOS,可以在 APP Store 中下载,是一款免费增值的应用,所谓免费增值,就是基础功能是免费的,如果想使用它的高级功能,就要收费了。好消息是,对于大多数用户而言,基础功能就够用了。在国内使用的话,iPic 默认是将图片上传至微博图床,此外,iPic 还支持其他图床,包括腾讯云、阿里云、七牛、又拍、Imgur、Flickr、Amazon S3,但是添加这些图床属于收费功能。

    图片上传后会返回 URL,这个 URL 没有权限控制,任何人都可以访问。

    iPic 详细的使用文档:

    uPic

    uPic 也是只能在 macOS 上使用,是一款开源、强大、美观且操作简单的图片即文件上传工具,可以在命令行中通过 Homebrew 安装:

    brew cask install upic
    

    或者从 GitHub 上下载:https://github.com/gee1k/uPic/releases

    uPic 的详细使用文档:https://blog.svend.cc/upic/en/

    关于 uPic 的具体使用,值得用一篇文章来详细介绍,由于篇幅限制,这里就不赘述了,如果读者对 uPic 的使用感兴趣,就使劲点赞吧!点赞多的话我就专门写篇文章讲解 uPic 的使用。

    PS:上面这两种图片上传服务使用起来比较简单,但是只有在 macOS 上才能用。下面要介绍的图片上传服务,在 Windows / Linux / macOS 上都能用,但是可能需要一定的编程基础才能理解,如果大家看起来比较费劲,可以文章在后面留言,我可以针对每一种上传方式都单独写一篇文章介绍,争取让没有编程基础的小白也能看懂。

    PicGo-Core(命令行应用)

    PicGo-Core 是一个支持图片上传的开源 node(Node.js)模块,它也支持用插件扩展功能。

    PicGo-Core 的 GitHub 地址:https://github.com/PicGo/PicGo-Core

    因为是 node 模块,所以有两种方式安装并使用 PicGo-Core。

    安装预编译好的 PicGo-Core 二进制程序

    Typora 提供了一个 PicGo 的预编译二进制文件,使用 nexe 将node 模块打包成一个二进制文件。你只需在偏好设置面板中点击「下载或更新」按钮,Typora 就会开始下载,并直接把它放在 Typora 支持的文件夹下。由于安全规则的限制, macOS Catalina 并不支持下载预编译的 PicGo-Core 二进制文件。所以这种方式只能在 Windows 或 Linux 上使用, macOS 的用户只能用第二种方式。

    下载的 PicGo-Core 二进制文件的存放位置

    下载的 PicGo-Core 二进制文件会被保存到%AppData/Typora/picgo/目录下,其中,%AppData在不同的操作系统中含义不同,在 Windows 中,%AppDataC:/User/[username]/Roaming/AppData,在 Linux 中,%AppData~/.config。你可以在偏好设置面板中点击「打开主题文件夹」,然后打开它的上一层文件夹,就会看到「picgo」文件夹。

    通过 Node.js 包管理器安装 PicGo-Core

    这种方式需要依赖 Node.js 运行环境,如果你电脑上已经安装了 Node.js 或者 yarn,你可以在命令行中运行下面的命令来安装 PicGo-Core:

    npm install picgo -g
    
    # or
    
    yarn global add picgo
    

    然后你可以在命令行中输入which picgo,来获取 PicGo-Core 实际的安装位置。然后偏好设置面板中选择「Custom Command」作为图片上传服务,然后在「命令」输入框中输入命令[your node path] [your picgo-core path] upload。如果在环境变量 PATH 中设置了 node 和 picgo,也可以直接输入picgo upload。如下图所示。

    配置 PicGo-Core

    配置 PicGo-Core 有以下几种方式:

    方式 1:编辑配置文件

    配置文件名叫做 config.json,路径如下:

    • Linux / macOS:~/.picgo/config.json
    • Windows:C:\Users\[your user name]\.picgo\config.json

    更多细节,可以参考文档(仅有中文版本):https://picgo.github.io/PicGo-Core-Doc/zh/guide/config.html#%E9%BB%98%E8%AE%A4%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6

    方式 2:通过命令行界面配置

    如果你是通过 node 安装的 PicGo-Core,那么可以输入picgo -h来获取 picgo 的帮助,根据帮助文档对 PicGo-Core 进行配置。

    如果你是在 Typora 的偏好设置面板中下载的 PicGo-Core 二进制文件,那你可以找到二进制文件的保存路径,然后在命令行中打开这个路径,输入命令./picgo -h获取帮助文档,并进行配置。

    关于如何在命令行中使用 PicGo-Core,可以参考这个文档:https://github.com/PicGo/PicGo-Core#use-in-cli

    插件使用

    使用命令行可以安装 PicGo-Core 的插件。

    PicGo.app(仅支持中文)

    PicGo 是构建自 PicGo-Core 的图形化界面应用,由于它仅支持中文用户界面,所以只有将 Typora 的语言设置为中文才能看到这个选项。

    Typora 要求 PicGo 必须是 2.2.0 及以上版本,并且要开启 PicGo-Server,PicGo-Server 是 PicGo 内部的一个小型服务,用于配合其他应用来调用 PicGo 进行上传,默认监听 36677 端口。关于 PicGo-Server 的详细说明可以参考:https://picgo.github.io/PicGo-Doc/zh/guide/config.html#picgo-server%E8%AE%BE%E7%BD%AE。当 Typora 使用 PicGo 上传图片时,PicGo 会启动并一直保持运行。

    安装 PicGo 之后,Linux / Windows 用户需要在偏好设置面板上设置 PicGo 二进制文件的路径。

    PicGo.app 和 PicGo-Core 的区别

    • 目前 PicGo.app 仅有中文版。
    • PicGo.app 提供了图形用户界面,因此与命令行界面的 PicGo-Core 相比,前者更加容易使用。
    • 使用 PicGo-Core 上传图片耗费的计算机资源更少,因为只有在上传图片的时候 PicGo-Core 的进程才会运行,上传结束后(不管成功还是失败),进程便会退出。但是如果使用 PicGo.app 上传,PicGo.app 会一直保持运行,无法自动退出。并且,PicGo.app 是一个电子应用程序,它会消耗更多计算资源。
    • PicGo.app 和 PicGo-Core 使用不同的配置文件,但是你可以把 PicGo.app de 配置文件中的picBed下面的 json 对象复制到 PicGo-Core 的配置文件中。
    • PicGo.app 还提供了其他的特性,比如上传历史、自动重命名等。

    自定义命令

    你还可以配置自定义命令去上传图片,在自定义的命令中,可以使用上面没有列出的工具或者甚至是你自己写一个工具或者脚本。Typora 会自动将所有需要上传的图片追加到你填写的命令后面。你自定义的命令必须在标准输出中输出图片的 URL,一个占一行,Typora 会根据上传图片的数量从标准输出的最后 N 行中读取图片的 URL。N 是上传图片的数量。

    举个例子,比如你写了一个工具,名叫 upload-image.sh,然后你可以在「命令」输入框中输入[some path]/upload-image.sh,Typora 会执行命令[some path]/upload-image.sh "image-path-1" "image-path-2"上传位于image-path-1image-path-2中的图片,然后这个命令应该返回类似如下信息:

    Upload Success:
    http://remote-image-1.png
    http://remote-image-2.png
    

    然后 Typora 就会从标准输出中读取到两个远程图片 URL,并将 Markdown 文件中原始的本地图片路径替换成 URL。

    你可以点击「验证图片上传选项」按钮去验证你自定义的命令是否生效。

    在自定义命令中使用当前文件名或者当前路径

    在自定义命令中可以使用${filename}${filepath},在命令执行时,他们会被替换为当前 Markdown 文件的文件名和路径,如果是还没有保存的「未命名」文件,他们会被替换为空字符串。

    插入图片时自动上传

    全局设置

    在偏好设置面板中,「插入图片时…」下方的下拉框中,选择「上传图片」选项,Typora 就会在你插入图片时自动上传。如果只想上传本地图片,那么就只勾选「对本地位置的图片应用上述规则」,如果插入的图片本来就位于网络上,但是你想再上传一遍到图床中,那就把「对网络位置的图片应用上述规则」也勾选上。

    针对每一个文件单独设置

    首先,你要勾选上偏好设置面板中的「允许根据 YAML 设置自动上传图片」,然后,如果你的 Markdown 文件在 YAML 中包含如下配置:

    typora-copy-images-to: upload
    

    当你插入图片时,Typora 会使用这个选项自动上传图片。

    或者你也可以在菜单栏中点击格式→图像→当插入本地图片时…插入 YAML 配置,开启这个功能。

    上传图片的使用

    上传指定的图片

    可以在 Typora 中,右键点击要上传的图片,选择「上传图片」。

    在这里插入图片描述

    上传所有本地图片

    如果你的 Markdown 文件中包含大量的本地图片,你想一次性把他们都上传,可以点击菜单栏中的格式→图像→上传所有本地图片

    参考资料:

    1. https://support.typora.io/Upload-Image/
    2. https://support.typora.io/Images/#when-insert-local-image%E2%80%A6

    欢迎关注我的微信公众号,扫描下方二维码或微信搜索:王虾片,就可以找到我,我会持续为你分享 IT 技术。

    qrcode_for_gh_3cfa9b908804_258

    展开全文
  • xutils上传图片网上找了好多,demo比较少,而且都没有写一个清晰的流程,so。。自己写的
  • Vue+Element+Springboot实现图片上传

    千次阅读 2021-11-24 13:52:51
    最近没事刚好联系下vue+springboot前段后分离的项目、用上了图片上传功能、记录一下。 前端待提交的表单部分代码。 <el-form-item label="封面图片"> <el-upload v-model="dataForm.title" class=...
  • Android图片上传的两种方式

    千次阅读 2021-10-22 13:16:32
    图片上传,以及带参数的图片上传是Android开发中,很常见的需求。但也是接口联调难度相对比较大的技术实现,本文介绍两种可靠的图片上传方式。一是通过 MultipartBody 来实现;二是通过图片转字符串来实现。 一、...
  • 菜鸟的springboot项目图片上传及图片路径分析

    千次阅读 多人点赞 2020-06-07 22:41:04
    菜鸟的springboot项目图片上传及图片路径分析说明一、图片路径分析二、实现图片上传(1)单文件上传(非异步)(2)单文件上传(异步)三、总结 说明 本文记录一下springboot项目的图片上传的相关知识,主要解决项目...
  • swfupload 和 plupload 图片上传 实时预览

    千次下载 热门讨论 2013-12-27 14:06:05
    这个Demo 实现了swfupload和plupload图片上传实时预览
  • flash图片上传插件

    千次下载 热门讨论 2013-04-11 15:31:37
    包含整理好的swfuploadv250.Net开发包,SWFUpload_v250_beta_3_samples,和一个asp.net+flash插件
  • jquery 多图片上传预览

    热门讨论 2013-05-02 21:54:48
    java file upload java file upload java file upload
  • 使用Vue实现图片上传的三种方式

    千次阅读 2020-12-19 09:44:50
    项目中需要上传图片可谓是经常遇到的需求,本文将介绍 3 种不同的图片上传方式,在这总结分享一下,有什么建议或者意见,请大家踊跃提出来。没有业务场景的功能都是耍流氓,那么我们先来模拟一个需要实现的业务场景...
  • vue中实现图片上传

    千次阅读 2022-03-06 11:06:31
    前言:之前感觉上传图片很难,每次写都感觉特别麻烦,但思路清楚之后就感觉还行,没有想像中那个复杂,接触vue之后感觉很多地方相对于之前而言更加方便,用多个组件,相同的可以进行组件复用,减少代码的重复,下面...
  • Java服务器处理图片上传

    千次阅读 2018-07-05 23:58:13
    由于近期刚接触公司的图片上传的功能,能力有限。目前只列出已实现并可用的三种: 第一、浏览器端上传图片实现; 第二、微信端上传图片实现; 第三、手机App上传图片实现。(注:与微信端上传图片原理一致.) ...
  • 图片上传测试用例

    千次阅读 2020-04-29 15:36:23
    1.功能测试 (1)选择符合要求的图片上传--------上传成功;...(3)查看,下载上传成功的图片--------上传图片可查看或下载(根据需求) (4)删除上传成功的图片-------------可删除(根据需求...
  • 安卓图片上传服务端+客户端

    热门讨论 2015-05-19 09:32:40
    安卓图片上传,服务端servlet+客户端...
  • SpringBoot上传图片(多图片上传

    千次阅读 2019-03-22 16:15:07
    图片上传 , image/jpeg, image/jpg" multiple="multiple" onchange="checkImage(this)"> ()" value="上传"/> <script src="/webjars/jquery/3.3.1/jquery.min.js"></script> var curFiles = [];//...
  • node.js实现图片上传

    千次阅读 多人点赞 2018-12-14 10:09:22
    图片上传到服务器 将图片的路径保存到数据库 图片上传到服务器 下载第三方插件multer npm install multer --save 先写一个上传图片的接口 在路由文件夹中创建一个upload.js文件: const express=require('...
  • 使用vue+elementUi+springBoot实现图片上传,图片删除,图片展示环境配置准备环境使用软件图片上传图片删除图片显示所有代码均为参考,每个人的方法不一样,效果不一样,该代码仅供参考,大佬们别嫌弃环境配置 jdk...
  • SpringBoot集成阿里云OSS图片上传

    千次阅读 2019-05-13 18:56:47
    简述 最近做的公司项目,图片比较多,不想给其存储到自己服务器上,就买了阿里云的OSS服务器来哦进行存储,其实集成第三方平台,一般没...-- 图片上传 SDK 阿里云oss --> <dependency> <groupId>c...
  • 毕竟只是一个新手,解决这种复杂点的问题(相对而言),还是需要花费大量时间的,这篇文章花了两天的时间才实现的功能,现在就记录一下使用springboot怎么实现文件上传下载的。 我这里使用的是 springboot 2.0.3,...
  • 不过 Typora 已经支持图片上传功能了,在配置之前,要确保软件的版本更新到最新,老版本是不支持图片上传功能,配置中没有上传图片该选项: 更新软件 下载最新的软件 https://www.typora.io/#windows,本文中久一...
  • 在很多的网站都可以实现上传头像,可以选择自己喜欢的图片做头像,从本地上传,下次登录时可以直接显示出已经上传的头像,那么这个是如何实现的呢?   下面说一下我的实现过程(只是个人实现思路,实际网站怎...
  • c#图片上传和文件上传Api接口

    千次阅读 2020-05-17 17:53:59
    1.图片上传 Random rd = new Random(); #region 图片上传 /// <summary> /// 图片上传 /// </summary> /// <param name="type">projectPhoto 项目图片 projectRecordPhoto 项目大事记图片 ...
  • //给图片添加删除 function mouseChange() { $(document).on("mouseenter mouseleave", ".file-iteme", function (event) { if (event.type === "mouseenter") { ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 620,287
精华内容 248,114
关键字:

图片上传