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:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ module.exports = {
|
|||||||
version: 'v2.7',
|
version: 'v2.7',
|
||||||
|
|
||||||
/** 最后更新日期,外显在设置页「关于」 */
|
/** 最后更新日期,外显在设置页「关于」 */
|
||||||
updatedAt: '2026-07-09',
|
updatedAt: '2026-07-10',
|
||||||
|
|
||||||
/** 开发者名称 / 微信号,设置页点击可复制 */
|
/** 开发者名称 / 微信号,设置页点击可复制 */
|
||||||
developer: '刘承',
|
developer: '刘承',
|
||||||
|
|||||||
@@ -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,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);
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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,16 +286,20 @@
|
|||||||
<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 首次点击不关闭的问题。
|
||||||
|
外层 view 注入 themeStyle,确保弹窗跟随暗黑模式切换。 -->
|
||||||
|
<view style="{{themeStyle}}">
|
||||||
<ui-modal
|
<ui-modal
|
||||||
visible="{{showClearConfirm}}"
|
visible="{{showClearConfirm}}"
|
||||||
position="center"
|
position="center"
|
||||||
@@ -313,4 +319,4 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</ui-modal>
|
</ui-modal>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||||
|
|||||||
+22
-6
@@ -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)
|
|
||||||
_navColorTimer = setTimeout(() => {
|
|
||||||
try {
|
try {
|
||||||
wx.setNavigationBarColor({
|
wx.setNavigationBarColor({
|
||||||
frontColor: '#ffffff',
|
frontColor: '#ffffff',
|
||||||
backgroundColor: primary
|
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
@@ -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}`)
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user