我用 vuex 做状态管理,七牛云做图床。
项目地址:多图片上传组件
效果展示
项目执行流程
首先,让我们来分析一下实现多图片上传的流程:
前端向后端请求用来上传图片至服务器的 token
后端为每张要上传的图片生成一个图片名,并用这个图片名生成 token
后端将图片名和 token 返回给前端
前端拿到 token 以后,将图片上传至服务器
上传成功以后,前端将图片名发给后端
后端将图片名存入数据库
项目实现过程
1. 我们要利用 element-ui 的 Upload 组件布置界面:
//upload.vue
<el-upload
:action= domain
ref=”upload”
accept=’image/jpeg,image/gif,image/png’
:auto-upload=”false”
:http-request=”upqiniu”
:limit=”limit”
:multiple=”multiple”
list-type=”picture-card”
:before-upload=”beforeUpload”
:on-preview=”handlePictureCardPreview”
:on-change=”handldChange”
:on-remove=”handleRemove”>
<i class=”el-icon-plus”></i>
</el-upload>
<el-dialog :visible.sync=”dialogVisible”>
<img width=”100%” :src=”dialogImageUrl” alt=””>
</el-dialog>
domain 指的是我们的上传地址,upqiniu 是我们自定义的上传方法,beforeUpload 是图片上传前执行的方法。关于该组件的其他用法可以在 element 的官方文档查阅:Upload 上传
2. 对图片进行压缩
// upload.vue
imgQuality: 0.5, // 压缩图片的质量
dataURItoBlob(dataURI, type) {
var binary = atob(dataURI.split(‘,’)[1]);
var array = [];
for(var i = 0; i < binary.length; i++) {
array.push(binary.charCodeAt(i));
}
return new Blob([new Uint8Array(array)], {type: type});
},
beforeUpload(param) {
// 对图片进行压缩
const imgSize = param.size / 1024 / 1024
if(imgSize > 1) {
const _this = this
return new Promise(resolve => {
const reader = new FileReader()
const image = new Image()
image.onload = (imageEvent) => {
const canvas = document.createElement(‘canvas’);
const context = canvas.getContext(‘2d’);
const width = image.width * _this.imgQuality
const height = image.height * _this.imgQuality
canvas.width = width;
canvas.height = height;
context.clearRect(0, 0, width, height);
context.drawImage(image, 0, 0, width, height);
const dataUrl = canvas.toDataURL(param.type);
const blobData = _this.dataURItoBlob(dataUrl, param.type);
resolve(blobData)
}
reader.onload = (e => { image.src = e.target.result;});
reader.readAsDataURL(param);
})
}
}
压缩图片实现起来比较简单。就是在 beforeUpload() 方法里面 return 一个 Promise,Promise 里面我们把图片的长度和宽度按比例进行缩小,并把图片画到 canvas 上,然后把 canvas 转成一个 blod 对象。
3. 前端向后端请求上传 token。
//upload.vue
upqiniu(param) {
let filetype = ”
if (param.file.type === ‘image/png’) {
filetype = ‘png’
} else {
filetype = ‘jpg’
}
const formdata = {
filetype: filetype,
param: param
}
this.actionGetUploadToken(formdata)
}
// vuex/action.js
actionGetUploadToken({commit}, obj) {
const msg = {
filetype: obj.filetype
}
usersApi.getImgUploadToken(msg).then((response) => {
if(response.stateCode === 200) {
commit(‘uploadImg’, {‘token’: response.token, ‘key’: response.key, ‘param’: obj.param})
}
}, (error) => {
console.log(` 获取图片上传凭证错误:${error}`)
commit(‘uploadImgError’)
})
},
4. 后端生成上传 token,并发给前端,我用 Python 实现。
filetype = data.get(‘filetype’)
# 构建鉴权对象
q = Auth(configs.get(‘qiniu’).get(‘AK’), configs.get(‘qiniu’).get(‘SK’))
# 生成图片名
salt = ”.join(random.sample(string.ascii_letters + string.digits, 8))
key = salt + ‘_’ + str(int(time.time())) + ‘.’ + filetype
# 生成上传 Token,可以指定过期时间等
token = q.upload_token(configs.get(‘qiniu’).get(‘bucket_name’), key, 3600)
return Response({“stateCode”: 200, “token”: token, “key”: key}, 200)
5. 前端接收 token,开始向服务器上传图片
// vuex/state.js
imgName: [], // 图片名数组
// vuex/mutations.js
uploadImg(state, msg) {
const config = {
useCdnDomain: true,
region: qiniu.region.z2
}
var putExtra = {
fname: msg.param.file.name,
params: {},
mimeType: [“image/png”, “image/jpeg”, “image/gif”]
};
var observer = {
next(res){
},
error(err){
console.log(` 图片上传错误信息:${err.message}`)
},
complete(res){
console.log(` 图片上传成功:${res.key}`)
state.imgName.push(res.key)
}
}
var observable = qiniu.upload(msg.param.file, msg.key, msg.token, putExtra, config)
// 上传开始
var subscription = observable.subscribe(observer)
},
6. 上传成功以后,将图片名存入数据库
// 用到 upload.vue 的界面
this.imgsList = this.imgName.map(key => `http://${this.qiniuaddr}/${key}`)
switch(this.imgsList.length) {
case 4:
this.img4 = this.imgsList[3]
case 3:
this.img3 = this.imgsList[2]
case 2:
this.img2 = this.imgsList[1]
case 1:
this.img1 = this.imgsList[0]
}
let obj = {
goods_img1: this.img1,
goods_img2:this.img2,
goods_img3:this.img3,
goods_img4:this.img4
}
// 将信息发送给后端
this.actionPublish(obj)