backup: snapshot before optimization

This commit is contained in:
2026-06-04 16:25:43 +08:00
commit ff2700c067
47 changed files with 3332 additions and 0 deletions
+73
View File
@@ -0,0 +1,73 @@
var plans = {
beginner: {
id: 'beginner',
name: '初级 (7天)',
totalDays: 7,
description: '适合初学者,从30秒起步',
days: [
{ day: 1, target: 30 },
{ day: 2, target: 40 },
{ day: 3, target: 50 },
{ day: 4, target: 60 },
{ day: 5, target: 70 },
{ day: 6, target: 80 },
{ day: 7, target: 90 }
]
},
intermediate: {
id: 'intermediate',
name: '中级 (14天)',
totalDays: 14,
description: '有一定基础,从60秒起步',
days: [
{ day: 1, target: 60 }, { day: 2, target: 60 },
{ day: 3, target: 75 }, { day: 4, target: 75 },
{ day: 5, target: 90 }, { day: 6, target: 90 },
{ day: 7, target: 105 }, { day: 8, target: 105 },
{ day: 9, target: 120 }, { day: 10, target: 120 },
{ day: 11, target: 135 }, { day: 12, target: 135 },
{ day: 13, target: 150 }, { day: 14, target: 150 }
]
},
advanced: {
id: 'advanced',
name: '高级 (30天)',
totalDays: 30,
description: '挑战自我,从90秒起步',
days: (function () {
var arr = []
for (var i = 1; i <= 30; i++) {
arr.push({ day: i, target: 90 + Math.floor((i - 1) / 3) * 15 })
}
return arr
})()
}
}
function getPlan(planId) {
return plans[planId] || plans.beginner
}
function getTodayTarget(planId, currentDay) {
var plan = getPlan(planId)
for (var i = 0; i < plan.days.length; i++) {
if (plan.days[i].day === currentDay) return plan.days[i].target
}
return plan.days[0].target
}
function getPlanDay(planId, records) {
var plan = getPlan(planId)
var now = new Date()
var today = now.getFullYear() + '-' +
('0' + (now.getMonth() + 1)).slice(-2) + '-' +
('0' + now.getDate()).slice(-2)
var dates = {}
records.forEach(function (r) {
if (r.date !== today) dates[r.date] = true
})
var trainedDays = Object.keys(dates).length
return Math.min(trainedDays + 1, plan.totalDays)
}
module.exports = { plans: plans, getPlan: getPlan, getTodayTarget: getTodayTarget, getPlanDay: getPlanDay }
+132
View File
@@ -0,0 +1,132 @@
var RECORDS_KEY = 'training_records'
var SETTINGS_KEY = 'user_settings'
var STREAK_KEY = 'current_streak'
function getRecords() {
return wx.getStorageSync(RECORDS_KEY) || {}
}
function saveRecord(record) {
record.id = Date.now()
var records = getRecords()
var month = record.date.substring(0, 7)
if (!records[month]) records[month] = []
records[month].push(record)
records[month].sort(function (a, b) { return b.date.localeCompare(a.date) })
wx.setStorageSync(RECORDS_KEY, records)
}
function deleteRecord(recordId) {
var records = getRecords()
Object.keys(records).forEach(function (month) {
records[month] = records[month].filter(function (r) { return r.id !== recordId })
if (records[month].length === 0) delete records[month]
})
wx.setStorageSync(RECORDS_KEY, records)
}
function getRecordsByMonth(month) {
var records = getRecords()
return records[month] || []
}
function getTotalStats() {
var records = getRecords()
var totalDuration = 0
var totalSessions = 0
var maxDuration = 0
Object.keys(records).forEach(function (key) {
var list = records[key]
list.forEach(function (r) {
totalDuration += r.duration
totalSessions++
if (r.duration > maxDuration) maxDuration = r.duration
})
})
return { totalDuration: totalDuration, totalSessions: totalSessions, maxDuration: maxDuration }
}
function getSettings() {
return wx.getStorageSync(SETTINGS_KEY) || {
planId: 'beginner',
dailyReminder: true,
reminderTime: '08:00',
voiceGuide: true,
vibrate: true
}
}
function saveSettings(settings) {
wx.setStorageSync(SETTINGS_KEY, settings)
}
function getStreak() {
return wx.getStorageSync(STREAK_KEY) || { count: 0, lastDate: '' }
}
function updateStreak(date) {
var streak = getStreak()
var today = date || getToday()
var yesterday = getDateOffset(today, -1)
if (streak.lastDate === today) return streak
if (streak.lastDate === yesterday) {
streak.count += 1
} else {
streak.count = 1
}
streak.lastDate = today
wx.setStorageSync(STREAK_KEY, streak)
return streak
}
function getToday() {
return formatDate(new Date())
}
function formatDate(date) {
var y = date.getFullYear()
var m = String(date.getMonth() + 1)
if (m.length < 2) m = '0' + m
var d = String(date.getDate())
if (d.length < 2) d = '0' + d
return y + '-' + m + '-' + d
}
function getDateOffset(dateStr, offset) {
var d = new Date(dateStr)
d.setDate(d.getDate() + offset)
return formatDate(d)
}
function getTodayRecord() {
var today = getToday()
var month = today.substring(0, 7)
var records = getRecordsByMonth(month)
var totalDuration = 0
var hasRecord = false
for (var i = 0; i < records.length; i++) {
if (records[i].date === today) {
totalDuration += records[i].duration
hasRecord = true
}
}
return hasRecord ? { date: today, duration: totalDuration } : null
}
module.exports = {
getRecords: getRecords,
saveRecord: saveRecord,
deleteRecord: deleteRecord,
getRecordsByMonth: getRecordsByMonth,
getTotalStats: getTotalStats,
getSettings: getSettings,
saveSettings: saveSettings,
getStreak: getStreak,
updateStreak: updateStreak,
getToday: getToday,
formatDate: formatDate,
getDateOffset: getDateOffset,
getTodayRecord: getTodayRecord
}
+103
View File
@@ -0,0 +1,103 @@
var 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'
}
]
var DEFAULT_THEME_ID = 'orange'
var THEME_KEY = 'app_theme'
var _lastNavColor = ''
var _navColorTimer = null
function getThemeById(id) {
return THEMES.find(function (t) { return t.id === id }) || THEMES[0]
}
function getCurrentTheme() {
var id = wx.getStorageSync(THEME_KEY) || DEFAULT_THEME_ID
return getThemeById(id)
}
function _setNavBarColor(primary) {
if (_lastNavColor === primary) return
_lastNavColor = primary
if (_navColorTimer) clearTimeout(_navColorTimer)
_navColorTimer = setTimeout(function () {
try {
wx.setNavigationBarColor({
frontColor: '#ffffff',
backgroundColor: primary
})
} catch (e) {}
}, 50)
}
function setTheme(id) {
wx.setStorageSync(THEME_KEY, id)
var theme = getThemeById(id)
_setNavBarColor(theme.primary)
var app = getApp()
if (app && app.globalData) app.globalData.theme = theme
return theme
}
function applyThemeToPage(self) {
var theme = getCurrentTheme()
self.setData({
theme: theme,
themeStyle: '--primary:' + theme.primary + ';--primary-light:' + theme.primaryLight + ';--primary-bg:' + theme.primaryBg + ';--primary-rgb:' + theme.primaryRgb + ';'
})
_setNavBarColor(theme.primary)
}
function getThemeStyle(theme) {
var t = theme || getCurrentTheme()
return '--primary:' + t.primary + ';--primary-light:' + t.primaryLight + ';--primary-bg:' + t.primaryBg + ';--primary-rgb:' + t.primaryRgb + ';'
}
module.exports = {
THEMES: THEMES,
DEFAULT_THEME_ID: DEFAULT_THEME_ID,
getThemeById: getThemeById,
getCurrentTheme: getCurrentTheme,
setTheme: setTheme,
applyThemeToPage: applyThemeToPage,
getThemeStyle: getThemeStyle
}
+92
View File
@@ -0,0 +1,92 @@
function Timer(options) {
options = options || {}
this.onTick = options.onTick || function () {}
this.onComplete = options.onComplete || function () {}
this._duration = 0
this._remaining = 0
this._elapsed = 0
this._startTime = 0
this._pausedTime = 0
this._intervalId = null
this._running = false
this._paused = false
this._completed = false
}
Timer.prototype.start = function (duration) {
this._duration = duration
this._remaining = duration
this._elapsed = 0
this._completed = false
this._running = true
this._paused = false
this._startTime = Date.now()
this._tick()
var self = this
this._intervalId = setInterval(function () { self._tick() }, 1000)
}
Timer.prototype.pause = function () {
if (!this._running || this._paused) return
this._paused = true
this._pausedTime = Date.now()
clearInterval(this._intervalId)
this._intervalId = null
}
Timer.prototype.resume = function () {
if (!this._running || !this._paused) return
this._startTime += Date.now() - this._pausedTime
this._paused = false
this._tick()
var self = this
this._intervalId = setInterval(function () { self._tick() }, 1000)
}
Timer.prototype.stop = function () {
this._running = false
this._paused = false
clearInterval(this._intervalId)
this._intervalId = null
return this._elapsed
}
Timer.prototype._tick = function () {
if (!this._running || this._paused) return
this._elapsed = Math.floor((Date.now() - this._startTime) / 1000)
this._remaining = Math.max(0, this._duration - this._elapsed)
this.onTick({
remaining: this._remaining,
elapsed: this._elapsed,
duration: this._duration
})
if (this._remaining <= 0 && !this._completed) {
this._completed = true
this.onComplete({ elapsed: this._elapsed, duration: this._duration })
}
}
Object.defineProperty(Timer.prototype, 'isRunning', {
get: function () { return this._running && !this._paused }
})
Object.defineProperty(Timer.prototype, 'isPaused', {
get: function () { return this._paused }
})
Object.defineProperty(Timer.prototype, 'isCompleted', {
get: function () { return this._completed }
})
Object.defineProperty(Timer.prototype, 'remaining', {
get: function () { return this._remaining }
})
Object.defineProperty(Timer.prototype, 'elapsed', {
get: function () { return this._elapsed }
})
module.exports = Timer
+44
View File
@@ -0,0 +1,44 @@
function formatTime(seconds) {
var m = Math.floor(seconds / 60)
var s = seconds % 60
return (m < 10 ? '0' : '') + m + ':' + (s < 10 ? '0' : '') + s
}
function formatDuration(seconds) {
if (seconds < 60) return seconds + '秒'
var m = Math.floor(seconds / 60)
var s = seconds % 60
return s > 0 ? m + '分' + s + '秒' : m + '分钟'
}
function getDaysInMonth(year, month) {
return new Date(year, month, 0).getDate()
}
function getMonthCalendar(year, month) {
var firstDay = new Date(year, month - 1, 1)
var lastDay = new Date(year, month, 0)
var daysInMonth = lastDay.getDate()
var startDayOfWeek = firstDay.getDay()
var weeks = []
var week = [null, null, null, null, null, null, null]
for (var d = 1; d <= daysInMonth; d++) {
var dow = (startDayOfWeek + d - 1) % 7
week[dow] = d
if (dow === 6 || d === daysInMonth) {
weeks.push(week.slice())
week = [null, null, null, null, null, null, null]
}
}
return weeks
}
module.exports = {
formatTime: formatTime,
formatDuration: formatDuration,
getDaysInMonth: getDaysInMonth,
getMonthCalendar: getMonthCalendar
}