效果圖:?
?參考文檔:
Vue-Cropper 文檔Vue-Cropper 文檔
安裝VueCropper
//npm安裝
npm install vue-cropper@next -d --save//yarn安裝
yarn add vue-cropper@next
引入組件
?在main.ts中全局注冊:
import VueCropper from 'vue-cropper';
import 'vue-cropper/dist/index.css';const app = createApp(App);
app.use(VueCropper);
app.mount('#app');
局部:
import 'vue-cropper/dist/index.css';
import { VueCropper } from 'vue-cropper';
代碼層:
頁面(父組件)
<template><div class="p-4"><imgv-if="photo":src="photo"class="avatar"@click="handleClick"/><el-icon v-else class="avatar-uploader-icon" @click="handleClick"><Plus/></el-icon><ImageCropperv-model="visible"@onSuccess="onSuccess":image="photo"></ImageCropper></div>
</template><script setup lang="ts">
import ImageCropper from "@/components/imageCropper.vue";//裁剪圖片組件
import { uploadPicturesApi } from "@/api/common/index";
import { ElMessage } from "element-plus";
import { ref } from "vue";
const visible = ref<boolean>(false); //上傳圖片彈框
const photo=ref<string>('')
// 打開彈框
const handleClick = (img: string) => {console.log(img);visible.value = true;
};const handleClickImgUrl = () => {console.log(formInfo.value);
};/*** 上傳圖片成功* @param data File*/
const onSuccess = async (data: File) => {console.log(data);try {
//接口上傳為formDataconst res = await uploadPicturesApi(data);if (res.code == 200) {formInfo.value.photo = res.data;//接口返回圖片地址ElMessage.success("上傳成功");visible.value = false;}console.log(res);} catch (error) {ElMessage.error("上傳失敗");}
};
</script><style lang="scss" scoped>
.avatar {width: 160px;height: 160px;display: block;/* border: 1px dashed #8c939d; */border-radius: 6px;
}.el-icon.avatar-uploader-icon {font-size: 28px;color: #8c939d;width: 160px;height: 160px;text-align: center;border: 1px dashed #8c939d;border-radius: 6px;
}
</style>
?封裝剪切圖片組件(子組件):
<template><el-dialogappend-to-body:close-on-click-modal="false"v-model="visible"title="圖片裁剪"width="800px"@close="handleClose"><div class="cropper-cut"><div class="picUpload-wraper"><div class="pic-left-wraper"><VueCropperref="cropperRef":img="option.img":outputType="option.outputType":info="true":full="option.full":canMove="option.canMove":canScale="option.canScale":canMoveBox="option.canMoveBox":fixed="option.fixed":fixedBox="option.fixedBox":original="option.original":autoCrop="option.autoCrop":autoCropWidth="cropWith || option.autoCropWidth":autoCropHeight="cropHeight || option.autoCropHeight":centerBox="option.centerBox":high="option.high":infoTrue="option.infoTrue":enlarge="option.enlarge"@realTime="realTime"></VueCropper></div><div class="pic-right-wraper"><div class="title">圖片預覽</div><div :style="previewStyle" class="previewImg" v-show="option.img"><div :style="previews.div"><img :src="previews.url" :style="previews.img" /></div></div></div></div><div class="pic-operate"><inputstyle="display: none"@change="handleChange"ref="fileRef"type="file"name=""id=""/><el-button @click="fileRef?.click()">選擇圖片</el-button><el-button @click="changeScale(1)" :icon="Plus"> </el-button><el-button @click="changeScale(-1)" :icon="Minus"> </el-button><el-button @click="rotateRight()" :icon="RefreshRight"> </el-button></div></div><template #footer><div class="dialog-footer"><el-button @click="$emit('update:modelValue', false)">取消</el-button><el-button @click="saveHeadPic">保存</el-button></div></template></el-dialog>
</template><script setup lang="ts">
import { reactive, ref, watch } from "vue";
import { VueCropper } from "vue-cropper";
import "vue-cropper/dist/index.css";
import { Plus, Minus, RefreshRight } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus";const props = defineProps({// 窗口modelValue: {type: Boolean,default: false,},// 大小limitSize: {type: Number,default: 100,},// 寬cropWith: {type: String,default: "350px",},// 高cropHeight: {type: String,default: "350px",},accept: {type: Array<String>,default: () => [],},// 校驗regExp: {type: RegExp,default: /\.(jpg|jpeg|png)$/i,},
});const emit = defineEmits(["update:modelValue", "onSuccess"]);const visible = ref<boolean>(false);const option = reactive({img: "",outputSize: 1, // 裁剪生成圖片的質量 1 0.1 - 1outputType: "png", // 裁剪生成圖片的格式 jpg (jpg 需要傳入jpeg) jpeg || png || webpinfo: true, // 裁剪框的大小信息 true true || falsecanScale: true, // 圖片是否允許滾輪縮放 true true || falseautoCrop: true, //是否默認生成截圖框 false true || falseautoCropWidth: 240, //默認生成截圖框寬度 容器的80% 0~maxautoCropHeight: 300, //默認生成截圖框高度 容器的80% 0~maxfixed: false, //是否開啟截圖框寬高固定比例 true true | false// fixedNumber: [1, 1], //截圖框的寬高比例 [1, 1] [寬度, 高度]full: false, //是否輸出原圖比例的截圖 false true | falsefixedBox: true, //固定截圖框大小 不允許改變 false true | falsecanMove: false, //上傳圖片是否可以移動 true true | falsecanMoveBox: true, //截圖框能否拖動 true true | falseoriginal: false, //上傳圖片按照原始比例渲染 false true | falsecenterBox: false, //截圖框是否被限制在圖片里面 false true | falsehigh: false, //是否按照設備的dpr 輸出等比例圖片 true true | falseinfoTrue: false, //true 為展示真實輸出圖片寬高 false 展示看到的截圖框寬高 false true | falsemaxImgSize: 2000, //限制圖片最大寬度和高度 2000 0-maxenlarge: 1, //圖片根據截圖框輸出比例倍數 1 0-max(建議不要太大不然會卡死的呢)mode: "contain", //圖片默認渲染方式 contain contain , cover, 100px, 100% auto
});const previewStyle = ref<any>({});
const previews = ref<any>({});const fileRef = ref<HTMLInputElement>();
const cropperRef = ref<typeof VueCropper>();
let _file: File | null = null;watch(() => props.modelValue,(val) => {visible.value = val;}
);/*** 選擇圖片* @param e*/
const handleChange = (e: Event) => {const _target = e.target as HTMLInputElement;// console.log(_target)if (_target.files && _target.files.length > 0) {_file = _target.files[0];if (_file.size / 1024 / 1024 > props.limitSize) {ElMessage.warning(`圖片大小不能超過${props.limitSize}MB`);return;}// const type = _file.name.split('.')[1]if (!props.regExp.test(_file.name)) {ElMessage.warning(`請上傳jpg、jpeg、png格式圖片`);return;}const reader = new FileReader();reader.readAsDataURL(_file);reader.onload = (e) => {option.img = e.target?.result as string;};}
};
/*** 實時預覽事件* @param data*/
const realTime = (data: any) => {previews.value = data;previewStyle.value = {width: data.w + "px",height: data.h + "px",overflow: "hidden",margin: "0",zoom: 160 / data.w,};
};
/*** 放大縮小* @param num*/
const changeScale = (num: number) => {num = num || 1;cropperRef.value?.changeScale(num);
};
/*** 旋轉圖片*/
const rotateRight = () => {cropperRef.value?.rotateRight();
};/*** 取消清空*/
const handleClose = () => {// console.log('close')option.img = "";fileRef.value && (fileRef.value.value = "");previews.value = {};previewStyle.value = {};cropperRef.value?.clearCrop();_file = null;emit("update:modelValue", false);
};/***上傳保存圖片*/
const saveHeadPic = () => {if (!option.img) return ElMessage.warning("請先上傳圖片");cropperRef.value?.getCropBlob((blob: Blob) => {// blob 轉 fileif (_file) {const file = new File([blob], _file.name, { type: _file.type });emit("onSuccess", file);} else {ElMessage.error("獲取圖片失敗");}});
};
</script><style lang="scss" scoped>
.picUpload-wraper {height: fit-content;display: flex;justify-content: space-between;.pic-left-wraper {width: 540px;height: 360px;border: 1px solid #ddd;}.pic-right-wraper {padding: 20px;width: 200px;max-height: 360px;border: 1px solid #ddd;background: #fafafa;.title {margin-bottom: 10px;font-size: 14px;font-weight: 600;}.previewImg {max-height: 360px !important;overflow: hidden;}}
}
.pic-operate {margin-top: 10px;
}
</style>