vue3中使用el-upload+vue-cropper实现图片上传裁剪图片

vue3中使用el-upload+vue-cropper实现图片上传裁剪图片,裁剪可放大、缩小、旋转
上传完图片可以放大预览,也可删除
效果图如下

vue3中使用el-upload+vue-cropper实现图片上传裁剪图片_第1张图片

1、第一步安装vue-cropper

npm install vue-cropper

2、第二步封装组件
在src》components 下新建CropperImage文件夹,文件夹中新建index.vue,index.vue代码如下

<template>
  <el-dialog
    :title="title"
    :modelValue="dialogVisible"
    :show-close="true"
    :close-on-click-modal="false"
    :close-on-press-escape="false"
    :before-close="closeDialog"
    destroy-on-close
    width="900px"
  >
    <el-row>
        <el-col :xs="24" :md="12" :style="{ height: '350px' }">
          <div class="cropper-container">
            <!-- 待上传图片 -->
            <div v-show="!options.img" >
              <el-upload
                class="upload"
                ref="elUpload"
                action="#"
                :on-change="upload"
                :accept="accpetType"
                :limit="limit"
                :on-exceed="handleExceed"
                :show-file-list="false"
                :auto-upload="false"
              >
                <el-button slot="trigger"  type="primary" ref="uploadBtn">
                  选择图片
                </el-button>
              </el-upload>
              <div>
                请上传
                <template v-if="fileSize">
                  大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
                </template>
                <template v-if="fileType">
                  格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b>
                </template>
                的文件
              </div>
            </div>
            <!-- 已上传图片 -->
            <div v-show="options.img" class="cropper-crop">
              <vueCropper
                class="crop-box"
                ref="cropper"
                :img="options.img"
                :info="true"
                :autoCrop="options.autoCrop"
                :fixedBox="options.fixedBox"
                :canMoveBox="options.canMoveBox"
                :autoCropWidth="options.autoCropWidth"
                :autoCropHeight="options.autoCropHeight"
                :centerBox="options.centerBox"
                :fixed="options.fixed"
                :fixedNumber="options.fixedNumber"
                :canMove="options.canMove"
                :canScale="options.canScale"
                @realTime="realTime"
              ></vueCropper>
            </div>
          </div>
        </el-col>
        <el-col :xs="24" :md="12" :style="{ height: '350px' }">
             <!-- :style="`width:${cWidth}px;height:${cHeight}px;`" -->
            <div class="cropper-upload-preview" :style="`width:${options.previews.w}px;height:${options.previews.h}px;`">
              <img :src="options.previews.url" :style="options.previews.img" />
            </div>
         
        </el-col>
      </el-row>
      <br />
      <el-row>
        <el-col :lg="2" :md="2">
           <div class="reupload" @click="reupload">
            <el-button type="primary" v-show="options.img">重新上传<el-icon class="el-icon--right"><Upload /></el-icon></el-button>
          </div>
        </el-col>
        <el-col :lg="{ span: 1, offset: 2 }" :md="2">
          <el-button icon="Plus" @click="changeScale(1)"></el-button>
        </el-col>
        <el-col :lg="{ span: 1, offset: 1 }" :md="2">
          <el-button icon="Minus" @click="changeScale(-1)"></el-button>
        </el-col>
        <el-col :lg="{ span: 1, offset: 1 }" :md="2">
          <el-button icon="RefreshLeft" @click="rotateLeft()"></el-button>
        </el-col>
        <el-col :lg="{ span: 1, offset: 1 }" :md="2">
          <el-button icon="RefreshRight" @click="rotateRight()"></el-button>
        </el-col>
        <el-col :lg="{ span: 2, offset: 6 }" :md="2">
          <el-button type="primary" @click="getCrop()">提 交</el-button>
        </el-col>
      </el-row>
      
    
    <template #footer>
    <span  class="dialog-footer">
      <div>
        <el-button @click="closeDialog">取 消</el-button>
        <!-- <el-button type="primary" @click="getCrop">确 定</el-button> -->
      </div>
    </span>
    </template>
  </el-dialog>
