Bootstrap

vue3+ElementPlus+VueCropper实现上传图片

前言

我们需要上传图片,然后弹框进行裁剪(放大、缩小)。

效果

实现

裁剪组件.vue

<template>
    <div>
        <!--裁剪图片-->
        <el-dialog v-model="tailorDialogVisible" @open="onTailorDialogOpen" @close="onTailorDialogCancel" title="编辑图片" width="680" align-center>
            <div style=" width: 550px; height: 450px; margin: 30px auto;" v-loading="loading"
                 element-loading-text="Loading...">
                <vue-cropper ref="cropper"
                             :img="tailorOption.img"
                             :output-type="tailorOption.outputType"
                             :full="tailorOption.full"
                             :auto-crop="tailorOption.autoCrop"
                             :auto-crop-width="tailorOption.autoCropWidth"
                             :auto-crop-height="tailorOption.autoCropHeight"
                             :center-box="tailorOption.centerBox"
                             :max-img-size="tailorOption.max"
                             mode="cover"
                             @imgLoad="onTailorLoad"
                ></vue-cropper>
            </div>
            <div style="display: flex; margin: 0 200px; justify-content: center; align-items: center">
                <div style="width: 40px;text-align: left; cursor: pointer;">
                    <!-- <el-icon @click="onScaleMinus(0.1)" :size="16"><Minus /></el-icon> -->
                    <span @click="onScaleMinus(0.1)">-号</span>
                </div>
                <div style="flex: 1">
                    <el-slider v-model="tailorAmplificationValue" :min="1" :max="3" :step="0.1" @change="onChangTailorAmplificationSlider" />
                </div>
                <div style="width: 40px;text-align: right; cursor: pointer;">
                    <!-- <el-icon @click="onScaleAdd(0.1)" :size="16"><Plus /></el-icon> -->
                    <span @click="onScaleAdd(0.1)">+号</span>
                </div>
            </div>
            <div style="display: flex; margin: 10px 200px 40px 200px; justify-content: center; align-items: center">
                <div style="width: 40px;text-align: left; cursor: pointer;">
                  <span @click="onRotateLeft">左旋</span>
                  <!-- <el-icon @click="onRotateLeft" :size="16"><Plus /></el-icon> -->
                    <!-- <img @click="onRotateLeft" style="width: 14px; height: 14px" src="../../../public/img/recipeManage/left_rotate.png" alt=""> -->
                </div>
                <div style="flex: 1">
                    <el-slider v-model="tailorSpinValue" :min="-180" :max="180" :step="90" @change="onChangTailorSpinSlider" />
                </div>
                <div style="width: 40px;text-align: right; cursor: pointer;">
                  <span @click="onRotateRight">右旋</span>
                  <!-- <el-icon @click="onRotateRight" :size="16"><Plus /></el-icon> -->
                    <!-- <img @click="onRotateRight" style="width: 14px; height: 14px" src="../../../public/img/recipeManage/right_rotate.png" alt=""> -->
                </div>
            </div>

            <div style="padding-top: 20px;" class="recipeManage_border_top">
                <div style="display: flex; align-items: center; justify-content: center">
                    <div>
                        <el-button type="primary" plain @click="onTailorDialogCancel">取消</el-button>
                    </div>
                    <div style="margin-left: 20px">
                        <el-button type="primary" @click="onTailorDialogSubmit">确认</el-button>
                    </div>
                </div>
            </div>
        </el-dialog>
    </div>
</template>

