From 1eff3d60c4e4f05ccd64a6a4ae5878f1e8858452 Mon Sep 17 00:00:00 2001 From: cnliucheng Date: Fri, 10 Jul 2026 09:40:04 +0800 Subject: [PATCH] =?UTF-8?q?fix(ui):=20=E5=A4=9A=E9=A1=B9=20UI=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=20=E2=80=94=20=E5=BC=B9=E7=AA=97=E9=81=AE=E6=8C=A1/?= =?UTF-8?q?=E6=8C=89=E9=92=AE=E5=8F=AF=E8=A7=81=E6=80=A7/=E6=9A=97?= =?UTF-8?q?=E9=BB=91=E9=97=AA=E7=83=81/=E4=BA=91=E5=87=BD=E6=95=B0?= =?UTF-8?q?=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 计划编辑弹窗移出 .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 --- app.js | 3 ++ app.wxss | 2 - cloudfunctions/getOpenid/package.json | 2 +- cloudfunctions/leaderboard/package.json | 2 +- cloudfunctions/tts/index.js | 2 +- cloudfunctions/tts/package.json | 2 +- components/ui-btn/ui-btn.wxss | 2 +- components/ui-modal/ui-modal.wxss | 6 +-- config.js | 2 +- custom-tab-bar/index.js | 11 +++--- custom-tab-bar/index.wxss | 2 +- pages/index/index.js | 2 +- pages/settings/settings.js | 4 +- pages/settings/settings.wxml | 50 ++++++++++++++----------- pages/settings/settings.wxss | 32 ++++++++-------- pages/timer/timer.js | 2 +- utils/darkMode.js | 2 +- utils/icons.js | 6 ++- utils/plan.js | 4 +- utils/theme.js | 40 ++++++++++++++------ utils/util.js | 2 +- 21 files changed, 104 insertions(+), 76 deletions(-) diff --git a/app.js b/app.js index 02758ac..3afbaeb 100644 --- a/app.js +++ b/app.js @@ -39,6 +39,9 @@ App({ this.globalData.theme = themeMod.getCurrentTheme() + // 窗口背景色 — 冷启动就设好,避免暗黑模式下切 tab 闪白 + themeMod.applyWindowBg() + // Sync with cloud: always locate our doc first (pullAll caches // _openid / _docId), then either restore or push. The previous flow // only pulled when local was empty, so on a cold start with non-empty diff --git a/app.wxss b/app.wxss index da29c4c..9d8e653 100644 --- a/app.wxss +++ b/app.wxss @@ -12,8 +12,6 @@ page { 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif; font-size: 28rpx; 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 diff --git a/cloudfunctions/getOpenid/package.json b/cloudfunctions/getOpenid/package.json index 2fddb65..3aca64d 100644 --- a/cloudfunctions/getOpenid/package.json +++ b/cloudfunctions/getOpenid/package.json @@ -3,6 +3,6 @@ "version": "1.0.0", "main": "index.js", "dependencies": { - "wx-server-sdk": "latest" + "wx-server-sdk": "~2.6.3" } } \ No newline at end of file diff --git a/cloudfunctions/leaderboard/package.json b/cloudfunctions/leaderboard/package.json index 5190dec..f80e81e 100644 --- a/cloudfunctions/leaderboard/package.json +++ b/cloudfunctions/leaderboard/package.json @@ -3,6 +3,6 @@ "version": "1.0.0", "main": "index.js", "dependencies": { - "wx-server-sdk": "latest" + "wx-server-sdk": "~2.6.3" } } diff --git a/cloudfunctions/tts/index.js b/cloudfunctions/tts/index.js index 337a8df..50a9cc5 100644 --- a/cloudfunctions/tts/index.js +++ b/cloudfunctions/tts/index.js @@ -16,7 +16,7 @@ // those prefixes are reserved by Tencent Cloud SCF. Use the TTS_ prefix. const cloud = require('wx-server-sdk') -cloud.init() +cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) // Polished, fixed prompts. const PROMPTS = { diff --git a/cloudfunctions/tts/package.json b/cloudfunctions/tts/package.json index 08aed36..5e1095f 100644 --- a/cloudfunctions/tts/package.json +++ b/cloudfunctions/tts/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "main": "index.js", "dependencies": { - "wx-server-sdk": "latest", + "wx-server-sdk": "~2.6.3", "tencentcloud-sdk-nodejs": "^4.0.0" } } diff --git a/components/ui-btn/ui-btn.wxss b/components/ui-btn/ui-btn.wxss index 00a4782..78cbbf7 100644 --- a/components/ui-btn/ui-btn.wxss +++ b/components/ui-btn/ui-btn.wxss @@ -55,7 +55,7 @@ } .ui-btn--danger { - background: var(--card-bg); + background: rgba(var(--danger-rgb), 0.06); color: var(--danger); border: 2rpx solid rgba(var(--danger-rgb), 0.3); } diff --git a/components/ui-modal/ui-modal.wxss b/components/ui-modal/ui-modal.wxss index 6655f52..c75c539 100644 --- a/components/ui-modal/ui-modal.wxss +++ b/components/ui-modal/ui-modal.wxss @@ -24,9 +24,9 @@ width: 100%; border-radius: 32rpx 32rpx 0 0; padding: 28rpx 32rpx 40rpx; - /* bottom sheet 需要避开 Dock TabBar(高度 ~110rpx + 40rpx 间隙), - 否则最底下的按钮会被 toolbar 遮挡。150rpx = dock 高度 + margin。 */ - padding-bottom: calc(150rpx + env(safe-area-inset-bottom)); + /* bottom sheet 需要避开 Dock TabBar (占视口底部 ~158rpx)。 + 168rpx 留出可见间距。 */ + padding-bottom: calc(168rpx + env(safe-area-inset-bottom)); animation: uiModalSlideUp 0.3s cubic-bezier(0.32, 0.72, 0, 1); } diff --git a/config.js b/config.js index fa66032..d6f460e 100644 --- a/config.js +++ b/config.js @@ -7,7 +7,7 @@ module.exports = { version: 'v2.7', /** 最后更新日期,外显在设置页「关于」 */ - updatedAt: '2026-07-09', + updatedAt: '2026-07-10', /** 开发者名称 / 微信号,设置页点击可复制 */ developer: '刘承', diff --git a/custom-tab-bar/index.js b/custom-tab-bar/index.js index 6ddc1f2..8255682 100644 --- a/custom-tab-bar/index.js +++ b/custom-tab-bar/index.js @@ -1,9 +1,10 @@ const themeMod = require('../utils/theme') +const darkMod = require('../utils/darkMode') // 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. 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 // 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 DARK_INACTIVE = '#98989F' -const _getInactiveColor = () => { - const darkMod = require('../utils/darkMode') - return darkMod.getInitialDarkMode() ? DARK_INACTIVE : LIGHT_INACTIVE -} +const _getInactiveColor = () => + darkMod.getInitialDarkMode() ? DARK_INACTIVE : LIGHT_INACTIVE const _initialInactiveColor = _getInactiveColor() @@ -97,6 +96,8 @@ Component({ '/pages/settings/settings' ] if (index === this.data.selected) return + // 切 tab 前设窗口背景色,避免真机过渡动画露出白色底色 + try { themeMod.applyWindowBg() } catch (e) {} wx.switchTab({ url: pages[index] }) }, // Called by settings.onSelectDarkMode - the dock isn't in diff --git a/custom-tab-bar/index.wxss b/custom-tab-bar/index.wxss index e9648cd..4af4b23 100644 --- a/custom-tab-bar/index.wxss +++ b/custom-tab-bar/index.wxss @@ -1,6 +1,6 @@ .dock { position: fixed; - bottom: calc(40rpx + env(safe-area-inset-bottom)); + bottom: calc(8rpx + env(safe-area-inset-bottom)); left: 50%; transform: translateX(-50%); width: calc(100% - 80rpx); diff --git a/pages/index/index.js b/pages/index/index.js index ac06be4..2a02ca9 100644 --- a/pages/index/index.js +++ b/pages/index/index.js @@ -85,7 +85,7 @@ Page({ const allRecords = Object.values(storage.getRecords()).flat() const customPlans = storage.getCustomPlans() 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 theme = themeMod.getCurrentTheme() diff --git a/pages/settings/settings.js b/pages/settings/settings.js index 380b363..4254acd 100644 --- a/pages/settings/settings.js +++ b/pages/settings/settings.js @@ -1,10 +1,10 @@ const storage = require('../../utils/storage') const themeMod = require('../../utils/theme') +const darkMod = require('../../utils/darkMode') const planMod = require('../../utils/plan') const iconsMod = require('../../utils/icons') const cloud = require('../../utils/cloud') const config = require('../../config') -const darkMod = require('../../utils/darkMode') const util = require('../../utils/util') /** @@ -217,7 +217,7 @@ Page({ this.setData({ currentThemeId: id }) try { const tb = this.getTabBar() - if (tb) tb.setData({ themeStyle: themeMod.getThemeStyle(theme) }) + if (tb) tb.setData({ themeStyle: themeMod.getThemeStyle(theme, darkMod.getInitialDarkMode()) }) } catch (e) {} themeMod.applyThemeToPage(this) }, diff --git a/pages/settings/settings.wxml b/pages/settings/settings.wxml index df12838..97ecbf0 100644 --- a/pages/settings/settings.wxml +++ b/pages/settings/settings.wxml @@ -197,12 +197,14 @@ Made with ❤️ for better fitness + + - - + + 编辑训练计划 @@ -284,33 +286,37 @@ 恢复默认 + + 取消 + 保存 - 取消 - - - 清除数据 - 将删除所有训练记录和连续打卡数据,此操作不可恢复。确定继续吗? - - - 取消 - - - 确定清除 + 避免开发者工具中 wx.showModal 首次点击不关闭的问题。 + 外层 view 注入 themeStyle,确保弹窗跟随暗黑模式切换。 --> + + + + 清除数据 + 将删除所有训练记录和连续打卡数据,此操作不可恢复。确定继续吗? + + + 取消 + + + 确定清除 + - - - + + diff --git a/pages/settings/settings.wxss b/pages/settings/settings.wxss index c209b13..c6a9c29 100644 --- a/pages/settings/settings.wxss +++ b/pages/settings/settings.wxss @@ -370,12 +370,11 @@ box-sizing: border-box; background: var(--card-bg); border-radius: 32rpx 32rpx 0 0; - padding: 12rpx 32rpx 40rpx; - /* Sheet is z-index 1000 above the tab bar (z-index 999), but the tab bar - still visually covers the bottom of the sheet. Add the tab bar's - height (110rpx + safe area) to padding-bottom so "保存/取消" sit - clearly above the tab bar. */ - padding-bottom: calc(150rpx + env(safe-area-inset-bottom)); + padding: 12rpx 32rpx 0; + /* Dock 原生 cover-view 层永远在 webview 上方。 + dock 占视口底部: 8rpx(bottom) + 118rpx(高度) + 32rpx(shadow) ≈ 158rpx。 + 168rpx 给「保存/取消」留出 10rpx 可见间距。 */ + padding-bottom: calc(168rpx + env(safe-area-inset-bottom)); animation: planEditorSlideUp 0.3s cubic-bezier(0.32, 0.72, 0, 1); } @@ -506,6 +505,7 @@ } .editor-action-reset, +.editor-action-cancel, .editor-action-save { flex: 1; height: 80rpx; @@ -519,7 +519,7 @@ } .editor-action-reset { - background: var(--card-bg); + background: var(--bg-soft); color: var(--text-secondary); border: 2rpx solid var(--border); } @@ -528,6 +528,16 @@ 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 { background: linear-gradient(135deg, var(--primary), var(--primary-light)); color: #FFFFFF; @@ -538,14 +548,6 @@ opacity: 0.9; } -.editor-cancel { - display: block; - text-align: center; - font-size: 26rpx; - color: var(--text-secondary); - padding: 12rpx 0 0; -} - /* ---- 清除数据确认弹窗 ---- */ .confirm-dialog { diff --git a/pages/timer/timer.js b/pages/timer/timer.js index 13ea84d..662b41a 100644 --- a/pages/timer/timer.js +++ b/pages/timer/timer.js @@ -72,7 +72,7 @@ Page({ const allRecords = Object.values(storage.getRecords()).flat() const customPlans = storage.getCustomPlans() 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)) } diff --git a/utils/darkMode.js b/utils/darkMode.js index 2611d99..cc2d884 100644 --- a/utils/darkMode.js +++ b/utils/darkMode.js @@ -8,7 +8,7 @@ const STORAGE_KEY = 'dark_mode_pref' function _readSystemTheme() { try { const info = wx.getSystemInfoSync() - return info.theme === 'dark' ? true : false + return info.theme === 'dark' } catch (e) { return false } diff --git a/utils/icons.js b/utils/icons.js index 13c41f2..d211ef2 100644 --- a/utils/icons.js +++ b/utils/icons.js @@ -118,10 +118,12 @@ const build = (theme) => { const out = {} Object.keys(PATHS).forEach((name) => { if (name.endsWith('Fill')) { - const base = name.slice(0, -4) - out[base] = _svg(PATHS[name], INACTIVE) + // Fill variant: filled path + primary color. + // 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) } else { + // Outlined variant: outlined path + inactive gray. out[name] = _svg(PATHS[name], INACTIVE) } }) diff --git a/utils/plan.js b/utils/plan.js index 772dc30..0a6f4d7 100644 --- a/utils/plan.js +++ b/utils/plan.js @@ -83,8 +83,8 @@ const getTodayTarget = (planId, currentDay) => { * Calculate the current plan day based on training records since plan start. * Only counts records from the current plan's start date onward. */ -const getPlanDay = (planId, records, planStartDate) => { - const plan = getPlan(planId) +const getPlanDay = (planId, records, planStartDate, customPlans) => { + const plan = getPlan(planId, customPlans) const today = formatDate(new Date()) // Filter records: same plan, after plan start date, and not today diff --git a/utils/theme.js b/utils/theme.js index c89de56..9e991a6 100644 --- a/utils/theme.js +++ b/utils/theme.js @@ -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 THEME_KEY = 'app_theme' -let _navColorTimer = null const getThemeById = (id) => THEMES.find(t => t.id === id) || THEMES[0] @@ -61,15 +63,12 @@ const getCurrentTheme = () => { } const _setNavBarColor = (primary) => { - if (_navColorTimer) clearTimeout(_navColorTimer) - _navColorTimer = setTimeout(() => { - try { - wx.setNavigationBarColor({ - frontColor: '#ffffff', - backgroundColor: primary - }) - } catch (e) {} - }, 50) + try { + wx.setNavigationBarColor({ + frontColor: '#ffffff', + backgroundColor: primary + }) + } catch (e) {} } const setTheme = (id) => { @@ -98,7 +97,7 @@ const getThemeStyle = (theme, isDark = false) => { const t = theme || getCurrentTheme() 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:#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};` } @@ -113,12 +112,29 @@ const applyThemeToPage = (self) => { } self.setData({ theme, themeStyle: nextStyle, isDark }) _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 = { THEMES, DEFAULT_THEME_ID, BASE_VARS, + applyWindowBg, getThemeById, getCurrentTheme, setTheme, diff --git a/utils/util.js b/utils/util.js index 7b8a27a..da2a73c 100644 --- a/utils/util.js +++ b/utils/util.js @@ -82,7 +82,7 @@ const fileToDataURI = (filePath, maxBytes = 512 * 1024) => { encoding: 'base64', success: (res) => { // 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' resolve(`data:${mime};base64,${res.data}`) },