uniapp封装图片上传组件,使用v-model双向绑定

使用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>
相关推荐
我叫张小白。3 小时前
Vue3监视系统全解析
前端·javascript·vue.js·前端框架·vue3
WYiQIU8 小时前
11月面了7.8家前端岗,兄弟们12月我先躺为敬...
前端·vue.js·react.js·面试·前端框架·飞书
谢尔登8 小时前
简单聊聊webpack摇树的原理
运维·前端·webpack
娃哈哈哈哈呀8 小时前
formData 传参 如何传数组
前端·javascript·vue.js
zhu_zhu_xia9 小时前
vue3+vite打包出现内存溢出问题
前端·vue
tsumikistep9 小时前
【前后端】接口文档与导入
前端·后端·python·硬件架构
行走的陀螺仪10 小时前
.vscode 文件夹配置详解
前端·ide·vscode·编辑器·开发实践
2503_9284115610 小时前
11.24 Vue-组件2
前端·javascript·vue.js
Bigger10 小时前
🎨 用一次就爱上的图标定制体验:CustomIcons 实战
前端·react.js·icon
谢尔登11 小时前
原来Webpack在大厂中这样进行性能优化!
前端·webpack·性能优化