vue3中使用el-upload+vue-cropper实现图片上传裁剪图片,裁剪可放大、缩小、旋转
上传完图片可以放大预览,也可删除
效果图如下
1、第一步安装vue-cropper
npm install vue-cropper
2、第二步封装组件
在src》components 下新建CropperImage文件夹,文件夹中新建index.vue,index.vue代码如下
<template>
<el-dialog
:title="title"
:modelValue="dialogVisible"
:show-close="true"
:close-on-click-modal="false"
:close-on-press-escape="false"
:before-close="closeDialog"
destroy-on-close
width="900px"
>
<el-row>
<el-col :xs="24" :md="12" :style="{ height: '350px' }">
<div class="cropper-container">
<!-- 待上传图片 -->
<div v-show="!options.img" >
<el-upload
class="upload"
ref="elUpload"
action="#"
:on-change="upload"
:accept="accpetType"
:limit="limit"
:on-exceed="handleExceed"
:show-file-list="false"
:auto-upload="false"
>
<el-button slot="trigger" type="primary" ref="uploadBtn">
选择图片
</el-button>
</el-upload>
<div>
请上传
<template v-if="fileSize">
大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
</template>
<template v-if="fileType">
格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b>
</template>
的文件
</div>
</div>
<!-- 已上传图片 -->
<div v-show="options.img" class="cropper-crop">
<vueCropper
class="crop-box"
ref="cropper"
:img="options.img"
:info="true"
:autoCrop="options.autoCrop"
:fixedBox="options.fixedBox"
:canMoveBox="options.canMoveBox"
:autoCropWidth="options.autoCropWidth"
:autoCropHeight="options.autoCropHeight"
:centerBox="options.centerBox"
:fixed="options.fixed"
:fixedNumber="options.fixedNumber"
:canMove="options.canMove"
:canScale="options.canScale"
@realTime="realTime"
></vueCropper>
</div>
</div>
</el-col>
<el-col :xs="24" :md="12" :style="{ height: '350px' }">
<!-- :style="`width:${cWidth}px;height:${cHeight}px;`" -->
<div class="cropper-upload-preview" :style="`width:${options.previews.w}px;height:${options.previews.h}px;`">
<img :src="options.previews.url" :style="options.previews.img" />
</div>
</el-col>
</el-row>
<br />
<el-row>
<el-col :lg="2" :md="2">
<div class="reupload" @click="reupload">
<el-button type="primary" v-show="options.img">重新上传<el-icon class="el-icon--right"><Upload /></el-icon></el-button>
</div>
</el-col>
<el-col :lg="{ span: 1, offset: 2 }" :md="2">
<el-button icon="Plus" @click="changeScale(1)"></el-button>
</el-col>
<el-col :lg="{ span: 1, offset: 1 }" :md="2">
<el-button icon="Minus" @click="changeScale(-1)"></el-button>
</el-col>
<el-col :lg="{ span: 1, offset: 1 }" :md="2">
<el-button icon="RefreshLeft" @click="rotateLeft()"></el-button>
</el-col>
<el-col :lg="{ span: 1, offset: 1 }" :md="2">
<el-button icon="RefreshRight" @click="rotateRight()"></el-button>
</el-col>
<el-col :lg="{ span: 2, offset: 6 }" :md="2">
<el-button type="primary" @click="getCrop()">提 交</el-button>
</el-col>
</el-row>
<template #footer>
<span class="dialog-footer">
<div>
<el-button @click="closeDialog">取 消</el-button>
<!-- <el-button type="primary" @click="getCrop">确 定</el-button> -->
</div>
</span>
</template>
</el-dialog>
</template>
<script>
import {ref,reactive} from "vue"
import 'vue-cropper/dist/index.css';
import { VueCropper } from 'vue-cropper';
import { ElMessage } from 'element-plus';
import { importNewImg } from "@/api/back/home.js"; //上传接口
export default {
props: {
dialogVisible: {
type: Boolean,
default: () => {
return false;
},
},
title: {
type: String,
default: "",
},
cWidth: {
type: [Number, String],
default: ""
},
cHeight: {
type: [Number, String],
default: ""
},
// 图片数量限制
limit: {
type: Number,
default: 5,
},
// 大小限制(MB)
fileSize: {
type: Number,
default: 5,
},
// 文件类型
fileType: {
type: Array,
default: () => ["png", "jpg", "jpeg"],
},
accpetType: {
type: String,
default: () => ".png,.jpg,.jpeg",
},
},
components:{
VueCropper
},
setup(props, { emit }) {
const { proxy } = getCurrentInstance();
const cropper=ref()
const uploadBtn=ref()
const elUpload=ref()
const options=reactive({
img: '', // 原图文件
autoCrop: true, // 默认生成截图框
fixedBox: false, // 固定截图框大小
canMoveBox: true, // 截图框可以拖动
autoCropWidth: props.cWidth, // 截图框宽度
autoCropHeight: props.cHeight, // 截图框高度
maxImgSize:200,
fixed: true, // 截图框宽高固定比例
fixedNumber: [1, 1], // 截图框的宽高比例
centerBox: true, // 截图框被限制在图片里面
canMove: false, // 上传图片不允许拖动
canScale: true, // 上传图片不允许滚轮缩放
previews: {}, //预览数据
})
// vueCropper组件 裁剪配置信息
// 实时预览
const realTime = (data) => {
// options.previews = data;
let previews = data
let h = 0.5
let w = 0.2
options.previews = {
width: previews.w + "px",
height: previews.h + "px",
url:previews.url,
img:previews.img,
overflow: "hidden",
margin: "0",
zoom: h
}
}
// 文件个数超出
const handleExceed = () => {
ElMessage.error(`上传数量不能超过 ${props.limit} 个!`);
}
// 读取原图
const upload = (file, uploadFiles) => {
//console.log(file);
let isImg = false;
let fileExtension = "";
if (props.fileType.length) {
if (file.name.lastIndexOf(".") > -1) {
fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1);
}
isImg = props.fileType.some((type) => {
if (file.raw.type.indexOf(type) > -1) return true;
if (fileExtension && fileExtension.indexOf(type) > -1) return true;
return false;
});
} else {
isImg = file.type.indexOf("image") > -1;
}
if (!isImg) {
ElMessage.error(`格式不正确, 请上传${props.fileType.join("/")}图片格式文件!`);
return false;
} else {
isImg = true;
}
if (props.fileSize) {
const isLt = file.size / 1024 / 1024 < props.fileSize;
if (!isLt) {
proxy.$modal.msgError(`上传大小不能超过 ${props.fileSize} MB!`);
fileList.value = [];
return false;
}
}
/*
const isIMAGE = file.raw.type === 'image/jpeg' || file.raw.type === 'image/png'
const isLt5M = file.raw.size / 1024 / 1024 < 5
if (!isIMAGE) {
ElMessage.warning("请选择 jpg、png 格式的图片" );
return false
}
if (!isLt5M) {
ElMessage.warning("图片大小不能超过 5MB" );
return false
}
*/
let reader = new FileReader()
reader.readAsDataURL(file.raw)
//console.log(reader);
reader.onload = e => {
options.img = e.target.result // base64
}
elUpload.value.clearFiles(); //这里处理重新上传时,upload组件change事件错误问题
}
// 获取截图信息
const getCrop=()=> {
// 获取截图的 base64 数据
cropper.value.getCropData((data) => {
});
// 获取截图的 blob 数据
cropper.value.getCropBlob(data => {
let formData = new FormData();
//第三个参数是规定以什么为后缀,接口是根据后缀来返回地址格式的
formData.append("file", data,'chris.jpg');
//上传接口
importNewImg(formData).then(res=>{
console.log("222-res",res);
emit('cropperImgMethod',res.fileName)
closeDialog()
})
// emit('closeCropperDialog')
// closeDialog()
})
}
// 重新上传
const reupload=()=> {
uploadBtn.value.ref.click()
}
// 关闭弹框
const closeDialog=()=> {
emit('closeCropperDialog')
options.img = ''
}
/** 向左旋转 */
const rotateLeft=()=> {
proxy.$refs.cropper.rotateLeft();
}
/** 向右旋转 */
const rotateRight=()=> {
proxy.$refs.cropper.rotateRight();
}
/** 图片缩放 */
const changeScale=(num)=> {
num = num || 1;
proxy.$refs.cropper.changeScale(num);
}
return {
options,
cropper,
uploadBtn,
elUpload,
realTime,
handleExceed,
rotateLeft,
rotateRight,
changeScale,
upload,
getCrop,
reupload,
closeDialog
}
}
}
</script>
<style lang="scss" scoped>
.dialog-footer {
display: flex;
justify-content: flex-end;
align-items: center;
font-size: 14px;
.reupload {
color: #409eff;
cursor: pointer;
}
}
.cropper-container {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height:100%;
background-color: #f0f2f5;
margin-right: 10px;
border-radius: 4px;
.upload {
text-align: center;
margin-bottom: 24px;
}
.cropper-crop {
width: 560px;
height: 350px;
position: relative;
.crop-box {
width: 100%;
height: 100%;
border-radius: 4px;
overflow: hidden;
}
}
.cropper-upload-preview{
position: absolute;
top: 50%;
transform: translate(50%, -50%);
width: 200px;
height: 200px;
border-radius: 0;
box-shadow: 0 0 4px #ccc;
overflow: hidden;
>img{
width:100%!important;
}
}
}
</style>
3、第三步使用组件
<template>
<div>
<!-- cropper str -->
<div class="imgCorpper" v-if="!cropperObj.imgShow" @click="cropperObj.openCropperView">
<el-icon><Plus /></el-icon>
</div>
<div class="imgCorpper" v-else @mouseenter="cropperObj.buttonopen" @mouseleave="cropperObj.buttonexit">
<div class="imgCorpperY">
<el-image
:src="cropperObj.previewsImgUrl"
:zoom-rate="1.2"
:preview-src-list="cropperObj.srcList"
:initial-index="4"
fit="cover"/>
<el-image-viewer
ref="previewViewer"
style="z-index:10"
v-if="cropperObj.showImageViewer"
:on-close="cropperObj.closeImgView"
@close="cropperObj.closeImgView"
:url-list="cropperObj.srcList"
/>
<div :class="cropperObj.showBack?'imgcurrent show':'imgcurrent'" >
<div class="imgBack"></div>
<div class="imgicon">
<el-icon @click="cropperObj.showPic"><ZoomIn /></el-icon>
<el-icon @click="cropperObj.delePic"><Delete /></el-icon>
</div>
</div>
</div>
</div>
<ImgCropper
v-if="cropperObj.cVisible"
:dialogVisible.sync="cropperObj.cVisible"
:title="cropperObj.ctitle"
:cWidth="200"
:cHeight="200"
:limit="1"
:fileSize="10"
:fileType="cropperObj.fileTypeList"
@cropperImgMethod="cropperObj.cropperImgMethod"
@closeCropperDialog="cropperObj.closeCropperView"
></ImgCropper>
<!-- cropper end -->
</div>
</template>
<script setup>
// 图片上传裁剪 str
import ImgCropper from '@/components/CropperImage/index.vue'
const cropperObj = reactive({
fileTypeList: ["png", "jpg", "jpeg"],
cVisible:false, // 显示切图弹框
ctitle:"", // 弹框标题
imgShow:false, //是否有图片
previewsImgUrl:"", //图片地址
srcList:[], //图片预览地址数组
showBack:false, // 预览删除图层是否展示
showImageViewer:false, // 是否预览
previewViewer:"",
// 开启剪切弹框
openCropperView: () => {
console.log("ddds");
cropperObj.ctitle="图片剪切"
cropperObj.cVisible = true
},
// 关闭弹框所触发的事件
closeCropperView: (data)=> {
cropperObj.cVisible = false
},
// 剪切图地址
cropperImgMethod: (data) =>{
cropperObj.srcList =[]
cropperObj.previewsImgUrl =window.serverUrl.IMG_SERVER+data;
cropperObj.srcList=[cropperObj.previewsImgUrl]
cropperObj.imgShow = true
},
// 预览删除图层显示
buttonopen: ()=>{
cropperObj.showBack = true
},
// 预览删除图层隐藏
buttonexit: ()=>{
cropperObj.showBack = false
},
// 预览展示
showPic: ()=>{
cropperObj.showImageViewer = true
},
// 预览隐藏
closeImgView: ()=>{
cropperObj.showImageViewer = false
// previewViewer.value.showViewer = false
},
// 删除图片
delePic:()=>{
cropperObj.previewsImgUrl ="";
cropperObj.srcList=[]
cropperObj.showBack = false
cropperObj.imgShow = false
}
})
// 图片上传裁剪 end
</script>
<style lang="scss">
.imgCorpper{
display: flex;
justify-content: center;
align-items: center;
font-size:1rem;
width:148px;
height: 148px;
border:1px dashed #cdd0d6;
position: relative;
border-radius: 6px;
.el-icon{
width:0.5rem;
height: 0.5rem;
color:#909399;
}
.imgCorpperY{
position: relative;
width: 100%;
height: 100%;
border-radius: 6px;
overflow: hidden;
height: 100%;;
>img{
width: 100%;
height: 100%;;
}
.el-image{
width: 100%;
height: 148px;
max-height: 100%;
}
.imgcurrent{
display: none;
position: absolute;
width: 100%;
height: 100%;
top: 0;
.imgBack{
background: #00000082;
width: 100%;
height: 100%;
position: absolute;
z-index: 1;
}
.imgicon{
position: absolute;
width: 90%;
margin: 0 5%;
height: 100%;
display: flex;
justify-content: space-between;
align-items: center;
color: #fff;
z-index: 2;
.el-icon{
color: #fff;
font-size: 0.35rem;
}
}
}
>.show{
display:block;
}
}
}
</style>