// pages/summary/summary — 3 成果卡 // 「狗狗走了」视角;补充字段全选填(R7);Canvas 生成分享图;订阅提醒 const { call } = require('../../utils/cloud.js'); const store = require('../../utils/store.js'); const { formatDuration } = require('../../utils/format.js'); const { chooseAndUpload } = require('../../utils/media.js'); const { SUBSCRIBE_TEMPLATE_ID } = require('../../utils/config.js'); Page({ data: { loaded: false, walkId: '', primaryDogId: '', dogName: '', dogAvatar: '', headline: '', bigValue: '', bigUnit: '', timeText: '', distanceText: '—', streak: 0, newAchievements: [], weathers: [{ k: 'sunny', l: '☀️ 晴' }, { k: 'cloudy', l: '☁️ 阴' }, { k: 'rainy', l: '🌧️ 雨' }], moods: [{ k: 'happy', l: '遛得开心' }, { k: 'tired', l: '有点累' }, { k: 'excited', l: '很兴奋' }], weather: '', mood: '', note: '', photos: [], submitting: false, }, _attached: false, onLoad() { const sum = getApp().globalData.lastSummary; if (!sum) { wx.showToast({ title: '数据缺失', icon: 'none' }); setTimeout(() => wx.switchTab({ url: '/pages/home/home' }), 1000); return; } const r = (sum.results && sum.results[0]) || {}; const hasSteps = r.dogSteps != null; const dog = (store.getState().dogs || []).find((d) => d._id === r.dogId); this.setData({ loaded: true, walkId: sum.walkId, primaryDogId: r.dogId || '', dogName: r.name || '狗狗', dogAvatar: (dog && dog.avatar) || '', headline: hasSteps ? `${r.name}今天走了` : `${r.name}今天遛了`, bigValue: hasSteps ? String(r.dogSteps) : formatDuration(r.duration || 0), bigUnit: hasSteps ? '狗步' : '', timeText: formatDuration(r.duration || 0), distanceText: r.distance != null ? Number(r.distance).toFixed(1) : '—', streak: r.streak || 0, newAchievements: sum.newAchievements || [], }); }, pickWeather(e) { const k = e.currentTarget.dataset.k; this.setData({ weather: this.data.weather === k ? '' : k }); }, pickMood(e) { const k = e.currentTarget.dataset.k; this.setData({ mood: this.data.mood === k ? '' : k }); }, onNote(e) { this.setData({ note: e.detail.value }); }, async onPickPhoto() { const fileID = await chooseAndUpload('walk-photos'); if (fileID) this.setData({ photos: [fileID] }); }, // 选填字段回写(只回写一次;含雨天战士成就判定) async ensureAttached() { if (this._attached) return; const { weather, mood, note, photos, walkId } = this.data; // 无任何选填内容时直接返回,但不锁定:用户可能稍后再补填,待真正回写后才置位 if (!weather && !mood && !note && !photos.length) return; const res = await call('walkSave', { action: 'attach', walkId, weather: weather || null, mood: mood || null, note: note || null, photos, }); this._attached = true; if (res && res.ok && res.data.newAchievements && res.data.newAchievements.length) { wx.showToast({ title: `解锁 ${res.data.newAchievements[0].name}`, icon: 'none' }); } }, // ===== Canvas 生成分享图 ===== drawShareCard() { return new Promise((resolve) => { const q = wx.createSelectorQuery(); q.select('#shareCanvas').fields({ node: true, size: true }).exec((res) => { if (!res[0] || !res[0].node) return resolve(null); const canvas = res[0].node; const ctx = canvas.getContext('2d'); const dpr = (wx.getWindowInfo && wx.getWindowInfo().pixelRatio) || 2; const W = 600, H = 820; canvas.width = W * dpr; canvas.height = H * dpr; ctx.scale(dpr, dpr); // 背景 ctx.fillStyle = '#FBF8F3'; ctx.fillRect(0, 0, W, H); // 顶部色带 ctx.fillStyle = '#E8A04B'; ctx.fillRect(0, 0, W, 12); // 头像圆 ctx.fillStyle = '#F7E5CC'; ctx.beginPath(); ctx.arc(W / 2, 150, 70, 0, Math.PI * 2); ctx.fill(); ctx.textAlign = 'center'; ctx.fillStyle = '#2E2A24'; ctx.font = '600 34px sans-serif'; ctx.fillText(this.data.headline, W / 2, 290); ctx.fillStyle = '#C77E2E'; ctx.font = '700 96px sans-serif'; ctx.fillText(this.data.bigValue, W / 2, 400); if (this.data.bigUnit) { ctx.fillStyle = '#6E665B'; ctx.font = '500 28px sans-serif'; ctx.fillText(this.data.bigUnit, W / 2, 445); } // 三宫格 const cols = [ { v: this.data.timeText, k: '时长' }, { v: this.data.distanceText, k: '公里' }, { v: `第${this.data.streak}天`, k: '连续' }, ]; const baseY = 560; cols.forEach((c, i) => { const x = (W / 4) * (i + 1); ctx.fillStyle = '#2E2A24'; ctx.font = '600 36px sans-serif'; ctx.fillText(c.v, x, baseY); ctx.fillStyle = '#A89F92'; ctx.font = '400 24px sans-serif'; ctx.fillText(c.k, x, baseY + 40); }); // 页脚 ctx.fillStyle = '#A89F92'; ctx.font = '500 26px sans-serif'; ctx.fillText('汪圈 · 每一次遛狗都值得被记录', W / 2, 760); wx.canvasToTempFilePath({ canvas, success: (r) => resolve(r.tempFilePath), fail: () => resolve(null), }); }); }); }, async onShare() { if (this.data.submitting) return; this.setData({ submitting: true }); await this.ensureAttached(); const path = await this.drawShareCard(); this.setData({ submitting: false }); if (!path) return wx.showToast({ title: '生成失败', icon: 'none' }); wx.showShareImageMenu({ path }); // 用户可分享到朋友圈/好友或保存 }, // 订阅「遛狗提醒」(模板未配置时静默跳过) requestSubscribe() { if (!SUBSCRIBE_TEMPLATE_ID) return Promise.resolve(); return new Promise((resolve) => { wx.requestSubscribeMessage({ tmplIds: [SUBSCRIBE_TEMPLATE_ID], complete: resolve }); }); }, async onDone() { if (this.data.submitting) return; this.setData({ submitting: true }); await this.requestSubscribe(); // 需在用户点击手势内触发 await this.ensureAttached(); this.setData({ submitting: false }); this.sediment(); }, // 转发好友 / 朋友圈(小程序卡片) onShareAppMessage() { return { title: `${this.data.dogName}今天的遛狗成果`, path: '/pages/login/login' }; }, onShareTimeline() { return { title: `${this.data.dogName}今天也出门遛了一圈` }; }, onClose() { wx.switchTab({ url: '/pages/home/home' }); }, sediment() { const id = this.data.primaryDogId; if (id) wx.redirectTo({ url: `/pages/dog-detail/dog-detail?id=${id}` }); else wx.switchTab({ url: '/pages/home/home' }); }, });