首先要下载安装cropperjs插件
npm install cropperjs
组件内引入
import Cropper from 'cropperjs';
import 'cropperjs/dist/cropper.css';
封装组件
<template><div class="upload_img"><ElDialogv-model="showUploadDialog":class="'create'":align-center="true":title="title"lock-scrollwidth="560"@close="onCancel"><div class="upload_img_wrap"><div class="upload_img_title"><ElUploadref="uploadRef"action="#"class="avatar-uploader":auto-upload="false":show-file-list="false":limit="1":accept="'image/*'"@change="handleBeforeUpload"><Icon :icon="`svg-icon:upload_img_icon`" :size="18" /><span class="upload_text">请选择要上传的图片</span></ElUpload></div><div class="upload_img_content"><div class="edit_wrap"><img v-if="defaultAvatar" :src="defaultAvatar" ref="imgRef" /><p v-else>{{ placeholder }}</p></div><div class="preview_wrap" ref="previewRef"></div></div></div><template #footer><FooterBar @confirm="onConfirm" @cancel="onCancel" /></template></ElDialog></div>
</template>
<script setup lang="ts">
import Cropper from 'cropperjs';
import 'cropperjs/dist/cropper.css';
import { Icon } from '@/components/pc/Icon';
import FooterBar from '@/components/pc/iForm/src/form_dialog/footer_bar.vue';
import { uploadApi } from '@/api/iform';
import { cropperProps } from '@/types/cropper_types';
import { dataURLtoBlob } from '@/utils/index';const { props } = defineProps<{ props: cropperProps }>();
const title = ref(props?.title ?? '上传图片');
const placeholder = ref(props?.placeholder ?? '选择一张本地图片进行编辑');
const aspectRatio = ref(props?.aspectRatio ?? 1);
const cropBoxResizable = ref(props?.cropBoxResizable ?? false);
const exportImgWidth = ref(props?.exportImgWidth ?? 300);
const exportImgHeight = ref(props?.exportImgHeight ?? 300);
const exportImgType = ref(props?.exportImgType ?? 'image/png');const showUploadDialog = ref(true);
const emit = defineEmits(['confirm', 'cancel']);
const defaultAvatar = ref('');
const imgRef = ref();
const previewRef = ref();
let cropper;const uploadRef = ref();
// 裁剪相关配置
const initCropper = () => {cropper && cropper.destroy();nextTick(() => {cropper = new Cropper(imgRef.value, {aspectRatio: unref(aspectRatio),preview: previewRef.value,guides: false,autoCrop: true,autoCropArea: 0.5,movable: true,cropBoxResizable: unref(cropBoxResizable),scalable: true,zoomable: true,rotatable: true,dragMode: 'none',viewMode: 1,minCropBoxWidth: 100,minCropBoxHeight: 100,ready: () => {uploadRef.value?.clearFiles();},});});
};const acceptList = ['JPG', 'PNG', 'JPEG'];
const handleBeforeUpload = async (file) => {const { raw } = file;const fileType = getFileType(raw?.name || '');if (!acceptList.includes(fileType)) {$message({message: `仅支持${acceptList.join(',')}类型`,type: 'warning',});uploadRef.value?.clearFiles();} else {defaultAvatar.value = URL.createObjectURL(raw);initCropper();}
};onBeforeUnmount(() => {cropper && cropper.destroy();
});const getFileType = (fileName: string) => {const decimalIndex = fileName.lastIndexOf('.');if (decimalIndex === -1) {return '';}return fileName.substring(decimalIndex + 1).toLocaleUpperCase();
};const onConfirm = async () => {const base64Data = cropper.getCroppedCanvas({width: unref(exportImgWidth),height: unref(exportImgHeight),}).toDataURL(unref(exportImgType));const { blob, name } = dataURLtoBlob(base64Data);const formData: any = new FormData();formData.append('file', blob, name); // 第三个参数传入生成的随机文件名formData.append('uploadSource', 1);const loading = ElLoading.service({fullscreen: true,lock: true,background: 'rgba(0, 0, 0, 0.7)',});const res: any = await uploadApi(formData);loading.close();if (res?.code == 200 && res?.data != undefined) {emit('confirm', res.data?.lastingFileUrl || '');$message({message: `保存成功`,type: 'success',});} else {$message({message: res?.msg || '操作失败, 请检查网络',type: 'error',});return false;}
};
const onCancel = () => {emit('cancel');
};
</script>
<style lang="less" scoped>
.upload_img {.upload_img_wrap {.upload_img_title {padding-bottom: 12px;.avatar-uploader {width: 190px;display: flex;align-items: center;justify-content: center;padding: 5px 10px;box-sizing: border-box;border-radius: 6px;border: 1px solid @border-color;.upload_text {margin-left: 2px;color: @btn-color;}}}.upload_img_content {display: flex;align-items: flex-start;justify-content: space-between;.edit_wrap {width: 360px;height: 360px;background-color: @input-bg-gray;position: relative;> img {display: block;width: 100%;}> p {position: absolute;top: 50%;left: 50%;transform: translate(-50%, -50%);font-weight: 400;font-size: 14px;color: @tab-btn-color;width: 100%;text-align: center;}}.preview_wrap {width: 160px;height: 160px;overflow: hidden;margin-left: 24px;background-color: @input-bg-gray;border-radius: 6px;}}}:deep(.el-dialog__body) {padding: 5px 20px;}
}
</style>