利用vue-cropper实现本地图片裁剪处理后再上传

最近遇到了图片处理的问题:再上传图片前进行一些处理(裁剪、旋转、放大)然后再上传服务器。
我使用的是vue的一个开源组件:vue-cropper 进行处理,可以提供多种处理图片的功能。
下方代码就是该功能的组件封装,对应的axios交互方法根据具体的开发情况进行设置。成功后的效果如下所示:
利用vue-cropper实现本地图片裁剪处理后再上传_第1张图片

处理过程

刚开始由于没有经验,死脑筋的认为图片必须上传到服务器后才能够进行预览,处理,但是这样会让我们在裁剪图片时都会上传两次图片,这样势必会浪费掉服务器的资源,会使其占用大量的数据库资源和服务器压力。所以在网上找到了别人的解决方案。

//  选择本地图片
    uploadImg (e) { 
      //  上传图片
      const file = e.target.files[0];
      this.fullFile = file;
      if (!/\.(jpg|jpeg|png|JPG|PNG)$/.test(e.target.value)) {
        this.$message.error('图片类型必须是.jpeg、jpg、png中的一种');
        return false;
      } else if (this.fullFile.size / 1024 / 1024 > 2) {
        this.$message.error('图片大小不得超过2MB');
        return false;
      }
      const reader = new FileReader();
      reader.onload = e => {
        let data;
        if (typeof e.target.result === 'object') {
          // 把Array Buffer转化为blob 如果是base64不需要 
          data = window.URL.createObjectURL(new Blob([e.target.result]));
        } else {
          data = e.target.result; 
        }
        this.option.img = data;
      };
      // 转化为blob 
      reader.readAsArrayBuffer(file);
      //  打开图片处理弹窗
      this.dialogOpen = true;
    },

然后就可以通过官方文档进行相关的数据方法封装了。将裁剪框的尺寸等需要自定义的信息给封装一下,成了可复用的图片上传组件,完整代码如下。

<template>
<div class="imgs"> 
  <el-button type="primary" size="small" @click="$refs.selectImg.click()">选择图片</el-button>
  <input type="file" :value="imgFile" ref="selectImg" style="display: none" accept="image/png, image/jpeg, image/jpg" @change="uploadImg($event)">
  <div slot="tip"><span style="color: #F56C6C;">只能上传jpg/png文件,且不超过2MB</span></div>
  <!-- 图片裁剪弹窗 -->
  <el-dialog
  id="dialog"
  title="编辑图片" 
  :visible.sync="dialogOpen" 
  width="700px" 
  @close="cancel()" 
  append-to-body
  >
    <div class="cut">
      <div class="out">
        <!-- 裁剪图片预览 -->
        <div class="preview-box" :style="{'width': previews.w + 'px','height': previews.h + 'px','overflow': 'hidden'}">
          <img :src="previews.url" :style="previews.img">
        </div>
      </div>
      <!-- 图片裁剪部分 -->
      <div class="left">
        <vueCropper
          ref="cropper"
          :img="option.img"
          :outputSize="option.size"
          :outputType="option.outputType"
          :autoCrop="option.autoCrop"
          :fixed="option.fixed"
          :full="option.full"
          :fixedNumber="fixedNumber"
          :canScale="option.canScale"
          :high="option.high"
          @realTime="realTime"
          :imageUrl="imageUrl"
          :isWater="isWater"
        ></vueCropper>
      </div>
    </div>
    <div class="block">
      <div slot="tip" style="color: #409EFF; margin-left: 20%; margin-top: 10px;">预览图:</div>
      <div slot="tip" style="text-align: center; margin-top: 10px;"><span style="color: #F56C6C; text-align: center;">处理图片时可以操作滚轮进行放大缩小,且选图框只能按推荐比例缩放</span></div>
    </div>
     <div class="p" style="margin: 20px auto; width: 300px ">
      <el-progress :percentage="progress" v-if="progress !== 0" color="#409eff"></el-progress>
    </div>
    <!-- 图片操作 -->
     <div class="block" style="margin-top: 15px; text-align: center">
       <!-- 图片放大缩小 -->
      <el-button type="primary" size="small" icon="el-icon-plus" circle @click="changeScale(1)"></el-button>
      <el-button type="primary" size="small" icon="el-icon-minus" circle @click="changeScale(-1)"></el-button>
      <!-- 图片旋转 -->
      <el-button type="primary" size="small" icon="el-icon-refresh-left" circle @click="rotateLeft"></el-button>
      <el-button type="primary" size="small" icon="el-icon-refresh-right" circle @click="rotateRight"></el-button>
    </div>
   
    
    <!-- 底部确认取消按钮 -->
    <span slot="footer" class="dialog-footer">
      <el-button @click="cancel()">取 消</el-button>
      <el-button type="primary" @click="finish">确 定</el-button>
    </span>
  </el-dialog>