</template>

<script>
import {ref,reactive} from "vue"
import 'vue-cropper/dist/index.css';
import { VueCropper } from 'vue-cropper';
import { ElMessage } from 'element-plus';
import { importNewImg } from "@/api/back/home.js"; //上传接口
export default {
  props: {
    dialogVisible: {
      type: Boolean,
      default: () => {
        return false;
      },
    },
    title: {
      type: String, 
      default: "",
    },
    cWidth: {
      type: [Number, String],
      default: ""
    },
    cHeight: {
      type: [Number, String],
      default: ""
    },
    // 图片数量限制
    limit: {
      type: Number,
      default: 5,
    },
    // 大小限制(MB)
    fileSize: {
      type: Number,
      default: 5,
    },
    // 文件类型
    fileType: {
      type: Array,
      default: () => ["png", "jpg", "jpeg"],
    },
    accpetType: {
      type: String,
      default: () => ".png,.jpg,.jpeg",
    },
  },
  components:{
    VueCropper
  },
  setup(props, { emit }) {
    const { proxy } = getCurrentInstance();
    const cropper=ref()
    const uploadBtn=ref()
    const elUpload=ref()
    const options=reactive({
      img: '', // 原图文件
      autoCrop: true, // 默认生成截图框
      fixedBox: false, // 固定截图框大小
      canMoveBox: true, // 截图框可以拖动
      autoCropWidth: props.cWidth, // 截图框宽度
      autoCropHeight: props.cHeight, // 截图框高度
      maxImgSize:200,
      fixed: true, // 截图框宽高固定比例
      fixedNumber: [1, 1], // 截图框的宽高比例
      centerBox: true, // 截图框被限制在图片里面
      canMove: false, // 上传图片不允许拖动
      canScale: true, // 上传图片不允许滚轮缩放
      previews: {}, //预览数据
    })
    // vueCropper组件 裁剪配置信息
    
    // 实时预览
    const realTime = (data) => {
      // options.previews = data;
      let previews = data
      let h = 0.5
      let w = 0.2
      
      options.previews = {
        width: previews.w + "px",
        height: previews.h + "px",
        url:previews.url,
        img:previews.img,
        overflow: "hidden",
        margin: "0",
        zoom: h
      }

    }
    // 文件个数超出
    const handleExceed = () => {
      ElMessage.error(`上传数量不能超过 ${props.limit} 个!`);
    }
    // 读取原图
    const upload = (file, uploadFiles) => {
      //console.log(file);
      let isImg = false;
      let fileExtension = "";
      if (props.fileType.length) {
        if (file.name.lastIndexOf(".") > -1) {
          fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1);
        }
        isImg = props.fileType.some((type) => {
          if (file.raw.type.indexOf(type) > -1) return true;
          if (fileExtension && fileExtension.indexOf(type) > -1) return true;
          return false;
        });
      } else {
        isImg = file.type.indexOf("image") > -1;
      }
      if (!isImg) {
        ElMessage.error(`格式不正确, 请上传${props.fileType.join("/")}图片格式文件!`);
        return false;
      } else {
        isImg = true;
      }
      if (props.fileSize) {
        const isLt = file.size / 1024 / 1024 < props.fileSize;
        if (!isLt) {
          proxy.$modal.msgError(`上传大小不能超过 ${props.fileSize} MB!`);
          fileList.value = [];
          return false;
        }
      }
        
      /* 
        const isIMAGE = file.raw.type === 'image/jpeg' || file.raw.type === 'image/png'
        const isLt5M = file.raw.size / 1024 / 1024 < 5
        if (!isIMAGE) {
          ElMessage.warning("请选择 jpg、png 格式的图片" );
          return false
        }
        if (!isLt5M) {
          ElMessage.warning("图片大小不能超过 5MB" );
          return false
        } 
      */
      let reader = new FileReader()
      
      reader.readAsDataURL(file.raw)
      //console.log(reader);
      
      reader.onload = e => {
        options.img = e.target.result // base64
       
      }
      elUpload.value.clearFiles(); //这里处理重新上传时,upload组件change事件错误问题
    }
    // 获取截图信息
    const getCrop=()=> {
      // 获取截图的 base64 数据
      cropper.value.getCropData((data) => {
        
      });
      // 获取截图的 blob 数据
      cropper.value.getCropBlob(data => {
        let formData = new FormData();
        //第三个参数是规定以什么为后缀,接口是根据后缀来返回地址格式的
        formData.append("file", data,'chris.jpg');
        //上传接口
        importNewImg(formData).then(res=>{
          console.log("222-res",res);
          emit('cropperImgMethod',res.fileName)

          closeDialog()
        })
        // emit('closeCropperDialog')
        // closeDialog()
      })
    }
    // 重新上传
    const reupload=()=> {
      uploadBtn.value.ref.click()
    }
    // 关闭弹框
    const closeDialog=()=> {
      emit('closeCropperDialog')
      options.img = ''
    }
    /** 向左旋转 */
    const rotateLeft=()=> {
      proxy.$refs.cropper.rotateLeft();
    }
    /** 向右旋转 */
    const rotateRight=()=>  {
      proxy.$refs.cropper.rotateRight();
    }
    /** 图片缩放 */
    const changeScale=(num)=>  {
      num = num || 1;
      proxy.$refs.cropper.changeScale(num);
    }
    return {
      options,
      cropper,
      uploadBtn,
      elUpload,
      realTime,
      handleExceed,
      rotateLeft,
      rotateRight,
      changeScale,
      upload,
      getCrop,
      reupload,
      closeDialog
    }
  }
}


