关于uniapp解决单/多文件上传的解决思路

手机APP/开发
299
0
0
2024-07-03
标签   uni-app

前言

在uniapp开发过程中,有一个个人中心的上传头像的问题,属于是单文件上传,还有一个是用户发布日常动态的问题,可以带有多张图片,属于是多文件上传,如下是我的解决方案,做个记录吧~

后台 启动!!!

业务场景 1 - 上传头像

🗨️该页面完整代码如下:

<template>
	<view class="container">
		<view class="icon active">基本信息</view>
		<view class="avatar_img">
			<image class="img" :src="UserAvatarPic" @click="uploadAvatarImg"></image>
		</view>
		<view class="avatar_form">
			<view class="name" @click="changeName">
				<view class="left">昵称</view>
				<view class="right">
					<view class="a">{{ UserAvatarName }}</view>
					<image class="img" src="../../static/rm-more.png"></image>
				</view>
			</view>
			<view class="desc" @click="toSignature">
				<view class="left">简介</view>
				<view class="right">
					<image class="img" src="../../static/rm-more.png"></image>
				</view>
			</view>
			<view class="phone">
				<view class="left">绑定手机号</view>
				<view class="right">
					<view class="a">还未绑定手机号</view>
					<image class="img" src="../../static/rm-more.png"></image>
				</view>
			</view>
			<view class="id">
				<view class="left">用户 ID</view>
				<view view="right">{{ UserAvatarId }}</view>
			</view>
		</view>
	</view>
</template>

<script>
	import { modifyUserInfoAvatar } from '../../services/AboutUserInfo.js'
	import { mapState } from 'vuex'
	export default {
		computed: {
			...mapState('user_info', ['UserAvatarPic', 'UserAvatarName', 'UserAvatarId'])
		},
		data() {
			return {
				userId: null,
				tempPicUrl: '',
				avatarPicUrl: '',
				serverUrl: "http://localhost:8080/user/common/upload", // 接口地址
			}
		},
		mounted() {
			uni.getStorage({
			  key: 'userId',
			  success:  (res) => {
			    console.log(res.data)
					this.userId = res.data
			  }
			})
		},
		methods: {
			toSignature() {
				uni.navigateTo({
					url: "../../subpkg/Signature-define/Signature-define"
				})
			},
			changeName() {
				uni.navigateTo({
					url: "../../subpkg/change-name/change-name"
				})
			},
			uploadAvatarImg() {
				uni.chooseImage({
					count: 1,
					success: (res) => {
						this.tempPicUrl = res.tempFilePaths[0]
						console.log(this.tempPicUrl)
						uni.uploadFile({
							url: this.serverUrl,
							filePath: this.tempPicUrl,
							name: "file", // 服务器定义的文件字段为 file
							header: {
							  //设置用户访问的token信息
							  "authentication": uni.getStorageSync('token')
							 },
							success: (res) => {
								console.log('上传成功', res)
								console.log(res) //  后端返回的 data 是字符串
								let data = JSON.parse(res.data)
								console.log(data)
								this.avatarPicUrl = data.data
								this.modifyUserInfoAfterUpload(this.avatarPicUrl, this.userId)
								// 上传成功后使用 vuex 保存,但不做持久处理
								this.$store.commit('user_info/UpdateUserAvatarPic', this.avatarPicUrl)
							},
							fail: (error) => {
								console.log('上传失败', error)
								console.log(this.tempPicUrl)
							}
						})
					}
				})
			},
			async modifyUserInfoAfterUpload(avatarUrl, userId) {
			  const res = await modifyUserInfoAvatar(avatarUrl, userId)
			  console.log('调用结果:', res)
			}
		},
	}
</script>

