最近遇到了图片处理的问题:再上传图片前进行一些处理(裁剪、旋转、放大)然后再上传服务器。
我使用的是vue的一个开源组件:vue-cropper 进行处理,可以提供多种处理图片的功能。
下方代码就是该功能的组件封装,对应的axios交互方法根据具体的开发情况进行设置。成功后的效果如下所示:
刚开始由于没有经验,死脑筋的认为图片必须上传到服务器后才能够进行预览,处理,但是这样会让我们在裁剪图片时都会上传两次图片,这样势必会浪费掉服务器的资源,会使其占用大量的数据库资源和服务器压力。所以在网上找到了别人的解决方案。
// 选择本地图片
uploadImg (e) {
// 上传图片
const file = e.target.files[0];
this.fullFile = file;
if (!/\.(jpg|jpeg|png|JPG|PNG)$/.test(e.target.value)) {
this.$message.error('图片类型必须是.jpeg、jpg、png中的一种');
return false;
} else if (this.fullFile.size / 1024 / 1024 > 2) {
this.$message.error('图片大小不得超过2MB');
return false;
}
const reader = new FileReader();
reader.onload = e => {
let data;
if (typeof e.target.result === 'object') {
// 把Array Buffer转化为blob 如果是base64不需要
data = window.URL.createObjectURL(new Blob([e.target.result]));
} else {
data = e.target.result;
}
this.option.img = data;
};
// 转化为blob
reader.readAsArrayBuffer(file);
// 打开图片处理弹窗
this.dialogOpen = true;
},
然后就可以通过官方文档进行相关的数据方法封装了。将裁剪框的尺寸等需要自定义的信息给封装一下,成了可复用的图片上传组件,完整代码如下。
<template>
<div class="imgs">
<el-button type="primary" size="small" @click="$refs.selectImg.click()">选择图片</el-button>
<input type="file" :value="imgFile" ref="selectImg" style="display: none" accept="image/png, image/jpeg, image/jpg" @change="uploadImg($event)">
<div slot="tip"><span style="color: #F56C6C;">只能上传jpg/png文件,且不超过2MB</span></div>
<!-- 图片裁剪弹窗 -->
<el-dialog
id="dialog"
title="编辑图片"
:visible.sync="dialogOpen"
width="700px"
@close="cancel()"
append-to-body
>
<div class="cut">
<div class="out">
<!-- 裁剪图片预览 -->
<div class="preview-box" :style="{'width': previews.w + 'px','height': previews.h + 'px','overflow': 'hidden'}">
<img :src="previews.url" :style="previews.img">
</div>
</div>
<!-- 图片裁剪部分 -->
<div class="left">
<vueCropper
ref="cropper"
:img="option.img"
:outputSize="option.size"
:outputType="option.outputType"
:autoCrop="option.autoCrop"
:fixed="option.fixed"
:full="option.full"
:fixedNumber="fixedNumber"
:canScale="option.canScale"
:high="option.high"
@realTime="realTime"
:imageUrl="imageUrl"
:isWater="isWater"
></vueCropper>
</div>
</div>
<div class="block">
<div slot="tip" style="color: #409EFF; margin-left: 20%; margin-top: 10px;">预览图:</div>
<div slot="tip" style="text-align: center; margin-top: 10px;"><span style="color: #F56C6C; text-align: center;">处理图片时可以操作滚轮进行放大缩小,且选图框只能按推荐比例缩放</span></div>
</div>
<div class="p" style="margin: 20px auto; width: 300px ">
<el-progress :percentage="progress" v-if="progress !== 0" color="#409eff"></el-progress>
</div>
<!-- 图片操作 -->
<div class="block" style="margin-top: 15px; text-align: center">
<!-- 图片放大缩小 -->
<el-button type="primary" size="small" icon="el-icon-plus" circle @click="changeScale(1)"></el-button>
<el-button type="primary" size="small" icon="el-icon-minus" circle @click="changeScale(-1)"></el-button>
<!-- 图片旋转 -->
<el-button type="primary" size="small" icon="el-icon-refresh-left" circle @click="rotateLeft"></el-button>
<el-button type="primary" size="small" icon="el-icon-refresh-right" circle @click="rotateRight"></el-button>
</div>
<!-- 底部确认取消按钮 -->
<span slot="footer" class="dialog-footer">
<el-button @click="cancel()">取 消</el-button>
<el-button type="primary" @click="finish">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { VueCropper } from 'vue-cropper';
import { env } from '@/config';
import { UserModule } from '@/store/modules/user'; // vuex
import api from '@/api/index';
import uploadImg from '../../api/upload/index';
import UploadProxy from '@/api/upload/handler';
import axios from 'axios';
export default {
name: 'Uploadimg01',
props: {
// 裁剪框的长宽比例设置:例如:[2,1]代表图片裁剪框的长宽比例固定为为2:1.
fixedNumber: {
default: () => [],
type: Array,
},
// 将要裁剪的图片地址
imageUrl: {
type: String,
default: '',
},
// 是否添加水印,默认为不添加
isWater: {
type: Boolean,
default: false,
},
},
components: {
VueCropper,
},
created () {
this.option.img = this.imageUrl;
},
// 监听imageUrl是否被传入组件,进行动态赋值
watch: {
imageUrl (curInfo, oldInfo) {
if (curInfo) {
this.imageUrl = curInfo;
this.option.img = curInfo;
}
},
},
data () {
return {
headers: { 'idToken': UserModule.token },
// vue-cropper的参数设置
option: {
img: '', // 裁剪图片的地址。可以有三种形式:url 地址 || base64 || blob
size: 1, // 裁剪生成图片的质量, 默认:1, 参数范围:0.1 - 1。
outputType: 'png', // 裁剪生成图片的格式 默认: jpg (jpg 需要传入jpeg) 可选参数:jpeg || png || webp
autoCrop: true, // 是否默认生成截图框.默认为: false
canScale: true, // 图片是否允许滚轮缩放 默认;true 可选:true || false
high: true, // 是否按照设备的dpr 输出等比例图片
fixed: true, // 是否开启截图框宽高固定比例。默认:true
full: true, // 是否输出原图比例的截图(决定了裁剪图片的画面质量)。
},
previews: {}, // 预览图片的信息。
imgFile: '',
fullFile: {}, // 上传的图片信息
dialogOpen: false, // 弹框是否开启
progress: 0, // 进度条
show: false, // axios请求取消
config: {},
cancels: null,
};
},
computed: {
getTarget () {
let target = document.getElementById('dialog');
return target;
},
},
methods: {
// 修改图片大小 正数为变大 负数变小
changeScale (num) {
num = num || 1;
this.$refs.cropper.changeScale(num);
},
// 图片左旋转
rotateLeft () {
this.$refs.cropper.rotateLeft();
},
// 图片右旋转
rotateRight () {
this.$refs.cropper.rotateRight();
},
// 图片实时预览
realTime (data) {
this.previews = data;
},
//
cancel () {
if (this.cancels) {
this.progress = 0;
this.cancels();
}
this.progress = 0;
this.dialogOpen = false;
},
// 选择本地图片
uploadImg (e) {
this.option.img = '';
// 上传图片
const file = e.target.files[0];
this.fullFile = file;
if (!/\.(jpg|jpeg|png|JPG|PNG)$/.test(e.target.value)) {
this.$message.error('图片类型必须是.jpeg、jpg、png中的一种');
return false;
} else if (this.fullFile.size / 1024 / 1024 > 2) {
this.$message.error('图片大小不得超过2MB');
return false;
}
const reader = new FileReader();
reader.onload = e => {
let data;
if (typeof e.target.result === 'object') {
// 把Array Buffer转化为blob 如果是base64不需要
data = window.URL.createObjectURL(new Blob([e.target.result]));
} else {
data = e.target.result;
}
this.option.img = data;
};
// 转化为blob
reader.readAsArrayBuffer(file);
// 打开图片处理弹窗
this.dialogOpen = true;
},
// 获取裁剪得到的图片并上传
finish (type) {
this.$refs.cropper.getCropBlob(async (data) => {
// 将blob格式转换为文件。File() 构造器创建新的 File 对象实例。
const newFile = new window.File([data], this.fullFile.name, { type: this.fullFile.type });
// FormData 接口提供了一种表示表单数据的键值对的构造方式,它会使用表单一样的数据格式
let formData = new FormData();
// 添加图片
formData.append('file', newFile);
// 添加将要删除替换掉的图片路径
formData.append('oldFileName', this.imageUrl);
// 是否添加水印
formData.append('isWater', this.isWater);
// 添加新上传的图片名(会出错)
// formData.append('uploadName', this.fullFile.name);
// 上传图片文件
const upload = UploadProxy(env.baseUrl);
const CancelToken = axios.CancelToken;
this.config = {
onUploadProgress: (progressEvent) => {
let complete = parseInt(progressEvent.loaded / progressEvent.total * 100 | 0, 10);
this.progress = complete;
},
cancelToken: new CancelToken(c => {
this.cancels = c;
}),
};
let res = await upload.post('/upload/fileUpload', formData, this.config).then(res => res.data);
if (res.code === 10000) {
// 上传图片成功后关闭弹窗
this.dialogOpen = false;
this.imageUrl = res.data.downPath;
this.$message.success(res.msg);
// 将上传成功后的服务器返回数据传给父组件
this.$emit('onsuccess', res);
this.option.img = '';
} else {
if (res.msg) {
this.option.img = '';
this.$message.error(res.msg);
} else {
this.option.img = '';
this.$message.info('图片上传被取消');
}
}
// const res = await uploadImg(formData);
// if (res.code === 10000) {
// // 上传图片成功后关闭弹窗
// this.dialogOpen = false;
// this.imageUrl = res.data.downPath;
// this.$message.success(res.msg);
// // 将上传成功后的服务器返回数据传给父组件
// this.$emit('onsuccess', res);
// } else {
// this.$message.error(res.msg);
// }
});
},
},
};
</script>
<style lang='scss' scoped>
.btn-upload {
display:inline-block;
width: 70px;
padding: 0;
text-align: center;
line-height: 28px;
color: #FFF;
background-color: #409EFF;
border-color: #409EFF;
margin-right: 15px;
font-size: 12px;
border-radius: 4px;
cursor: pointer;
}
.cut {
width: 660px;
height: 340px;
display: flex;
justify-content: space-between;
.out {
width: 300px;
height: 100%;
float: left;
background: #F5F7FA;
display: flex;
align-items: center;
justify-content: center;
.preview-box {
width: 200px;
height: 100%;
}
}
.left {
width: 300px;
height: 100%;
float: left;
}
}
</style>