</script>

<style lang="scss" scoped>
.dialog-footer {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  font-size: 14px;
  .reupload {
    color: #409eff;
    cursor: pointer;
  }
}
.cropper-container {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  height:100%;
  background-color: #f0f2f5;
  margin-right: 10px;
  border-radius: 4px;
  .upload {
    text-align: center;
    margin-bottom: 24px;
  }
  .cropper-crop {
    width: 560px;
    height: 350px;
    position: relative;
    .crop-box {
      width: 100%;
      height: 100%;
      border-radius: 4px;
      overflow: hidden;
    }
  }
  .cropper-upload-preview{
    position: absolute;
    top: 50%;
    transform: translate(50%, -50%);
    width: 200px;
    height: 200px;
    border-radius: 0;
    box-shadow: 0 0 4px #ccc;
    overflow: hidden;
    >img{
      width:100%!important;
    }
  }
}
</style>


3、第三步使用组件

<template>

    <div>
      
      
      <!-- cropper str -->
      <div class="imgCorpper" v-if="!cropperObj.imgShow" @click="cropperObj.openCropperView">
        <el-icon><Plus /></el-icon>
      </div>
      <div class="imgCorpper" v-else  @mouseenter="cropperObj.buttonopen" @mouseleave="cropperObj.buttonexit">
        <div class="imgCorpperY">
          <el-image  
          :src="cropperObj.previewsImgUrl"
          :zoom-rate="1.2"
          :preview-src-list="cropperObj.srcList"
          :initial-index="4"
          fit="cover"/>
          <el-image-viewer
            ref="previewViewer"
            style="z-index:10"
            v-if="cropperObj.showImageViewer"
            :on-close="cropperObj.closeImgView"
            @close="cropperObj.closeImgView"
            :url-list="cropperObj.srcList"
          />
          <div :class="cropperObj.showBack?'imgcurrent show':'imgcurrent'"  >
            <div class="imgBack"></div>
            <div class="imgicon">
              <el-icon @click="cropperObj.showPic"><ZoomIn /></el-icon>
              <el-icon @click="cropperObj.delePic"><Delete /></el-icon>
            </div>
          </div>
        </div>
      </div>
      <ImgCropper 
        v-if="cropperObj.cVisible" 
        :dialogVisible.sync="cropperObj.cVisible" 
        :title="cropperObj.ctitle"
        :cWidth="200"
        :cHeight="200"
        :limit="1"
        :fileSize="10"
        :fileType="cropperObj.fileTypeList"
        @cropperImgMethod="cropperObj.cropperImgMethod"
        @closeCropperDialog="cropperObj.closeCropperView"
      ></ImgCropper>
      <!-- cropper end -->

    </div>
   

