feat:新增头像和昵称修改

This commit is contained in:
tsui110 2026-01-02 12:48:16 +08:00
parent 9c3775624f
commit 61df7fca5e
3 changed files with 236 additions and 8 deletions

View File

@ -222,6 +222,19 @@ export function getUserInvites(user_id, page = 1, page_size = 20) {
// 兼容性适配接口 (适配 pages/mine/index.vue)
// ============================================
// ============================================
// 用户信息修改 API
// ============================================
/**
* 修改用户信息
* @param {number} user_id - 用户ID
* @param {object} data - 用户数据 { nickname, avatar(base64) }
*/
export function modifyUser(user_id, data) {
return authRequest({ url: `/api/app/users/${user_id}`, method: 'PUT', data })
}
export function getUserInfo() {
const user_info = uni.getStorageSync('user_info')
if (user_info) return Promise.resolve(user_info)

View File

@ -654,11 +654,6 @@ function fetchExtraData(userId) {
box-shadow: 0 12rpx 32rpx rgba(7, 193, 96, 0.3);
}
&.toutiao-icon {
background: linear-gradient(135deg, #000000 0%, #1a1a1a 100%);
box-shadow: 0 12rpx 32rpx rgba(0, 0, 0, 0.3);
}
.icon-emoji {
font-size: 56rpx;
}
@ -678,6 +673,53 @@ function fetchExtraData(userId) {
}
}
/* 抖音登录面板 */
.toutiao-panel {
align-items: center;
justify-content: center;
padding-top: 48rpx;
.panel-icon-wrap {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 40rpx;
}
.panel-icon {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
&.toutiao-icon {
background: linear-gradient(135deg, #000000 0%, #1a1a1a 100%);
box-shadow: 0 12rpx 32rpx rgba(0, 0, 0, 0.3);
}
.icon-emoji {
font-size: 56rpx;
}
}
.panel-title {
font-size: 36rpx;
font-weight: 700;
color: $text-main;
margin-bottom: 12rpx;
text-align: center;
}
.panel-desc {
font-size: 26rpx;
color: $text-sub;
margin-bottom: 48rpx;
text-align: center;
}
}
/* 短信登录面板 */
.sms-panel {
.panel-title {

View File

@ -7,9 +7,23 @@
<view class="header-section">
<view class="user-info-card glass-card">
<view class="user-main">
<image class="avatar" :src="avatar || '/static/logo.png'" mode="aspectFill"></image>
<view class="avatar-wrapper" @click="handleEditAvatar" v-if="nickname">
<image class="avatar" :src="avatar || '/static/logo.png'" mode="aspectFill"></image>
<view class="avatar-edit-badge">
<text class="edit-icon">📷</text>
</view>
</view>
<image v-else class="avatar" :src="avatar || '/static/logo.png'" mode="aspectFill"></image>
<view class="user-meta">
<view class="name-row">
<view class="name-row" @click="handleEditNickname" v-if="nickname">
<text class="nickname">{{ nickname || '未登录' }}</text>
<text class="edit-nickname-icon"></text>
<view class="level-badge" v-if="title">
<image class="level-icon" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiNGRjZCMDAiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNNiAzSDE4TDIxIDlMOSAyMSAzIDlMNiAzWiIgLz48L3N2Zz4=" mode="aspectFit"></image>
<text class="level-text">Lv1 {{ title }}</text>
</view>
</view>
<view class="name-row" v-else>
<text class="nickname">{{ nickname || '未登录' }}</text>
<view class="level-badge" v-if="title">
<image class="level-icon" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiNGRjZCMDAiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNNiAzSDE4TDIxIDlMOSAyMSAzIDlMNiAzWiIgLz48L3N2Zz4=" mode="aspectFit"></image>
@ -454,7 +468,7 @@
<script>
import {
getUserInfo, getUserStats, getPointsBalance, getUserPoints, getUserCoupons, getItemCards,
getUserTasks, getTaskProgress, getInviteRecords
getUserTasks, getTaskProgress, getInviteRecords, modifyUser
} from '../../api/appUser.js'
export default {
@ -540,6 +554,124 @@ export default {
}
},
methods: {
//
handleEditAvatar() {
if (!this.userId) {
uni.showToast({ title: '请先登录', icon: 'none' })
return
}
uni.chooseImage({
count: 1,
sizeType: ['compressed'], //
sourceType: ['album', 'camera'],
success: (res) => {
const tempFilePath = res.tempFilePaths[0]
this.uploadAvatar(tempFilePath)
}
})
},
// base64
async uploadAvatar(filePath) {
uni.showLoading({ title: '上传中...' })
try {
// base64
const base64 = await this.fileToBase64(filePath)
//
const data = await modifyUser(this.userId, { avatar: base64 })
//
uni.setStorageSync('avatar', data.avatar || base64)
this.avatar = data.avatar || base64
const userInfo = uni.getStorageSync('user_info') || {}
userInfo.avatar = data.avatar || base64
uni.setStorageSync('user_info', userInfo)
uni.hideLoading()
uni.showToast({ title: '头像修改成功', icon: 'success' })
} catch (err) {
uni.hideLoading()
uni.showToast({ title: err.message || '上传失败', icon: 'none' })
}
},
// base64
fileToBase64(filePath) {
return new Promise((resolve, reject) => {
uni.getFileSystemManager().readFile({
filePath: filePath,
encoding: 'base64',
success: (res) => {
// data URI
const ext = filePath.split('.').pop().toLowerCase()
let mimeType = 'image/jpeg'
if (ext === 'png') mimeType = 'image/png'
if (ext === 'gif') mimeType = 'image/gif'
if (ext === 'webp') mimeType = 'image/webp'
resolve(`data:${mimeType};base64,${res.data}`)
},
fail: (err) => {
reject(err)
}
})
})
},
//
handleEditNickname() {
if (!this.userId) {
uni.showToast({ title: '请先登录', icon: 'none' })
return
}
uni.showModal({
title: '修改昵称',
editable: true,
placeholderText: '请输入新昵称',
content: this.nickname,
success: async (res) => {
if (res.confirm) {
const newNickname = res.content?.trim()
if (!newNickname) {
uni.showToast({ title: '昵称不能为空', icon: 'none' })
return
}
if (newNickname.length > 20) {
uni.showToast({ title: '昵称不能超过20个字符', icon: 'none' })
return
}
try {
uni.showLoading({ title: '修改中...' })
//
const data = await modifyUser(this.userId, { nickname: newNickname })
//
uni.setStorageSync('nickname', data.nickname || newNickname)
this.nickname = data.nickname || newNickname
const userInfo = uni.getStorageSync('user_info') || {}
userInfo.nickname = data.nickname || newNickname
uni.setStorageSync('user_info', userInfo)
uni.hideLoading()
uni.showToast({ title: '昵称修改成功', icon: 'success' })
} catch (err) {
uni.hideLoading()
uni.showToast({ title: err.message || '修改失败', icon: 'none' })
}
}
}
})
},
getInviteCode() {
const v = this.inviteCode || uni.getStorageSync('invite_code') || (uni.getStorageSync('user_info') || {}).invite_code || ''
return String(v || '').trim()
@ -1186,16 +1318,57 @@ export default {
margin-right: 24rpx;
}
.avatar-wrapper {
position: relative;
margin-right: 24rpx;
cursor: pointer;
.avatar {
margin-right: 0;
}
.avatar-edit-badge {
position: absolute;
bottom: 0;
right: 0;
width: 40rpx;
height: 40rpx;
background: linear-gradient(135deg, $brand-primary, $brand-secondary);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: 3rpx solid $bg-card;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.15);
.edit-icon {
font-size: 20rpx;
line-height: 1;
}
}
}
.user-meta { flex: 1; }
.name-row {
display: flex; align-items: center; margin-bottom: 12rpx;
cursor: pointer;
&:active {
opacity: 0.7;
}
}
.nickname {
font-size: $font-xl; font-weight: 900; color: $text-main;
margin-right: 16rpx;
}
.edit-nickname-icon {
font-size: 28rpx;
margin-right: 8rpx;
opacity: 0.6;
}
.level-badge {
background: linear-gradient(90deg, #333, #555);
color: $accent-gold;