feat: 使用微信原生chooseAvatar获取头像
改用 button open-type="chooseAvatar" 替代上传方案,用户只能选择 微信头像,确保头像来源安全可控。头像URL直接存储到数据库。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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
Reference in New Issue
Block a user