vue+elementUI上传图片笔记
裁剪和上传图片
上传图片组件
通过el-upload组件的 :before-upload="beforeUpload"进行图片的上传前校验,:http-request进行自定义上传,在这里是通过裁剪弹框的确定按钮最终上传图片,不通过el-upload上传,所以:http-request="httpRequest"其实不生效
<template slot="iconForm">
<el-upload
class="avatar-uploader"
:before-upload="beforeUpload"
:http-request="httpRequest"
:on-success="handleAvatarSuccess"
:show-file-list="false">
//有图片时显示上传图片
<img v-if="form.icon" :src="form.icon" alt="icon" class="avatar" />
//无图片时显示默认图标
<em v-else class="el-icon-plus avatar-uploader-icon"></em>
//修改和新增转态显示
<div v-if="tipShow" slot="tip" class="el-upload__tip">尺寸应为 82 * 82, 且不超过 500 k 的 jpg / png 文件</div>
//删除
<div v-if="form.icon && formType === 'edit'" class="uploader-operate" @click.stop="form.icon = ''">
<em style="margin-left: 20px" class="el-icon-delete"></em>
</div>
</el-upload>
</template>
裁剪弹框
<el-dialog title="图片裁剪预览" append-to-body :visible.sync="showImageCropper" width="40%">
<vueCropper
ref="cropper"
style="height: 400px"
//截图框移动
:can-move="option.canMove"
//截图框拖动
:can-move-box="option.canMoveBox"
:fixed-box="option.fixedBox"
:img="cropOption.image"
:fixed="cropOption.fixed"
:height="cropOption.height"
:outputSize="cropOption.size"
:outputType="cropOption.type"
:autoCrop="cropOption.autoCrop"
:autoCropWidth="cropOption.autoCropWidth"
:autoCropHeight="cropOption.autoCropHeight"
:original="cropOption.original"></vueCropper>
<span slot="footer" class="dialog-footer">
//点击确定上传图片
<el-button type="primary" @click="imageCropper">确 定</el-button>
</span>
</el-dialog>
在data里定义裁剪弹框的参数项,image是传入裁剪图片的地址:
cropOption: {
image: '',
size: 1,
type: 'png',
height: true,
fixed: true,
autoCrop: true,
autoCropWidth: 82,
autoCropHeight: 82,
original: true,
},
上传前校验
简单校验下图片类型和大小,生成上传图片的url后给裁剪框image传入,确保裁剪框打开时有图片显示和裁剪:
其中window.URL.createObjectURL(file)用于创建一个
DOMString,其中包含一个表示参数中给出的对象(在这个案例中是一个文件对象)的URL。这个URL的生命周期和创建它的窗口中的
document 绑定,这个新的对象URL指向的是被传入的File对象或Blob对象。
beforeUpload(file) {
// 上传前校验
const isJPG = ['image/jpeg', 'image/jpg', 'image/png'].includes(file.type);
const isLt500k = file.size / 1024 / 1024 < 0.5;
if (!isJPG) {
this.$message.warning('外部图标只能是 jpg / png 格式');
return false;
}
if (!isLt500k) {
this.$message.warning('外部图标大小不能超过 500k');
return false;
}
this.cropOption.image = window.URL.createObjectURL(file);
// 打开裁剪框
this.showImageCropper = true;
裁剪并上传图片
通过vueCropper的getCropBlob()获取裁剪后图片的Blob数据,然后处理成File对象,将file对象以formData 键值对格式传给接口,完成图片裁剪后的上传:
// 裁剪并上传图片
imageCropper() {
//获取裁剪框的图片信息
this.$refs.cropper.getCropBlob(data => {
const file = new File([data], 'icon.png', {
type: data.type,
lastModified: Date.now(),
});
//格式化图片
const formData = new FormData();
//添加属性
formData.append('file', file, file.name);
const loading = this.$loading({
lock: true,
text: '图片上传中, 请稍后...',
spinner: 'el-icon-loading',
});
//请求接口
axios
.post('/api/spang-system/oss/endpoint/put-file', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
})
.then(res => {
loading.close();
if (res.status && res.status === 200) {
this.$set(this.form, 'icon', res.data.data.link);
this.showImageCropper = false;
}
})
.catch(e => {
loading.close();
this.$message.error(`图片上传失败!${JSON.stringify(e)}`);
})
.finally(() => {
loading.close();
});
});
},
效果展示
控制台问题和解决方法
上文提到,el-upload的:http-request可以进行自定义上传,但是在本次业务中,不需要通过el-upload上传,通过裁剪框的确定按钮上传,所以:http-request="httpRequest"其实不生效,但是在实际运行中,upload组件会在选择上传图片后默认调用一次http-request的方法进行上传
- 尝试:设置 action=" "不生效,会将页面路由作为上传图片的接口调用
- 解决方法:自定义重置 httpRequest——:http-request=“httpRequest”,选择上传文件后进入该方法,返回false,不调用接口即可:
//覆盖默认上传,走裁剪的上传文件,不走组件自带
httpRequest() {
return false;
},
校验图片尺寸无裁剪直接上传
添加尺寸校验
beforeUpload里校验图片尺寸,符合校验的调用imageCropper()传入上传图片,对文件进行数据处理后再上传:
const isSize = new Promise(function (resolve, reject) {
const width = 82; // 限制图片尺寸
const height = 82;
const URL = window.URL || window.webkitURL;
const img = new Image();
img.onload = function () {
const valid = img.width === width && img.height === height;
valid ? resolve() : reject();
};
img.src = URL.createObjectURL(file);
}).then(
//valid为true时调用resolve()
() => {
return true;
},
//valid为false时调用reject()
() => {
this.$message.warning('外部图标尺寸应为 82 * 82');
return false;
}
);
isSize.then(res => {
console.log('res', res);
if (isJPG && isLt500k && res) {
this.imageCropper(file);
}
});
console.log(isSize);
需要注意的是, isSize返回的是promise对象,valid为true时返回值是true,valid为false时返回值是false,如果直接判断isSize来调用this.imageCropper(file),判断不生效,因为不是直接返回true/false,是Promise对象,会一直判断为true,都会执行上传操作,如果是不符合尺寸的图片会提示尺寸不符合,但仍会成功上传
所以通过then方法来取到我们所需要的PromiseResult值,PromiseResult就是返回的Promise对象返回的布尔值,如下:
isSize.then(res => {
console.log('res', res);
if (isJPG && isLt500k && res) {
this.imageCropper(file);
}
});
},
在then函数中,拿到 PromiseResult的值后直接进行上传判断即可。
上传时获取图片并处理
不需要裁剪框,先注释掉裁剪框相关代码(vueCropper组件、打开和关闭弹框操作),之前是通过裁剪框的确定按钮上传图片,现在取消了裁剪框,要怎么上传图片呢?在上传前的尺寸校验时,符合要求尺寸时将file文件作为参数传入imageCropper,在imageCropper中对文件进行格式处理,最后调用上传文件接口时传入。
beforeUpload中传入上传图片:
this.imageCropper(file);
- beforeUpload(file)——upload组件自带API,参数file,就是选择上传的图片文件,通过file可以直接获取
- getCropBlob()是获取裁剪后图片的Blob数据,然后处理成File对象,将file对象以formData 键值对格式传给接口,此时不需要通过裁剪组件获取上传文件,去掉这块代码,直接对upload自带的 beforeUpload(file)传入imageCropper方法的参数进行处理就可以
imageCropper() {
this.$refs.cropper.getCropBlob(data => {
const file = new File([data], 'icon.png', {
type: data.type,
lastModified: Date.now(),
});
const formData = new FormData();
formData.append('file', file, file.name);
这样就可以成功上传了,不符合尺寸时会提示尺寸问题,不会再上传:
效果展示
完整代码
最终的上传和校验代码如下:
//上传图片
imageCropper(file) {
const formData = new FormData();
formData.append('file', file, file.name);
const loading = this.$loading({
lock: true,
text: '图片上传中, 请稍后...',
spinner: 'el-icon-loading',
});
axios
.post('/api/spang-system/oss/endpoint/put-file', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
})
.then(res => {
loading.close();
if (res.status && res.status === 200) {
this.$set(this.form, 'icon', res.data.data.link);
this.showImageCropper = false;
}
})
.catch(e => {
loading.close();
this.$message.error(`图片上传失败!${JSON.stringify(e)}`);
})
.finally(() => {
loading.close();
});
},
// 图片上传前校验
beforeUpload(file) {
const isJPG = ['image/jpeg', 'image/jpg', 'image/png'].includes(file.type);
const isLt500k = file.size / 1024 / 1024 < 0.5;
if (!isJPG) {
this.$message.warning('外部图标只能是 jpg / png 格式');
return false;
}
if (!isLt500k) {
this.$message.warning('外部图标大小不能超过 500k');
return false;
}
const isSize = new Promise(function (resolve, reject) {
const width = 82; // 限制图片尺寸为
const height = 82;
const URL = window.URL || window.webkitURL;
const img = new Image();
img.onload = function () {
const valid = img.width === width && img.height === height;
valid ? resolve() : reject();
};
img.src = URL.createObjectURL(file);
}).then(
() => {
return true;
},
() => {
this.$message.warning('外部图标尺寸应为 82 * 82');
return false;
}
);
console.log('isSize', isSize);
isSize.then(res => {
console.log('res', res);
if (isJPG && isLt500k && res) {
this.imageCropper(file);
}
});
},