使用uniapp开发时大多会开发到图片上传这个功能,将其封装成组件,使其更方便使用,并且使用v-model 更好的双向绑定,不用单独监听事件。并且实现一次选择多张图片,循环上传。
以下是代码
js
<template>
<view class="grid-img">
<!-- 遍历v-model绑定的图片数组 -->
<view
v-for="(item, index) in value"
:key="index"
class="img-view"
:style="{ width: wihe[0], height: wihe[1] }"
>
<image
class="imgs"
:src="item"
:style="{ width: wihe[0], height: wihe[1] }"
></image>
<view class="img-close" @click="closeItem(index)">
<u-icon name="close" color="#fff" size="10"></u-icon>
</view>
</view>
<!-- 上传按钮:未达最大数量时显示 -->
<view v-if="value.length < maxCount" @click="startUpload">
<view class="upload-view" :style="{ width: wihe[0], height: wihe[1] }">
<u-icon name="plus" size="20" bold color="#BFBFBF"></u-icon>
</view>
</view>
</view>
</template>
<script>
import { upload } from '@/api/common.js';
import storage from '@/utils/storage.js';
import config from '@/config/config';
export default {
props: {
// v-model绑定的图片数组(核心)
value: {
type: Array,
default: () => []
},
maxCount: {
type: Number,
default: 1 // 最大上传总数(如2)
},
wihe: {
type: Array,
default: () => ['150rpx', '150rpx'] // 图片宽高
},
multiple: {
type: Boolean,
default: true // 是否允许选择多张(true=支持多图,false=仅单张)
},
maxLength: {
type: Number,
default: 100 // 单张图片大小限制(单位可自定义,如KB)
}
},
methods: {
// 点击上传:处理多图选择与批量上传
startUpload() {
// 剩余可上传数量(避免超过maxCount)
const remainCount = this.maxCount - this.value.length;
if (remainCount <= 0) return;
uni.chooseImage({
// 最多选择数量:multiple为true时取剩余数,false时最多1张
count: this.multiple ? remainCount : 1,
sizeType: ['original', 'compressed'],
success: (res) => {
const tempFilePaths = res.tempFilePaths; // 选中的所有图片路径
const total = tempFilePaths.length; // 选中图片总数
let completed = 0; // 已完成上传的数量(控制loading隐藏)
// 显示loading:所有图片上传完再隐藏
uni.showLoading({ title: `上传中(${completed}/${total})` });
// 循环上传每张图片
tempFilePaths.forEach((filePath) => {
this.uploadSingleFile(filePath, () => {
// 每张上传完成后更新计数与loading文本
completed++;
uni.showLoading({ title: `上传中(${completed}/${total})` });
// 所有图片上传完,隐藏loading
if (completed === total) {
uni.hideLoading();
}
});
});
}
});
},
// 单张图片上传:抽离为独立方法,便于循环调用
uploadSingleFile(filePath, onComplete) {
uni.uploadFile({
url: upload, // 上传接口地址
filePath: filePath,
name: 'file', // 接口接收文件的参数名(需与后端一致)
header: {
accessToken: storage.getAccessToken() // 身份令牌
},
success: (res) => {
const result = JSON.parse(res.data);
// 单张上传成功:同步到父组件数组
if (result.code === 200) {
this.uploadSuccess(result.result);
}
// 单张上传失败:提示错误(不影响其他图片)
else {
this.$u.toast(`图片上传失败:${result.msg}`);
}
},
fail: (err) => {
console.error('单张上传失败:', err);
this.$u.toast('图片上传失败,请重试');
},
complete: () => {
// 无论成功/失败,都标记为"已完成"(更新计数)
onComplete();
}
});
},
// 上传成功:通知父组件更新v-model数组
uploadSuccess(imgUrl) {
// 生成新数组(不直接修改props,遵循单向数据流)
const newImgList = [...this.value, imgUrl];
// 触发input事件,父组件v-model自动同步
this.$emit('input', newImgList);
},
// 删除图片:同步更新父组件数组
closeItem(index) {
// 过滤掉要删除的图片,生成新数组
const newImgList = this.value.filter((_, i) => i !== index);
this.$emit('input', newImgList);
}
}
};
</script>
<style lang="scss" scoped>
.grid-img {
display: flex;
gap: 16rpx;
flex-wrap: wrap;
align-items: flex-start;
}
.img-view {
position: relative;
overflow: hidden;
border-radius: 8rpx;
}
.imgs {
display: block;
width: 100%;
height: 100%;
object-fit: cover; // 避免图片拉伸变形
}
.img-close {
position: absolute;
top: -8rpx;
right: -8rpx;
width: 32rpx;
height: 32rpx;
background-color: rgba(0, 0, 0, 0.6);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
z-index: 10; // 确保关闭按钮在最上层
}
.upload-view {
background-color: #f6f6f6;
border: 1px dashed #ddd;
border-radius: 8rpx;
display: flex;
align-items: center;
justify-content: center;
}
</style>
使用方式:
js
<imgUpload
v-model="feedBack.images"
:maxCount="2"
:wihe="['150rpx', '150rpx']"
></imgUpload>