<style lang="scss">
.container {
	width: 100vw;
	height: 100vh;
	.icon {
		width: 100%;
		text-align: center;
		color: #460779;
		font-weight: 600;
	}
	.active {
		position: relative;
		z-index: 9999;
		color: #460779;
		font-weight: 800;
		&::after {
			position: absolute;
			z-index: -9999;
			content: "";
			width: 40rpx;
			height: 40rpx;
			background-color: rgba(70,7,121,.2);
			left: 50%;
			top: 90%;
			border-radius: 50%;
			transform: translate(-50%, -50%);
		} 
	}
	.avatar_img {
		width: 80%;
		height: 350rpx;
		margin: 0 auto;
		display: flex;
		align-items: center;
		justify-content: center;
		// background-color: red; // 背景板
		.img {
			width: 200rpx;
			height: 200rpx;
			border-radius: 50%;
		}
	}
	.avatar_form {
		width: 100%;
		.name {
			width: 90%;
			margin: 0 auto;
			border-bottom: 1rpx solid #ccc;
			display: flex;
			justify-content: space-between;
			box-sizing: border-box;
			padding-bottom: 30rpx;
			font-size: 35rpx;
			align-items: center;
			margin-bottom: 30rpx;
			.left {}
			.right {
				display: flex;
				align-items: center;
				.a {
					font-size: 35rpx;
					font-weight: 100;
					margin-right: 30rpx;
				}
				.img {
					width: 40rpx;
					height: 40rpx;
				}
			}
		}
		.sex {
			width: 90%;
			margin: 0 auto;
			border-bottom: 1rpx solid #ccc;
			display: flex;
			justify-content: space-between;
			box-sizing: border-box;
			padding-bottom: 30rpx;
			font-size: 35rpx;
			align-items: center;
			margin-bottom: 30rpx;
			.left {}
			.right {
				display: flex;
				align-items: center;
				.a {
					font-size: 35rpx;
					font-weight: 100;
					margin-right: 30rpx;
				}
				.img {
					width: 40rpx;
					height: 40rpx;
				}
			}
		}
		.desc {
			width: 90%;
			margin: 0 auto;
			border-bottom: 1rpx solid #ccc;
			display: flex;
			justify-content: space-between;
			box-sizing: border-box;
			padding-bottom: 30rpx;
			font-size: 35rpx;
			align-items: center;
			margin-bottom: 30rpx;
			.left {}
			.right {
				display: flex;
				align-items: center;
				.a {
					font-size: 35rpx;
					font-weight: 100;
					margin-right: 30rpx;
				}
				.img {
					width: 40rpx;
					height: 40rpx;
				}
			}
		}
		.phone {
			width: 90%;
			margin: 0 auto;
			border-bottom: 1rpx solid #ccc;
			display: flex;
			justify-content: space-between;
			box-sizing: border-box;
			padding-bottom: 30rpx;
			font-size: 35rpx;
			align-items: center;
			margin-bottom: 30rpx;
			.left {}
			.right {
				display: flex;
				align-items: center;
				.a {
					font-size: 35rpx;
					font-weight: 100;
					margin-right: 30rpx;
				}
				.img {
					width: 40rpx;
					height: 40rpx;
				}
			}
		}
		.id {
			width: 90%;
			margin: 0 auto;
			border-bottom: 1rpx solid #ccc;
			display: flex;
			justify-content: space-between;
			box-sizing: border-box;
			padding-bottom: 30rpx;
			font-size: 35rpx;
			align-items: center;
			margin-bottom: 30rpx;
			.left {}
			.right {
				display: flex;
				align-items: center;
				.a {
					font-size: 35rpx;
					font-weight: 100;
					margin-right: 30rpx;
				}
				.img {
					width: 40rpx;
					height: 40rpx;
				}
			}
		}
	
	}
}
</style>

🗨️核心代码如下:

uploadAvatarImg() {
	uni.chooseImage({
		count: 1, //上传数量 默认为 9
		success: (res) => {
			this.tempPicUrl = res.tempFilePaths[0]
			console.log(this.tempPicUrl)
			uni.uploadFile({
				url: this.serverUrl, // 对应后端接口的完整地址
				filePath: this.tempPicUrl, // 图片的临时路径
				name: "file", // 服务器定义的文件字段为 file
				header: { // 配置请求头信息 => jwt 校验
				  //设置用户访问的token信息
				  "authentication": uni.getStorageSync('token')
				 },
				success: (res) => {
					console.log('上传成功', res)
					console.log(res) //  后端返回的 data 是字符串
					let data = JSON.parse(res.data)
					console.log(data)
					this.avatarPicUrl = data.data
					this.modifyUserInfoAfterUpload(this.avatarPicUrl, this.userId)
					// 上传成功后使用 vuex 保存,但不做持久处理
					this.$store.commit('user_info/UpdateUserAvatarPic', this.avatarPicUrl)
				},
				fail: (error) => {
					console.log('上传失败', error)
					console.log(this.tempPicUrl)
				}
			})
		}
	})
},
async modifyUserInfoAfterUpload(avatarUrl, userId) {
  const res = await modifyUserInfoAvatar(avatarUrl, userId)
  console.log('调用结果:', res)
}

