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}`)
},