1d55df72b3
Major features: - Training plan editor: edit preset days/duration per plan (in-place override via custom_plans storage; preset ids preserved so existing records stay valid) - Voice prompts at 4 fixed points during training (halfway / 30s / 10s / done) via Tencent Cloud TTS (cloudfunctions/tts + utils/voice.js) - Free-training button: switched from outline (transparent) to ghost variant (theme-tinted) for visual weight Bug fixes: - 4 functional pages: SVG data URIs failed to render in WeChat because '\#' in colors was parsed as a data-URI fragment delimiter. encode the whole SVG via encodeURIComponent in utils/icons.js build(). - Old records (saved before id field was added) could not be deleted (data-id was empty, triggered defensive guard). Backfill stable legacy-<month>-<index>-<duration> ids in getRecords() and persist. - settings.js plan-row editor button event was bubbling up to the row's bindtap (which also fired onSelectPlan). Wrapped in catch:tap. UI: - Settings page: 3 hardcoded plans replaced with dynamic buildPlansList that overlays custom_plans on top of presets - Plan editor: bottom-sheet modal in settings page (regular view, not ui-modal — WeChat custom component root element drops position:fixed in this runtime) - Free-training button: rounded pill style, full-width, 24rpx gap from primary action; bottom sheet uses max-height: 88vh + internal scroll - Version bumped to v1.5, last updated 2026-06-10 Removed: - Daily reminder section (dailyReminder / reminderTime) — replaced by the 4 voice prompts which cover the same user need without requiring long-term scheduling that WeChat mini-programs can't actually do Misc: - utils/plan.js refactored to formula-driven: presets declare totalDays/startTarget/increment/cycleDays, days[] is generated. Same formula applies to custom plans. - Timer _remind() guards each prompt on minimum duration so short free-mode sessions don't fire 'last30' at the start - Cloud storage cloud_plans field added to data push payload; restored on first install via _restoreFromCloud - .gitignore added for local AI tool caches (.reasonix/, reasonix.toml, .codegraph/daemon.pid)
123 lines
3.0 KiB
JavaScript
123 lines
3.0 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'
|
|
}
|
|
]
|
|
|
|
const BASE_VARS = '--text:#333;--text-secondary:#999;--bg:#F5F5F5;--card-bg:#FFFFFF;--border:#EEE;--success:#4CAF50;--primary:#FF6B35;--primary-light:#FF8C5A;--primary-bg:#FFF3ED;--primary-rgb:255,107,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]
|
|
|
|
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) => {
|
|
if (_navColorTimer) clearTimeout(_navColorTimer)
|
|
_navColorTimer = setTimeout(() => {
|
|
try {
|
|
wx.setNavigationBarColor({
|
|
frontColor: '#ffffff',
|
|
backgroundColor: primary
|
|
})
|
|
} catch (e) {}
|
|
}, 50)
|
|
}
|
|
|
|
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) => {
|
|
const t = theme || getCurrentTheme()
|
|
return `${BASE_VARS}--primary:${t.primary};--primary-light:${t.primaryLight};--primary-bg:${t.primaryBg};--primary-rgb:${t.primaryRgb};`
|
|
}
|
|
|
|
const applyThemeToPage = (self) => {
|
|
const theme = getCurrentTheme()
|
|
const nextStyle = getThemeStyle(theme)
|
|
// 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 })
|
|
_setNavBarColor(theme.primary)
|
|
}
|
|
|
|
module.exports = {
|
|
THEMES,
|
|
DEFAULT_THEME_ID,
|
|
BASE_VARS,
|
|
getThemeById,
|
|
getCurrentTheme,
|
|
setTheme,
|
|
applyThemeToPage,
|
|
getThemeStyle
|
|
}
|