const { withDataCache } = require('../../utils/page-cache'); const { getLayout } = require('../../utils/layout'); // pages/checkin-detail/checkin-detail.js const { api, getImageUrl, API_BASE, uploadFileCompat } = require('../../utils/request'); const { createSwipeBack } = require('../../utils/util'); const { requestSubscribeAfterAction } = require('../../utils/subscribe'); const COOLDOWN = 5000; Page(withDataCache({ onResize: function() { this.setData(getLayout()); }, data: { loading: true, loadError: false, item: null, logs: [], logsPage: 1, logsTotal: 0, logsHasMore: false, logsLoading: false, logsError: false, freqLabel: '', typeLabel: '', myStreak: 0, partnerStreak: 0, showCheckModal: false, checkPhoto: '', checkNote: '', lastActionTime: 0, detailId: 0, myAvatar: '', partnerAvatar: '', myName: '我', partnerName: 'TA' }, onLoad: function(options) { this.setData(getLayout()); var id = options.id; if (id) { this.setData({ detailId: parseInt(id) }); this.loadCoupleInfo(); this.loadDetail(parseInt(id)); } var self = this; var swipe = createSwipeBack(function() { wx.navigateBack(); }); this.onTouchStart = swipe.onTouchStart; this.onTouchEnd = swipe.onTouchEnd; }, noop: function() {}, retryDetail: function() { this.loadDetail(this.data.detailId); }, onShow: function() { if (this.data.detailId) { this.loadCoupleInfo(); this.loadDetail(this.data.detailId); } }, // 从接口加载情侣头像和名称 loadCoupleInfo: function() { var self = this; return api.coupleInfo().then(function(info) { self._applyCoupleInfo(info); }).catch(function(e) { console.error(e); }); }, _applyCoupleInfo: function(info) { if (!info) return; var myUid = wx.getStorageSync('userId'); var p1 = info.partner1 || {}, p2 = info.partner2 || {}; var me = (p1.id == myUid) ? p1 : p2; var partner = (p1.id == myUid) ? p2 : p1; this.setData({ myAvatar: getImageUrl(me.avatar || ''), partnerAvatar: getImageUrl(partner.avatar || ''), myName: me.nickname || '我', partnerName: partner.nickname || 'TA' }); }, loadDetail: async function(id) { if (this._pageLoading) return; this._pageLoading = true; this._logsGeneration = (this._logsGeneration || 0) + 1; this.setData({ logsLoading: false, logsError: false }); this.setData({loading: !this._hasCachedData, loadError:false}); try { var data = await api.checkinDetail(id, { page: 1, page_size: 20 }); var item = data.item || {}; var myUid = wx.getStorageSync('userId'); var freqLabels = { daily: '每天', weekly: '每周', monthly: '每月', once: '一次', unlimited: '不限' }; var typeLabels = { both: '双方', first: '第一视角', her: '她打卡' }; var freqLabel = freqLabels[item.frequency] || '每天'; var typeLabel = typeLabels[item.type] || '双方'; // 创建人头像URL item.creator_avatar_url = item.creator_avatar ? getImageUrl(item.creator_avatar) : ''; // 使用本次页面加载的情侣信息 var myAvatar = this.data.myAvatar; var partnerAvatar = this.data.partnerAvatar; var myName = this.data.myName; var partnerName = this.data.partnerName; var logs = this.formatLogs(data.logs || []); // 根据打卡权限类型映射统计数据 var isCreator = (myUid == item.created_by); var showMyStats = (item.type == 'both') || (item.type == 'first'); var showPartnerStats = (item.type == 'both') || (item.type == 'her'); var checkinerIsMe = (item.type == 'first' && isCreator) || (item.type == 'her' && !isCreator); var displayAvatar = (item.type == 'both' || checkinerIsMe) ? myAvatar : partnerAvatar; var displayName = (item.type == 'both' || checkinerIsMe) ? myName : partnerName; var checkinToday = checkinerIsMe ? (data.my_today || 0) : (data.partner_today || 0); var checkinWeek = checkinerIsMe ? (data.my_week || 0) : (data.partner_week || 0); var checkinMonth = checkinerIsMe ? (data.my_month || 0) : (data.partner_month || 0); var checkinYear = checkinerIsMe ? (data.my_year || 0) : (data.partner_year || 0); var checkinStreak = checkinerIsMe ? (item.my_streak || 0) : (item.partner_streak || 0); var pageData = { item: item, logs: logs, logsPage: 1, logsTotal: Number(data.total) || logs.length, logsHasMore: data.has_more === true, logsError: false, freqLabel: freqLabel, typeLabel: typeLabel, myStreak: (item.type == 'both') ? (item.my_streak || 0) : checkinStreak, partnerStreak: (item.type == 'both') ? (item.partner_streak || 0) : 0, showMyStats: showMyStats, showPartnerStats: showPartnerStats, myToday: (item.type == 'both') ? (data.my_today || 0) : checkinToday, myWeek: (item.type == 'both') ? (data.my_week || 0) : checkinWeek, myMonth: (item.type == 'both') ? (data.my_month || 0) : checkinMonth, myYear: (item.type == 'both') ? (data.my_year || 0) : checkinYear, partnerToday: (item.type == 'both') ? (data.partner_today || 0) : 0, partnerWeek: (item.type == 'both') ? (data.partner_week || 0) : 0, partnerMonth: (item.type == 'both') ? (data.partner_month || 0) : 0, partnerYear: (item.type == 'both') ? (data.partner_year || 0) : 0, myAvatar: displayAvatar, myName: displayName, partnerAvatar: partnerAvatar, partnerName: partnerName }; this.setData(pageData); } catch(e) { this.setData({loadError:true}); console.error(e); } finally { this._pageLoading = false; this.setData({loading:false}); } }, formatLogs: function(logs) { return logs.map(function(log) { return Object.assign({}, log, { time: log.created_at ? log.created_at.substring(0, 16).replace('T', ' ') : '', photoUrl: log.photo ? getImageUrl(log.photo) : '' }); }); }, loadMoreLogs: async function() { if (this._pageLoading || this.data.loading || this.data.loadError || this.data.logsLoading || !this.data.logsHasMore) return; var generation = this._logsGeneration; var page = this.data.logsPage + 1; this.setData({ logsLoading: true, logsError: false }); try { var data = await api.checkinDetail(this.data.detailId, { page: page, page_size: 20 }); if (generation !== this._logsGeneration) return; var seen = new Set(this.data.logs.map(function(log) { return String(log.id); })); var added = this.formatLogs(data.logs || []).filter(function(log) { var id = String(log.id); if (seen.has(id)) return false; seen.add(id); return true; }); this.setData({ logs: this.data.logs.concat(added), logsPage: page, logsTotal: Number(data.total) || 0, logsHasMore: data.has_more === true }); } catch (e) { if (generation === this._logsGeneration) this.setData({ logsError: true }); } finally { if (generation === this._logsGeneration) this.setData({ logsLoading: false }); } }, // ===== 打卡操作 ===== showCheckinModal: function() { var now = Date.now(); if (now - this.data.lastActionTime < COOLDOWN) { wx.showToast({ title: '操作太快,请稍后', icon: 'none' }); return; } this.data.lastActionTime = now; this.setData({ showCheckModal: true, checkPhoto: '', checkNote: '' }); }, onCheckNoteInput: function(e) { this.setData({ checkNote: e.detail.value }); }, chooseCheckPhoto: function() { var self = this; wx.chooseImage({ count: 1, sizeType: ['compressed'], sourceType: ['album', 'camera'], success: function(res) { self.setData({ checkPhoto: res.tempFilePaths[0] }); } }); }, submitCheck: async function() { var item = this.data.item; if (!item || this._submittingCheck) return; this._submittingCheck = true; try { // 为本人接收TA的提醒补充授权。 await requestSubscribeAfterAction('checkin'); var photoPath = ''; if (this.data.checkPhoto) { wx.showLoading({ title: '上传中...' }); try { var upResult = await uploadFileCompat({ url: API_BASE + '/upload', filePath: this.data.checkPhoto, name: 'file', formData: { sub_dir: 'checkins' } }); photoPath = upResult.path || ''; } catch(e) { // 内容安全违规:抛出错误中断打卡并提示用户 if (e && e.error) { throw e; } photoPath = ''; } wx.hideLoading(); } var res = await api.checkinDo({ id: item.id, photo: photoPath, note: this.data.checkNote }); this.setData({ showCheckModal: false }); var toast = '打卡成功 ✅'; if (item.type === 'both' && item.partner_done_today !== true && !res.both_done) { toast = '已打卡,等待对方'; } wx.showToast({ title: toast, icon: 'success' }); // 刷新详情数据 this.loadDetail(item.id); } catch(e) { wx.hideLoading(); wx.showToast({ title: e.error || '打卡失败', icon: 'none' }); } finally { this._submittingCheck = false; } }, hideCheckModal: function() { this.setData({ showCheckModal: false }); }, // ===== 图片预览 ===== previewPhoto: function(e) { var url = e.currentTarget.dataset.url; if (url) { wx.previewImage({ urls: [url], current: url }); } }, goBack: function() { wx.navigateBack(); }, goHome: function() { wx.switchTab({ url: '/pages/index/index' }); }, goProfile: function() { wx.switchTab({ url: '/pages/profile/profile' }); } }, {"loadDetail": ["checkinDetail", "coupleInfo"]}));