核心思路就是:

首先 uploadAvatarImg 将用户上传的头像保存到 oss 服务器换取图片永久链接地址,之后将该永久链接地址通过 modifyUserInfoAfterUpload 的接口函数的调用,将该永久的链接地址提交给后台。

modifyUserInfoAfterUpload 接口封装函数是这样的:

// 修改用户头像
// 请求参数:
// "avatar": "",
// "name": "",
// "phone": "",
// "sex": "",
// "signature": "",
// "userId": 0
export function modifyUserInfoAvatar(avatar, userId) {
	return dgRequest.put({
		url: "/user/user",
		data: {
			avatar,
			userId
		}
	})
}

业务场景 2 - 用户发布动态

在这里插入图片描述

🗨️该功能页面的完整代码如下:

<template>
 <view class="container">
    <view class="container-header">
      <view @click="submitAll" class="submit-btn">发布</view>
    </view>
    <textarea
	   class="container-textarea"
       v-model="momentContent"
       placeholder="在这里你不用害怕被看见,可以释放你的分享欲..."
    >
	</textarea>
    <view class="image-container">
        <!-- 将 + 图标也视为一个图片项 -->
	    <block v-for="(img, index) in ossImgsUrl" :key="index">
			<image
			  class="image-item"
			  :src="img"
			  style="padding: 5rpx;"
			  @click="deleteImage(index)"
			>
			</image>
		</block>
      <view class="icon-container" @tap="chooseImage" style="padding: 5rpx;" v-if="momentPicture.length < 9">
        <view class="image-addIcon"></view>
      </view>
    </view>
    <view class="container-footer"></view>
  </view>
</template>

<script>
import { addDynamic } from '../../services/AboutDynamics.js'
export default {
  data() {
    return {
	  userId: null,
      momentContent: "", // 动态内容
      momentPicture: [], // 选中的图片路径数组
	  serverUrl: "http://localhost:8080/user/common/upload", // 图片上传接口
	  ossImgsUrl: []
    }
  },
  mounted() {
	  uni.getStorage({
	    key: 'userId',
	    success: (res) => {
	      console.log(res.data)
	  	  this.userId = res.data
	    }
	  })
  },
  methods: {
	async submitAll() {
		const res = await addDynamic(this.momentContent, this.ossImgsUrl, this.userId)
		console.log(res)
		if(res.code === 200) {
			uni.switchTab({
				url: "../../pages/add/add"
			})
		}
	},
	chooseImage() {
		// 选取图片列表
		uni.chooseImage({
		  count: 9,
		  success: (res) => {
			console.log(res.tempFilePaths)
			res.tempFilePaths.forEach((item, index) => {
			  this.momentPicture.push(item)
			  // 上传图片
			  uni.uploadFile({
				url: this.serverUrl, // 上传文件的接口地址
				filePath: item, // 要上传的文件路径
				name: 'file', // 文件对应的key,后端可以通过这个key获取文件
				header: {
				  // 设置用户访问的token信息
				  "authentication": uni.getStorageSync('token')
				},
				success: (uploadRes) => {
				  console.log(uploadRes)
				  let data = JSON.parse(uploadRes.data) // 后端返回给我的是一个字符串,处理一下
				  console.log(data)
				  // 将得到的在线地址保存到要提交的图片列表里
				  this.ossImgsUrl.push(data.data)
				},
				fail: (err) => {
				  console.error(err);
				  // 在这里可以处理上传失败后的逻辑
				}
			  });
			});
		  },
		});
	},
    deleteImage(index) {
      uni.showModal({
        title: "提示",
        content: "确定要删除这张图片吗?",
        success: (res) => {
          if (res.confirm) {
            this.momentPicture.splice(index, 1)
          }
        },
      })
    },

  }
}
</script>

