最终达到这样的效果:
以下省略了一些简单的代码,比如组件引入等
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>