最近在使用uni-app的上传功能时,发现目前只有APP端支持多张图一起上传,而小程序不支持,于是自己动手封装一下支持一次上传多张图片。
原理:其实底层还是一张图片一张图片上传,只是获取的时候可以一次获取多张,然后写代码自动依次处理这多张图片上传,即把图片多次上传的麻烦手动过程给自动化了。
<template>
<view class="container">
<view class="canvas-view">
<canvas id="firstCanvas" class="firstCanvas" canvas-id="firstCanvas" :style="{width: canvasWidth+'px', height: canvasHeight+'px'}"></canvas>
</view>
<view class="list-box">
<view
class="item"
v-for="(item, index) in imgList"
:key="index">
<image class="img" :src="item" mode="aspectFit" lazy-load="true" @tap="previewImage(index)"></image>
<text class="del-btn" @tap="delImg(index)">-</text>
</view>
<view
class="add-btn"
@tap="addImg"
v-if="imgList.length < maxCount">
<text class="iconfont icon"></text>
<text class="txt">添加图片</text>
</view>
</view>
</view>
</template>
<script>
/*
组件简介:图片压缩上传(可支持多图上传)
*/
export default {
props: {
defaultImgList: { // 默认图片列表
type: Array,
default: () => []
},
count: { // 图片一次上传限制数量
type: Number,
default: 5
},
maxCount: { // 最大上传数量
type: Number,
default: 5
},
sizeType: { // 可以指定是原图还是压缩图,默认二者都有
type: Array,
default: () => ['original', 'compressed']
},
sourceType: { // album 从相册选图,camera 使用相机,默认二者都有。如需直接开相机或直接选相册,请只使用一个选项
type: Array,
default: () => ['album']
},
destWidth: { // 输出图片的最大宽度
type: Number,
default: 500,
},
quality: { // 输出图片的质量,取值范围为 (0, 1],不在范围内时当作1.0处理
type: Number,
default: 0.6
}
},
data() {
return {
imgList: [], // 图片列表
uploadUrl: '', // 上传图片的服务器请求地址
visiable: false, // 显示组件的标识
context: undefined, // canvas 绘图上下文
canvasWidth: 300, // 默认宽度 300px、高度 225px
canvasHeight: 225,
};
},
mounted() {
if(this.defaultImgList.length > 0){
this.imgList = this.defaultImgList;
}
if(this.$store.uploadUrl){
this.visiable = true;
this.uploadUrl = this.$store.uploadUrl;
} else {
this.getUploadFileUrl();
}
this.context = uni.createCanvasContext('firstCanvas', this);
},
methods: {
/* 获取图片上传地址 */
getUploadFileUrl() {
this.$http.get({
url: 'api链接',
data: {
uploadFileType: 'saveHeadImg'
}
}).then(res => {
if(res.code === '0000'){
this.$store.commit('setUploadUrl', res.uploadUrl);
this.uploadUrl = res.uploadUrl;
this.visiable = true;
}
})
},
/* 删除图片 */
delImg(index) {
uni.showModal({
title: '',
content: '确认删除当前图片?',
success: (res) => {
if (res.confirm) {
this.imgList.splice(index, 1);
this.$emit('imgListChange', this.imgList);
uni.showToast({
title: '删除成功!'
});
}
}
});
},
/* 添加图片 */
addImg() {
if(!this.uploadUrl){
return;
}
// 从本地相册选择图片或使用相机拍照
uni.chooseImage({
count: this.count, // 默认5
sizeType: this.sizeType, //可以指定是原图还是压缩图,默认二者都有
sourceType: this.sourceType, //从相册选择
success: (res) => {
console.log(JSON.stringify(res.tempFilePaths));
const tempFilePaths = res.tempFilePaths;
this.compressImg(tempFilePaths, [], 0);
// 上传服务器
// this.uploadImg(tempFilePaths, 0);
}
});
},
/* 压缩图片 */
compressImg(tempFilePaths, compressPaths, index) {
uni.showLoading({
title: '处理中'
});
let i = index;
let arr = [...compressPaths];
uni.getImageInfo({
src: tempFilePaths[i],
success: (res) => {
let canvasWidth = res.width // 图片原始长宽
let canvasHeight = res.height;
let base = canvasWidth/canvasHeight;
if(canvasWidth > this.destWidth){
canvasWidth = this.destWidth;
canvasHeight = Math.floor(canvasWidth/base);
}
this.canvasWidth = canvasWidth;
this.canvasHeight = canvasHeight;
console.log(this.quality)
/*
canvas宽高重新设置后会有一段渲染的间隔,这个间隔可能导致画到画布的图片出现大小不一致的bug,这里做下300ms延迟处理兼容
*/
setTimeout(() => {
this.context.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
this.context.drawImage(res.path, 0, 0, this.canvasWidth, this.canvasHeight);
this.context.draw(false, () => {
// 将图片画到canvas上面,使用Canvas压缩
uni.canvasToTempFilePath({
x: 0,
y: 0,
width: this.canvasWidth,
height: this.canvasHeight,
destWidth: this.canvasWidth,
destHeight: this.canvasHeight,
canvasId: 'firstCanvas',
fileType: 'png',
quality: this.quality,
success: (res) => {
// 在H5平台下,tempFilePath 为 base64
console.log(res.tempFilePath)
i++;
arr.push(res.tempFilePath);
if(i < tempFilePaths.length){
this.compressImg(tempFilePaths, arr, i);
} else {
console.log('压缩后的图片:', arr);
uni.hideLoading();
// 上传服务器
this.uploadImg(arr, 0);
}
},
fail: (err)=>{
console.log(err)
}
}, this);
});
}, 300);
},
fail: (err) => {
console.log(err)
}
});
},
/* 上传图片 */
uploadImg(tempFilePaths, index) {
let i = index;
this.$http.upload({
url: this.uploadUrl,
filePath: tempFilePaths[i],
name: 'file',
success: (res) => {
this.imgList = [...this.imgList, ...res.data];
i++;
if(i < tempFilePaths.length){
this.uploadImg(tempFilePaths, i);
} else {
console.log('服务器返回的图片列表:', this.imgList)
this.$emit('imgListChange', this.imgList);
}
},
fail: (err) => {
console.log(err)
}
});
}
}
}
</script>
<style scoped lang="scss">
@import 'mp-image-upload.scss';
</style>
<mp-image-upload @imgListChange="imgListChange" v-if="!isDrawbackMoney"></mp-image-upload>
/* 监听图片变化 */
imgListChange(data) {
this.imgList = [...data];
},
css代码
.container {
.canvas-view {
opacity: 0;
position: fixed;
right: -10000px;
bottom: -10000px;
width: 0px;
height: 0px;
overflow: hidden;
}
.list-box {
display: flex;
flex-wrap: wrap;
.item {
position: relative;
width: 127rpx;
height: 127rpx;
border-radius: 4rpx;
margin: 0rpx 13rpx 13rpx 0rpx;
border:1px solid #EDEDED;
.img {
display: block;
width: 100%;
height: 100%;
}
.del-btn {
position: absolute;
top: -15rpx;
right: -15rpx;
width: 30rpx;
height: 30rpx;
text-align: center;
line-height: 30rpx;
background-color: #BA1C22;
color: #FFFFFF;
font-size: 30rpx;
font-weight: bold;
border-radius: 50%;
}
}
.add-btn {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
width: 127rpx;
height: 127rpx;
border-radius: 4rpx;
background-color: #FAFAFA;
border:1px solid #EDEDED;
text-align: center;
.icon {
color: #CCCCCC;
font-size: 38rpx;
line-height: 38rpx;
}
.txt {
margin-top: 12rpx;
display: block;
font-size: 22rpx;
font-weight: 400;
color: #CCCCCC;
line-height: 32rpx;
}
}
}
}