From c41b3e138e8ab01856fb8c183e60d7954466558e Mon Sep 17 00:00:00 2001 From: cnliucheng Date: Wed, 8 Jul 2026 14:10:25 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=A4=B4=E5=83=8F=E8=BD=AC=20base64=20?= =?UTF-8?q?=E6=8C=81=E4=B9=85=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WeChat chooseAvatar 返回的 wxfile:// 临时 URL 几天后失效, 头像会变灰。加 util.fileToDataURI 把临时文件读成 data URI 存到本地(限制 512KB,超出走降级方案),从根上解决。 --- pages/settings/settings.js | 37 ++++++++++++++++++++++---------- utils/util.js | 43 +++++++++++++++++++++++++++++++++++++- 2 files changed, 68 insertions(+), 12 deletions(-) diff --git a/pages/settings/settings.js b/pages/settings/settings.js index b5f2218..eabf840 100644 --- a/pages/settings/settings.js +++ b/pages/settings/settings.js @@ -136,19 +136,34 @@ Page({ // --- Profile handlers --- - onChooseAvatar(e) { + async onChooseAvatar(e) { const avatarUrl = e.detail.avatarUrl if (!avatarUrl) return - this.setData({ avatarUrl }) - const saved = storage.saveProfile({ - nickname: this.data.nickName, - avatarUrl - }) - this.setData({ - nickName: saved.nickname || this.data.nickName, - avatarUrl: saved.avatarUrl - }) - wx.showToast({ title: '头像已更新', icon: 'success', duration: 1200 }) + 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 }) + } 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) { diff --git a/utils/util.js b/utils/util.js index 048ae57..7b8a27a 100644 --- a/utils/util.js +++ b/utils/util.js @@ -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 }