</template>

<script setup>

  // 图片上传裁剪 str
  import ImgCropper from '@/components/CropperImage/index.vue'
  const cropperObj = reactive({
    fileTypeList: ["png", "jpg", "jpeg"],
    cVisible:false, // 显示切图弹框
    ctitle:"", // 弹框标题
    imgShow:false, //是否有图片
    previewsImgUrl:"", //图片地址
    srcList:[], //图片预览地址数组
    showBack:false, // 预览删除图层是否展示
    showImageViewer:false, // 是否预览
    previewViewer:"",
    // 开启剪切弹框
    openCropperView: () => {
      console.log("ddds");
      cropperObj.ctitle="图片剪切"
      cropperObj.cVisible = true 
    },
    // 关闭弹框所触发的事件
    closeCropperView: (data)=> {
      cropperObj.cVisible = false
    },
    // 剪切图地址
    cropperImgMethod: (data) =>{
      cropperObj.srcList =[]
      cropperObj.previewsImgUrl =window.serverUrl.IMG_SERVER+data;
      cropperObj.srcList=[cropperObj.previewsImgUrl]
      cropperObj.imgShow = true
    },
    // 预览删除图层显示
    buttonopen: ()=>{
      cropperObj.showBack = true
    },
    // 预览删除图层隐藏
    buttonexit: ()=>{
      cropperObj.showBack = false
    },
    // 预览展示
    showPic: ()=>{
      cropperObj.showImageViewer = true
    },
    // 预览隐藏
    closeImgView: ()=>{
      cropperObj.showImageViewer = false
      // previewViewer.value.showViewer = false
    },
    // 删除图片
    delePic:()=>{
      cropperObj.previewsImgUrl ="";
      cropperObj.srcList=[]
      cropperObj.showBack = false
      cropperObj.imgShow = false
    }

  })      
  // 图片上传裁剪 end
  

</script>

<style lang="scss">
 .imgCorpper{
   display: flex;
   justify-content: center;
   align-items: center;
   font-size:1rem;
   width:148px;
   height: 148px;
   border:1px dashed #cdd0d6;
   position: relative;
   border-radius: 6px;
   .el-icon{
     width:0.5rem;
     height: 0.5rem;
     color:#909399;
   }
   .imgCorpperY{
      position: relative;
      width: 100%;
      height: 100%;
      border-radius: 6px;
      overflow: hidden;
      height: 100%;;
      >img{
        width: 100%;
        height: 100%;;
      }
      .el-image{
            width: 100%;
            height: 148px;
            max-height: 100%;
         }
      .imgcurrent{
          display: none;
          position: absolute;
          width: 100%;
          height: 100%;
          top: 0;
          .imgBack{
            background: #00000082;
            width: 100%;
            height: 100%;
            position: absolute;
            z-index: 1;
          }
          .imgicon{
            position: absolute;
            width: 90%;
            margin: 0 5%;
            height: 100%;
            display: flex;
            justify-content: space-between;
            align-items: center;
            color: #fff;
            z-index: 2;
            .el-icon{
              color: #fff;
              font-size: 0.35rem;
            }
          }
      }
      >.show{
        display:block;
      }
   }
 }
</style>

你可能感兴趣的:(vue.js,前端,javascript)