fix: 头像转 base64 持久化

WeChat chooseAvatar 返回的 wxfile:// 临时 URL 几天后失效,
头像会变灰。加 util.fileToDataURI 把临时文件读成 data URI
存到本地(限制 512KB,超出走降级方案),从根上解决。
This commit is contained in:
2026-07-08 14:10:25 +08:00
parent ddfcd14252
commit c41b3e138e
2 changed files with 68 additions and 12 deletions
+18 -3
View File
@@ -136,19 +136,34 @@ Page({
// --- Profile handlers ---
onChooseAvatar(e) {
async onChooseAvatar(e) {
const avatarUrl = e.detail.avatarUrl
if (!avatarUrl) return
this.setData({ avatarUrl })
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
avatarUrl: permanentUrl
})
this.setData({
nickName: saved.nickname || this.data.nickName,
avatarUrl: saved.avatarUrl
})
wx.showToast({ title: '头像已更新', icon: 'success', duration: 1200 })
} 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 })
}
},
onNicknameBlur(e) {
+42 -1
View File
@@ -54,11 +54,52 @@ const formatDate = (date) => {
/** Strip the time portion of a formatDate string → "YYYY-MM-DD". */
const _dateOnly = (s) => (typeof s === 'string' && s.length >= 10) ? s.substring(0, 10) : ''
/**
* Convert a local file path (typically `wxfile://...` from chooseAvatar) to a
* base64 data URI. Used to make temporary file URLs survive the WeChat
* expiration window (a few days) by embedding the bytes inline.
*
* Rejects if the file exceeds `maxBytes` (default 512KB) so a stray huge
* image doesn't blow the 10MB total wx.storage budget. Caller should
* fall back to the raw URL on rejection.
*/
const fileToDataURI = (filePath, maxBytes = 512 * 1024) => {
return new Promise((resolve, reject) => {
if (!filePath || typeof wx.getFileSystemManager !== 'function') {
reject(new Error('FileSystemManager unavailable'))
return
}
const fs = wx.getFileSystemManager()
fs.getFileInfo({
filePath,
success: (info) => {
if (info.size > maxBytes) {
reject(new Error(`File too large: ${info.size} > ${maxBytes}`))
return
}
fs.readFile({
filePath,
encoding: 'base64',
success: (res) => {
// chooseAvatar returns PNG; allow JPG/GIF/WEBP fallbacks.
const m = (filePath.match(/\.(\w+)(?:\?|$)/) || [, 'png'])[1].toLowerCase()
const mime = { png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif', webp: 'image/webp' }[m] || 'image/png'
resolve(`data:${mime};base64,${res.data}`)
},
fail: reject
})
},
fail: reject
})
})
}
module.exports = {
formatTime,
formatDuration,
getDaysInMonth,
getMonthCalendar,
formatDate,
dateOnly: _dateOnly
dateOnly: _dateOnly,
fileToDataURI
}