Files
wx_pbzc/utils/theme.js
T
lc 6176f2c341 feat(ui): 记录页月份联动与多项 UI 美化/修复
- records: 底部列表与月份选择器联动,新增 [本月|最近] 分段控件;标题/空态随模式切换
- leaderboard: 领奖台布局(冠军居中+皇冠)、次数标注于底座且三档水平对齐;头像 cloud:// 批量预解析提速(云函数需重新部署)
- index/timer: 主色底图标改白色变体修复隐形;目标数字按微信字体缩放补偿真机遮挡
- settings: 配色方案横滑渐变色卡;头像上传前压缩至 200px
- theme: 新增 teal 主题;暗黑背景统一对齐
- 圆形头像双保险(image 自身 border-radius)修复月/年榜方图

注: cloudfunctions/leaderboard 改动须在开发者工具重新部署才生效。
2026-07-22 09:36:22 +08:00

169 lines
5.4 KiB
JavaScript

const THEMES = [
{
id: 'orange',
name: '活力橙',
primary: '#FF6B35',
primaryLight: '#FF8C5A',
primaryBg: '#FFF3ED',
primaryRgb: '255,107,53'
},
{
id: 'blue',
name: '深海蓝',
primary: '#2979FF',
primaryLight: '#5C9CFF',
primaryBg: '#EDF4FF',
primaryRgb: '41,121,255'
},
{
id: 'green',
name: '森林绿',
primary: '#43A047',
primaryLight: '#6EC072',
primaryBg: '#EDF7EE',
primaryRgb: '67,160,71'
},
{
id: 'purple',
name: '优雅紫',
primary: '#7E57C2',
primaryLight: '#A08AD6',
primaryBg: '#F5F0FA',
primaryRgb: '126,87,194'
},
{
id: 'pink',
name: '樱花粉',
primary: '#EC407A',
primaryLight: '#F06A9A',
primaryBg: '#FDEEF3',
primaryRgb: '236,64,122'
},
{
id: 'teal',
name: '湖青',
primary: '#00BCD4',
primaryLight: '#4DD0E1',
primaryBg: '#E0F7FA',
primaryRgb: '0,188,212'
}
]
// 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'
const getThemeById = (id) => THEMES.find(t => t.id === id) || THEMES[0]
const getCurrentTheme = () => {
let id = wx.getStorageSync(THEME_KEY)
if (!id) {
// fallback: try recovering from user_settings backup
try {
const s = wx.getStorageSync('user_settings')
if (s && s.themeId) id = s.themeId
} catch (e) {}
}
return getThemeById(id || DEFAULT_THEME_ID)
}
const _setNavBarColor = (primary) => {
try {
wx.setNavigationBarColor({
frontColor: '#ffffff',
backgroundColor: primary
})
} catch (e) {}
}
const setTheme = (id) => {
try {
wx.setStorageSync(THEME_KEY, id)
// redundant backup in user_settings
try {
const s = wx.getStorageSync('user_settings') || {}
s.themeId = id
wx.setStorageSync('user_settings', s)
} catch (e) {}
} catch (e) {
console.error('Failed to save theme:', e)
}
const theme = getThemeById(id)
_setNavBarColor(theme.primary)
const app = getApp()
if (app && app.globalData) app.globalData.theme = theme
// sync to cloud (lazy-require to avoid circular deps)
try { require('./cloud').pushAll() } catch (e) {}
return theme
}
const getThemeStyle = (theme, isDark = false) => {
const t = theme || getCurrentTheme()
// Dark palette is unified with theme.json (#131316 family) so the chrome
// background and the page background stop disagreeing. Card is brightened
// to keep elevation readable on the darker backdrop.
const baseVars = isDark
? '--text:#E5E5E7;--text-secondary:#98989F;--bg:#131316;--bg-gradient-start:#131316;--bg-gradient-end:#1C1C20;--card-bg:#26262B;--bg-soft:#343439;--border:#343439;--success:#30D158;--success-rgb:48,209,88;--danger:#FF6B6B;--danger-rgb:255,107,107;'
: `--text:#333333;--text-secondary:#999999;--bg:${t.primaryBg};--bg-gradient-start:${t.primaryBg};--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;`
return `${baseVars}--primary:${t.primary};--primary-light:${t.primaryLight};--primary-bg:${t.primaryBg};--primary-rgb:${t.primaryRgb};`
}
const applyThemeToPage = (self) => {
const darkMod = require('./darkMode')
const theme = getCurrentTheme()
const isDark = darkMod.getInitialDarkMode()
const nextStyle = getThemeStyle(theme, isDark)
// Skip setData if nothing changed — avoid unnecessary re-renders on every onShow
if (self.data && self.data.themeStyle === nextStyle) {
return
}
self.setData({ theme, themeStyle: nextStyle, isDark })
_setNavBarColor(theme.primary)
// theme.json 的 dark section 只响应系统暗黑模式。用户手动选择暗黑
// 但系统是明亮时,窗口背景仍为浅色,切 tab 会闪白。这里用 API 强制设置
// 窗口背景,让 tab 切换时的瞬间底色与当前模式一致。
const bg = isDark ? '#131316' : theme.primaryBg
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 ? '#131316' : getCurrentTheme().primaryBg
wx.setBackgroundColor({ backgroundColor: bg, backgroundColorTop: bg, backgroundColorBottom: bg })
} catch (e) {}
}
/**
* 冷启动尽早调用:设导航栏色 + 窗口背景。
* 原生导航栏在 JS 执行前用 app.json 写死的橙色,这里在 onLaunch 第一时间
* 切到用户主题色,把冷启动橙->主题色的跳变窗口压到最小(无法完全消除,
* 因为 app.json 静态值在 onLaunch 之前就已应用)。
*/
const applyChrome = () => {
try {
_setNavBarColor(getCurrentTheme().primary)
applyWindowBg()
} catch (e) {}
}
module.exports = {
THEMES,
DEFAULT_THEME_ID,
BASE_VARS,
applyWindowBg,
applyChrome,
getThemeById,
getCurrentTheme,
setTheme,
applyThemeToPage,
getThemeStyle
}