fix: 振动定时器清理、完成保存守卫、暗黑冷启动、头像云存储
- timer: 振动 setTimeout 入栈追踪,暂停/停止/卸载/重练统一清理, 避免页面销毁后残留震动(_clearVibrateTimers) - timer: _saving 守卫统一到 _saveAndShowCompletion,自然完成与手动 结束两路径都防重复保存;修复 onTrainAgain 不重置会话状态导致 再练一次后第二次无语音/振动且保存被挡 - app.wxss: @media dark 补全 --text/--card-bg/--bg-soft/--border/ --success/--danger,冷启动即为完整深色,不再背景深卡片白的割裂闪烁 - settings: 头像优先上传云存储存 fileID,三级回退(fileID->base64-> 临时URL),pushAll 不再每次传输图片字节 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+37
-22
@@ -140,31 +140,46 @@ Page({
|
||||
async onChooseAvatar(e) {
|
||||
const avatarUrl = e.detail.avatarUrl
|
||||
if (!avatarUrl) return
|
||||
|
||||
// Store the avatar durably. Prefer a cloud-storage fileID: it keeps the
|
||||
// profile doc small and avoids re-transferring the image bytes on every
|
||||
// pushAll. Fall back to an inline base64 data URI if cloud storage is
|
||||
// unavailable, and to the raw temp URL as a last resort.
|
||||
let permanentUrl = null
|
||||
try {
|
||||
// chooseAvatar returns a wxfile:// temporary URL that expires in a few
|
||||
// days. Read the bytes and store as a data URI so the avatar survives
|
||||
// long-term. Falls back to the raw URL on size/read error.
|
||||
const permanentUrl = await util.fileToDataURI(avatarUrl)
|
||||
this.setData({ avatarUrl: permanentUrl })
|
||||
const saved = storage.saveProfile({
|
||||
nickname: this.data.nickName,
|
||||
avatarUrl: permanentUrl
|
||||
})
|
||||
this.setData({
|
||||
nickName: saved.nickname || this.data.nickName,
|
||||
avatarUrl: saved.avatarUrl
|
||||
})
|
||||
wx.showToast({ title: '头像已更新', icon: 'success', duration: 1200 })
|
||||
permanentUrl = await this._uploadAvatar(avatarUrl)
|
||||
} catch (err) {
|
||||
console.warn('avatar convert failed, saving temp URL:', err)
|
||||
// Fallback: save the raw wxfile URL. Will eventually expire.
|
||||
this.setData({ avatarUrl })
|
||||
storage.saveProfile({
|
||||
nickname: this.data.nickName,
|
||||
avatarUrl
|
||||
})
|
||||
wx.showToast({ title: '头像已更新', icon: 'success', duration: 1200 })
|
||||
console.warn('[avatar] cloud upload failed, trying base64:', err)
|
||||
try {
|
||||
permanentUrl = await util.fileToDataURI(avatarUrl)
|
||||
} catch (err2) {
|
||||
console.warn('[avatar] base64 convert failed, saving temp URL:', err2)
|
||||
permanentUrl = avatarUrl
|
||||
}
|
||||
}
|
||||
|
||||
this.setData({ avatarUrl: permanentUrl })
|
||||
const saved = storage.saveProfile({
|
||||
nickname: this.data.nickName,
|
||||
avatarUrl: permanentUrl
|
||||
})
|
||||
this.setData({
|
||||
nickName: saved.nickname || this.data.nickName,
|
||||
avatarUrl: saved.avatarUrl
|
||||
})
|
||||
wx.showToast({ title: '头像已更新', icon: 'success', duration: 1200 })
|
||||
},
|
||||
|
||||
/**
|
||||
* Upload the chosen avatar to cloud storage and return its fileID.
|
||||
* Throws if cloud is disabled or the upload fails - caller falls back.
|
||||
*/
|
||||
async _uploadAvatar(filePath) {
|
||||
if (!cloud.enabled) throw new Error('cloud not enabled')
|
||||
const cloudPath = `avatar/${Date.now()}-${Math.floor(Math.random() * 1e6)}.png`
|
||||
const res = await wx.cloud.uploadFile({ cloudPath, filePath })
|
||||
if (!res || !res.fileID) throw new Error('uploadFile returned no fileID')
|
||||
return res.fileID
|
||||
},
|
||||
|
||||
onNicknameBlur(e) {
|
||||
|
||||
Reference in New Issue
Block a user