diff --git a/app.js b/app.js index a1a7859..17a552a 100644 --- a/app.js +++ b/app.js @@ -1,7 +1,11 @@ const themeMod = require('./utils/theme') +const cloud = require('./utils/cloud') App({ onLaunch() { + // Init cloud storage (no-op if not configured) + cloud.init() + const settings = wx.getStorageSync('user_settings') if (!settings) { wx.setStorageSync('user_settings', { @@ -12,11 +16,37 @@ App({ vibrate: true }) } + const streak = wx.getStorageSync('current_streak') if (!streak) { wx.setStorageSync('current_streak', { count: 0, lastDate: '' }) } + this.globalData.theme = themeMod.getCurrentTheme() + + // If local records are empty, try pulling from cloud + const records = wx.getStorageSync('training_records') + if (!records || Object.keys(records).length === 0) { + cloud.pullAll().then((cloudData) => { + if (!cloudData) return + try { + if (cloudData.records && Object.keys(cloudData.records).length > 0) { + wx.setStorageSync('training_records', cloudData.records) + } + if (cloudData.settings) { + wx.setStorageSync('user_settings', cloudData.settings) + } + if (cloudData.streak) { + wx.setStorageSync('current_streak', cloudData.streak) + } + if (cloudData.themeId) { + themeMod.setTheme(cloudData.themeId) + } + } catch (e) { + console.error('Cloud restore failed:', e) + } + }) + } }, globalData: { diff --git a/pages/settings/settings.js b/pages/settings/settings.js index e01adf5..14b99ff 100644 --- a/pages/settings/settings.js +++ b/pages/settings/settings.js @@ -112,6 +112,8 @@ Page({ if (res.confirm) { wx.removeStorageSync('training_records') wx.removeStorageSync('current_streak') + // also clear cloud data + try { require('../../utils/cloud').clearAll() } catch (e) {} wx.showToast({ title: '已清除', icon: 'success', duration: 1500 }) } } diff --git a/project.config.json b/project.config.json index 1922dca..6ef8d18 100644 --- a/project.config.json +++ b/project.config.json @@ -47,6 +47,7 @@ "disableSWC": true }, "compileType": "miniprogram", + "cloudfunctionRoot": "cloudfunctions/", "libVersion": "3.3.4", "appid": "wxb7f1bc86924869bc", "projectname": "pbzc", diff --git a/utils/cloud.js b/utils/cloud.js new file mode 100644 index 0000000..0b08927 --- /dev/null +++ b/utils/cloud.js @@ -0,0 +1,102 @@ +const DB_COLLECTION = 'plank_data' + +let _db = null +let _pushTimer = null +let _enabled = false + +const getDb = () => { + if (!_enabled) return null + if (!_db) { + try { _db = wx.cloud.database() } catch (e) { _enabled = false } + } + return _db +} + +/** + * Call once from app.js onLaunch to initialize cloud. + * Gracefully degrades if cloud environment isn't configured yet. + */ +const init = () => { + try { + wx.cloud.init({ traceUser: true }) + _enabled = true + } catch (e) { + _enabled = false + } +} + +/** + * Pull all user data from cloud. Returns null if cloud is unavailable or no data exists. + */ +const pullAll = async () => { + const db = getDb() + if (!db) return null + try { + const coll = db.collection(DB_COLLECTION) + const res = await coll.where({ _openid: '{openid}' }).get() + if (res && res.data && res.data.length > 0) { + return res.data[0] + } + return null + } catch (e) { + console.error('Cloud pull failed:', e) + return null + } +} + +/** + * Schedule a cloud push (debounced at 2s). + * Call this after any local data mutation. + */ +const pushAll = () => { + if (!_enabled) return + if (_pushTimer) clearTimeout(_pushTimer) + _pushTimer = setTimeout(() => _doPush(), 2000) +} + +const _doPush = async () => { + const db = getDb() + if (!db) return + try { + // Lazily require to avoid circular dependency at module-load time + const storage = require('./storage') + const themeMod = require('./theme') + + const data = { + records: storage.getRecords(), + settings: storage.getSettings(), + streak: storage.getStreak(), + themeId: themeMod.getCurrentTheme().id, + updatedAt: new Date() + } + + const coll = db.collection(DB_COLLECTION) + const existing = await coll.where({ _openid: '{openid}' }).get() + if (existing && existing.data && existing.data.length > 0) { + await coll.doc(existing.data[0]._id).update({ data }) + } else { + await coll.add({ data }) + } + } catch (e) { + console.error('Cloud push failed:', e) + } +} + +/** + * Delete all cloud data for current user. + */ +const clearAll = async () => { + const db = getDb() + if (!db) return + try { + const coll = db.collection(DB_COLLECTION) + const existing = await coll.where({ _openid: '{openid}' }).get() + if (existing && existing.data && existing.data.length > 0) { + await coll.doc(existing.data[0]._id).remove() + } + } catch (e) { + console.error('Cloud clear failed:', e) + } +} + +module.exports = { init, pullAll, pushAll, clearAll, get enabled() { return _enabled } } diff --git a/utils/storage.js b/utils/storage.js index b69f611..bde7aad 100644 --- a/utils/storage.js +++ b/utils/storage.js @@ -1,4 +1,5 @@ const { formatDate } = require('./util') +const cloud = require('./cloud') const RECORDS_KEY = 'training_records' const SETTINGS_KEY = 'user_settings' @@ -14,6 +15,7 @@ const saveRecord = (record) => { records[month].push(record) records[month].sort((a, b) => b.date.localeCompare(a.date)) wx.setStorageSync(RECORDS_KEY, records) + cloud.pushAll() } const deleteRecord = (recordId) => { @@ -23,6 +25,7 @@ const deleteRecord = (recordId) => { if (records[month].length === 0) delete records[month] }) wx.setStorageSync(RECORDS_KEY, records) + cloud.pushAll() } const getRecordsByMonth = (month) => { @@ -55,6 +58,7 @@ const getSettings = () => wx.getStorageSync(SETTINGS_KEY) || { const saveSettings = (settings) => { wx.setStorageSync(SETTINGS_KEY, settings) + cloud.pushAll() } const getStreak = () => wx.getStorageSync(STREAK_KEY) || { count: 0, lastDate: '' } @@ -73,6 +77,7 @@ const updateStreak = (date) => { } streak.lastDate = today wx.setStorageSync(STREAK_KEY, streak) + cloud.pushAll() return streak } diff --git a/utils/theme.js b/utils/theme.js index cc2e2ec..aab9ae5 100644 --- a/utils/theme.js +++ b/utils/theme.js @@ -88,6 +88,9 @@ const setTheme = (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 }