<style lang="scss" scoped>
  .container {
	width: 90vw;
	margin: 0 auto;
	
    .container-header {
      display: flex;
      height: 50rpx;
      width: 100%;
      justify-content: flex-end;
      .submit-btn {
        width: 120rpx;
        height: 50rpx;
        background-color: deepskyblue;
        color: #fff;
        font-size: x-small;
        text-align: center;
        line-height: 50rpx;
        border-radius: 20rpx;
        margin: 20rpx 20rpx 0 0;
      }
    }
    .container-textarea {
      width: 100%;
	  box-shadow: 1px 1px 7rpx 2rpx rgba(0, 0, 0, 0.1);
      margin-top: 35rpx;
	  border-radius: 20rpx;
	  box-sizing: border-box;
	  padding: 30rpx;
    }
    .image-container {
      width: 100%;
	  box-sizing: border-box;
      display: flex;
      flex-wrap: wrap;
	  margin-top: 30rpx;
      .image-item {
        width: 30%;
        height: 200rpx;
      }
      .icon-container {
        display: flex;
        justify-content: center;
        align-items: center;
        width: 30%;
        height: 200rpx;
		border-radius: 15rpx;
        background-color: #eee;
        .image-addIcon {
          width: 80rpx;
          height: 80rpx;
          background-color: rgba(0,0,0,.2);
          border-radius: 50%;
          display: flex;
          justify-content: center;
          align-items: center;

          &::before,
          &::after {
            content: "";
            position: absolute;
            width: 60rpx;
            height: 8rpx;
            background-color: white;
            border-radius: 4rpx;
          }

          &::before {
            transform: rotate(90deg);
          }
        }
      }
    }
    .container-footer {
		height: 200rpx;
	}
  }
</style>

🗨️该核心代码如下:

async submitAll() {
	const res = await addDynamic(this.momentContent, this.ossImgsUrl, this.userId)
	console.log(res)
	if(res.code === 200) {
		uni.switchTab({
			url: "../../pages/add/add"
		})
	}
},
chooseImage() {
	// 选取图片列表
	uni.chooseImage({
	  count: 9,
	  success: (res) => {
		console.log(res.tempFilePaths)
		res.tempFilePaths.forEach((item, index) => {
		  this.momentPicture.push(item)
		  // 上传图片
		  uni.uploadFile({
			url: this.serverUrl, // 上传文件的接口地址
			filePath: item, // 要上传的文件路径
			name: 'file', // 文件对应的key,后端可以通过这个key获取文件
			header: {
			  // 设置用户访问的token信息
			  "authentication": uni.getStorageSync('token')
			},
			success: (uploadRes) => {
			  console.log(uploadRes)
			  let data = JSON.parse(uploadRes.data) // 后端返回给我的是一个字符串,处理一下
			  console.log(data)
			  // 将得到的在线地址保存到要提交的图片列表里
			  this.ossImgsUrl.push(data.data)
			},
			fail: (err) => {
			  console.error(err);
			  // 在这里可以处理上传失败后的逻辑
			}
		  });
		});
	  },
	});
},

也是很简单的,就是用户使用 foreach 方法,实际还是一个单文件上传,用户选择图片,将图片临时链接数组遍历,进行单文件上传,得到永久地址,组成一个集合就行,最后 submitAll 调用接口函数 addDynamic。

addDynamic 封装如下:

// 添加动态接口A
// "categoryId": 0,
// "momentContent": "",
// "momentCreateDate": "",
// "momentId": 0,
// "momentPicture": [],
// "userId": 0
export function addDynamic(momentContent, momentPicture, userId) {
	return dgRequest.put({
		url: "/user/moment/addMoment",
		data: {
			momentContent,
			momentPicture,
			userId
		}
	})
}

结语

天气很冷,大家记得多穿衣,继续加油呀,可以放松,但不可以放弃❤️