fix(ui): 多项 UI 修复 — 弹窗遮挡/按钮可见性/暗黑闪烁/云函数版本

- 计划编辑弹窗移出 .container 避免 fixed 降级 + 修复滚动
- dock bottom 40rpx→8rpx 紧贴底部, sheet padding 同步调整
- 计划编辑器取消/恢复默认按钮可见性修复(bg 匹配容器)
- ui-btn--danger 按钮可见性修复
- 清除确认弹窗跟随暗黑模式切换
- icons.js build() 中 Fill 路径覆盖 outlined 路径修复
- plan.js getPlanDay 忽略 customPlans 修复
- darkMode.js 冗余三元简化
- util.js fileToDataURI fallback 可读性提升
- theme.js: BASE_VARS 与 getThemeStyle 统一来源, 去 50ms navbar 延迟
- app.wxss: 移除 page transition 和 background-color 减少闪白
- 云函数 wx-server-sdk 版本统一 ~2.6.3, tts cloud.init 一致化
- 闪白问题: wx.setBackgroundColor 三层防护(onLaunch/applyThemeToPage/switchTab)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-07-10 09:40:04 +08:00
parent d5121d349f
commit 1eff3d60c4
21 changed files with 104 additions and 76 deletions
+3
View File
@@ -39,6 +39,9 @@ App({
this.globalData.theme = themeMod.getCurrentTheme() this.globalData.theme = themeMod.getCurrentTheme()
// 窗口背景色 — 冷启动就设好,避免暗黑模式下切 tab 闪白
themeMod.applyWindowBg()
// Sync with cloud: always locate our doc first (pullAll caches // Sync with cloud: always locate our doc first (pullAll caches
// _openid / _docId), then either restore or push. The previous flow // _openid / _docId), then either restore or push. The previous flow
// only pulled when local was empty, so on a cold start with non-empty // only pulled when local was empty, so on a cold start with non-empty
-2
View File
@@ -12,8 +12,6 @@ page {
'Hiragino Sans GB', 'Microsoft YaHei', sans-serif; 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
font-size: 28rpx; font-size: 28rpx;
color: var(--text); color: var(--text);
background-color: var(--bg);
transition: background-color 0.3s ease, color 0.3s ease;
} }
/* dark mode overrides (only for system dark mode; user choice goes through /* dark mode overrides (only for system dark mode; user choice goes through
+1 -1
View File
@@ -3,6 +3,6 @@
"version": "1.0.0", "version": "1.0.0",
"main": "index.js", "main": "index.js",
"dependencies": { "dependencies": {
"wx-server-sdk": "latest" "wx-server-sdk": "~2.6.3"
} }
} }
+1 -1
View File
@@ -3,6 +3,6 @@
"version": "1.0.0", "version": "1.0.0",
"main": "index.js", "main": "index.js",
"dependencies": { "dependencies": {
"wx-server-sdk": "latest" "wx-server-sdk": "~2.6.3"
} }
} }
+1 -1
View File
@@ -16,7 +16,7 @@
// those prefixes are reserved by Tencent Cloud SCF. Use the TTS_ prefix. // those prefixes are reserved by Tencent Cloud SCF. Use the TTS_ prefix.
const cloud = require('wx-server-sdk') const cloud = require('wx-server-sdk')
cloud.init() cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
// Polished, fixed prompts. // Polished, fixed prompts.
const PROMPTS = { const PROMPTS = {
+1 -1
View File
@@ -3,7 +3,7 @@
"version": "1.0.0", "version": "1.0.0",
"main": "index.js", "main": "index.js",
"dependencies": { "dependencies": {
"wx-server-sdk": "latest", "wx-server-sdk": "~2.6.3",
"tencentcloud-sdk-nodejs": "^4.0.0" "tencentcloud-sdk-nodejs": "^4.0.0"
} }
} }
+1 -1
View File
@@ -55,7 +55,7 @@
} }
.ui-btn--danger { .ui-btn--danger {
background: var(--card-bg); background: rgba(var(--danger-rgb), 0.06);
color: var(--danger); color: var(--danger);
border: 2rpx solid rgba(var(--danger-rgb), 0.3); border: 2rpx solid rgba(var(--danger-rgb), 0.3);
} }
+3 -3
View File
@@ -24,9 +24,9 @@
width: 100%; width: 100%;
border-radius: 32rpx 32rpx 0 0; border-radius: 32rpx 32rpx 0 0;
padding: 28rpx 32rpx 40rpx; padding: 28rpx 32rpx 40rpx;
/* bottom sheet 需要避开 Dock TabBar(高度 ~110rpx + 40rpx 间隙), /* bottom sheet 需要避开 Dock TabBar (占视口底部 ~158rpx)。
否则最底下的按钮会被 toolbar 遮挡。150rpx = dock 高度 + margin。 */ 168rpx 留出可见间距。 */
padding-bottom: calc(150rpx + env(safe-area-inset-bottom)); padding-bottom: calc(168rpx + env(safe-area-inset-bottom));
animation: uiModalSlideUp 0.3s cubic-bezier(0.32, 0.72, 0, 1); animation: uiModalSlideUp 0.3s cubic-bezier(0.32, 0.72, 0, 1);
} }
+1 -1
View File
@@ -7,7 +7,7 @@ module.exports = {
version: 'v2.7', version: 'v2.7',
/** 最后更新日期,外显在设置页「关于」 */ /** 最后更新日期,外显在设置页「关于」 */
updatedAt: '2026-07-09', updatedAt: '2026-07-10',
/** 开发者名称 / 微信号,设置页点击可复制 */ /** 开发者名称 / 微信号,设置页点击可复制 */
developer: '刘承', developer: '刘承',
+6 -5
View File
@@ -1,9 +1,10 @@
const themeMod = require('../utils/theme') const themeMod = require('../utils/theme')
const darkMod = require('../utils/darkMode')
// Resolve the active theme synchronously at module load so the first paint // Resolve the active theme synchronously at module load so the first paint
// already has the correct CSS variables — avoids a flash of the default theme. // already has the correct CSS variables — avoids a flash of the default theme.
const _initialTheme = themeMod.getCurrentTheme() const _initialTheme = themeMod.getCurrentTheme()
const _initialThemeStyle = themeMod.getThemeStyle(_initialTheme) const _initialThemeStyle = themeMod.getThemeStyle(_initialTheme, darkMod.getInitialDarkMode())
// Inactive icon color follows the dark mode: matches --text-secondary in dark // Inactive icon color follows the dark mode: matches --text-secondary in dark
// mode and a softer gray in light mode. Kept in sync with utils/theme.js so // mode and a softer gray in light mode. Kept in sync with utils/theme.js so
@@ -11,10 +12,8 @@ const _initialThemeStyle = themeMod.getThemeStyle(_initialTheme)
const LIGHT_INACTIVE = '#999999' const LIGHT_INACTIVE = '#999999'
const DARK_INACTIVE = '#98989F' const DARK_INACTIVE = '#98989F'
const _getInactiveColor = () => { const _getInactiveColor = () =>
const darkMod = require('../utils/darkMode') darkMod.getInitialDarkMode() ? DARK_INACTIVE : LIGHT_INACTIVE
return darkMod.getInitialDarkMode() ? DARK_INACTIVE : LIGHT_INACTIVE
}
const _initialInactiveColor = _getInactiveColor() const _initialInactiveColor = _getInactiveColor()
@@ -97,6 +96,8 @@ Component({
'/pages/settings/settings' '/pages/settings/settings'
] ]
if (index === this.data.selected) return if (index === this.data.selected) return
// 切 tab 前设窗口背景色,避免真机过渡动画露出白色底色
try { themeMod.applyWindowBg() } catch (e) {}
wx.switchTab({ url: pages[index] }) wx.switchTab({ url: pages[index] })
}, },
// Called by settings.onSelectDarkMode - the dock isn't in // Called by settings.onSelectDarkMode - the dock isn't in
+1 -1
View File
@@ -1,6 +1,6 @@
.dock { .dock {
position: fixed; position: fixed;
bottom: calc(40rpx + env(safe-area-inset-bottom)); bottom: calc(8rpx + env(safe-area-inset-bottom));
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%);
width: calc(100% - 80rpx); width: calc(100% - 80rpx);
+1 -1
View File
@@ -85,7 +85,7 @@ Page({
const allRecords = Object.values(storage.getRecords()).flat() const allRecords = Object.values(storage.getRecords()).flat()
const customPlans = storage.getCustomPlans() const customPlans = storage.getCustomPlans()
const plan = planMod.getPlan(settings.planId, customPlans) const plan = planMod.getPlan(settings.planId, customPlans)
const planDay = planMod.getPlanDay(settings.planId, allRecords, settings.planStartDate) const planDay = planMod.getPlanDay(settings.planId, allRecords, settings.planStartDate, customPlans)
const target = planMod.getTodayTarget(settings.planId, Math.min(planDay, plan.totalDays)) const target = planMod.getTodayTarget(settings.planId, Math.min(planDay, plan.totalDays))
const theme = themeMod.getCurrentTheme() const theme = themeMod.getCurrentTheme()
+2 -2
View File
@@ -1,10 +1,10 @@
const storage = require('../../utils/storage') const storage = require('../../utils/storage')
const themeMod = require('../../utils/theme') const themeMod = require('../../utils/theme')
const darkMod = require('../../utils/darkMode')
const planMod = require('../../utils/plan') const planMod = require('../../utils/plan')
const iconsMod = require('../../utils/icons') const iconsMod = require('../../utils/icons')
const cloud = require('../../utils/cloud') const cloud = require('../../utils/cloud')
const config = require('../../config') const config = require('../../config')
const darkMod = require('../../utils/darkMode')
const util = require('../../utils/util') const util = require('../../utils/util')
/** /**
@@ -217,7 +217,7 @@ Page({
this.setData({ currentThemeId: id }) this.setData({ currentThemeId: id })
try { try {
const tb = this.getTabBar() const tb = this.getTabBar()
if (tb) tb.setData({ themeStyle: themeMod.getThemeStyle(theme) }) if (tb) tb.setData({ themeStyle: themeMod.getThemeStyle(theme, darkMod.getInitialDarkMode()) })
} catch (e) {} } catch (e) {}
themeMod.applyThemeToPage(this) themeMod.applyThemeToPage(this)
}, },
+28 -22
View File
@@ -197,12 +197,14 @@
<text class="about-copy">Made with ❤️ for better fitness</text> <text class="about-copy">Made with ❤️ for better fitness</text>
</view> </view>
</ui-card> </ui-card>
</view>
<!-- /container — 关闭在这里,让下面的弹窗 fixed 相对视口而非 .container -->
<!-- 训练计划编辑器:用普通 view 不用 ui-modal, <!-- 训练计划编辑器:用普通 view 不用 ui-modal,
因为 WeChat 自定义组件根元素上 position: fixed 会被吃,导致 body 被推到容器末尾。 因为 WeChat 自定义组件根元素上 position: fixed 会被吃,导致 body 被推到容器末尾。
普通 view 的 position: fixed 在小程序里 100% 生效。 --> 普通 view 的 position: fixed 在小程序里 100% 生效。 -->
<view wx:if="{{showPlanEditor}}" class="plan-editor-mask" catchtouchmove="onNoop"> <view wx:if="{{showPlanEditor}}" class="plan-editor-mask" style="{{themeStyle}}">
<view class="plan-editor-mask__bg" bindtap="onCloseEditor"></view> <view class="plan-editor-mask__bg" bindtap="onCloseEditor" catchtouchmove="onNoop"></view>
<view class="plan-editor-sheet" catchtap="onNoop"> <view class="plan-editor-sheet" catchtap="onNoop">
<view class="plan-editor-handle"></view> <view class="plan-editor-handle"></view>
<view class="editor-title">编辑训练计划</view> <view class="editor-title">编辑训练计划</view>
@@ -284,33 +286,37 @@
<view class="editor-action-reset" wx:if="{{editingIsCustom}}" bindtap="onResetPlan"> <view class="editor-action-reset" wx:if="{{editingIsCustom}}" bindtap="onResetPlan">
<text>恢复默认</text> <text>恢复默认</text>
</view> </view>
<view class="editor-action-cancel" bindtap="onCloseEditor">
<text>取消</text>
</view>
<view class="editor-action-save" bindtap="onSavePlan"> <view class="editor-action-save" bindtap="onSavePlan">
<text>保存</text> <text>保存</text>
</view> </view>
</view> </view>
<text class="editor-cancel" bindtap="onCloseEditor">取消</text>
</view> </view>
</view> </view>
<!-- 清除数据确认弹窗:用自定义 ui-modal 替代 wx.showModal <!-- 清除数据确认弹窗:用自定义 ui-modal 替代 wx.showModal
避免开发者工具中 wx.showModal 首次点击不关闭的问题 --> 避免开发者工具中 wx.showModal 首次点击不关闭的问题
<ui-modal 外层 view 注入 themeStyle,确保弹窗跟随暗黑模式切换。 -->
visible="{{showClearConfirm}}" <view style="{{themeStyle}}">
position="center" <ui-modal
showHandle="{{false}}" visible="{{showClearConfirm}}"
bind:close="onCancelClear" position="center"
> showHandle="{{false}}"
<view class="confirm-dialog" catchtap="onNoop"> bind:close="onCancelClear"
<text class="confirm-title">清除数据</text> >
<text class="confirm-desc">将删除所有训练记录和连续打卡数据,此操作不可恢复。确定继续吗?</text> <view class="confirm-dialog" catchtap="onNoop">
<view class="confirm-actions"> <text class="confirm-title">清除数据</text>
<view class="confirm-btn confirm-btn--cancel" bindtap="onCancelClear"> <text class="confirm-desc">将删除所有训练记录和连续打卡数据,此操作不可恢复。确定继续吗?</text>
<text>取消</text> <view class="confirm-actions">
</view> <view class="confirm-btn confirm-btn--cancel" bindtap="onCancelClear">
<view class="confirm-btn confirm-btn--danger" bindtap="onConfirmClear"> <text>取消</text>
<text>确定清除</text> </view>
<view class="confirm-btn confirm-btn--danger" bindtap="onConfirmClear">
<text>确定清除</text>
</view>
</view> </view>
</view> </view>
</view> </ui-modal>
</ui-modal> </view>
</view>
+17 -15
View File
@@ -370,12 +370,11 @@
box-sizing: border-box; box-sizing: border-box;
background: var(--card-bg); background: var(--card-bg);
border-radius: 32rpx 32rpx 0 0; border-radius: 32rpx 32rpx 0 0;
padding: 12rpx 32rpx 40rpx; padding: 12rpx 32rpx 0;
/* Sheet is z-index 1000 above the tab bar (z-index 999), but the tab bar /* Dock 原生 cover-view 层永远在 webview 上方。
still visually covers the bottom of the sheet. Add the tab bar's dock 占视口底部: 8rpx(bottom) + 118rpx(高度) + 32rpx(shadow) ≈ 158rpx。
height (110rpx + safe area) to padding-bottom so "保存/取消" sit 168rpx 给「保存/取消」留出 10rpx 可见间距。 */
clearly above the tab bar. */ padding-bottom: calc(168rpx + env(safe-area-inset-bottom));
padding-bottom: calc(150rpx + env(safe-area-inset-bottom));
animation: planEditorSlideUp 0.3s cubic-bezier(0.32, 0.72, 0, 1); animation: planEditorSlideUp 0.3s cubic-bezier(0.32, 0.72, 0, 1);
} }
@@ -506,6 +505,7 @@
} }
.editor-action-reset, .editor-action-reset,
.editor-action-cancel,
.editor-action-save { .editor-action-save {
flex: 1; flex: 1;
height: 80rpx; height: 80rpx;
@@ -519,7 +519,7 @@
} }
.editor-action-reset { .editor-action-reset {
background: var(--card-bg); background: var(--bg-soft);
color: var(--text-secondary); color: var(--text-secondary);
border: 2rpx solid var(--border); border: 2rpx solid var(--border);
} }
@@ -528,6 +528,16 @@
background: var(--bg-soft); background: var(--bg-soft);
} }
.editor-action-cancel {
background: var(--bg-soft);
color: var(--text-secondary);
border: 2rpx solid var(--border);
}
.editor-action-cancel:active {
background: var(--border);
}
.editor-action-save { .editor-action-save {
background: linear-gradient(135deg, var(--primary), var(--primary-light)); background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: #FFFFFF; color: #FFFFFF;
@@ -538,14 +548,6 @@
opacity: 0.9; opacity: 0.9;
} }
.editor-cancel {
display: block;
text-align: center;
font-size: 26rpx;
color: var(--text-secondary);
padding: 12rpx 0 0;
}
/* ---- 清除数据确认弹窗 ---- */ /* ---- 清除数据确认弹窗 ---- */
.confirm-dialog { .confirm-dialog {
+1 -1
View File
@@ -72,7 +72,7 @@ Page({
const allRecords = Object.values(storage.getRecords()).flat() const allRecords = Object.values(storage.getRecords()).flat()
const customPlans = storage.getCustomPlans() const customPlans = storage.getCustomPlans()
const plan = planMod.getPlan(settings.planId, customPlans) const plan = planMod.getPlan(settings.planId, customPlans)
planDay = planMod.getPlanDay(settings.planId, allRecords, settings.planStartDate) planDay = planMod.getPlanDay(settings.planId, allRecords, settings.planStartDate, customPlans)
target = planMod.getTodayTarget(settings.planId, Math.min(planDay, plan.totalDays)) target = planMod.getTodayTarget(settings.planId, Math.min(planDay, plan.totalDays))
} }
+1 -1
View File
@@ -8,7 +8,7 @@ const STORAGE_KEY = 'dark_mode_pref'
function _readSystemTheme() { function _readSystemTheme() {
try { try {
const info = wx.getSystemInfoSync() const info = wx.getSystemInfoSync()
return info.theme === 'dark' ? true : false return info.theme === 'dark'
} catch (e) { } catch (e) {
return false return false
} }
+4 -2
View File
@@ -118,10 +118,12 @@ const build = (theme) => {
const out = {} const out = {}
Object.keys(PATHS).forEach((name) => { Object.keys(PATHS).forEach((name) => {
if (name.endsWith('Fill')) { if (name.endsWith('Fill')) {
const base = name.slice(0, -4) // Fill variant: filled path + primary color.
out[base] = _svg(PATHS[name], INACTIVE) // Do NOT write out[base] here — that would overwrite the outlined
// variant set by the base-name entry below with the wrong path.
out[name] = _svg(PATHS[name], p) out[name] = _svg(PATHS[name], p)
} else { } else {
// Outlined variant: outlined path + inactive gray.
out[name] = _svg(PATHS[name], INACTIVE) out[name] = _svg(PATHS[name], INACTIVE)
} }
}) })
+2 -2
View File
@@ -83,8 +83,8 @@ const getTodayTarget = (planId, currentDay) => {
* Calculate the current plan day based on training records since plan start. * Calculate the current plan day based on training records since plan start.
* Only counts records from the current plan's start date onward. * Only counts records from the current plan's start date onward.
*/ */
const getPlanDay = (planId, records, planStartDate) => { const getPlanDay = (planId, records, planStartDate, customPlans) => {
const plan = getPlan(planId) const plan = getPlan(planId, customPlans)
const today = formatDate(new Date()) const today = formatDate(new Date())
// Filter records: same plan, after plan start date, and not today // Filter records: same plan, after plan start date, and not today
+28 -12
View File
@@ -41,10 +41,12 @@ const THEMES = [
} }
] ]
const BASE_VARS = '--text:#333;--text-secondary:#999;--bg:#F5F5F5;--card-bg:#FFFFFF;--border:#EEE;--success:#4CAF50;--success-rgb:76,175,80;--danger:#E53935;--danger-rgb:229,57,53;--primary:#FF6B35;--primary-light:#FF8C5A;--primary-bg:#FFF3ED;--primary-rgb:255,107,53;' // Light-mode base CSS custom properties shared between the cold-start export
// (used by every page as initial data) and getThemeStyle(isDark=false).
// Update here ONLY — getThemeStyle picks it up for the light branch.
const BASE_VARS = '--text:#333333;--text-secondary:#999999;--bg:#F5F5F5;--bg-gradient-start:#F5F5F5;--bg-gradient-end:#FAFAFA;--card-bg:#FFFFFF;--bg-soft:#F0F0F0;--border:#EEEEEE;--success:#4CAF50;--success-rgb:76,175,80;--danger:#E53935;--danger-rgb:229,57,53;'
const DEFAULT_THEME_ID = 'orange' const DEFAULT_THEME_ID = 'orange'
const THEME_KEY = 'app_theme' const THEME_KEY = 'app_theme'
let _navColorTimer = null
const getThemeById = (id) => THEMES.find(t => t.id === id) || THEMES[0] const getThemeById = (id) => THEMES.find(t => t.id === id) || THEMES[0]
@@ -61,15 +63,12 @@ const getCurrentTheme = () => {
} }
const _setNavBarColor = (primary) => { const _setNavBarColor = (primary) => {
if (_navColorTimer) clearTimeout(_navColorTimer) try {
_navColorTimer = setTimeout(() => { wx.setNavigationBarColor({
try { frontColor: '#ffffff',
wx.setNavigationBarColor({ backgroundColor: primary
frontColor: '#ffffff', })
backgroundColor: primary } catch (e) {}
})
} catch (e) {}
}, 50)
} }
const setTheme = (id) => { const setTheme = (id) => {
@@ -98,7 +97,7 @@ const getThemeStyle = (theme, isDark = false) => {
const t = theme || getCurrentTheme() const t = theme || getCurrentTheme()
const baseVars = isDark const baseVars = isDark
? '--text:#E5E5E7;--text-secondary:#98989F;--bg:#2C2C2E;--bg-gradient-start:#2C2C2E;--bg-gradient-end:#38383A;--card-bg:#3A3A3C;--bg-soft:#48484A;--border:#48484A;--success:#30D158;--success-rgb:48,209,88;--danger:#FF6B6B;--danger-rgb:255,107,107;' ? '--text:#E5E5E7;--text-secondary:#98989F;--bg:#2C2C2E;--bg-gradient-start:#2C2C2E;--bg-gradient-end:#38383A;--card-bg:#3A3A3C;--bg-soft:#48484A;--border:#48484A;--success:#30D158;--success-rgb:48,209,88;--danger:#FF6B6B;--danger-rgb:255,107,107;'
: '--text:#333333;--text-secondary:#999999;--bg:#F5F5F5;--bg-gradient-start:#F5F5F5;--bg-gradient-end:#FAFAFA;--card-bg:#FFFFFF;--bg-soft:#F0F0F0;--border:#EEEEEE;--success:#4CAF50;--success-rgb:76,175,80;--danger:#E53935;--danger-rgb:229,57,53;' : BASE_VARS
return `${baseVars}--primary:${t.primary};--primary-light:${t.primaryLight};--primary-bg:${t.primaryBg};--primary-rgb:${t.primaryRgb};` return `${baseVars}--primary:${t.primary};--primary-light:${t.primaryLight};--primary-bg:${t.primaryBg};--primary-rgb:${t.primaryRgb};`
} }
@@ -113,12 +112,29 @@ const applyThemeToPage = (self) => {
} }
self.setData({ theme, themeStyle: nextStyle, isDark }) self.setData({ theme, themeStyle: nextStyle, isDark })
_setNavBarColor(theme.primary) _setNavBarColor(theme.primary)
// theme.json 的 dark section 只响应系统暗黑模式。用户手动选择暗黑
// 但系统是明亮时,窗口背景仍为浅色,切 tab 会闪白。这里用 API 强制设置
// 窗口背景,让 tab 切换时的瞬间底色与当前模式一致。
const bg = isDark ? '#0F0F12' : '#F5F5F5'
try { wx.setBackgroundColor({ backgroundColor: bg, backgroundColorTop: bg, backgroundColorBottom: bg }) } catch (e) {}
}
/** 仅设置窗口背景色,不触发页面 setData — 供 app.js 冷启动使用 */
const applyWindowBg = () => {
try {
const darkMod = require('./darkMode')
const isDark = darkMod.getInitialDarkMode()
const bg = isDark ? '#0F0F12' : '#F5F5F5'
wx.setBackgroundColor({ backgroundColor: bg, backgroundColorTop: bg, backgroundColorBottom: bg })
} catch (e) {}
} }
module.exports = { module.exports = {
THEMES, THEMES,
DEFAULT_THEME_ID, DEFAULT_THEME_ID,
BASE_VARS, BASE_VARS,
applyWindowBg,
getThemeById, getThemeById,
getCurrentTheme, getCurrentTheme,
setTheme, setTheme,
+1 -1
View File
@@ -82,7 +82,7 @@ const fileToDataURI = (filePath, maxBytes = 512 * 1024) => {
encoding: 'base64', encoding: 'base64',
success: (res) => { success: (res) => {
// chooseAvatar returns PNG; allow JPG/GIF/WEBP fallbacks. // chooseAvatar returns PNG; allow JPG/GIF/WEBP fallbacks.
const m = (filePath.match(/\.(\w+)(?:\?|$)/) || [, 'png'])[1].toLowerCase() const m = ((filePath.match(/\.(\w+)(?:\?|$)/) || [])[1] || 'png').toLowerCase()
const mime = { png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif', webp: 'image/webp' }[m] || 'image/png' 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}`) resolve(`data:${mime};base64,${res.data}`)
}, },