天天看點

vue-cropper圖檔上傳剪裁壓縮元件

安裝

npm install vue-cropper

yarn add vue-cropper
           

注冊

元件内使用
import { VueCropper }  from 'vue-cropper' 
components: {
  VueCropper,
},

main.js裡面使用
import VueCropper from 'vue-cropper' 

Vue.use(VueCropper)

cdn方式使用
<script src="vuecropper.js"></script>
Vue.use(window['vue-cropper'])

nuxt 使用方式
if(process.browser) {
  vueCropper = require('vue-cropper')
  Vue.use(vueCropper.default)
}
           

使用

<template>
  <div>
    <!-- 多圖檔上傳 -->
    <el-upload v-if="multiple" action='string' list-type="picture-card" accept="image/*" :on-preview="handlePreview" :auto-upload="false" :on-remove="handleRemove" :http-request="upload" :on-change="consoleFL" :file-list="uploadList">
      <i class="el-icon-plus"></i>
    </el-upload>
    <!-- 單圖檔上傳 -->
    <el-upload v-else class="avatar-uploader" action="'string'" :auto-upload="false" :show-file-list="false" :on-change="handleCrop" :http-request="upload">
      <img v-if="imageUrl" :src="imageUrl" class="avatar" ref="singleImg" @mouseenter="mouseEnter" @mouseleave="mouseLeave" :style="{width:width+'px',height:height+'px'}">
      <i v-else class="el-icon-plus avatar-uploader-icon" :style="{width:width+'px',height:height+'px','line-height':height+'px','font-size':height/6+'px'}"></i>
      <!-- 單圖檔上傳狀态顯示 -->
      <!-- <div v-if="imageUrl" class="reupload" ref="reupload" @click.stop="handlePreviewSingle" @mouseenter="mouseEnter" @mouseleave="mouseLeave" :style="{width:reuploadWidth+'px',height:reuploadWidth+'px','line-height':reuploadWidth+'px','font-size':reuploadWidth/5+'px'}">重新上傳</div> -->
      <div id="uploadIcon" v-if="imageUrl" ref="reupload" @mouseenter="mouseEnter" @mouseleave="mouseLeave" :style="{width:'100%'}">
        <i class="el-icon-zoom-in" @click.stop="handlePreviewSingle" :style="{color:'#2E2E2E',fontSize:'25px',display:'inline-block',paddingRight:'15px'}"></i>
        <i class="el-icon-upload" :style="{color:'#2E2E2E',fontSize:'25px',display:'inline-block'}"></i>
      </div>
      <div class="reupload" ref="uploading" :style="{width:reuploadWidth+'px',height:reuploadWidth+'px','line-height':reuploadWidth+'px','font-size':reuploadWidth/5+'px'}">上傳中..</div>
      <div class="reupload" ref="failUpload" :style="{width:reuploadWidth+'px',height:reuploadWidth+'px','line-height':reuploadWidth+'px','font-size':reuploadWidth/5+'px'}">上傳失敗</div>
    </el-upload>
    <!-- 多圖檔預覽彈窗 -->
    <el-dialog :visible.sync="dialogVisible">
      <img width="100%" :src="dialogImageUrl" alt="">
    </el-dialog>
    <!-- 剪裁元件彈窗 -->
    <el-dialog :visible.sync="cropperModel" width="1100px" :before-close="beforeClose">
      <Cropper :img-file="file" ref="vueCropper" :fixedNumber="fixedNumber" @upload="upload">
      </Cropper>
    </el-dialog>
  </div>
