1. Install and introduce vue-cropper (refer to: https://www.npmjs.com/package/vue-cropper)
npm install vue-cropper@next import 'vue-cropper/dist/index.css' import { VueCropper } from "vue-cropper";
2. Complete component code
<template> <div class="avatar-container" @click="editImage()"> <img :src="options.img" title="Click to upload" class="img-box" /> <el-dialog title="cropped picture" v-model="dialogVisible" width="800px" append-to-body @opened="openDialog" @close="closeDialog"> <el-row> <el-col :span="12" style="height: 300px;"> <vue-cropper ref="cropper" :img="options.img" :info="true" :autoCrop="options.autoCrop" :autoCropWidth="options.autoCropWidth" :autoCropHeight="options.autoCropHeight" :fixedBox="options.fixedBox" :outputType="options.outputType" @realTime="realTime" v-if="showCropper" /> </el-col> <el-col :span="12" style="height: 300px;"> <div class="preview-box"> <img :src="previews.url" :style="previews.img" /> </div> </el-col> </el-row> <el-row style="margin-top: 12px;"> <el-col :span="12"> <el-row> <el-col :span="8"> <el-upload action="#" :http-request="() => {}" :before-upload="beforeUpload" :show-file-list="false" > <el-button>Select</el-button> </el-upload> </el-col> <el-col :span="4"> <el-button :icon="Plus" @click="changeScale(1)"></el-button> </el-col> <el-col :span="4"> <el-button :icon="Minus" @click="changeScale(-1)"></el-button> </el-col> <el-col :span="4"> <el-button :icon="RefreshLeft" @click="rotateLeft()"></el-button> </el-col> <el-col :span="4"> <el-button :icon="RefreshRight" @click="rotateRight()"></el-button> </el-col> </el-row> </el-col> <el-col :span="4" :offset="8"> <el-button type="primary" @click="uploadImg()">Submit</el-button> </el-col> </el-row> </el-dialog> </div> </template> <script setup lang="ts"> import { Plus, Minus, RefreshLeft, RefreshRight } from '@element-plus/icons-vue' import { ElMessage } from 'element-plus' import { uploadImage } from "@/api/target/manage"; import "vue-cropper/dist/index.css"; import { VueCropper } from "vue-cropper"; const { proxy } = getCurrentInstance(); const props = defineProps({ dataInfo: { type: Object, default: {} } }) const dialogVisible = ref(false); const showCropper = ref(false); // cropper configuration. For more configuration, please refer to https://www.npmjs.com/package/vue-cropper const options = reactive({ img: props.dataInfo.img, // The address of the cropped image autoCropWidth: 200, //The default width of the screenshot box generated is 80% of the default container autoCropHeight: 200, //The default height of the screenshot box is 80% of the default container outputType: "png", // Format of cropped image jpeg, png, webp autoCrop: true, // Whether to generate a screenshot box by default fixedBox: false, // Fixed screenshot box size }); const previews = ref({ url: '' }) watch( () => props.dataInfo, () => { options.img = props.dataInfo.img; }, { deep: true, immediate: true } ) //Edit picture const editImage = () => { dialogVisible.value = true; } //Open the cropping pop-up window const openDialog = () => { showCropper.value = true; } // Modify the size of the image. Positive numbers make it bigger, negative numbers make it smaller. const changeScale = (num: number) => { num = num || 1; proxy.$refs.cropper.changeScale(num); } // Rotate 90 degrees to the left const rotateLeft = () => { proxy.$refs.cropper.changeScale(); } // Rotate 90 degrees to the right const rotateRight = () => { proxy.$refs.cropper.rotateRight(); } //Upload image processing const beforeUpload = (rawFile: any) => { if (rawFile.type.indexOf("image/") == -1) { ElMessage.error('Please upload image type files!') return false } if (rawFile.size / 1024 / 1024 > 2) { ElMessage.error('File size cannot exceed 2MB!') return false } const reader = new FileReader(); reader.readAsDataURL(rawFile); reader.onload = () => { options.img = reader.result; }; } // upload image const uploadImg = () => { proxy.$refs.cropper.getCropBlob((data: any) => { let formData = new FormData(); formData.append("file", data); let params = { ID: 1 } uploadImage(params, formData).then((res: any) => { if(res.code == 200) { options.img = res.data; props.dataInfo.img = options.img; showCropper.value = false; dialogVisible.value = false; ElMessage.success("Upload successful!"); } else { ElMessage.error(res.message || 'Upload failed!') } }); }); } // Real-time preview event const realTime = (data: any) => { previews.value = data; } //Close pop-up window const closeDialog = () => { options.img = props.dataInfo.img; } </script> <style lang='scss' scoped> .avatar-container { position: relative; display: flex; justify-content: center; height: 10vw; width: 10vw; & amp;:hover { & amp;::after { content: " + "; position: absolute; width: 100%; height: 100%; border-radius: 50%; display: flex; justify-content: center; align-items: center; font-size: 30px; font-weight: bold; color: #fff; background: rgba(0, 0, 0, 0.5); cursor: pointer; } } .img-box { border-radius: 50%; border: 1px solid #ccc; width: 10vw; height: 10vw; } } .preview-box { position: absolute; top: 50%; transform: translate(50%, -50%); width: 200px; height: 200px; border-radius: 50%; border: 1px solid #ccc; overflow: hidden; } </style>
3. Component usage
<template> <div class="avatar-box"> <Avatar :dataInfo="dataInfo" /> </div> </template> <script setup lang="ts"> import { ref } from 'vue'; import Avatar from '@/components/Avatar/index.vue' interface DataFace { img: string [propName: string]: any } const dataInfo = ref<DataFace>({ img: 'https://img1.baidu.com/it/u=3717210657,1724010864 & amp;fm=253 & amp;fmt=auto & amp;app=138 & amp;f=PNG?w=500 & amp;h=500' }) </script> <style lang="scss" scoped> .avatar-box { width: 300px; display: flex; justify-content: center; } </style>