refactor: ES6+ modernization + 2 bugfixes

- var→const/let, function→arrow/class across all 13 JS files
- Timer: prototype→class with ES6 getters
- plan days: IIFE→Array.from declarative generation
- storage/plan: unified formatDate via util.js
- Bugfix: getPlanDay now filters by planId (plan switch accuracy)
- Bugfix: getTodayRecord searches all months (cross-month boundary)
- WXML/WXSS unchanged; public API unchanged
This commit is contained in:
2026-06-04 16:29:25 +08:00
parent ff2700c067
commit 8c7f633541
13 changed files with 404 additions and 494 deletions
+4 -4
View File
@@ -1,8 +1,8 @@
var themeMod = require('./utils/theme') const themeMod = require('./utils/theme')
App({ App({
onLaunch: function () { onLaunch() {
var settings = wx.getStorageSync('user_settings') const settings = wx.getStorageSync('user_settings')
if (!settings) { if (!settings) {
wx.setStorageSync('user_settings', { wx.setStorageSync('user_settings', {
planId: 'beginner', planId: 'beginner',
@@ -12,7 +12,7 @@ App({
vibrate: true vibrate: true
}) })
} }
var streak = wx.getStorageSync('current_streak') const streak = wx.getStorageSync('current_streak')
if (!streak) { if (!streak) {
wx.setStorageSync('current_streak', { count: 0, lastDate: '' }) wx.setStorageSync('current_streak', { count: 0, lastDate: '' })
} }
+22 -29
View File
@@ -1,4 +1,4 @@
var util = require('../../utils/util') const util = require('../../utils/util')
Component({ Component({
properties: { properties: {
@@ -12,70 +12,63 @@ Component({
}, },
observers: { observers: {
'year, month, records': function (year, month, records) { 'year, month, records'(year, month, records) {
this._compute(year, month, records) this._compute(year, month, records)
} }
}, },
lifetimes: { lifetimes: {
ready: function () { ready() {
this._compute(this.properties.year, this.properties.month, this.properties.records) const { year, month, records } = this.properties
this._compute(year, month, records)
} }
}, },
methods: { methods: {
_compute: function (year, month, records) { _compute(year, month, records) {
var weeks = util.getMonthCalendar(year, month) const weeks = util.getMonthCalendar(year, month)
var recordMap = {} const recordMap = {}
if (records && records.length) { if (records && records.length) {
records.forEach(function (r) { records.forEach((r) => {
var d = parseInt(r.date.split('-')[2]) const d = parseInt(r.date.split('-')[2])
recordMap[d] = (recordMap[d] || 0) + r.duration recordMap[d] = (recordMap[d] || 0) + r.duration
}) })
} }
var data = weeks.map(function (week) { const data = weeks.map((week) =>
return week.map(function (day) { week.map((day) => {
if (!day) return null if (!day) return null
var duration = recordMap[day] || 0 const duration = recordMap[day] || 0
var color = 'transparent' let color = 'transparent'
if (duration > 0) { if (duration > 0) {
if (duration < 30) color = '#FFE0D0' if (duration < 30) color = '#FFE0D0'
else if (duration < 60) color = '#FFB088' else if (duration < 60) color = '#FFB088'
else if (duration < 120) color = '#FF6B35' else if (duration < 120) color = '#FF6B35'
else color = '#E05520' else color = '#E05520'
} }
return { day: day, duration: duration, color: color } return { day, duration, color }
}) })
}) )
this.setData({ weeks: data }) this.setData({ weeks: data })
}, },
onDayTap: function (e) { onDayTap(e) {
var day = e.currentTarget.dataset.day const day = e.currentTarget.dataset.day
if (!day) return if (!day) return
var cell = null const cell = this.data.weeks.flat().find(c => c && c.day === day)
var weeks = this.data.weeks
for (var i = 0; i < weeks.length; i++) {
for (var j = 0; j < weeks[i].length; j++) {
var c = weeks[i][j]
if (c && c.day === day) { cell = c; break }
}
if (cell) break
}
if (!cell || cell.duration <= 0) return if (!cell || cell.duration <= 0) return
this.triggerEvent('daytap', { this.triggerEvent('daytap', {
year: this.properties.year, year: this.properties.year,
month: this.properties.month, month: this.properties.month,
day: day, day,
duration: cell.duration duration: cell.duration
}) })
}, },
getHeatColor: function (cell) { getHeatColor(cell) {
if (!cell || cell.duration <= 0) return 'transparent' if (!cell || cell.duration <= 0) return 'transparent'
var d = cell.duration const d = cell.duration
if (d < 30) return '#FFE0D0' if (d < 30) return '#FFE0D0'
if (d < 60) return '#FFB088' if (d < 60) return '#FFB088'
if (d < 120) return '#FF6B35' if (d < 120) return '#FF6B35'
+26 -31
View File
@@ -1,4 +1,4 @@
var util = require('../../utils/util') const util = require('../../utils/util')
Component({ Component({
properties: { properties: {
@@ -17,8 +17,8 @@ Component({
}, },
observers: { observers: {
'remaining, status, duration, size, ringWidth, primaryColor': function () { 'remaining, status, duration, size, ringWidth, primaryColor'() {
var texts = { idle: '准备开始', running: '坚持住', paused: '已暂停', completed: '完成!' } const texts = { idle: '准备开始', running: '坚持住', paused: '已暂停', completed: '完成!' }
this.setData({ this.setData({
displayTime: util.formatTime(this.data.remaining), displayTime: util.formatTime(this.data.remaining),
statusText: texts[this.data.status] || '' statusText: texts[this.data.status] || ''
@@ -28,45 +28,40 @@ Component({
}, },
lifetimes: { lifetimes: {
ready: function () { ready() {
var self = this const query = this.createSelectorQuery()
var query = this.createSelectorQuery()
query.select('#ringCanvas') query.select('#ringCanvas')
.fields({ node: true, size: true }) .fields({ node: true, size: true })
.exec(function (res) { .exec((res) => {
if (!res || !res[0] || !res[0].node) return if (!res || !res[0] || !res[0].node) return
var canvas = res[0].node const canvas = res[0].node
var dpr = wx.getSystemInfoSync().pixelRatio || 2 const dpr = wx.getSystemInfoSync().pixelRatio || 2
var displaySize = self.data.size const displaySize = this.data.size
canvas.width = displaySize * dpr canvas.width = displaySize * dpr
canvas.height = displaySize * dpr canvas.height = displaySize * dpr
self._ctx = canvas.getContext('2d') this._ctx = canvas.getContext('2d')
self._dpr = dpr this._dpr = dpr
self._draw() this._draw()
}) })
} }
}, },
methods: { methods: {
_draw: function () { _draw() {
var ctx = this._ctx const ctx = this._ctx
if (!ctx) return if (!ctx) return
var size = this.data.size const { size, ringWidth, duration, remaining, primaryColor } = this.data
var dpr = this._dpr const dpr = this._dpr
var ringWidth = this.data.ringWidth
var duration = this.data.duration
var remaining = this.data.remaining
var primaryColor = this.data.primaryColor
var w = size * dpr const w = size * dpr
var h = size * dpr const h = size * dpr
ctx.clearRect(0, 0, w, h) ctx.clearRect(0, 0, w, h)
var cx = w / 2 const cx = w / 2
var cy = h / 2 const cy = h / 2
var radius = (size - ringWidth) / 2 * dpr const radius = (size - ringWidth) / 2 * dpr
var lw = ringWidth * dpr const lw = ringWidth * dpr
// track ring // track ring
ctx.beginPath() ctx.beginPath()
@@ -76,11 +71,11 @@ Component({
ctx.lineCap = 'round' ctx.lineCap = 'round'
ctx.stroke() ctx.stroke()
// progress arc (clockwise from 12 o'clock, represents remaining time) // progress arc (clockwise from 12 o'clock)
var ratio = duration > 0 ? Math.max(0, Math.min(1, remaining / duration)) : 0 const ratio = duration > 0 ? Math.max(0, Math.min(1, remaining / duration)) : 0
if (ratio > 0.001) { if (ratio > 0.001) {
var startAngle = -Math.PI / 2 const startAngle = -Math.PI / 2
var endAngle = startAngle + ratio * Math.PI * 2 const endAngle = startAngle + ratio * Math.PI * 2
ctx.beginPath() ctx.beginPath()
ctx.arc(cx, cy, radius, startAngle, endAngle) ctx.arc(cx, cy, radius, startAngle, endAngle)
ctx.strokeStyle = primaryColor ctx.strokeStyle = primaryColor
+8 -8
View File
@@ -1,4 +1,4 @@
var themeMod = require('../utils/theme') const themeMod = require('../utils/theme')
Component({ Component({
data: { data: {
@@ -7,23 +7,23 @@ Component({
}, },
lifetimes: { lifetimes: {
attached: function () { attached() {
var theme = themeMod.getCurrentTheme() const theme = themeMod.getCurrentTheme()
this.setData({ themeStyle: themeMod.getThemeStyle(theme) }) this.setData({ themeStyle: themeMod.getThemeStyle(theme) })
} }
}, },
pageLifetimes: { pageLifetimes: {
show: function () { show() {
var theme = themeMod.getCurrentTheme() const theme = themeMod.getCurrentTheme()
this.setData({ themeStyle: themeMod.getThemeStyle(theme) }) this.setData({ themeStyle: themeMod.getThemeStyle(theme) })
} }
}, },
methods: { methods: {
switchTab: function (e) { switchTab(e) {
var index = e.currentTarget.dataset.index const index = e.currentTarget.dataset.index
var pages = [ const pages = [
'/pages/index/index', '/pages/index/index',
'/pages/records/records', '/pages/records/records',
'/pages/settings/settings' '/pages/settings/settings'
+27 -32
View File
@@ -1,7 +1,7 @@
var storage = require('../../utils/storage') const storage = require('../../utils/storage')
var planMod = require('../../utils/plan') const planMod = require('../../utils/plan')
var util = require('../../utils/util') const util = require('../../utils/util')
var themeMod = require('../../utils/theme') const themeMod = require('../../utils/theme')
Page({ Page({
data: { data: {
@@ -22,38 +22,33 @@ Page({
customInput: '' customInput: ''
}, },
onLoad: function () { onLoad() {
this.refresh() this.refresh()
}, },
onShow: function () { onShow() {
themeMod.applyThemeToPage(this) themeMod.applyThemeToPage(this)
try { try {
var tb = this.getTabBar() const tb = this.getTabBar()
if (tb) tb.setData({ selected: 0 }) if (tb) tb.setData({ selected: 0 })
} catch (e) {} } catch (e) {}
this.refresh() this.refresh()
}, },
onPullDownRefresh: function () { onPullDownRefresh() {
this.refresh() this.refresh()
wx.stopPullDownRefresh() wx.stopPullDownRefresh()
}, },
refresh: function () { refresh() {
var settings = storage.getSettings() const settings = storage.getSettings()
var streak = storage.getStreak() const streak = storage.getStreak()
var todayRecord = storage.getTodayRecord() const todayRecord = storage.getTodayRecord()
var records = [] const allRecords = Object.values(storage.getRecords()).flat()
var allRecords = storage.getRecords() const plan = planMod.getPlan(settings.planId)
Object.keys(allRecords).forEach(function (key) { const planDay = planMod.getPlanDay(settings.planId, allRecords)
records = records.concat(allRecords[key]) const target = planMod.getTodayTarget(settings.planId, Math.min(planDay, plan.totalDays))
})
var plan = planMod.getPlan(settings.planId)
var planDay = planMod.getPlanDay(settings.planId, records)
var target = planMod.getTodayTarget(settings.planId, Math.min(planDay, plan.totalDays))
this.setData({ this.setData({
todayTarget: target, todayTarget: target,
@@ -68,37 +63,37 @@ Page({
}) })
}, },
onStartTrain: function () { onStartTrain() {
wx.navigateTo({ url: '/pages/timer/timer' }) wx.navigateTo({ url: '/pages/timer/timer' })
}, },
onFreeTrain: function () { onFreeTrain() {
this.setData({ showPicker: true, customInput: '', customDuration: 60 }) this.setData({ showPicker: true, customInput: '', customDuration: 60 })
}, },
onClosePicker: function () { onClosePicker() {
this.setData({ showPicker: false }) this.setData({ showPicker: false })
}, },
onSelectPreset: function (e) { onSelectPreset(e) {
var val = e.currentTarget.dataset.value const val = e.currentTarget.dataset.value
this.setData({ customDuration: val, customInput: '' }) this.setData({ customDuration: val, customInput: '' })
}, },
onCustomInput: function (e) { onCustomInput(e) {
var val = parseInt(e.detail.value) || 0 const val = parseInt(e.detail.value) || 0
this.setData({ customInput: e.detail.value, customDuration: val }) this.setData({ customInput: e.detail.value, customDuration: val })
}, },
onConfirmFree: function () { onConfirmFree() {
var dur = this.data.customDuration const dur = this.data.customDuration
if (!dur || dur <= 0) { if (!dur || dur <= 0) {
wx.showToast({ title: '请输入有效时长', icon: 'none' }) wx.showToast({ title: '请输入有效时长', icon: 'none' })
return return
} }
this.setData({ showPicker: false }) this.setData({ showPicker: false })
wx.navigateTo({ url: '/pages/timer/timer?free=' + dur }) wx.navigateTo({ url: `/pages/timer/timer?free=${dur}` })
}, },
noop: function () {} noop() {}
}) })
+41 -57
View File
@@ -1,6 +1,6 @@
var storage = require('../../utils/storage') const storage = require('../../utils/storage')
var util = require('../../utils/util') const util = require('../../utils/util')
var themeMod = require('../../utils/theme') const themeMod = require('../../utils/theme')
Page({ Page({
data: { data: {
@@ -19,36 +19,31 @@ Page({
dayDetail: {} dayDetail: {}
}, },
onLoad: function () { onLoad() {
this.refresh() this.refresh()
}, },
onShow: function () { onShow() {
themeMod.applyThemeToPage(this) themeMod.applyThemeToPage(this)
try { try {
var tb = this.getTabBar() const tb = this.getTabBar()
if (tb) tb.setData({ selected: 1 }) if (tb) tb.setData({ selected: 1 })
} catch (e) {} } catch (e) {}
this.refresh() this.refresh()
}, },
onPullDownRefresh: function () { onPullDownRefresh() {
this.refresh() this.refresh()
wx.stopPullDownRefresh() wx.stopPullDownRefresh()
}, },
refresh: function () { refresh() {
var stats = storage.getTotalStats() const stats = storage.getTotalStats()
var monthKey = this.data.currentYear + '-' + const monthKey = `${this.data.currentYear}-${String(this.data.currentMonth).padStart(2, '0')}`
(this.data.currentMonth < 10 ? '0' : '') + this.data.currentMonth const records = storage.getRecordsByMonth(monthKey)
var records = storage.getRecordsByMonth(monthKey)
var allRecords = [] const allRecords = Object.values(storage.getRecords()).flat()
var all = storage.getRecords() allRecords.sort((a, b) => b.date.localeCompare(a.date))
Object.keys(all).forEach(function (key) {
allRecords = allRecords.concat(all[key])
})
allRecords.sort(function (a, b) { return b.date.localeCompare(a.date) })
this.setData({ this.setData({
totalDuration: stats.totalDuration, totalDuration: stats.totalDuration,
@@ -61,48 +56,38 @@ Page({
}) })
}, },
onPrevMonth: function () { onPrevMonth() {
var year = this.data.currentYear let { currentYear, currentMonth } = this.data
var month = this.data.currentMonth if (currentMonth === 1) {
if (month === 1) { currentYear--
year-- currentMonth = 12
month = 12
} else { } else {
month-- currentMonth--
} }
this.setData({ currentYear: year, currentMonth: month }) this.setData({ currentYear, currentMonth })
this.refresh() this.refresh()
}, },
onNextMonth: function () { onNextMonth() {
var year = this.data.currentYear let { currentYear, currentMonth } = this.data
var month = this.data.currentMonth if (currentMonth === 12) {
if (month === 12) { currentYear++
year++ currentMonth = 1
month = 1
} else { } else {
month++ currentMonth++
} }
this.setData({ currentYear: year, currentMonth: month }) this.setData({ currentYear, currentMonth })
this.refresh() this.refresh()
}, },
onDayTap: function (e) { onDayTap(e) {
var detail = e.detail const { year, month, day } = e.detail
var dateStr = detail.year + '-' + const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`
(detail.month < 10 ? '0' : '') + detail.month + '-' +
(detail.day < 10 ? '0' : '') + detail.day
var allRecords = [] const allRecords = Object.values(storage.getRecords()).flat()
var all = storage.getRecords() const sessions = allRecords.filter(r => r.date === dateStr)
Object.keys(all).forEach(function (key) { const totalDur = sessions.reduce((sum, r) => sum + r.duration, 0)
allRecords = allRecords.concat(all[key]) const calories = Math.round(totalDur * 0.068)
})
var sessions = allRecords.filter(function (r) { return r.date === dateStr })
var totalDur = 0
sessions.forEach(function (r) { totalDur += r.duration })
var calories = Math.round(totalDur * 0.068)
this.setData({ this.setData({
showDayDetail: true, showDayDetail: true,
@@ -111,27 +96,26 @@ Page({
sessions: sessions.length, sessions: sessions.length,
duration: totalDur, duration: totalDur,
durationText: util.formatDuration(totalDur), durationText: util.formatDuration(totalDur),
calories: calories calories
} }
}) })
}, },
onCloseDayDetail: function () { onCloseDayDetail() {
this.setData({ showDayDetail: false }) this.setData({ showDayDetail: false })
}, },
noop: function () {}, noop() {},
onDeleteRecord: function (e) { onDeleteRecord(e) {
var id = e.currentTarget.dataset.id const id = e.currentTarget.dataset.id
var self = this
wx.showModal({ wx.showModal({
title: '删除记录', title: '删除记录',
content: '确定要删除这条训练记录吗?', content: '确定要删除这条训练记录吗?',
success: function (res) { success: (res) => {
if (res.confirm) { if (res.confirm) {
storage.deleteRecord(id) storage.deleteRecord(id)
self.refresh() this.refresh()
wx.showToast({ title: '已删除', icon: 'none', duration: 1200 }) wx.showToast({ title: '已删除', icon: 'none', duration: 1200 })
} }
} }
+25 -27
View File
@@ -1,5 +1,5 @@
var storage = require('../../utils/storage') const storage = require('../../utils/storage')
var themeMod = require('../../utils/theme') const themeMod = require('../../utils/theme')
Page({ Page({
data: { data: {
@@ -19,9 +19,9 @@ Page({
vibrate: true vibrate: true
}, },
onLoad: function () { onLoad() {
var s = storage.getSettings() const s = storage.getSettings()
var theme = themeMod.getCurrentTheme() const theme = themeMod.getCurrentTheme()
themeMod.applyThemeToPage(this) themeMod.applyThemeToPage(this)
this.setData({ this.setData({
currentPlanId: s.planId, currentPlanId: s.planId,
@@ -33,70 +33,68 @@ Page({
}) })
}, },
onShow: function () { onShow() {
try { try {
var tb = this.getTabBar() const tb = this.getTabBar()
if (tb) tb.setData({ selected: 2 }) if (tb) tb.setData({ selected: 2 })
} catch (e) {} } catch (e) {}
themeMod.applyThemeToPage(this) themeMod.applyThemeToPage(this)
}, },
onSelectTheme: function (e) { onSelectTheme(e) {
var id = e.currentTarget.dataset.id const id = e.currentTarget.dataset.id
var theme = themeMod.setTheme(id) const theme = themeMod.setTheme(id)
this.setData({ currentThemeId: id }) this.setData({ currentThemeId: id })
try { try {
var tb = this.getTabBar() const tb = this.getTabBar()
if (tb) { if (tb) tb.setData({ themeStyle: themeMod.getThemeStyle(theme) })
tb.setData({ themeStyle: themeMod.getThemeStyle(theme) })
}
} catch (e) {} } catch (e) {}
themeMod.applyThemeToPage(this) themeMod.applyThemeToPage(this)
}, },
onSelectPlan: function (e) { onSelectPlan(e) {
var planId = e.currentTarget.dataset.id const planId = e.currentTarget.dataset.id
var s = storage.getSettings() const s = storage.getSettings()
s.planId = planId s.planId = planId
storage.saveSettings(s) storage.saveSettings(s)
this.setData({ currentPlanId: planId }) this.setData({ currentPlanId: planId })
wx.showToast({ title: '计划已切换', icon: 'success', duration: 1200 }) wx.showToast({ title: '计划已切换', icon: 'success', duration: 1200 })
}, },
onToggleReminder: function (e) { onToggleReminder(e) {
var s = storage.getSettings() const s = storage.getSettings()
s.dailyReminder = e.detail.value s.dailyReminder = e.detail.value
storage.saveSettings(s) storage.saveSettings(s)
this.setData({ dailyReminder: e.detail.value }) this.setData({ dailyReminder: e.detail.value })
}, },
onReminderTimeChange: function (e) { onReminderTimeChange(e) {
var s = storage.getSettings() const s = storage.getSettings()
s.reminderTime = e.detail.value s.reminderTime = e.detail.value
storage.saveSettings(s) storage.saveSettings(s)
this.setData({ reminderTime: e.detail.value }) this.setData({ reminderTime: e.detail.value })
}, },
onToggleVoice: function (e) { onToggleVoice(e) {
var s = storage.getSettings() const s = storage.getSettings()
s.voiceGuide = e.detail.value s.voiceGuide = e.detail.value
storage.saveSettings(s) storage.saveSettings(s)
this.setData({ voiceGuide: e.detail.value }) this.setData({ voiceGuide: e.detail.value })
}, },
onToggleVibrate: function (e) { onToggleVibrate(e) {
var s = storage.getSettings() const s = storage.getSettings()
s.vibrate = e.detail.value s.vibrate = e.detail.value
storage.saveSettings(s) storage.saveSettings(s)
this.setData({ vibrate: e.detail.value }) this.setData({ vibrate: e.detail.value })
}, },
onCopyWechat: function () { onCopyWechat() {
wx.setClipboardData({ wx.setClipboardData({
data: '刘承', data: '刘承',
success: function () { success: () => {
wx.showToast({ title: '已复制', icon: 'success', duration: 1500 }) wx.showToast({ title: '已复制', icon: 'success', duration: 1500 })
} }
}) })
+29 -36
View File
@@ -1,7 +1,7 @@
var Timer = require('../../utils/timer') const Timer = require('../../utils/timer')
var storage = require('../../utils/storage') const storage = require('../../utils/storage')
var planMod = require('../../utils/plan') const planMod = require('../../utils/plan')
var themeMod = require('../../utils/theme') const themeMod = require('../../utils/theme')
Page({ Page({
data: { data: {
@@ -19,26 +19,21 @@ Page({
isFreeMode: false isFreeMode: false
}, },
onLoad: function (options) { onLoad(options) {
themeMod.applyThemeToPage(this) themeMod.applyThemeToPage(this)
var target let target
var planDay = 1 let planDay = 1
var isFreeMode = false let isFreeMode = false
if (options && options.free) { if (options && options.free) {
target = parseInt(options.free) || 60 target = parseInt(options.free) || 60
isFreeMode = true isFreeMode = true
} else { } else {
var settings = storage.getSettings() const settings = storage.getSettings()
var records = [] const allRecords = Object.values(storage.getRecords()).flat()
var allRecords = storage.getRecords() const plan = planMod.getPlan(settings.planId)
Object.keys(allRecords).forEach(function (key) { planDay = planMod.getPlanDay(settings.planId, allRecords)
records = records.concat(allRecords[key])
})
var plan = planMod.getPlan(settings.planId)
planDay = planMod.getPlanDay(settings.planId, records)
target = planMod.getTodayTarget(settings.planId, Math.min(planDay, plan.totalDays)) target = planMod.getTodayTarget(settings.planId, Math.min(planDay, plan.totalDays))
} }
@@ -47,19 +42,18 @@ Page({
remaining: target, remaining: target,
todayTarget: target, todayTarget: target,
planDay: isFreeMode ? 0 : Math.min(planDay, 99), planDay: isFreeMode ? 0 : Math.min(planDay, 99),
isFreeMode: isFreeMode isFreeMode
}) })
var self = this
this._timer = new Timer({ this._timer = new Timer({
onTick: function (tick) { onTick: (tick) => {
self.setData({ this.setData({
remaining: tick.remaining > 0 ? tick.remaining : 0, remaining: tick.remaining > 0 ? tick.remaining : 0,
overtime: tick.remaining <= 0 ? tick.elapsed - self.data.duration : 0 overtime: tick.remaining <= 0 ? tick.elapsed - this.data.duration : 0
}) })
}, },
onComplete: function () { onComplete: () => {
self.setData({ status: 'completed', isCompleted: true }) this.setData({ status: 'completed', isCompleted: true })
if (storage.getSettings().vibrate) { if (storage.getSettings().vibrate) {
try { wx.vibrateLong() } catch (e) {} try { wx.vibrateLong() } catch (e) {}
} }
@@ -68,11 +62,11 @@ Page({
}) })
}, },
onUnload: function () { onUnload() {
if (this._timer) this._timer.stop() if (this._timer) this._timer.stop()
}, },
onStart: function () { onStart() {
if (this.data.isPaused) { if (this.data.isPaused) {
this._timer.resume() this._timer.resume()
this.setData({ isRunning: true, isPaused: false, status: 'running' }) this.setData({ isRunning: true, isPaused: false, status: 'running' })
@@ -83,26 +77,25 @@ Page({
this.setData({ isRunning: true, status: 'running' }) this.setData({ isRunning: true, status: 'running' })
}, },
onPause: function () { onPause() {
if (!this.data.isRunning || this.data.isPaused) return if (!this.data.isRunning || this.data.isPaused) return
this._timer.pause() this._timer.pause()
this.setData({ isPaused: true, status: 'paused' }) this.setData({ isPaused: true, status: 'paused' })
}, },
onStop: function () { onStop() {
var self = this
wx.showModal({ wx.showModal({
title: '结束训练', title: '结束训练',
content: '确定要结束本次训练吗?', content: '确定要结束本次训练吗?',
success: function (res) { success: (res) => {
if (res.confirm) self.finishTraining() if (res.confirm) this.finishTraining()
} }
}) })
}, },
finishTraining: function () { finishTraining() {
var elapsed = this._timer.stop() const elapsed = this._timer.stop()
var today = storage.getToday() const today = storage.getToday()
storage.saveRecord({ storage.saveRecord({
date: today, date: today,
duration: elapsed, duration: elapsed,
@@ -111,7 +104,7 @@ Page({
}) })
storage.updateStreak(today) storage.updateStreak(today)
wx.showToast({ title: '已记录 ' + elapsed + '秒', icon: 'none', duration: 1500 }) wx.showToast({ title: `已记录 ${elapsed}`, icon: 'none', duration: 1500 })
setTimeout(function () { wx.navigateBack() }, 1500) setTimeout(() => { wx.navigateBack() }, 1500)
} }
}) })
+27 -48
View File
@@ -1,73 +1,52 @@
var plans = { const { formatDate } = require('./util')
const plans = {
beginner: { beginner: {
id: 'beginner', id: 'beginner',
name: '初级 (7天)', name: '初级 (7天)',
totalDays: 7, totalDays: 7,
description: '适合初学者,从30秒起步', description: '适合初学者,从30秒起步',
days: [ days: Array.from({ length: 7 }, (_, i) => ({ day: i + 1, target: 30 + i * 10 }))
{ 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: { intermediate: {
id: 'intermediate', id: 'intermediate',
name: '中级 (14天)', name: '中级 (14天)',
totalDays: 14, totalDays: 14,
description: '有一定基础,从60秒起步', description: '有一定基础,从60秒起步',
days: [ days: Array.from({ length: 14 }, (_, i) => {
{ day: 1, target: 60 }, { day: 2, target: 60 }, const pair = Math.floor(i / 2)
{ day: 3, target: 75 }, { day: 4, target: 75 }, return { day: i + 1, target: 60 + pair * 15 }
{ 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: { advanced: {
id: 'advanced', id: 'advanced',
name: '高级 (30天)', name: '高级 (30天)',
totalDays: 30, totalDays: 30,
description: '挑战自我,从90秒起步', description: '挑战自我,从90秒起步',
days: (function () { days: Array.from({ length: 30 }, (_, i) => ({
var arr = [] day: i + 1,
for (var i = 1; i <= 30; i++) { target: 90 + Math.floor(i / 3) * 15
arr.push({ day: i, target: 90 + Math.floor((i - 1) / 3) * 15 }) }))
}
return arr
})()
} }
} }
function getPlan(planId) { const getPlan = (planId) => plans[planId] || plans.beginner
return plans[planId] || plans.beginner
const getTodayTarget = (planId, currentDay) => {
const plan = getPlan(planId)
const day = plan.days.find(d => d.day === currentDay)
return day ? day.target : plan.days[0].target
} }
function getTodayTarget(planId, currentDay) { const getPlanDay = (planId, records) => {
var plan = getPlan(planId) const plan = getPlan(planId)
for (var i = 0; i < plan.days.length; i++) { const today = formatDate(new Date())
if (plan.days[i].day === currentDay) return plan.days[i].target const trainedDays = new Set(
} records
return plan.days[0].target .filter(r => r.date !== today && r.planId === planId)
} .map(r => r.date)
).size
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) return Math.min(trainedDays + 1, plan.totalDays)
} }
module.exports = { plans: plans, getPlan: getPlan, getTodayTarget: getTodayTarget, getPlanDay: getPlanDay } module.exports = { plans, getPlan, getTodayTarget, getPlanDay }
+65 -80
View File
@@ -1,73 +1,68 @@
var RECORDS_KEY = 'training_records' const { formatDate } = require('./util')
var SETTINGS_KEY = 'user_settings'
var STREAK_KEY = 'current_streak'
function getRecords() { const RECORDS_KEY = 'training_records'
return wx.getStorageSync(RECORDS_KEY) || {} const SETTINGS_KEY = 'user_settings'
} const STREAK_KEY = 'current_streak'
function saveRecord(record) { const getRecords = () => wx.getStorageSync(RECORDS_KEY) || {}
const saveRecord = (record) => {
record.id = Date.now() record.id = Date.now()
var records = getRecords() const records = getRecords()
var month = record.date.substring(0, 7) const month = record.date.substring(0, 7)
if (!records[month]) records[month] = [] if (!records[month]) records[month] = []
records[month].push(record) records[month].push(record)
records[month].sort(function (a, b) { return b.date.localeCompare(a.date) }) records[month].sort((a, b) => b.date.localeCompare(a.date))
wx.setStorageSync(RECORDS_KEY, records) wx.setStorageSync(RECORDS_KEY, records)
} }
function deleteRecord(recordId) { const deleteRecord = (recordId) => {
var records = getRecords() const records = getRecords()
Object.keys(records).forEach(function (month) { Object.keys(records).forEach((month) => {
records[month] = records[month].filter(function (r) { return r.id !== recordId }) records[month] = records[month].filter((r) => r.id !== recordId)
if (records[month].length === 0) delete records[month] if (records[month].length === 0) delete records[month]
}) })
wx.setStorageSync(RECORDS_KEY, records) wx.setStorageSync(RECORDS_KEY, records)
} }
function getRecordsByMonth(month) { const getRecordsByMonth = (month) => {
var records = getRecords() const records = getRecords()
return records[month] || [] return records[month] || []
} }
function getTotalStats() { const getTotalStats = () => {
var records = getRecords() const records = getRecords()
var totalDuration = 0 let totalDuration = 0
var totalSessions = 0 let totalSessions = 0
var maxDuration = 0 let maxDuration = 0
Object.keys(records).forEach(function (key) { Object.keys(records).forEach((key) => {
var list = records[key] records[key].forEach((r) => {
list.forEach(function (r) {
totalDuration += r.duration totalDuration += r.duration
totalSessions++ totalSessions++
if (r.duration > maxDuration) maxDuration = r.duration if (r.duration > maxDuration) maxDuration = r.duration
}) })
}) })
return { totalDuration: totalDuration, totalSessions: totalSessions, maxDuration: maxDuration } return { totalDuration, totalSessions, maxDuration }
} }
function getSettings() { const getSettings = () => wx.getStorageSync(SETTINGS_KEY) || {
return wx.getStorageSync(SETTINGS_KEY) || { planId: 'beginner',
planId: 'beginner', dailyReminder: true,
dailyReminder: true, reminderTime: '08:00',
reminderTime: '08:00', voiceGuide: true,
voiceGuide: true, vibrate: true
vibrate: true
}
} }
function saveSettings(settings) { const saveSettings = (settings) => {
wx.setStorageSync(SETTINGS_KEY, settings) wx.setStorageSync(SETTINGS_KEY, settings)
} }
function getStreak() { const getStreak = () => wx.getStorageSync(STREAK_KEY) || { count: 0, lastDate: '' }
return wx.getStorageSync(STREAK_KEY) || { count: 0, lastDate: '' }
}
function updateStreak(date) { const updateStreak = (date) => {
var streak = getStreak() const streak = getStreak()
var today = date || getToday() const today = date || getToday()
var yesterday = getDateOffset(today, -1) const yesterday = getDateOffset(today, -1)
if (streak.lastDate === today) return streak if (streak.lastDate === today) return streak
@@ -81,52 +76,42 @@ function updateStreak(date) {
return streak return streak
} }
function getToday() { const getToday = () => formatDate(new Date())
return formatDate(new Date())
}
function formatDate(date) { const getDateOffset = (dateStr, offset) => {
var y = date.getFullYear() const d = new Date(dateStr)
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) d.setDate(d.getDate() + offset)
return formatDate(d) return formatDate(d)
} }
function getTodayRecord() { const getTodayRecord = () => {
var today = getToday() const today = getToday()
var month = today.substring(0, 7) const records = getRecords()
var records = getRecordsByMonth(month) let totalDuration = 0
var totalDuration = 0 let hasRecord = false
var hasRecord = false Object.keys(records).forEach((month) => {
for (var i = 0; i < records.length; i++) { records[month].forEach((r) => {
if (records[i].date === today) { if (r.date === today) {
totalDuration += records[i].duration totalDuration += r.duration
hasRecord = true hasRecord = true
} }
} })
})
return hasRecord ? { date: today, duration: totalDuration } : null return hasRecord ? { date: today, duration: totalDuration } : null
} }
module.exports = { module.exports = {
getRecords: getRecords, getRecords,
saveRecord: saveRecord, saveRecord,
deleteRecord: deleteRecord, deleteRecord,
getRecordsByMonth: getRecordsByMonth, getRecordsByMonth,
getTotalStats: getTotalStats, getTotalStats,
getSettings: getSettings, getSettings,
saveSettings: saveSettings, saveSettings,
getStreak: getStreak, getStreak,
updateStreak: updateStreak, updateStreak,
getToday: getToday, getToday,
formatDate: formatDate, formatDate,
getDateOffset: getDateOffset, getDateOffset,
getTodayRecord: getTodayRecord getTodayRecord
} }
+27 -29
View File
@@ -1,4 +1,4 @@
var THEMES = [ const THEMES = [
{ {
id: 'orange', id: 'orange',
name: '活力橙', name: '活力橙',
@@ -41,25 +41,23 @@ var THEMES = [
} }
] ]
var DEFAULT_THEME_ID = 'orange' const DEFAULT_THEME_ID = 'orange'
var THEME_KEY = 'app_theme' const THEME_KEY = 'app_theme'
var _lastNavColor = '' let _lastNavColor = ''
var _navColorTimer = null let _navColorTimer = null
function getThemeById(id) { const getThemeById = (id) => THEMES.find(t => t.id === id) || THEMES[0]
return THEMES.find(function (t) { return t.id === id }) || THEMES[0]
}
function getCurrentTheme() { const getCurrentTheme = () => {
var id = wx.getStorageSync(THEME_KEY) || DEFAULT_THEME_ID const id = wx.getStorageSync(THEME_KEY) || DEFAULT_THEME_ID
return getThemeById(id) return getThemeById(id)
} }
function _setNavBarColor(primary) { const _setNavBarColor = (primary) => {
if (_lastNavColor === primary) return if (_lastNavColor === primary) return
_lastNavColor = primary _lastNavColor = primary
if (_navColorTimer) clearTimeout(_navColorTimer) if (_navColorTimer) clearTimeout(_navColorTimer)
_navColorTimer = setTimeout(function () { _navColorTimer = setTimeout(() => {
try { try {
wx.setNavigationBarColor({ wx.setNavigationBarColor({
frontColor: '#ffffff', frontColor: '#ffffff',
@@ -69,35 +67,35 @@ function _setNavBarColor(primary) {
}, 50) }, 50)
} }
function setTheme(id) { const setTheme = (id) => {
wx.setStorageSync(THEME_KEY, id) wx.setStorageSync(THEME_KEY, id)
var theme = getThemeById(id) const theme = getThemeById(id)
_setNavBarColor(theme.primary) _setNavBarColor(theme.primary)
var app = getApp() const app = getApp()
if (app && app.globalData) app.globalData.theme = theme if (app && app.globalData) app.globalData.theme = theme
return theme return theme
} }
function applyThemeToPage(self) { const applyThemeToPage = (self) => {
var theme = getCurrentTheme() const theme = getCurrentTheme()
self.setData({ self.setData({
theme: theme, theme,
themeStyle: '--primary:' + theme.primary + ';--primary-light:' + theme.primaryLight + ';--primary-bg:' + theme.primaryBg + ';--primary-rgb:' + theme.primaryRgb + ';' themeStyle: `--primary:${theme.primary};--primary-light:${theme.primaryLight};--primary-bg:${theme.primaryBg};--primary-rgb:${theme.primaryRgb};`
}) })
_setNavBarColor(theme.primary) _setNavBarColor(theme.primary)
} }
function getThemeStyle(theme) { const getThemeStyle = (theme) => {
var t = theme || getCurrentTheme() const t = theme || getCurrentTheme()
return '--primary:' + t.primary + ';--primary-light:' + t.primaryLight + ';--primary-bg:' + t.primaryBg + ';--primary-rgb:' + t.primaryRgb + ';' return `--primary:${t.primary};--primary-light:${t.primaryLight};--primary-bg:${t.primaryBg};--primary-rgb:${t.primaryRgb};`
} }
module.exports = { module.exports = {
THEMES: THEMES, THEMES,
DEFAULT_THEME_ID: DEFAULT_THEME_ID, DEFAULT_THEME_ID,
getThemeById: getThemeById, getThemeById,
getCurrentTheme: getCurrentTheme, getCurrentTheme,
setTheme: setTheme, setTheme,
applyThemeToPage: applyThemeToPage, applyThemeToPage,
getThemeStyle: getThemeStyle getThemeStyle
} }
+72 -87
View File
@@ -1,92 +1,77 @@
function Timer(options) { class Timer {
options = options || {} constructor(options = {}) {
this.onTick = options.onTick || function () {} this.onTick = options.onTick || (() => {})
this.onComplete = options.onComplete || function () {} this.onComplete = options.onComplete || (() => {})
this._duration = 0 this._duration = 0
this._remaining = 0 this._remaining = 0
this._elapsed = 0 this._elapsed = 0
this._startTime = 0 this._startTime = 0
this._pausedTime = 0 this._pausedTime = 0
this._intervalId = null this._intervalId = null
this._running = false this._running = false
this._paused = false this._paused = false
this._completed = 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 })
} }
start(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()
this._intervalId = setInterval(() => { this._tick() }, 1000)
}
pause() {
if (!this._running || this._paused) return
this._paused = true
this._pausedTime = Date.now()
clearInterval(this._intervalId)
this._intervalId = null
}
resume() {
if (!this._running || !this._paused) return
this._startTime += Date.now() - this._pausedTime
this._paused = false
this._tick()
this._intervalId = setInterval(() => { this._tick() }, 1000)
}
stop() {
this._running = false
this._paused = false
clearInterval(this._intervalId)
this._intervalId = null
return this._elapsed
}
_tick() {
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 })
}
}
get isRunning() { return this._running && !this._paused }
get isPaused() { return this._paused }
get isCompleted() { return this._completed }
get remaining() { return this._remaining }
get elapsed() { return this._elapsed }
} }
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 module.exports = Timer
+31 -26
View File
@@ -1,31 +1,28 @@
function formatTime(seconds) { const formatTime = (seconds) => {
var m = Math.floor(seconds / 60) const m = Math.floor(seconds / 60)
var s = seconds % 60 const s = seconds % 60
return (m < 10 ? '0' : '') + m + ':' + (s < 10 ? '0' : '') + s return `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`
} }
function formatDuration(seconds) { const formatDuration = (seconds) => {
if (seconds < 60) return seconds + '秒' if (seconds < 60) return `${seconds}`
var m = Math.floor(seconds / 60) const m = Math.floor(seconds / 60)
var s = seconds % 60 const s = seconds % 60
return s > 0 ? m + '分' + s + '秒' : m + '分钟' return s > 0 ? `${m}${s}` : `${m}分钟`
} }
function getDaysInMonth(year, month) { const getDaysInMonth = (year, month) => new Date(year, month, 0).getDate()
return new Date(year, month, 0).getDate()
}
function getMonthCalendar(year, month) { const getMonthCalendar = (year, month) => {
var firstDay = new Date(year, month - 1, 1) const firstDay = new Date(year, month - 1, 1)
var lastDay = new Date(year, month, 0) const daysInMonth = new Date(year, month, 0).getDate()
var daysInMonth = lastDay.getDate() const startDayOfWeek = firstDay.getDay()
var startDayOfWeek = firstDay.getDay()
var weeks = [] const weeks = []
var week = [null, null, null, null, null, null, null] let week = [null, null, null, null, null, null, null]
for (var d = 1; d <= daysInMonth; d++) { for (let d = 1; d <= daysInMonth; d++) {
var dow = (startDayOfWeek + d - 1) % 7 const dow = (startDayOfWeek + d - 1) % 7
week[dow] = d week[dow] = d
if (dow === 6 || d === daysInMonth) { if (dow === 6 || d === daysInMonth) {
weeks.push(week.slice()) weeks.push(week.slice())
@@ -36,9 +33,17 @@ function getMonthCalendar(year, month) {
return weeks return weeks
} }
module.exports = { const formatDate = (date) => {
formatTime: formatTime, const y = date.getFullYear()
formatDuration: formatDuration, const m = String(date.getMonth() + 1).padStart(2, '0')
getDaysInMonth: getDaysInMonth, const d = String(date.getDate()).padStart(2, '0')
getMonthCalendar: getMonthCalendar return `${y}-${m}-${d}`
}
module.exports = {
formatTime,
formatDuration,
getDaysInMonth,
getMonthCalendar,
formatDate
} }