feat: 添加头像下方修改按钮和头像上传功能
- mine.vue: 头像下方添加灰色修改按钮 - api.js: 添加getBaseUrl方法 - UserController.java: 添加uploadAvatar接口 - WebConfig.java: 配置静态文件服务 - 支持修改用户名和上传头像
This commit is contained in:
1 parent
a10be4ca13
commit
1a48452ccd
4 files changed
+158
-30
No files matched your search
@@ -0,0 +1,15 @@
|
||||
package xuan.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
registry.addResourceHandler("/uploads/**")
|
||||
.addResourceLocations("file:uploads/");
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,18 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import xuan.entity.UserInfo;
|
||||
import xuan.service.UserInfoService;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/user")
|
||||
@@ -319,4 +326,54 @@ public class UserController {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@PostMapping("/uploadAvatar")
|
||||
public Map<String, Object> uploadAvatar(
|
||||
@RequestParam("file") MultipartFile file,
|
||||
@RequestParam("userId") String userId) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
try {
|
||||
if (file.isEmpty()) {
|
||||
result.put("success", false);
|
||||
result.put("error", "请选择文件");
|
||||
return result;
|
||||
}
|
||||
|
||||
String uploadDir = "uploads/avatars/";
|
||||
File dir = new File(uploadDir);
|
||||
if (!dir.exists()) {
|
||||
dir.mkdirs();
|
||||
}
|
||||
|
||||
String originalFilename = file.getOriginalFilename();
|
||||
String extension = "";
|
||||
if (originalFilename != null && originalFilename.contains(".")) {
|
||||
extension = originalFilename.substring(originalFilename.lastIndexOf("."));
|
||||
}
|
||||
|
||||
String filename = UUID.randomUUID().toString() + extension;
|
||||
Path filePath = Paths.get(uploadDir + filename);
|
||||
Files.write(filePath, file.getBytes());
|
||||
|
||||
String avatarUrl = "/uploads/avatars/" + filename;
|
||||
|
||||
UserInfo user = userInfoService.updateAvatarUrl(userId, avatarUrl);
|
||||
if (user != null) {
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("avatarUrl", avatarUrl);
|
||||
result.put("success", true);
|
||||
result.put("data", data);
|
||||
} else {
|
||||
result.put("success", false);
|
||||
result.put("error", "用户不存在");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
result.put("success", false);
|
||||
result.put("error", "文件上传失败: " + e.getMessage());
|
||||
} catch (Exception e) {
|
||||
result.put("success", false);
|
||||
result.put("error", e.getMessage());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<view class="header-card">
|
||||
<view class="avatar" @click="previewAvatar">
|
||||
<image v-if="avatar" :src="avatar" class="avatar-image" mode="aspectFill"></image>
|
||||
<text v-else class="avatar-text">👤</text>
|
||||
<view class="avatar-edit-icon" @click.stop="editUserInfo">
|
||||
<text class="edit-icon-text">✎</text>
|
||||
<view class="avatar-wrapper">
|
||||
<view class="avatar" @click="previewAvatar">
|
||||
<image v-if="avatar" :src="avatar" class="avatar-image" mode="aspectFill"></image>
|
||||
<text v-else class="avatar-text">👤</text>
|
||||
</view>
|
||||
<view class="avatar-edit-btn" @click="editUserInfo">
|
||||
<text class="avatar-edit-text">修改</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="user-info">
|
||||
@@ -124,7 +126,12 @@ export default {
|
||||
this.coins = userData.coins || 0
|
||||
this.vipLevel = userData.vipLevel || 0
|
||||
this.username = userData.nickname || userData.username || '玄机用户'
|
||||
this.avatar = userData.avatarUrl || ''
|
||||
|
||||
let avatarUrl = userData.avatarUrl || ''
|
||||
if (avatarUrl && !avatarUrl.startsWith('http') && !avatarUrl.startsWith('wxfile://')) {
|
||||
avatarUrl = api.getBaseUrl().replace('/api', '') + avatarUrl
|
||||
}
|
||||
this.avatar = avatarUrl
|
||||
|
||||
uni.setStorageSync('userCoins', this.coins)
|
||||
uni.setStorageSync('vipLevel', this.vipLevel)
|
||||
@@ -226,14 +233,64 @@ export default {
|
||||
count: 1,
|
||||
sizeType: ['compressed'],
|
||||
sourceType: ['album', 'camera'],
|
||||
success: (res) => {
|
||||
success: async (res) => {
|
||||
const tempFilePaths = res.tempFilePaths
|
||||
this.avatar = tempFilePaths[0]
|
||||
uni.setStorageSync('userAvatar', tempFilePaths[0])
|
||||
uni.showToast({
|
||||
title: '头像已更新',
|
||||
icon: 'success'
|
||||
})
|
||||
uni.showLoading({ title: '上传中...' })
|
||||
|
||||
try {
|
||||
const uploadRes = await new Promise((resolve, reject) => {
|
||||
uni.uploadFile({
|
||||
url: api.getBaseUrl() + '/user/uploadAvatar',
|
||||
filePath: tempFilePaths[0],
|
||||
name: 'file',
|
||||
formData: {
|
||||
userId: this.userId
|
||||
},
|
||||
success: (uploadRes) => {
|
||||
try {
|
||||
const data = JSON.parse(uploadRes.data)
|
||||
resolve(data)
|
||||
} catch (e) {
|
||||
reject(e)
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
uni.hideLoading()
|
||||
|
||||
if (uploadRes.success && uploadRes.data) {
|
||||
this.avatar = uploadRes.data.avatarUrl || uploadRes.data
|
||||
uni.setStorageSync('userAvatar', this.avatar)
|
||||
|
||||
const userInfoStr = uni.getStorageSync('userInfo')
|
||||
if (userInfoStr) {
|
||||
const userInfo = JSON.parse(userInfoStr)
|
||||
userInfo.avatarUrl = this.avatar
|
||||
uni.setStorageSync('userInfo', JSON.stringify(userInfo))
|
||||
}
|
||||
|
||||
uni.showToast({
|
||||
title: '头像已更新',
|
||||
icon: 'success'
|
||||
})
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: uploadRes.error || '上传失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
uni.hideLoading()
|
||||
console.error('上传头像失败:', e)
|
||||
uni.showToast({
|
||||
title: '上传失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
@@ -280,6 +337,13 @@ page {
|
||||
box-shadow: 0 16rpx 48rpx rgba(16, 185, 129, 0.25);
|
||||
}
|
||||
|
||||
.avatar-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.avatar{
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
@@ -288,8 +352,6 @@ page {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto 24rpx;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -303,22 +365,15 @@ page {
|
||||
font-size: 60rpx;
|
||||
}
|
||||
|
||||
.avatar-edit-icon {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
background: #10B981;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 2rpx solid #ffffff;
|
||||
.avatar-edit-btn {
|
||||
margin-top: 12rpx;
|
||||
padding: 8rpx 24rpx;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.edit-icon-text {
|
||||
font-size: 20rpx;
|
||||
.avatar-edit-text {
|
||||
font-size: 24rpx;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
|
||||
@@ -435,5 +435,6 @@ export default {
|
||||
shareApi,
|
||||
toolApi,
|
||||
coinConsumeApi,
|
||||
userApi
|
||||
userApi,
|
||||
getBaseUrl: () => BASE_URL
|
||||
}
|
||||
Reference in new issue
Block a user