前言
在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 | |
} | |
}) | |
} |
结语
天气很冷,大家记得多穿衣,继续加油呀,可以放松,但不可以放弃❤️