</template>
<script>
import Cropper from './cropper';
// import axios from '@/assets/js/axios'
export default {
  name: 'uploader',
  props: {     // 這裡幾個屬性可以寫在data中,這是原部落客寫法,應該是又套了一層
    targetUrl: {
      // 上傳位址
      type: String,
      // default: '/storage/upload'
      default: `${process.env.API_ROOT}/sys/oss/upload`
    },
    multiple: {
      // 多圖開關
      type: Boolean,
      default: false
    },
    initUrl: {
      // 初始圖檔連結
      default: ''
    },
    fixedNumber: {
      // 剪裁框比例設定
      default: function () {
        return [1.5, 1];
      }
    },
    width: {
      // 單圖剪裁框寬度
      type: Number,
      default: 178
    },
    height: {
      // 單圖剪裁框高度
      type: Number,
      default: 178
    }
  },
  data () {
    return {
      file: '', // 目前被選擇的圖檔檔案
      imageUrl: '', // 單圖情況框内圖檔連結
      dialogImageUrl: '', // 多圖情況彈窗内圖檔連結
      uploadList: [], // 上傳圖檔清單
      reupload: true, // 控制"重新上傳"開關
      dialogVisible: false, // 展示彈窗開關
      cropperModel: false, // 剪裁元件彈窗開關
      reuploadWidth: this.height * 0.7, // 動态改變”重新上傳“大小
    };
  },
  updated () {
    if (this.$refs.vueCropper) {
      this.$refs.vueCropper.Update();
    }
  },
  watch: {
    initUrl: function (val) {
      // 監聽傳入初始化圖檔
      // console.info('watch');
      if (val) {
        if (typeof this.initUrl === 'string') {
          this.imageUrl = val;
        } else {
          this.uploadList = this.formatImgArr(val);
         // this.$emit('imgupload', this.uploadList);
        }
      }
    }
  },
  mounted () {
    if (typeof this.initUrl === 'string') {
      this.imageUrl = this.initUrl;
    } else {
      this.uploadList = this.formatImgArr(this.initUrl);
    }
  },
  methods: {
    /** **************************** multiple多圖情況 **************************************/
    handlePreview (file) {
      // 點選進行圖檔展示
      this.dialogImageUrl = file.url;
      this.dialogVisible = true;
    }, 
    handleRemove (file, fileList) {
      // 删除圖檔後更新圖檔檔案清單并通知父級變化
      this.uploadList = fileList;
      this.$emit('imgupload', this.uploadList);
      // this.$emit('imgupload', this.formatImgArr(this.uploadList));
    },
    consoleFL (file, fileList) {
      // 彈出剪裁框,将目前檔案設定為檔案
      this.cropperModel = true;
      this.file = file;
      // this.uploadList = fileList;
    },
    /************************************************************************************/

    /** **************************** single單圖情況 **************************************/
    handlePreviewSingle (file) { // 點選進行圖檔展示
      this.dialogImageUrl = this.file.url;
      this.dialogVisible = true;
    },
    mouseEnter () { // 滑鼠劃入顯示“重新上傳”
      this.$refs.reupload.style.display = 'block';
      if (this.$refs.failUpload.style.display === 'block') {
        this.$refs.failUpload.style.display = 'none';
      }
      this.$refs.singleImg.style.opacity = '0.6';
    },
    mouseLeave () {
      // 滑鼠劃出隐藏“重新上傳”
      this.$refs.reupload.style.display = 'none';
      this.$refs.singleImg.style.opacity = '1';
    },
    handleCrop (file, files) {
      // console.log(file);
      // 點選彈出剪裁框
      this.cropperModel = true;
      this.file = file;
      // this.imageUrl = file.url
    },
    /************************************************************************************/

    async upload (data) {
      // 自定義upload事件
      if (!this.multiple) {
        // 如果單圖,則顯示正在上傳
        this.$refs.uploading.style.display = 'block';
      }
      let img = new Image();
      img.src = data;
      img.onload = async () => {
        // let _data = this.compress(img);
        let blob = this.dataURItoBlob(data);
        let formData = new FormData(); 
        formData.append('file', blob, this.file.name); // 有的背景需要傳檔案名,不然會報錯
        this.imgUpload(formData);
      };
    },
    async imgUpload(formData) {
      const res = await this.$http({
        url: 'sys/oss/upload',
        method: 'post',
        data: formData,
        headers: {
          'Content-Type': 'multipart/form-data'
        }
      });
      if (!this.multiple) {
        // 上傳完成後隐藏正在上傳
        this.$refs.uploading.style.display = 'none';
      }
      if (res.data.code === 0) {
        // 上傳成功将照片傳回父元件
        const currentPic = res.data.url;
        if (this.multiple) {
          this.uploadList.push({
            url: currentPic,
            uid: '111'
          });
          this.$emit('imgupload', this.uploadList);// 根據自己實際項目需要将照片傳回給父元件
          // this.uploadList.pop();
          // this.$emit('imgupload', this.formatImgArr(this.uploadList));
        } else {
          this.$emit('imgupload', currentPic);
        }
        this.$refs.vueCropper.isDisabled = false;
      } else {
        // 上傳失敗則顯示上傳失敗,如多圖則從圖檔清單删除圖檔
        if (!this.multiple) {
          this.$refs.failUpload.style.display = 'block';
        } else {
          this.uploadList.pop();
        }
        this.$refs.vueCropper.isDisabled = false;
      }
      this.cropperModel = false;
    },
    formatImgArr (arr) {
      const result = arr.map((item, index) => {
        if (typeof item === 'string') {
          return {
            url: item,
            uid: `index${index}`
          };
        } else {
          return item.url;
        }
      });
      return result;
    },
    beforeClose () {
      // this.uploadList.pop();
      console.log(this.uploadList);
      this.cropperModel = false;
    },
    // 壓縮圖檔
    compress(img) {
      let canvas = document.createElement('canvas');
      let ctx = canvas.getContext('2d');
      // let initSize = img.src.length;
      let width = img.width;
      let height = img.height;
      canvas.width = width;
      canvas.height = height;
      // 鋪底色
      ctx.fillStyle = '#fff';
      ctx.fillRect(0, 0, canvas.width, canvas.height);
      ctx.drawImage(img, 0, 0, width, height);
      // 進行壓縮
      let ndata = canvas.toDataURL('image/jpeg', 0.8);
      return ndata;
    },
    // base64轉成bolb對象
    dataURItoBlob(base64Data) {
      let byteString;
      if (base64Data.split(',')[0].indexOf('base64') >= 0) { byteString = atob(base64Data.split(',')[1]); } else { byteString = unescape(base64Data.split(',')[1]); }
      let mimeString = base64Data.split(',')[0].split(':')[1].split(';')[0];
      let ia = new Uint8Array(byteString.length);
      for (let i = 0; i < byteString.length; i++) {
        ia[i] = byteString.charCodeAt(i);
      }
      return new Blob([ia], { type: mimeString });
    }
  },
  components: {
    Cropper
  }
};
</script>
<style>
  .avatar-uploader .el-upload {
    border: 1px dashed #d9d9d9;
    border-radius: 6px;
    cursor: pointer;
    position: relative;
    overflow: hidden;
  }
  .avatar-uploader .el-upload:hover {
    border-color: #409eff;
  }
  .avatar-uploader-icon {
    color: #8c939d;
    text-align: center;
  }
  .avatar {
    display: block;
  }
  .reupload {
    border-radius: 50%;
    position: absolute;
    color: #fff;
    background-color: #000000;
    opacity: 0.6;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    display: none;
  }
  #uploadIcon{
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    display: none;
  }
</style>