Gate record features and storage entry points with invitation guidance; enforce record access in the backend. Add homepage greetings, right-swipe profile dismissal, and move the smoking switch to settings. Simplify API base configuration and remove the unused legacy tab bar component.
228 lines
9.7 KiB
JavaScript
228 lines
9.7 KiB
JavaScript
const { withRecordAccess } = require('../../utils/record-access');
|
|
const { tryLoginRenewal } = require('../../utils/subscribe');
|
|
const { uploadReference } = require('../../utils/file-media');
|
|
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 { guardStorageSpace } = require('../../utils/storage-guard');
|
|
const COOLDOWN = 5000;
|
|
Page(withRecordAccess(withDataCache({
|
|
onLoginSubscribeTap: function() { return tryLoginRenewal(); },
|
|
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: String(id) });
|
|
this.loadCoupleInfo();
|
|
this.loadDetail(String(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;
|
|
guardStorageSpace(0).then(function(ok) {
|
|
if (!ok) return;
|
|
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 {
|
|
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 = uploadReference(upResult);
|
|
} catch(e) {
|
|
throw e;
|
|
}
|
|
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"]})));
|