</div>
</template>

<script>
import { VueCropper } from 'vue-cropper'; 
import { env } from '@/config';
import { UserModule } from '@/store/modules/user'; // vuex
import api from '@/api/index';
import uploadImg from '../../api/upload/index';
import UploadProxy from '@/api/upload/handler';
import axios from 'axios';

export default {
  name: 'Uploadimg01',
  props: {
    //  裁剪框的长宽比例设置:例如:[2,1]代表图片裁剪框的长宽比例固定为为2:1.
    fixedNumber: {
      default: () => [],
      type: Array,
    },
    //  将要裁剪的图片地址
    imageUrl: {
      type: String,
      default: '',
    },
     //  是否添加水印,默认为不添加
    isWater: {
      type: Boolean,
      default: false,
    },
  },
  components: {
    VueCropper,
  },
  created () {
    this.option.img = this.imageUrl;
  },
  //  监听imageUrl是否被传入组件,进行动态赋值
  watch: {
    imageUrl (curInfo, oldInfo) {
      if (curInfo) {
        this.imageUrl = curInfo;
        this.option.img = curInfo;
      }
    },
  },
  data () {
    return {
      headers: { 'idToken': UserModule.token },
      //  vue-cropper的参数设置
      option: {
        img: '',  //  裁剪图片的地址。可以有三种形式:url 地址 || base64 || blob
        size: 1, // 裁剪生成图片的质量,	默认:1,	参数范围:0.1 - 1。
        outputType: 'png', // 裁剪生成图片的格式	默认: jpg (jpg 需要传入jpeg)	可选参数:jpeg || png || webp
        autoCrop: true, //  是否默认生成截图框.默认为: false
        canScale: true, //  图片是否允许滚轮缩放	默认;true	可选:true || false
        high: true, //  是否按照设备的dpr 输出等比例图片
        fixed: true, // 是否开启截图框宽高固定比例。默认:true
        full: true, //  是否输出原图比例的截图(决定了裁剪图片的画面质量)。
      },
      previews: {}, //  预览图片的信息。
      imgFile: '',
      fullFile: {}, //  上传的图片信息
      dialogOpen: false, // 弹框是否开启
      progress: 0, // 进度条
      show: false, //  axios请求取消
      config: {},
      cancels: null,
    };
  },
  computed: {
    getTarget () {
      let target = document.getElementById('dialog');
      return target;
    },
  },
  methods: {
    //  修改图片大小 正数为变大 负数变小
    changeScale (num) {
      num = num || 1;
      this.$refs.cropper.changeScale(num);
    },
    //  图片左旋转
    rotateLeft () {
      this.$refs.cropper.rotateLeft();
    },
    //  图片右旋转
    rotateRight () {
      this.$refs.cropper.rotateRight();
    },
    //  图片实时预览
    realTime (data) {
      this.previews = data;
    },
    // 
    cancel () {
      if (this.cancels) {
        this.progress = 0;
        this.cancels();
      }
      this.progress = 0;
      this.dialogOpen = false;
    },
    //  选择本地图片
    uploadImg (e) {
      this.option.img = '';
      //  上传图片
      const file = e.target.files[0];
      this.fullFile = file;
      if (!/\.(jpg|jpeg|png|JPG|PNG)$/.test(e.target.value)) {
        this.$message.error('图片类型必须是.jpeg、jpg、png中的一种');
        return false;
      } else if (this.fullFile.size / 1024 / 1024 > 2) {
        this.$message.error('图片大小不得超过2MB');
        return false;
      }
      const reader = new FileReader();
      reader.onload = e => {
        let data;
        if (typeof e.target.result === 'object') {
          // 把Array Buffer转化为blob 如果是base64不需要 
          data = window.URL.createObjectURL(new Blob([e.target.result]));
        } else {
          data = e.target.result;
        }
        this.option.img = data;
      };
      // 转化为blob 
      reader.readAsArrayBuffer(file);
      //  打开图片处理弹窗
      this.dialogOpen = true;
      
    },
    //  获取裁剪得到的图片并上传
    finish (type) {
      this.$refs.cropper.getCropBlob(async (data) => {
        //  将blob格式转换为文件。File() 构造器创建新的 File 对象实例。
        const newFile = new window.File([data], this.fullFile.name, { type: this.fullFile.type });
        //  FormData 接口提供了一种表示表单数据的键值对的构造方式,它会使用表单一样的数据格式
        let formData = new FormData();
        //  添加图片
        formData.append('file', newFile);
        //  添加将要删除替换掉的图片路径
        formData.append('oldFileName', this.imageUrl);
        //  是否添加水印
        formData.append('isWater', this.isWater);
        //  添加新上传的图片名(会出错)
        // formData.append('uploadName', this.fullFile.name);
        //  上传图片文件
        const upload = UploadProxy(env.baseUrl);
        const CancelToken = axios.CancelToken;
        this.config = {
          onUploadProgress: (progressEvent) => {
            let complete = parseInt(progressEvent.loaded / progressEvent.total * 100 | 0, 10);
            this.progress = complete;
          },
          cancelToken: new CancelToken(c => {
            this.cancels = c;
          }),
        };
        let res = await upload.post('/upload/fileUpload', formData, this.config).then(res => res.data);
        if (res.code === 10000) {
          //  上传图片成功后关闭弹窗
          this.dialogOpen = false;
          this.imageUrl = res.data.downPath;
          this.$message.success(res.msg);
          //  将上传成功后的服务器返回数据传给父组件
          this.$emit('onsuccess', res);
          this.option.img = '';
        } else {
          if (res.msg) {
            this.option.img = '';
            this.$message.error(res.msg);
          } else {
            this.option.img = '';
            this.$message.info('图片上传被取消');
          }
        }
        // const res = await uploadImg(formData);
        // if (res.code === 10000) {
        //   //  上传图片成功后关闭弹窗
        //   this.dialogOpen = false;
        //   this.imageUrl = res.data.downPath;
        //   this.$message.success(res.msg);
        //   //  将上传成功后的服务器返回数据传给父组件
        //   this.$emit('onsuccess', res);
        // } else {
        //   this.$message.error(res.msg);
        // }
      });
    },
  },
};
</script>

<style lang='scss' scoped>
.btn-upload {
 display:inline-block;
 width: 70px;
 padding: 0;
 text-align: center;
 line-height: 28px;
 color: #FFF;
 background-color: #409EFF;
 border-color: #409EFF;
 margin-right: 15px;
 font-size: 12px;
 border-radius: 4px;
 cursor: pointer;
}
.cut {
  width: 660px;
  height: 340px;
  display: flex;
  justify-content: space-between;
  .out {
    width: 300px;
    height: 100%;
    float: left;
    background: #F5F7FA;
    display: flex;
    align-items: center;
    justify-content: center;
    .preview-box {
      width: 200px;
      height: 100%;
    }
  }
  .left {
    width: 300px;
    height: 100%;
    float: left;
  }
}
</style>

你可能感兴趣的:(利用vue-cropper实现本地图片裁剪处理后再上传)