feat: 修复清除数据、语音缓存、排行榜限制,新增项目配置文件
- 修复 cloud.clearAll 静默失败导致重启后数据恢复 - 清除数据弹窗改用自定义 ui-modal 替代 wx.showModal - TTS 语音合成增加 fileID 持久化缓存,重启不再重新合成 - 排行榜限制前15名,maxRank 参数化到 config.js - 新增 config.js 统一管理版本号/更新日期/开发者/排行榜限制 - progress-ring 消除 getSystemInfoSync 弃用警告 - voice.js 增加 InnerAudioContext 错误监听 - storage/util 完善用户资料、打卡日期精度、记录日志
This commit is contained in:
+56
-14
@@ -1,10 +1,11 @@
|
||||
const { formatDate } = require('./util')
|
||||
const { formatDate, dateOnly } = require('./util')
|
||||
const cloud = require('./cloud')
|
||||
|
||||
const RECORDS_KEY = 'training_records'
|
||||
const SETTINGS_KEY = 'user_settings'
|
||||
const STREAK_KEY = 'current_streak'
|
||||
const CUSTOM_PLANS_KEY = 'custom_plans'
|
||||
const PROFILE_KEY = 'user_profile'
|
||||
|
||||
/**
|
||||
* Backfill an `id` for any record that doesn't have one.
|
||||
@@ -52,6 +53,7 @@ const saveRecord = (record) => {
|
||||
records[month].push(record)
|
||||
records[month].sort((a, b) => b.date.localeCompare(a.date))
|
||||
wx.setStorageSync(RECORDS_KEY, records)
|
||||
console.log('[storage] saveRecord date=' + record.date + ' duration=' + record.duration)
|
||||
cloud.pushAll()
|
||||
}
|
||||
|
||||
@@ -105,6 +107,34 @@ const saveSettings = (settings) => {
|
||||
cloud.pushAll()
|
||||
}
|
||||
|
||||
/**
|
||||
* User profile (nickname + avatar). Stored locally and pushed to the
|
||||
* cloud alongside records/settings so the leaderboard can show real
|
||||
* names instead of masked openids.
|
||||
*
|
||||
* Shape: { nickname: string, avatarUrl: string (wxfile://...) }
|
||||
* Both fields are optional; absent profile returns `{}`.
|
||||
*/
|
||||
const getProfile = () => {
|
||||
const raw = wx.getStorageSync(PROFILE_KEY)
|
||||
if (raw && typeof raw === 'object' && !Array.isArray(raw)) return raw
|
||||
return {}
|
||||
}
|
||||
|
||||
const saveProfile = (profile) => {
|
||||
// Strip empties so we don't push {nickname: '', avatarUrl: ''}
|
||||
const clean = {}
|
||||
if (profile && typeof profile.nickname === 'string' && profile.nickname.trim()) {
|
||||
clean.nickname = profile.nickname.trim().slice(0, 16)
|
||||
}
|
||||
if (profile && typeof profile.avatarUrl === 'string' && profile.avatarUrl) {
|
||||
clean.avatarUrl = profile.avatarUrl
|
||||
}
|
||||
wx.setStorageSync(PROFILE_KEY, clean)
|
||||
cloud.pushAll()
|
||||
return clean
|
||||
}
|
||||
|
||||
const getStreak = () => wx.getStorageSync(STREAK_KEY) || { count: 0, lastDate: '' }
|
||||
|
||||
/**
|
||||
@@ -142,25 +172,30 @@ const resetCustomPlan = (planId) => {
|
||||
* Validate streak against actual training records.
|
||||
* Call on app launch to correct any inconsistencies
|
||||
* (e.g. after deleting the only record for a day).
|
||||
*
|
||||
* Streak compares date-ONLY portions of `lastDate` so that training
|
||||
* twice on the same day (with different seconds in `record.date`)
|
||||
* doesn't accidentally reset the streak.
|
||||
*/
|
||||
const validateStreak = () => {
|
||||
const streak = getStreak()
|
||||
if (!streak.lastDate) return streak
|
||||
|
||||
const today = getToday()
|
||||
const yesterday = getDateOffset(today, -1)
|
||||
const todayOnly = dateOnly(today)
|
||||
const yesterdayOnly = dateOnly(getDateOffset(today, -1))
|
||||
const records = getRecords()
|
||||
const allDates = new Set()
|
||||
Object.values(records).forEach(monthRecs => {
|
||||
monthRecs.forEach(r => allDates.add(r.date))
|
||||
monthRecs.forEach(r => allDates.add(dateOnly(r.date)))
|
||||
})
|
||||
|
||||
// If lastDate is today, check today still has records
|
||||
if (streak.lastDate === today) {
|
||||
if (!allDates.has(today)) {
|
||||
if (dateOnly(streak.lastDate) === todayOnly) {
|
||||
if (!allDates.has(todayOnly)) {
|
||||
// Today's records were deleted
|
||||
if (allDates.has(yesterday)) {
|
||||
streak.lastDate = yesterday
|
||||
if (allDates.has(yesterdayOnly)) {
|
||||
streak.lastDate = yesterdayOnly
|
||||
streak.count = Math.max(1, streak.count - 1)
|
||||
} else {
|
||||
streak.count = 0
|
||||
@@ -178,17 +213,20 @@ const validateStreak = () => {
|
||||
|
||||
const updateStreak = (date) => {
|
||||
const streak = getStreak()
|
||||
const today = date || getToday()
|
||||
const yesterday = getDateOffset(today, -1)
|
||||
const todayFull = date || getToday()
|
||||
const todayOnly = dateOnly(todayFull)
|
||||
const yesterdayOnly = dateOnly(getDateOffset(todayFull, -1))
|
||||
|
||||
if (streak.lastDate === today) return streak
|
||||
if (dateOnly(streak.lastDate) === todayOnly) return streak
|
||||
|
||||
if (streak.lastDate === yesterday) {
|
||||
if (dateOnly(streak.lastDate) === yesterdayOnly) {
|
||||
streak.count += 1
|
||||
} else {
|
||||
streak.count = 1
|
||||
}
|
||||
streak.lastDate = today
|
||||
// Store the date-ONLY portion so subsequent comparisons are stable
|
||||
// across multiple same-day trainings with different seconds.
|
||||
streak.lastDate = todayOnly
|
||||
wx.setStorageSync(STREAK_KEY, streak)
|
||||
cloud.pushAll()
|
||||
return streak
|
||||
@@ -197,8 +235,10 @@ const updateStreak = (date) => {
|
||||
const getToday = () => formatDate(new Date())
|
||||
|
||||
const getDateOffset = (dateStr, offset) => {
|
||||
// Use YYYY/MM/DD for iOS compatibility (YYYY-MM-DD may fail on some iOS)
|
||||
const d = new Date(dateStr.replace(/-/g, '/'))
|
||||
// Strip the time portion (formatDate now includes HH:MM:SS) and convert
|
||||
// dashes to slashes — `new Date("YYYY/MM/DD")` is the only reliably-
|
||||
// parsed form across both V8 (WeChat dev tools) and JSCore (iOS).
|
||||
const d = new Date(dateOnly(dateStr).replace(/-/g, '/'))
|
||||
d.setDate(d.getDate() + offset)
|
||||
return formatDate(d)
|
||||
}
|
||||
@@ -230,6 +270,8 @@ module.exports = {
|
||||
getCustomPlans,
|
||||
saveCustomPlan,
|
||||
resetCustomPlan,
|
||||
getProfile,
|
||||
saveProfile,
|
||||
getStreak,
|
||||
validateStreak,
|
||||
updateStreak,
|
||||
|
||||
Reference in New Issue
Block a user