<script setup>
  import 'vue-cropper/dist/index.css'
  import { VueCropper }  from "vue-cropper";
  // import { postImgData } from '@/api/base/public';
  import { ref, defineProps, toRefs, defineEmits } from 'vue';

  const emit = defineEmits(['close', 'submit']);
  const props = defineProps({
    tailorDialogImg: {
      default: ''
    },
    autoCropWidth: {
      default: 180
    },
    autoCropHeight: {
      default: 180
    },
    tailorDialogVisible: {
      type: Boolean,
      required: true,
      default: false
    },
  });

  const { tailorDialogVisible, tailorDialogImg, autoCropWidth, autoCropHeight } = toRefs(props);

  const loading = ref(false);
  // 裁剪图片
  const cropper = ref("");

  // const tailorDialogVisible = ref(visible.value);

  const tailorAmplificationValue = ref(0);
  const tailorSpinValue = ref(0);
  const tailorOption =  ref({
    img: "",
    full: false,
    outputType: 'png',
    autoCrop: true,
    autoCropWidth: 180,
    autoCropHeight: 180,
    centerBox: true,
    max: 99999,
  });

  // 放大
  let oldTailorAmplificationValue = 0;
  const onScaleAdd = (num) => {
    if (tailorAmplificationValue.value === 3) return;

    // tailorAmplificationValue.value = Number(parseFloat(tailorAmplificationValue.value + num).toFixed(1));
    tailorAmplificationValue.value = +(parseFloat(tailorAmplificationValue.value + num).toFixed(1));

    oldTailorAmplificationValue = tailorAmplificationValue.value;
    cropper.value.changeScale(tailorAmplificationValue.value);
  };
  const onScaleMinus = (num) =>  {
    if (tailorAmplificationValue.value === 1) return;

    tailorAmplificationValue.value = tailorAmplificationValue.value - num;

    oldTailorAmplificationValue = -tailorAmplificationValue.value;
    cropper.value.changeScale(-tailorAmplificationValue.value);
  };
  const onChangTailorAmplificationSlider = (value) => {
    if (oldTailorAmplificationValue > value) {
      cropper.value.changeScale(-value);
    } else {
      cropper.value.changeScale(value);
    }
    oldTailorAmplificationValue = value;
  };

  // 旋转
  let oldTailorRotateValue = 0;
  const onChangTailorSpinSlider = (value) => {
    if (value > 0) {
      cropper.value.rotateRight();
    } else {
      cropper.value.rotateLeft();
    }
  };
  const onRotateLeft = () => {
    if (cropper.value.rotate === -2) return false;
    cropper.value.rotateLeft();
    oldTailorRotateValue = cropper.value.rotate * 90
    console.log("===oldTailorRotateValue===", oldTailorRotateValue)
    tailorSpinValue.value = cropper.value.rotate * 90
  };
  const onRotateRight = () =>  {
    if (cropper.value.rotate === 2) return false;
    cropper.value.rotateRight();
    oldTailorRotateValue = cropper.value.rotate * 90
    console.log("===oldTailorRotateValue===", oldTailorRotateValue)
    tailorSpinValue.value = cropper.value.rotate * 90
  };

  const onTailorDialogOpen = () => {
    console.log("===弹框打开===");
    loading.value = true;
    tailorOption.value.img = tailorDialogImg.value;
    tailorOption.value.autoCropWidth = autoCropWidth.value;
    tailorOption.value.autoCropHeight = autoCropHeight.value;
  };
  const onTailorLoad = (msg) => {
    console.log("===msg===", msg)
    loading.value = false;
  };

  const onTailorDialogCancel = () => {
    emit('close');
    loading.value = false;
    tailorOption.value.img = "";
    tailorAmplificationValue.value = 0;
    tailorSpinValue.value = 0;
  };
  const onTailorDialogSubmit = () => {
    cropper.value.getCropData(data => {
      tailorAmplificationValue.value = 0;
      tailorSpinValue.value = 0;
      tailorOption.value.img = "";
      emit('submit', data);
    })
    // cropper.value.getCropBlob(data => {
    //   // 创建 FormData 对象并添加 Blob 对象
    //   const formData = new FormData();
    //   formData.append('file', data, '1.png');
    //   // postImgData(formData).then((res) => {
    //   //   if (res.data.code === 200) {
    //   //     emit('submit', res.data.data);
    //   //   }
    //   // }).catch(err => {});
    //   tailorAmplificationValue.value = 0;
    //   tailorSpinValue.value = 0;
    //   tailorOption.value.img = "";
    //   emit('submit', '');
    // })
    // let blob = await cropper.value.getCropBlob();
    // // 创建 FormData 对象并添加 Blob 对象
    // const formData = new FormData();
    // formData.append('file', blob, '1.png');
    // let res = postImgData(formData);
    //
    // emit('submit', res.data.data);
    //
    // tailorAmplificationValue.value = 0;
    // tailorSpinValue.value = 0;
    // tailorOption.value.img = "";

  };
</script>

<style>

</style>

使用组件.vue

<template>
  <div>
    <el-upload action="#" :http-request="onImgRequest" :show-file-list="false" :before-upload="(file) => onImgUpload(file)">
      <div style="height: 100px; width: 100px; background-color: red;">上传</div>
    </el-upload>

    <el-image v-if="imagesUrl" :src="imagesUrl"></el-image>

     <tailorImage :tailorDialogVisible="tailorDialogVisible" :tailorDialogImg="tailorDialogImg" @submit="tailorDialogImgSucc"></tailorImage>
     <!-- <el-image :src="src" /> -->
  </div>
</template>
<script setup>
import tailorImage from "./components/TailorImage.vue"
import {ref} from "vue"
import { ElMessage } from 'element-plus';

const tailorDialogVisible = ref(false)
const tailorDialogImg = ref("")

const imagesUrl = ref("")


const onImgRequest = () => {};
const onImgUpload = (rawFile) => {
  if (rawFile.type.indexOf('image/') === -1) {
    ElMessage.error('文件格式错误,请上传图片类型,如: JPG,PNG后缀的文件');
    return false;
  } else {
    const reader = new FileReader();
    reader.readAsDataURL(rawFile);
    reader.onload = () => {
      tailorDialogImg.value = reader.result;
      tailorDialogVisible.value = true;
    };
  }
};

const tailorDialogImgSucc = (data) => {
  // console.log("===tailorDialogImgSucc===", data)
  imagesUrl.value = data;
  tailorDialogVisible.value = false;
  tailorDialogImg.value = "";
}

// const test = () => {
//   tailorDialogVisible.value = true
//   console.log("===裁剪图片====");
// }
</script>
<style>
  
</style>

;