1.vue3+element-plus+vue-cropper实现裁剪图片
- element-UI官网
- element-plus官网
- vue-cropper
- vue3使用vue-cropper安装:
npm install vue-cropper@next
2.vue-cropper插件:
<vue-cropper :img="option.img" />
<script setup>
import {reactive} from "vue";
const option = reactive({
img:'https://img1.baidu.com/it/u=4049022245,514596079&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500'
})
script>
3.效果图:
4.实现cropperUpload组件:
<template>
<div class="uploadMian">
<div class="img-item" v-for="(item, index) in fileList" :key="index">
<img :src="item.src" />
<el-icon class="uploader-close" @click="delFn(index)"><Close />el-icon>
<div v-if="item.isSuccess" class="uploader-Check"><el-icon ><Check />el-icon>div>
<div class="button-div" v-if="item.file && isCropper">
<el-button type="success" @click="uploadFileFn(item, index)"
>上传el-button
>
<el-button type="primary" @click="cropperFn(item, index)"
>裁剪el-button
>
div>
div>
<el-upload
v-if="multiple || (!multiple && fileList.length == 0)"
class="avatar-uploader"
action="#"
:accept="
acceptArray.length > 0
? acceptArray.map((n) => acceptType[n]).join(',')
: '*'
"
:http-request="!isCropper ? uploadFileFn : () => {}"
:multiple="multiple"
:show-file-list="false"
:before-upload="beforeAvatarUpload"
>
<el-icon class="avatar-uploader-icon"><Plus />el-icon>
el-upload>
div>
<el-dialog title="裁切图片" v-model="showCropper" width="550px">
<div class="cropper-content">
<div class="cropper-box">
<div class="cropper">
<vue-cropper
ref="cropperRefs"
:img="option.img"
:output-size="option.outputSize"
:info="option.info"
:can-scale="option.canScale"
:auto-crop="option.autoCrop"
:auto-crop-width="option.autoCropWidth"
:auto-crop-height="option.autoCropHeight"
:fixed="option.fixed"
:fixed-number="option.fixedNumber"
:full="option.full"
:fixed-box="option.fixedBox"
:can-move="option.canMove"
:can-move-box="option.canMoveBox"
:original="option.original"
:center-box="option.centerBox"
:height="option.height"
:info-true="option.infoTrue"
:max-img-size="option.maxImgSize"
:enlarge="option.enlarge"
:mode="option.mode"
:limit-min-size="option.limitMinSize"
/>
div>
div>
div>
<span slot="footer">
<div class="dialog-footer">
<el-button @click="showCropper = false">取 消el-button>
<el-button type="primary" @click="onSubmit">确 定el-button>
div>
span>
el-dialog>
template>
<script setup>
import { ref, reactive, watch } from "vue";
import { Plus, Close,Check } from "@element-plus/icons-vue";
import "vue-cropper/dist/index.css";
import { VueCropper } from "vue-cropper";
const props = defineProps({
otherData: {
type: Object,
default: () => {},
},
headers: {
type: Object,
default: () => {},
},
modelValue: {
type: Array,
default: () => {
return [];
},
},
multiple: {
type: Boolean,
default: false,
},
size: {
type: Number,
default: 10 * 1024 * 1024,
},
isCropper: {
type: Boolean,
default: true,
},
sendUrl: {
type: String,
default: "",
},
});
const emits = defineEmits(["update:modelValue"]);
const cropperRefs = ref();
const cropperCb = ref(null);
const showCropper = ref(false);
let fileList = reactive([]);
const acceptArray = reactive(["png", "jpg", "jpeg"]);
const acceptType = reactive({
doc: "application/msword",
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
ppt: "application/vnd.ms-powerpoint",
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
xls: "application/vnd.ms-excel",
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
pdf: "application/pdf",
csv: ".csv",
txt: "text/plain",
image: "image/*",
png: "image/png",
gif: "image/gif",
jpg: "image/jpg",
jpeg: "image/jpeg",
});
watch(
props.modelValue,
(value) => {
const valueList = value || [];
let newFileList = [];
valueList.forEach((item) => {
const indexThis=fileList.findIndex(n=>n.src==item)
if(indexThis==-1){
newFileList.push({
src: item,
isSuccess: true,
});
}
});
fileList.unshift(...newFileList);
},
{ immediate: true, deep: true }
);
watch(
fileList,
(value) => {
const valueList = value
.map((n) => {
if (n.isSuccess) {
return n.src;
}
return null;
})
.filter((n) => n != null);
emits("update:modelValue", valueList);
},
{ deep: true }
);
const option = reactive({
img: "",
outputSize: 1,
outputType: "jpeg",
info: false,
canScale: true,
autoCrop: true,
autoCropWidth: 230,
autoCropHeight: 150,
fixed: false,
fixedNumber: [1.53, 1],
full: false,
fixedBox: false,
canMove: true,
canMoveBox: true,
original: true,
centerBox: true,
high: false,
infoTrue: false,
maxImgSize: 3000,
enlarge: 1,
mode: "550px 400px",
limitMinSize: [108, 108],
minCropBoxWidth: 108,
minCropBoxHeight: 108,
});
const judegFileSize = (file) => {
const filterSize = (size) => {
const pow1024 = (num) => {
return Math.pow(1024, num);
};
if (!size) return "";
if (size < pow1024(1)) return size + " B";
if (size < pow1024(2)) return (size / pow1024(1)).toFixed(0) + " KB";
if (size < pow1024(3)) return (size / pow1024(2)).toFixed(0) + " MB";
if (size < pow1024(4)) return (size / pow1024(3)).toFixed(0) + " GB";
return (size / pow1024(4)).toFixed(2) + " TB";
};
let retunBoolean = true;
let fileSize = file.size;
const fileExtArray = file.name.split(".");
const judegFn = () => {
if (acceptArray.indexOf(fileExtArray.at(-1)) == -1) {
alert(`${file.name}上传失败,只能上传${acceptArray.join("、")}`);
retunBoolean = false;
}
};
if (acceptArray.length > 0) {
if (acceptArray.indexOf("image") != -1) {
var pattern = /(\.jpg|\.jpeg|\.png|\.gif)$/i;
if (!pattern.test(`.${fileExtArray.at(-1)}`)) {
judegFn();
}
} else {
judegFn();
}
}
if (retunBoolean) {
if (props.size > 0 && fileSize > props.size) {
alert(`最大上传${filterSize(props.size)}`);
retunBoolean = false;
}
}
return retunBoolean;
};
const beforeAvatarUpload = (rawFile) => {
let retunBoolean = judegFileSize(rawFile);
if (retunBoolean) {
fileList.push({
src: URL.createObjectURL(rawFile),
file: rawFile,
});
}
return retunBoolean;
};
const cropperFn = (item, index) => {
showCropper.value = true;
option.img = URL.createObjectURL(item.file);
const reader = new FileReader();
reader.readAsDataURL(item.file);
cropperCb.value = (res) => {
if (res) {
cropperRefs.value.getCropBlob((data) => {
const result = new File([data], item.file.name, {
type: item.file.type,
lastModified: Date.now(),
});
result["uid"] = item.file.uid;
fileList.splice(index, 1, {
src: URL.createObjectURL(result),
file: result,
});
showCropper.value = false;
});
}
};
};
const delFn = (index) => {
fileList.splice(index, 1);
};
const onSubmit = () => {
if (cropperCb.value) cropperCb.value(true);
};
const uploadFileFn = (item) => {
if (props.sendUrl == "") return false;
const successFn = (url) => {
const index = fileList.findIndex((n) => {
if (n.file && n.file.uid == item.file.uid) {
return true;
}
return false;
});
if (index != -1) {
fileList.splice(index, 1, {
src: url,
file: item.file,
isSuccess: true,
});
}
};
const formData = new FormData();
formData.append("file", item.file);
if (props.otherData) {
Object.keys(props.otherData).forEach((key) => {
formData.append(key, props.otherData[key]);
});
}
fetch(props.sendUrl, {
method: "POST",
body: formData,
headers: props.headers,
"Content-type": "multipart/form-data",
})
.then((respone) => respone.json())
.then((res) => {
successFn("成功的url");
})
.catch((error) => {
});
};
script>
<style scoped lang="scss">
.el-icon.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
border: 1px solid #ccc;
width: 178px;
height: 178px;
text-align: center;
}
.uploadMian {
vertical-align: top;
display: flex;
flex-wrap: wrap;
}
.avatar-uploader {
}
.img-item {
display: inline-block;
width: 178px;
height: 178px;
margin-right: 10px;
border: 1px solid #ccc;
position: relative;
img {
width: 100%;
height: 100%;
object-fit: contain;
position: relative;
z-index: 9;
}
&:hover{
.el-icon.uploader-close {
display: flex !important;
}
}
.uploader-Check{
width: 40px;
height: 40px;
position: absolute;
z-index: 18;
top: 0;
left: 0;
display: flex;
background-color: #67c23a;
clip-path: polygon(0 0 ,100% 0, 0 100%);
-webkit-clip-path:polygon(0 0 ,100% 0,0 100% );
.el-icon{
position: absolute;
top: 4px;
left: 4px;
color: #fff;
}
}
.el-icon.uploader-close {
display: none;
position: absolute;
z-index: 20;
top: -5px;
right: -5px;
width: 20px;
height: 20px;
background-color: red;
justify-content: center;
align-items: center;
border-radius: 50%;
color: #fff;
font-size: 12px;
cursor: pointer;
}
.button-div {
position: absolute;
height: 45px;
z-index: 20;
bottom: 0;
left: 0;
width: 100%;
background-color: rgba(0, 0, 0, 0.2);
display: flex;
justify-content: space-around;
align-items: center;
}
}
.cropper-content {
display: flex;
display: -webkit-flex;
justify-content: flex-end;
.cropper-box {
width: 550px;
.cropper {
width: auto;
height: 400px;
}
}
.show-preview {
flex: 1;
-webkit-flex: 1;
display: flex;
display: -webkit-flex;
justify-content: center;
.preview {
overflow: hidden;
border: 1px solid #67c23a;
background: #cccccc;
}
}
}
.dialog-footer {
display: flex;
justify-content: center;
margin-top: 10px;
}
style>
5.使用:
<cropperUpload :otherData="{a:100}" :headers="{}" v-model="urlList" :multiple="true" sendUrl="https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15" />
<script setup>
import cropperUpload from "./cropperUpload.vue";
const urlList = reactive(['https://img1.baidu.com/it/u=4049022245,514596079&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500'])
script>