feat(api): 升级 HaveADrink 依赖并实现用户头像上传功能
- 将 HaveADrink 依赖从 1.0.15 升级至 1.0.16 版本 - 新增 UpdateUserAvatar API 接口用于更新用户头像 - 添加 isValidAvatar 工具函数验证头像 URL 有效性 - 在登录流程中集成头像上传逻辑,支持微信头像选择 - 更新多个组件中的头像显示逻辑,过滤无效头像地址 - 优化 tabBar 主题应用逻辑,修复异步错误处理问题 - 修复微信新规范下头像选择的临时路径处理机制
This commit is contained in:
@@ -28,7 +28,7 @@
|
||||
>
|
||||
<!-- 头像 -->
|
||||
<view class="conv-avatar-wrap">
|
||||
<image v-if="conv.avatar" class="conv-avatar" :src="conv.avatar" mode="aspectFill"></image>
|
||||
<image v-if="isValidAvatar(conv.avatar)" class="conv-avatar" :src="conv.avatar" mode="aspectFill"></image>
|
||||
<view v-else class="conv-avatar conv-avatar-ph">
|
||||
<text class="conv-avatar-text">{{ conv.nickname ? conv.nickname[0] : '酒' }}</text>
|
||||
</view>
|
||||
@@ -69,6 +69,7 @@ import EmptyState from '../../components/EmptyState.vue'
|
||||
import { GetConversations } from '../../common/api'
|
||||
import wsManager from '../../common/websocket'
|
||||
import themeMixin from '../../common/theme-mixin'
|
||||
import { isValidAvatar } from '../../common/utils'
|
||||
|
||||
export default {
|
||||
mixins: [themeMixin],
|
||||
@@ -93,6 +94,7 @@ export default {
|
||||
this.unbindWsEvents()
|
||||
},
|
||||
methods: {
|
||||
isValidAvatar,
|
||||
async loadConversations() {
|
||||
this.loading = true
|
||||
try {
|
||||
|
||||
+49
-18
@@ -90,7 +90,7 @@
|
||||
|
||||
<script>
|
||||
import themeMixin from '../../common/theme-mixin'
|
||||
import client, { saveAuthTokens } from '../../common/api'
|
||||
import client, { saveAuthTokens, UploadImage, UpdateUserAvatar } from '../../common/api'
|
||||
import { handlePendingInvite } from '../../common/invite'
|
||||
import wsManager from '../../common/websocket'
|
||||
|
||||
@@ -102,6 +102,7 @@ export default {
|
||||
loginLoading: false,
|
||||
agreed: false,
|
||||
avatarUrl: '',
|
||||
avatarTempPath: '', // 待上传的 wxfile:// 临时路径(选头像时暂存,登录后带 token 上传)
|
||||
nickname: ''
|
||||
}
|
||||
},
|
||||
@@ -123,10 +124,14 @@ export default {
|
||||
},
|
||||
|
||||
// ===== 微信新规范:头像选择 =====
|
||||
// chooseAvatar 返回的是 wxfile://tmp_ 本地临时路径(微信随时会清理、仅本机有效),
|
||||
// 上传接口需要登录 token,而选头像时还未登录,因此这里只暂存临时路径,
|
||||
// 等 handleLogin 拿到 token 后再上传
|
||||
onChooseAvatar(e) {
|
||||
if (e.detail && e.detail.avatarUrl) {
|
||||
this.avatarUrl = e.detail.avatarUrl
|
||||
}
|
||||
const tempUrl = e.detail && e.detail.avatarUrl
|
||||
if (!tempUrl) return
|
||||
this.avatarUrl = tempUrl
|
||||
this.avatarTempPath = tempUrl
|
||||
},
|
||||
|
||||
// ===== 微信新规范:昵称输入 =====
|
||||
@@ -157,30 +162,56 @@ export default {
|
||||
if (!loginRes || !loginRes.code) {
|
||||
throw new Error('获取微信code失败')
|
||||
}
|
||||
// 2. 调用后端微信登录接口
|
||||
const loginParams = {
|
||||
// 2. 调用后端微信登录接口换取 token(上传头像接口需要鉴权)
|
||||
const firstResp = await client.WechatLogin({
|
||||
code: loginRes.code,
|
||||
nickName: this.nickname.trim() || '',
|
||||
avatarUrl: this.avatarUrl || ''
|
||||
}
|
||||
const resp = await client.WechatLogin(loginParams)
|
||||
avatarUrl: ''
|
||||
})
|
||||
// errcode 兜底:后端业务错误码非 0 时视为登录失败
|
||||
if (resp && resp.errcode && resp.errcode !== 0) {
|
||||
throw { msg: resp.errmsg || '登录失败' }
|
||||
if (firstResp && firstResp.errcode && firstResp.errcode !== 0) {
|
||||
throw { msg: firstResp.errmsg || '登录失败' }
|
||||
}
|
||||
if (!resp || !resp.token) {
|
||||
if (!firstResp || !firstResp.token) {
|
||||
throw { msg: '登录响应缺少 token' }
|
||||
}
|
||||
|
||||
// 3. 保存 token 和用户信息
|
||||
saveAuthTokens(resp)
|
||||
if (resp.user) {
|
||||
uni.setStorageSync('user_info', JSON.stringify(resp.user))
|
||||
|
||||
// 3. 保存 token 和用户信息(此时已有鉴权能力)
|
||||
saveAuthTokens(firstResp)
|
||||
if (firstResp.user) {
|
||||
uni.setStorageSync('user_info', JSON.stringify(firstResp.user))
|
||||
}
|
||||
uni.setStorageSync('is_logged_in', 'true')
|
||||
uni.setStorageSync('is_guest', 'false')
|
||||
uni.setStorageSync('is_first_launch', 'false')
|
||||
loginOk = true
|
||||
|
||||
// 4. 有头像时:带 token 上传临时文件,再调 UpdateUserAvatar 写入后端
|
||||
// (头像环节失败不阻断登录)
|
||||
if (this.avatarTempPath) {
|
||||
try {
|
||||
const upRes = await UploadImage({ filePath: this.avatarTempPath })
|
||||
const remoteUrl = upRes && upRes.data && upRes.data.url
|
||||
if (remoteUrl && /^https?:\/\//.test(remoteUrl)) {
|
||||
const avatarResp = await UpdateUserAvatar({ avatar: remoteUrl })
|
||||
if (avatarResp && avatarResp.data && avatarResp.data.ok) {
|
||||
// 同步更新本地缓存的用户头像
|
||||
try {
|
||||
const cached = JSON.parse(uni.getStorageSync('user_info') || '{}')
|
||||
cached.avatar = remoteUrl
|
||||
uni.setStorageSync('user_info', JSON.stringify(cached))
|
||||
} catch (cacheErr) { /* ignore */ }
|
||||
} else {
|
||||
uni.showToast({ title: '头像保存失败,可稍后在个人主页修改', icon: 'none' })
|
||||
}
|
||||
} else {
|
||||
uni.showToast({ title: '头像上传失败,可稍后在个人主页修改', icon: 'none' })
|
||||
}
|
||||
} catch (avatarErr) {
|
||||
console.warn('头像上传/保存失败,不影响登录:', avatarErr)
|
||||
uni.showToast({ title: '头像上传失败,可稍后在个人主页修改', icon: 'none' })
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// 打印完整错误,方便定位具体失败环节
|
||||
console.warn('后端登录失败,降级为本地模式:', e && e.stack ? e.stack : e)
|
||||
@@ -194,7 +225,7 @@ export default {
|
||||
const userInfo = {
|
||||
id: `user_${Date.now()}`,
|
||||
nickname: this.nickname.trim() || '酒友',
|
||||
avatar: this.avatarUrl || '',
|
||||
avatar: '',
|
||||
joinedAt: new Date().toISOString().slice(0, 10),
|
||||
preferences: null
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
</button>
|
||||
<view class="hero-content">
|
||||
<view class="avatar-ring">
|
||||
<image v-if="user.avatar" class="hero-avatar" :src="user.avatar" mode="aspectFill"></image>
|
||||
<image v-if="isValidAvatar(user.avatar)" class="hero-avatar" :src="user.avatar" mode="aspectFill"></image>
|
||||
<view v-else class="hero-avatar hero-avatar-placeholder">
|
||||
<text class="hero-avatar-icon">👤</text>
|
||||
</view>
|
||||
@@ -174,7 +174,7 @@
|
||||
<script>
|
||||
import AchievementBadge from '../../components/AchievementBadge.vue'
|
||||
import client, { clearAuth } from '../../common/api'
|
||||
import { checkAchievements, getCatIcon, isIconPath, calcLocalStreak } from '../../common/utils'
|
||||
import { checkAchievements, getCatIcon, isIconPath, calcLocalStreak, isValidAvatar } from '../../common/utils'
|
||||
import { ACHIEVEMENTS, DRINK_CATEGORIES } from '../../common/constants'
|
||||
import themeMixin from '../../common/theme-mixin'
|
||||
|
||||
@@ -271,11 +271,12 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isValidAvatar,
|
||||
isIconPath,
|
||||
// 构建分享名片的 query 参数(携带公开档案数据)
|
||||
buildShareQuery() {
|
||||
const parts = [`nick=${encodeURIComponent(this.user.nickname || '酒友')}`]
|
||||
if (this.user.avatar) parts.push(`avatar=${encodeURIComponent(this.user.avatar)}`)
|
||||
if (isValidAvatar(this.user.avatar)) parts.push(`avatar=${encodeURIComponent(this.user.avatar)}`)
|
||||
parts.push(`days=${this.stats.totalDays || 0}`)
|
||||
parts.push(`records=${this.stats.totalRecords || 0}`)
|
||||
parts.push(`cups=${this.stats.totalCups || 0}`)
|
||||
|
||||
Reference in New Issue
Block a user