const store = require('../../utils/store.js'); const api = require('../../utils/api.js'); const { toastErr } = require('../../utils/ui.js'); const { syncTabBar } = require('../../utils/tabbar.js'); const recordTypes = require('../../utils/recordTypes.js'); const petmeta = require('../../utils/petmeta.js'); function mapTask(t) { return { id: t.id, title: t.title, sub: t.description, priority: t.priority, done: t.done, sheet: t.sheet_type }; } // 时间轴上的相对时间。首页关心的是「什么时候记的」,精确到分只在今天有意义 function fmtWhen(iso) { if (!iso) return ''; const d = new Date(iso); if (isNaN(d.getTime())) return ''; const p = (n) => (n < 10 ? '0' + n : '' + n); const now = new Date(); const sameDay = d.toDateString() === now.toDateString(); if (sameDay) return '今天 ' + p(d.getHours()) + ':' + p(d.getMinutes()); const y = new Date(now.getTime() - 86400000); if (d.toDateString() === y.toDateString()) return '昨天 ' + p(d.getHours()) + ':' + p(d.getMinutes()); return d.getMonth() + 1 + '月' + d.getDate() + '日'; } const TL_PAGE = 10; Page({ data: { pet: {}, zodiac: '', daysTogether: 0, sexIcon: '', sexTone: '', tasks: [], planHint: '', drift: {}, needNextPlan: false, monthCost: 0, timeline: [], tlPage: 1, hasMore: false, }, onLoad() { this._unsub = store.subscribe((pet) => { this.applyPet(pet); if (!this._inited) return; this.loadAll(); }); }, onUnload() { if (this._unsub) this._unsub(); }, onShow() { syncTabBar(this); recordTypes.load().catch(() => {}); store .ready() .then(() => { this._inited = true; this.applyPet(store.getPet()); this.loadAll(); }) .catch((e) => wx.showToast({ title: e.message || '加载失败', icon: 'none' })); }, // 门面上那几样都是本地算的,不用等接口 applyPet(pet) { const p = pet || {}; const badge = petmeta.genderBadge(p.gender); this.setData({ pet: p, zodiac: petmeta.zodiac(p.birthday), daysTogether: petmeta.daysTogether(p), sexIcon: badge.icon, sexTone: badge.tone, }); }, loadAll() { this.loadTasks(); this.loadTimeline(1); this.loadEntries(); this.loadDrift(); }, loadTasks() { const id = store.currentPetId(); if (!id) return; api .getTasks(id) .then((tasks) => this.setData({ tasks: (tasks || []).map(mapTask) })) .catch((e) => toastErr(e)); }, // 两个入口上的副标题:计划还剩几件、本月花了多少。 // 空着的话入口就是两个没有信息量的图标 // 阶段漂移检查。放在 loadEntries 里而不是单独一次 —— 它和「计划还剩几件」 // 都是首页那一屏要的,一起拉省一个来回 loadDrift() { const id = store.currentPetId(); if (!id) return; api .petStageDrift(id) .then((d) => this.setData({ drift: d || {} })) .catch(() => this.setData({ drift: {} })); }, loadEntries() { const id = store.currentPetId(); if (!id) return; // 副标题优先说「该排下月计划了」——那是有时效的事; // 本月还剩几件是随时能看的,让位给它 api .planMonthStatus(id) .then((st) => { const s = st || {}; this.setData({ needNextPlan: !!s.need_next, planHint: s.need_next ? (s.next_label || '下月') + '计划还没排' : s.this_undone > 0 ? '本月还有 ' + s.this_undone + ' 件' : s.this_month > 0 ? '本月都做完了' : '还没有安排', }); }) .catch(() => this.setData({ needNextPlan: false, planHint: '' })); api .getBill(id, 'month') .then((b) => this.setData({ monthCost: (b && b.total) || 0 })) .catch(() => this.setData({ monthCost: 0 })); }, loadTimeline(page) { const id = store.currentPetId(); if (!id) return; api .getRecords(id, { page, pageSize: TL_PAGE }) .then((res) => { const list = (res.list || []).map((r) => ({ id: r.id, type: r.type, tone: (recordTypes.get(r.type) || {}).tone || 'tone-1', title: r.title, description: r.description, image: r.image_url || '', timeText: fmtWhen(r.occurred_at), })); const merged = page === 1 ? list : this.data.timeline.concat(list); this.setData({ timeline: merged, tlPage: page, hasMore: merged.length < (res.total || 0) }); }) .catch(() => {}); }, loadMore() { if (this.data.hasMore) this.loadTimeline(this.data.tlPage + 1); }, previewImage(e) { const url = e.currentTarget.dataset.url; if (url) wx.previewImage({ urls: [url], current: url }); }, onDeleteRecord(e) { const id = e.currentTarget.dataset.id; wx.showModal({ title: '删掉这条记录?', content: '趋势和统计会跟着变。', confirmColor: '#EE7D73', success: (r) => { if (!r.confirm) return; api .deleteRecord(id) .then(() => this.loadTimeline(1)) .catch((err) => toastErr(err, '删除失败')); }, }); }, onTapTask(e) { const i = e.currentTarget.dataset.index; const t = this.data.tasks[i]; // 任务关联了记录类型:去记那一笔。未完成的带上 taskId, // 在记录页保存成功后才回填这条任务完成(onShow 回来自动刷新任务+最近记录) if (t.sheet) { let url = '/pages/addrecord/addrecord?type=' + t.sheet; if (!t.done) url += '&taskId=' + t.id; wx.navigateTo({ url }); return; } // 没有关联记录类型的任务(如遛狗),直接切换完成 api .toggleTask(t.id) .then((res) => { const tasks = this.data.tasks.slice(); tasks[i] = Object.assign({}, tasks[i], { done: res.done }); this.setData({ tasks }); }) .catch((err) => toastErr(err, '操作失败')); }, goPlan() { wx.navigateTo({ url: '/pages/plan/plan' }); }, // 去建档页改阶段。改完回来 onShow 会重新拉一次漂移状态,提示自然消失。 // 不在首页直接改:改阶段之后要不要套新方案是同一件事,放在一起做才连贯 // 点「一起生活的天数」空态,去建档页补到家日期 onDaysTap() { const id = this.data.pet && this.data.pet.id; if (!this.data.daysTogether && id) wx.navigateTo({ url: '/pages/petform/petform?id=' + id }); }, // 身份卡:门面卡右上角进,用当前宠物 goIDCard() { const id = this.data.pet && this.data.pet.id; if (id) wx.navigateTo({ url: '/pages/idcard/idcard?id=' + id }); }, goStageUpdate() { const id = this.data.pet && this.data.pet.id; if (id) wx.navigateTo({ url: '/pages/petform/petform?id=' + id }); }, goExpense() { wx.navigateTo({ url: '/pages/expense/expense' }); }, goRecord() { wx.navigateTo({ url: '/pages/record/record' }); }, onQuickRecord() { wx.navigateTo({ url: '/pages/record/record' }); }, onAddPet() { wx.navigateTo({ url: '/pages/petform/petform' }); }, onHeroTap() { const id = this.data.pet && this.data.pet.id; wx.navigateTo({ url: '/pages/petform/petform' + (id ? '?id=' + id : '') }); }, onShareAppMessage() { return { title: '肉垫计划 · 每天照顾好毛孩子', path: '/pages/home/home' }; }, onShareTimeline() { return { title: '肉垫计划 · 每天照顾好毛孩子' }; }, });