最终达到这样的效果:

以下省略了一些简单的代码,比如组件引入等
1、引入element ui上传组件
<upload-img :pic="form.bgImage" @getUrlFn="getImgUrl" @modalShowFn="getModalStatus"></upload-img>
2、uploadImg组件内容
<template>
<div>
<!-- accept="image/gif, image/jpeg,image/jpg,image/png,image/bmp" -->
<el-upload
class="avatar-uploader"
v-loading="loading"
:action="action"
:data="uploadData"
:show-file-list="false"
name="file"
:on-success="handleAvatarSuccess"
:on-progress="handProcess"
:http-request="picUpload"
:before-upload="beforeAvatarUpload"
>
<img v-if="imageUrl" :src="imageUrl" class="avatar" />
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
<div v-if="maskBox" @mouseleave="maskBox = false" class="mask">
<i @click="handleRemove" style="font-size: 24px" class="el-icon-delete"></i>
</div>
</div>
</template>
<script>
export default {
props: {
pic: {
type: String,
default: ''
}
},
data() {
return {
action: '上传地址',
disabled: false,
uploadData: {
file: '',
project: ''
},
imageUrl: '',
loading: false,
maskBox:false,
}
},
watch: {
pic: {
handler(newval, oldval) {
console.log(newval)
if (newval) {
this.imageUrl = newval
}
},
deep: true
}
},
created() {
this.imageUrl = this.picUrl
},
methods: {
// 图片上传前触发裁剪组件
// 将图片读出并在完成时触发裁剪
picUpload(option) {
let file = option.file
let reader = new FileReader();
if (file) {
reader.readAsDataURL(file)
}
reader.onload = () => {
let src = reader.result
// this.cropperShow = true
// this.cropperImg = src
let obj = {
cropperShow:true,
cropperImg:src
}
this.$emit("modalShowFn",obj)
}
},
handleRemove(file) {
this.imageUrl = '';
this.maskBox = false;
this.$emit('getUrlFn', '')
},
handleAvatarSuccess(res, file) {
this.imageUrl = res.browser;
this.loading = false;
this.$emit('getUrlFn', res.browser, file.raw)
},
beforeAvatarUpload(file) {
const isLt10M = file.size / 1024 / 1024 < 10;
const isJPG = file.type === 'image/jpeg' || file.type === 'image/gif' || file.type === 'image/jpg' || file.type === 'image/bmp' || file.type === 'image/png';
if (!isLt10M) {
this.$message.error('上传图片大小不能超过 10MB!');
}
if (!isJPG) {
this.$message.error('上传头像图片只能是 JPG/JPEG/GIF/BMP 格式!');
}
return isJPG && isLt10M;
},
handProcess(event, file) {
this.loading = true;
}
}
}
</script>
<style scope>
.avatar-uploader .el-upload {
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
border: 1px solid #dcdee2;
}
.avatar-uploader .el-upload:hover {
border-color: #409eff;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
200px;
height: 200px;
line-height: 148px;
text-align: center;
}
.avatar {
200px;
height: 200px;
display: block;
position: relative;
z-index: 99;
}
.el-icon-plus:before {
line-height: 200px;
}
</style>
3、安装vue-cropper,在页面中引入vue-cropper
npm install vue-cropper --save
import vueCropper from "vue-cropper"
4、用vue-cropper封装图片裁剪组件
组件放入modal中
<Modal v-model="isShowCropper" class="cropperModal" :closable="false" width="530px"> <div class="cropperModalBody" style="520px;height:350px"> <img-cropper :cropperOption="cropperOption" :img="cropperImg" :file="cropperFile" @cropperFinish="postUploadFile" @close="()=>{isShowCropper=false}" ></img-cropper> </div> <div slot="footer"></div> </Modal>
一些方法
getImgUrl(url) { this.form.bgImage = url }, getModalStatus(obj) { this.cropperImg = obj.cropperImg; this.isShowCropper = obj.cropperShow; }, // 文件上传-接口-上传文件 postUploadFile(blob) { //将blob转换为file let file = new File([blob], '图片.png', { type: 'image/png', lastModified: Date.now() }); file.uid = Date.now(); var fd = new FormData(); fd.append("file", file, "图片.png"); fd.append("project", "micropark_coordination"); uploadFile(fd).then(res => { if (res.code === 200) { this.form.bgImage = res.browser; // this.$refs.upload.clearFiles() this.isShowCropper = false; this.cropperImg = null; this.cropperFile = {}; } }); }
5、img-cropper组件内容
<template>
<div class="vue-cropper-box">
<div class="vue-cropper-header">
<p class="title" style="font-size:16px">
图片裁剪
<Icon type="ios-close" @click="close()" />
</p>
</div>
<div class="vue-cropper-operate">
<button class="basicButton" @click="cropperChangeScale(1)">放大</button>
<button class="basicButton" @click="cropperChangeScale(-1)">缩小</button>
<button class="basicButton" @click="cropperRotateLeft">左旋转</button>
<button class="basicButton" @click="cropperRotateRight">右旋转</button>
<!-- <button class="basicButton" @click="cropperDown('blob')">下载</button> -->
<button class="basicButton" @click="cropperFinish">确定裁剪</button>
</div>
<div class="vue-cropper-content" style="500px;height:310px">
<vueCropper
ref="cropper"
:img="img"
:outputSize="cropperOption.outputSize"
:outputType="cropperOption.outputType"
:info="cropperOption.info"
:full="cropperOption.full"
:canMove="cropperOption.canMoveBox"
:canMoveBox="cropperOption.canMoveBox"
:original="cropperOption.original"
:canScale="cropperOption.canScale"
:autoCrop="true"
:autoCropWidth="200"
:autoCropHeight="200"
:fixedBox="true"
:fixed="cropperOption.fixed"
:fixedNumber="cropperOption.fixedNumber"
:centerBox="cropperOption.centerBox"
:infoTrue="cropperOption.infoTrue"
@realTime="realTime"
@imgLoad="imgLoad"
></vueCropper>
</div>
</div>
</template>
<script>
import vueCropper from "vue-cropper"
export default {
name: 'by-cropper',
components: { vueCropper },
// inject: ['reload'],
filters: {},
props: {
file: {
default: () => { },
required: true
},
img: {
default: "",
required: true
},
cropperOption: {
default: () => ({
// img: '', // 裁剪图片的地址
info: true, // 裁剪框的大小信息
outputSize: 1, // 裁剪生成图片的质量
full: false, // 输出原图比例截图 props名full
outputType: 'png', // 裁剪生成图片的格式
canMove: true, // 能否拖动图片
original: false, // 上传图片是否显示原始宽高
canMoveBox: true, // 能否拖动截图框
canScale: false, // 图片是否允许滚轮缩放
autoCrop: true, // 是否默认生成截图框
autoCropWidth: 200, // 默认生成截图框宽度
autoCropHeight: 200, // 默认生成截图框高度
fixedBox: true, // 截图框固定大小
fixed: true, // 是否开启截图框宽高固定比例
fixedNumber: [1, 1], // 截图框的宽高比例
original: false, // 上传图片按照原始比例渲染
centerBox: true, // 截图框是否被限制在图片里面
infoTrue: true // true 为展示真实输出图片宽高 false 展示看到的截图框宽高
})
}
},
data() {
return {
};
},
computed: {
// computeFunction() {
// return value;
// }
},
watch: {
},
created() {
// this.init();
},
mounted() {
this.init();
},
methods: {
init() {
this.cropperOption.img = this.file
},
cropperChangeScale(num) {
num = num || 1;
this.$refs.cropper.changeScale(num);
},
// 左旋转
cropperRotateLeft() {
this.$refs.cropper.rotateLeft();
},
// 右旋转
cropperRotateRight() {
this.$refs.cropper.rotateRight();
},
// 下载图片
cropperDown(type) {
let aLink = document.createElement('a');
aLink.download = 'author-img';
if (type === 'blob') {
this.$refs.cropper.getCropBlob(data => {
this.downImg = window.URL.createObjectURL(data);
aLink.href = window.URL.createObjectURL(data);
aLink.click();
});
} else {
this.$refs.cropper.getCropData(data => {
this.downImg = data;
aLink.href = data;
aLink.click();
});
}
},
// 确定裁剪
cropperFinish(type) {
if (type === 'Blob') {
this.$refs.cropper.getCropBlob((data) => {
let file = data;
file.name = this.file.name;
this.$emit('cropperFinish', file, data);
});
} else {
this.$refs.cropper.getCropData(data => {
// 将剪裁后base64的图片转化为file格式
let file = this.convertBase64UrlToBlob(data);
file.name = this.file.name;
this.$emit('cropperFinish', file, data);
});
}
},
// 将base64的图片转换为file文件
convertBase64UrlToBlob(urlData) {
let bytes = window.atob(urlData.split(',')[1]); // 去掉url的头,并转换为byte
// 转化为base64
// reader.readAsDataURL(file)
// 转化为blob
// 处理异常,将ascii码小于0的转换为大于0
let ab = new ArrayBuffer(bytes.length);
let ia = new Uint8Array(ab);
for (let i = 0; i < bytes.length; i++) {
ia[i] = bytes.charCodeAt(i);
}
return new Blob([ab], { type: 'image/jpeg' });
},
// 实时预览函数
realTime(data) {
// console.log('realTime');
},
// 图片已加载
imgLoad(msg) {
// console.log('imgLoad');
// console.log(msg);
},
close() {
this.$emit('close');
}
}
};
</script>
<style lang="less" scope>
// 基础按钮
.basicButton {
font-size: 16px;
100px;
padding: 8px 10px;
// line-height: 0.4rem;
border: none;
border-radius: 20px;
color: #fff;
background: #5cadff;
outline: none;
&:not(:last-child) {
margin-right: 20px;
}
}
.vue-cropper-header {
.title {
font-weight: 600;
display: flex;
justify-content: space-between;
padding-top: 20px;
margin-bottom: 10px;
.ivu-icon .ivu-icon-ios-close {
font-size: 20px;
}
}
}
/* 图片裁剪工具 */
.vue-cropper-box {
z-index: 3001;
position: absolute;
top: 0;
background: #fff;
.vue-cropper-operate {
display: flex;
align-items: center;
justify-content: center;
padding: 0.2rem;
}
.vue-cropper-content {
position: relative;
overflow-y: auto;
.vue-cropper {
position: absolute;
}
}
}
</style>