const store = require('../../utils/store.js'); const api = require('../../utils/api.js'); const { toastErr } = require('../../utils/ui.js'); function pad2(n) { return n < 10 ? '0' + n : '' + n; } function ymd(d) { return d.getFullYear() + '-' + pad2(d.getMonth() + 1) + '-' + pad2(d.getDate()); } // "2026-07-04" → "7月4日" function fmtMD(date) { const p = (date || '').split('-'); return p.length < 3 ? '' : +p[1] + '月' + +p[2] + '日'; } function parseYMD(s) { const p = (s || '').split('-'); return p.length < 3 ? null : new Date(+p[0], +p[1] - 1, +p[2]); } // 自然月网格。周一为一周起点,首尾补空格对齐星期。 // 按月切而不是「从计划开始日起 30 天」:那样窗口跨两个月, // 用户看着一个 7月26日—8月25日 的日历,既不像本月也不像下月。 function buildCells(year, month, byDate, todayStr) { const first = new Date(year, month - 1, 1); const lead = (first.getDay() + 6) % 7; const total = new Date(year, month, 0).getDate(); const cells = []; for (let i = 0; i < lead; i++) cells.push({ blank: true }); for (let d = 1; d <= total; d++) { const s = year + '-' + pad2(month) + '-' + pad2(d); cells.push({ day: d, date: s, count: (byDate[s] || []).length, today: s === todayStr }); } while (cells.length % 7) cells.push({ blank: true }); return cells; } // off=0 本月,off=1 下月 function monthOf(off) { const n = new Date(); const d = new Date(n.getFullYear(), n.getMonth() + off, 1); return { year: d.getFullYear(), month: d.getMonth() + 1 }; } function monthEnd(off) { const m = monthOf(off); return m.year + '-' + pad2(m.month) + '-' + pad2(new Date(m.year, m.month, 0).getDate()); } Page({ data: { pet: {}, plan: null, cells: [], monthOff: 0, monthLabel: '', monthEndLabel: '', nextCount: 0, pickStart: '', pickEnd: '', selDate: '', selLabel: '', dayTasks: [], editShow: false, editId: '', form: { title: '', date: '', description: '', daily: false }, saving: false, sheetShow: false, sheetType: '', }, onLoad() { this._unsub = store.subscribe((pet) => { this.setData({ pet }); if (this._inited) this.load(); }); }, onUnload() { if (this._unsub) this._unsub(); }, onShow() { store .ready() .then(() => { this._inited = true; this.setData({ pet: store.getPet() }); this.load(); }) .catch((e) => toastErr(e)); }, load() { const id = store.currentPetId(); if (!id) return; api .getPlan(id) .then((plan) => this.apply(plan)) .catch(() => this.setData({ plan: null, cells: [], dayTasks: [] })); }, apply(plan) { const tasks = plan.tasks || []; const byDate = {}; tasks.forEach((t) => { if (!t.date) return; (byDate[t.date] = byDate[t.date] || []).push(t); }); this._byDate = byDate; this.setData({ plan: { ...plan, tasks }, // 能排的区间:不早于计划开始日,不晚于下月月底。和后端 dayOffset 的校验一致 pickStart: (plan.start_date || '').slice(0, 10), pickEnd: monthEnd(1), }); this.renderMonth(this.data.monthOff); if (this._pendingSel) { // 刚加/改完,跳到那一天,不然用户看不到东西去哪了 this.selectDay(this._pendingSel); this._pendingSel = ''; } }, renderMonth(off) { const m = monthOf(off); const todayStr = ymd(new Date()); const byDate = this._byDate || {}; const prefix = m.year + '-' + pad2(m.month); this.setData({ monthOff: off, monthLabel: m.year + ' 年 ' + m.month + ' 月', monthEndLabel: fmtMD(monthEnd(off)), cells: buildCells(m.year, m.month, byDate, todayStr), nextCount: Object.keys(byDate).filter((d) => d.indexOf(monthOf(1).year + '-' + pad2(monthOf(1).month)) === 0) .reduce((n, d) => n + byDate[d].length, 0), }); // 切到本月默认选今天;切到下月默认选 1 号(今天不在那个月里) const inMonth = todayStr.indexOf(prefix) === 0; this.selectDay(inMonth ? todayStr : prefix + '-01', todayStr); }, switchMonth(e) { this.renderMonth(Number(e.currentTarget.dataset.off)); }, selectDay(date, todayStr) { const t = todayStr || ymd(new Date()); this.setData({ selDate: date, selLabel: date === t ? '今天的安排' : fmtMD(date) + '的安排', dayTasks: (this._byDate || {})[date] || [], }); }, onTapDay(e) { const d = e.currentTarget.dataset.date; if (d) this.selectDay(d); }, toggleTask(e) { api .togglePlanTask(e.currentTarget.dataset.id) .then(() => this.load()) .catch((err) => toastErr(err, '操作失败')); }, // ── 加 / 改 ── openAdd() { this.setData({ editShow: true, editId: '', form: { title: '', date: this.data.selDate || ymd(new Date()), description: '', daily: false }, }); }, openEdit(e) { const t = (this.data.dayTasks || []).find((x) => x.id === e.currentTarget.dataset.id); if (!t) return; this.setData({ editShow: true, editId: t.id, form: { title: t.title, date: t.date || this.data.selDate, description: t.description || '', daily: false }, }); }, closeEdit() { this.setData({ editShow: false }); }, noop() {}, onFormTitle(e) { this.setData({ 'form.title': e.detail.value }); }, onFormDate(e) { this.setData({ 'form.date': e.detail.value }); }, onFormDesc(e) { this.setData({ 'form.description': e.detail.value }); }, onFormDaily(e) { this.setData({ 'form.daily': e.detail.value }); }, onSubmit() { if (this.data.saving) return; const f = this.data.form; if (!(f.title || '').trim()) return wx.showToast({ title: '写一下做什么', icon: 'none' }); const id = store.currentPetId(); if (!id) return; this.setData({ saving: true }); const req = this.data.editId ? api.updatePlanTask(this.data.editId, f) : api.addPlanTask(id, f); req .then(() => { this.setData({ saving: false, editShow: false }); // 加完/改完要跳到那一天,不然用户改了日期却看不到东西去哪了 this._pendingSel = f.date; this.load(); }) .catch((e) => { this.setData({ saving: false }); toastErr(e, '保存失败'); }); }, onDelete(e) { const { id, title } = e.currentTarget.dataset; wx.showModal({ title: '删掉这件事?', content: title || '', confirmColor: '#EE7D73', success: (r) => { if (!r.confirm) return; api .deletePlanTask(id) .then(() => this.load()) .catch((err) => toastErr(err, '删除失败')); }, }); }, closeSheet() { this.setData({ sheetShow: false }); }, onAddPet() { wx.navigateTo({ url: '/pages/petform/petform' }); }, onShareAppMessage() { return { title: '我给毛孩子做了份养护计划', path: '/pages/plan/plan' }; }, onShareTimeline() { return { title: '我给毛孩子做了份养护计划' }; }, });