feat: 使用微信原生chooseAvatar获取头像

改用 button open-type="chooseAvatar" 替代上传方案,用户只能选择
微信头像,确保头像来源安全可控。头像URL直接存储到数据库。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
richarjiang
2026-04-05 11:03:46 +08:00
parent 64b319ea19
commit 9c5dd4a911
2 changed files with 43 additions and 11 deletions

View File

@@ -2,17 +2,19 @@
<view class="info-page"> <view class="info-page">
<!-- Avatar section --> <!-- Avatar section -->
<view class="avatar-section"> <view class="avatar-section">
<view class="avatar-wrap"> <button class="avatar-btn" open-type="chooseAvatar" @chooseavatar="handleChooseAvatar">
<image <view class="avatar-wrap">
v-if="avatarUrl" <image
class="avatar" v-if="avatarUrl"
:src="avatarUrl" class="avatar"
mode="aspectFill" :src="avatarUrl"
/> mode="aspectFill"
<view v-else class="avatar-placeholder"> />
<text class="avatar-placeholder-text">{{ nicknameInitial }}</text> <view v-else class="avatar-placeholder">
<text class="avatar-placeholder-text">{{ nicknameInitial }}</text>
</view>
</view> </view>
</view> </button>
<text class="avatar-name">{{ form.nickname || '未设置昵称' }}</text> <text class="avatar-name">{{ form.nickname || '未设置昵称' }}</text>
<text class="avatar-hint">微信头像</text> <text class="avatar-hint">微信头像</text>
</view> </view>
@@ -91,6 +93,7 @@ const form = ref({
}) })
const originalNickname = ref('') const originalNickname = ref('')
const saving = ref(false) const saving = ref(false)
const uploadingAvatar = ref(false)
// ─── Computed ───────────────────────────────────────────── // ─── Computed ─────────────────────────────────────────────
const isDirty = computed(() => form.value.nickname.trim() !== originalNickname.value) const isDirty = computed(() => form.value.nickname.trim() !== originalNickname.value)
@@ -122,6 +125,24 @@ const activeMembershipCount = computed(
() => userStore.user?.activeMembershipCount ?? userStore.activeMemberships.length, () => userStore.user?.activeMembershipCount ?? userStore.activeMemberships.length,
) )
// ─── Avatar upload ────────────────────────────────────────
async function handleChooseAvatar(e: { detail: { avatarUrl: string } }) {
const { avatarUrl } = e.detail
if (!avatarUrl) return
uploadingAvatar.value = true
try {
await userStore.updateProfile({ avatarUrl })
await userStore.fetchProfile()
uni.showToast({ title: '头像更新成功', icon: 'success' })
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : '更新失败,请重试'
uni.showToast({ title: msg, icon: 'none' })
} finally {
uploadingAvatar.value = false
}
}
// ─── Phone binding ──────────────────────────────────────── // ─── Phone binding ────────────────────────────────────────
async function handleGetPhone(e: { async function handleGetPhone(e: {
detail: { encryptedData: string; iv: string; errMsg: string } detail: { encryptedData: string; iv: string; errMsg: string }
@@ -204,6 +225,17 @@ onMounted(async () => {
border-bottom: 1rpx solid #f0ece8; border-bottom: 1rpx solid #f0ece8;
} }
.avatar-btn {
background: transparent;
border: none;
padding: 0;
margin: 0;
&::after {
border: none;
}
}
.avatar-wrap { .avatar-wrap {
position: relative; position: relative;
width: 160rpx; width: 160rpx;

File diff suppressed because one or more lines are too long