同步小程序与管理端更新,完善抽烟记录和解绑冷静期

This commit is contained in:
2026-10-09 22:15:51 +08:00
parent a68a4ef411
commit b10fc3c4ac
149 changed files with 3794 additions and 589 deletions
+154 -38
View File
@@ -1,12 +1,25 @@
const { withDataCache } = require('../../utils/page-cache');
const { getLayout } = require('../../utils/layout');
// pages/profile/profile.js - 与原网站一致的个人中心
const { api, getImageUrl } = require('../../utils/request');
const { saveScrollPosition, getScrollPosition } = require('../../utils/scroll-pos');
const { saveScrollPosition, resetScrollOnTabShow } = require('../../utils/scroll-pos');
const { requestSubscribe } = require('../../utils/subscribe');
Page({
Page(withDataCache({
onResize: function() { this.setData(getLayout()); },
data: {
scrollTop: 0,
statsLoading:false, statsError:false,
statisticItems:[
{key:'timeline',label:'记忆日',icon:'stat-timeline',count:'—',color:'pink'},
{key:'album',label:'相册',icon:'stat-album',count:'—',color:'yellow'},
{key:'diary',label:'日记',icon:'stat-book',count:'—',color:'mint'},
{key:'songs',label:'歌曲',icon:'stat-music',count:'—',color:'blue'},
{key:'checkin',label:'打卡',icon:'stat-check',count:'—',color:'purple'},
{key:'pet',label:'宠物',icon:'stat-pet',count:'—',color:'pink'},
{key:'period',label:'姨姨',icon:'stat-j-flower',count:'',color:'pink'},
{key:'settings',label:'更多',icon:'stat-settings',count:'',color:'gray'}
],
// 我的信息
myNickname: '未登录',
@@ -26,6 +39,7 @@ Page({
bindingKnown: false, bindingError: false,
partnerName: 'TA',
partnerDisplayId: '',
partnerId: 0,
partnerDetail: '',
partnerAvatarUrl: '',
partnerInitial: '?',
@@ -54,8 +68,9 @@ Page({
tempLoveDate: '',
tempLoveStory: '',
// 查看TA资料(只读)弹窗
// 编辑TA资料弹窗
showEditPartnerModal: false,
partnerSaving: false, partnerAvatarUploading: false,
editPartnerNickname: '',
editPartnerGender: 'male',
editPartnerGenderText: '男',
@@ -127,6 +142,7 @@ Page({
_needRefresh: true,
onScroll: function(e) {
this._scrollTop = e.detail.scrollTop;
saveScrollPosition('profile', e.detail.scrollTop);
},
@@ -137,7 +153,7 @@ Page({
this.setData({
userId: wx.getStorageSync('userId'),
coupleId: wx.getStorageSync('coupleId'),
scrollTop: getScrollPosition('profile')
scrollTop: 0
});
this.loadData();
this.loadUnbindStatus();
@@ -153,23 +169,28 @@ Page({
// 从分享邀请链接进入
if (options && options.inviter) {
var self = this;
setTimeout(function() { self.handleShareInvite(options.inviter); }, 800);
setTimeout(function() { self.handleShareInvite(options.inviter, options.invite_code); }, 800);
}
},
onShow: function() {
resetScrollOnTabShow(this, 'profile');
if (!wx.getStorageSync('coupleId') && !this.data.shareOutgoingCode) api.generateInviteLink().then(res => this.setData({shareOutgoingCode:res.code || ''})).catch(() => {});
const tabBar = this.getTabBar && this.getTabBar();
if (tabBar) tabBar.syncActive();
var uid = wx.getStorageSync('userId');
var cid = wx.getStorageSync('coupleId');
var self = this;
var savedTop = getScrollPosition('profile');
this.setData({ scrollTop: savedTop, bindingKnown: false, myLocation: '' });
// 延迟重新设置 scrollTop,解决从子页面 navigateBack 返回后滚动位置丢失的问题
setTimeout(function() {
self.setData({ scrollTop: savedTop });
}, 100);
this.setData({ userId: uid, coupleId: cid });
const app = getApp();
if (app.globalData.openProfileBind) {
app.globalData.openProfileBind = false;
this._openBindOnLoad = true;
this._dataLoads = {};
}
this.loadData();
this.loadProfileStats();
if (!cid && this.data.showInvitePanel && !this._pollTimer) this.startPollCoupleStatus();
// 如果已绑定情侣,关闭邀请面板
if (cid && this.data.showInvitePanel) {
this.setData({ showInvitePanel: false });
@@ -180,6 +201,10 @@ Page({
}
},
onHide: function() {
if (this._pollTimer) { clearInterval(this._pollTimer); this._pollTimer = null; }
},
onUnload: function() {
if (this._pollTimer) {
clearInterval(this._pollTimer);
@@ -188,7 +213,7 @@ Page({
},
onPullDownRefresh: async function() {
await this.loadData();
await Promise.all([this.loadData(),this.loadProfileStats()]);
wx.stopPullDownRefresh();
},
@@ -229,6 +254,7 @@ Page({
hasPartner: false,
partnerName: 'TA',
partnerDisplayId: '',
partnerId: 0,
partnerDetail: '',
partnerAvatarUrl: '',
partnerInitial: '?',
@@ -322,6 +348,7 @@ Page({
hasPartner: true,
partnerName: partner.nickname || partner.username || 'TA',
partnerDisplayId: partner.user_id || '',
partnerId: Number(partner.id),
partnerDetail: details.join(' · ') || 'TA的信息',
partnerAvatarUrl: getImageUrl(partner.avatar),
partnerInitial: (partner.nickname || partner.username || '?')[0],
@@ -405,6 +432,11 @@ Page({
}).catch(function() {});
},
_syncProfileTabBar: function() {
const tabBar = this.getTabBar && this.getTabBar();
if (tabBar && tabBar.setHidden) tabBar.setHidden(this.data.showEditProfileModal || this.data.showEditPartnerModal);
},
// 编辑资料
showEditProfileModal: function() {
var d = this.data;
@@ -421,6 +453,7 @@ Page({
editAvatarUrl: d.myAvatarUrl || '',
editNicknameInitial: d.myInitial || '?'
});
this._syncProfileTabBar();
},
onEditGenderChange: function(e) {
var idx = e.detail.value;
@@ -430,6 +463,7 @@ Page({
},
hideEditProfileModal: function() {
this.setData({ showEditProfileModal: false });
this._syncProfileTabBar();
},
onEditNicknameInput: function(e) {
this.setData({ editNickname: e.detail.value });
@@ -559,9 +593,11 @@ Page({
this.setData({ showPartnerDetailModal: false });
},
// 查看TA资料(只读)
// 编辑TA资料
showEditPartnerModal: function() {
const d = this.data;
this._editPartnerId = d.partnerId;
this._partnerOriginalStory = d.tempLoveStory;
var rawGender = d.partnerGenderRaw || '';
var genderText = rawGender === 'male' ? '男' : (rawGender === 'female' ? '女' : '未设置');
// 从 partnerDetail 中解析年龄(格式:XX岁)
@@ -570,7 +606,7 @@ Page({
this.setData({
showEditPartnerModal: true,
editPartnerNickname: d.partnerName === 'TA' ? '' : d.partnerName,
editPartnerGender: rawGender || 'male',
editPartnerGender: rawGender || '',
editPartnerGenderText: genderText,
editPartnerAge: partnerAge,
editPartnerBirthday: d.partnerBirthday || '',
@@ -578,10 +614,65 @@ Page({
editPartnerAvatarPath: '',
editPartnerNameInitial: d.partnerInitial || '?'
});
this._syncProfileTabBar();
},
hideEditPartnerModal: function() {
if (this.data.partnerSaving || this.data.partnerAvatarUploading) return;
this.setData({ showEditPartnerModal: false });
this._syncProfileTabBar();
},
onEditPartnerNicknameInput: function(e) {
if (!this.data.partnerSaving) this.setData({editPartnerNickname:e.detail.value});
},
onEditPartnerGenderChange: function(e) {
if (this.data.partnerSaving) return;
const index = Number(e.detail.value);
this.setData({editPartnerGender:['male','female'][index],editPartnerGenderText:['男','女'][index]});
},
onEditPartnerBirthdayInput: function(e) {
if (this.data.partnerSaving) return;
const birthday = e.detail.value;
const birth = new Date(birthday), today = new Date();
let age = today.getFullYear() - birth.getFullYear();
if (today.getMonth() < birth.getMonth() || (today.getMonth() === birth.getMonth() && today.getDate() < birth.getDate())) age--;
this.setData({editPartnerBirthday:birthday,editPartnerAge:String(Math.max(0,age))});
},
choosePartnerAvatar: function() {
if (this.data.partnerSaving || this.data.partnerAvatarUploading || !this._editPartnerId) return;
const partnerId = this._editPartnerId;
wx.chooseImage({count:1,sizeType:['compressed'],sourceType:['album','camera'],success:async res=>{
if (!this.data.showEditPartnerModal || partnerId !== this._editPartnerId) return;
this.setData({partnerAvatarUploading:true});
try {
const uploaded = await api.uploadPartnerAvatar(res.tempFilePaths[0],partnerId);
this.setData({editPartnerAvatarUrl:getImageUrl(uploaded.path),editPartnerAvatarPath:uploaded.path});
} catch(error) { wx.showToast({title:error.error || '头像上传失败',icon:'none'}); }
finally { this.setData({partnerAvatarUploading:false}); }
}});
},
savePartnerProfile: async function() {
const d = this.data;
if (d.partnerSaving || d.partnerAvatarUploading) return;
if (!d.hasPartner || !this._editPartnerId || this._editPartnerId !== d.partnerId) {
wx.showToast({title:'绑定信息已变化,请重新打开资料',icon:'none'});return;
}
const nickname = d.editPartnerNickname.trim();
if (!nickname) { wx.showToast({title:'请输入TA的昵称',icon:'none'});return; }
this.setData({partnerSaving:true});
try {
const result = await api.updatePartnerProfile({partner_id:this._editPartnerId,nickname,gender:d.editPartnerGender,age:parseInt(d.editPartnerAge) || 0,birthday:d.editPartnerBirthday,avatar:d.editPartnerAvatarPath || undefined});
if (!result || !result.success) throw result || {};
if (d.coupleId && (d.tempLoveDate !== d.loveStartDate || d.tempLoveStory !== this._partnerOriginalStory)) {
await api.updateCoupleInfo({love_start_date:d.tempLoveDate,love_story:d.tempLoveStory || ''});
}
this.setData({partnerSaving:false});
this.hideEditPartnerModal();
await this.loadData();
wx.showToast({title:'TA的资料已保存',icon:'success'});
} catch(error) { wx.showToast({title:error.error || '保存失败,请重试',icon:'none'}); }
finally { this.setData({partnerSaving:false}); }
},
// 加载解绑状态
loadUnbindStatus: function() {
var self = this;
@@ -610,8 +701,8 @@ Page({
} else {
this.doRespondUnbind();
}
} else if (status === 'agreed') {
wx.showToast({ title: '双方已同意解绑,正在处理中', icon: 'none' });
} else if (status === 'cooling') {
wx.showToast({ title: '正在72小时冷静期', icon: 'none' });
wx.navigateTo({ url: '/pages/unbind/unbind' });
} else {
this.doRequestUnbind();
@@ -622,7 +713,7 @@ Page({
var self = this;
wx.showModal({
title: '💔 解除绑定',
content: '发起解绑请求后,对方需要在72小时内确认同意。\n\n对方同意后,所有情侣记录将被永久删除(记忆日、日记、相册、纪念日、情话、歌曲、打卡),仅保留双方个人资料。\n\n你也可以在对方确认前撤销请求。',
content: '对方同意后进入72小时冷静期,期间任一方可取消。到期自动解绑并永久删除共同数据;个人抽烟记录保留。',
cancelText: '取消',
confirmText: '发起解绑',
confirmColor: '#FF4757',
@@ -647,7 +738,7 @@ Page({
var self = this;
wx.showModal({
title: '↩️ 撤销解绑',
content: '确定要撤销解绑请求吗?对方将不会收到任何通知。',
content: '取消后关系继续保留。确定取消解绑吗?',
cancelText: '取消',
confirmText: '撤销请求',
confirmColor: '#FF6B9D',
@@ -676,7 +767,7 @@ Page({
if (res.tapIndex === 0) {
wx.showModal({
title: '⚠️ 确认同意',
content: name + ' 发起了解绑请求。\n\n同意后,所有情侣记录将被永久删除(记忆日、日记、相册、歌曲、打卡),仅保留双方个人资料。\n\n此操作不可恢复!',
content: name + ' 发起了解绑请求。同意后进入72小时冷静期,期间可取消;到期自动解绑并永久删除共同数据,个人抽烟记录保留。',
cancelText: '取消',
confirmText: '同意解绑',
confirmColor: '#FF4757',
@@ -685,15 +776,8 @@ Page({
wx.showLoading({ title: '处理中...' });
api.unbindRespond({ agree: true }).then(function() {
wx.hideLoading();
wx.showToast({ title: '已同意解绑', icon: 'success' });
wx.removeStorageSync('coupleId');
wx.removeStorageSync('partnerId');
wx.removeStorageSync('partnerName');
wx.removeStorageSync('partnerAvatar');
wx.removeStorageSync('petHasPet');
setTimeout(function() {
wx.reLaunch({ url: '/pages/index/index' });
}, 1500);
wx.showToast({ title: '已进入72小时冷静期', icon: 'none' });
self.loadUnbindStatus();
}).catch(function(err) {
wx.hideLoading();
wx.showToast({ title: err.error || '操作失败', icon: 'none' });
@@ -747,7 +831,7 @@ Page({
// 跳转前保存当前滚动位置
_savePosBeforeLeave: function() {
saveScrollPosition('profile', this.data.scrollTop);
saveScrollPosition('profile', this._scrollTop == null ? this.data.scrollTop : this._scrollTop);
},
// ===== 邀请绑定 =====
@@ -796,9 +880,12 @@ Page({
},
sendInvite: async function() {
const { inviteTargetId, inviteMessage } = this.data;
const inviteTargetId = String(this.data.inviteTargetId || '').trim();
const { inviteMessage } = this.data;
if (!inviteTargetId) { wx.showToast({ title: '请输入对方ID', icon: 'none' }); return; }
if (inviteTargetId.length < 6) { wx.showToast({ title: 'ID为6位数字', icon: 'none' }); return; }
if (!/^[0-9]{6}$/.test(inviteTargetId)) { wx.showToast({ title: 'ID为6位数字', icon: 'none' }); return; }
// 邀请成功且对方接受时,需要通知邀请人,提前申请一次性订阅授权
setTimeout(function() { requestSubscribe('notify').catch(function() {}); }, 1500);
try {
wx.showLoading({ title: '发送中...' });
const res = await api.invitePartner({ target_user_id: inviteTargetId, message: inviteMessage });
@@ -880,6 +967,7 @@ Page({
// ===== 分享绑定 =====
onShareAppMessage: function() {
if (!this.data.shareOutgoingCode) return {title:'邀请你绑定情侣', path:'/pages/profile/profile'};
var displayId = wx.getStorageSync('displayId') || this.data.myDisplayId;
var nick = wx.getStorageSync('nickname') || '我';
if (!displayId) {
@@ -888,22 +976,22 @@ Page({
}
return {
title: nick + ' 邀请你绑定情侣 💕',
path: '/pages/profile/profile?inviter=' + displayId,
path: '/pages/profile/profile?inviter=' + displayId + '&invite_code=' + encodeURIComponent(this.data.shareOutgoingCode || ''),
imageUrl: '/images/share.png'
};
},
// 从分享链接进入时处理邀请绑定
handleShareInvite: function(inviterId) {
handleShareInvite: function(inviterId, code) {
if (!inviterId) return;
this.setData({ shareInviterId: inviterId, showShareBindModal: true, shareDisagree: false });
this.setData({ shareInviterId: inviterId, shareInviteCode: code || '', showShareBindModal: true, shareDisagree: false });
},
acceptShareBind: async function() {
var self = this;
try {
wx.showLoading({ title: '处理中...' });
const res = await api.acceptShareInvite({ inviter_id: this.data.shareInviterId });
const res = await api.acceptShareInvite({ inviter_id: this.data.shareInviterId, invite_code: this.data.shareInviteCode });
wx.hideLoading();
if (res.couple_id) {
wx.setStorageSync('coupleId', res.couple_id);
@@ -925,8 +1013,36 @@ Page({
this.setData({ showShareBindModal: false });
},
loadProfileStats: async function() {
this.setData({statsLoading:true,statsError:false});
const keys=['timeline','album','diary','songs','checkin','pet'];
const results=await Promise.allSettled([
api.timelineList(),api.albumList(),api.diaryList(),api.songsList(),api.checkinList(),
(this.data.coupleId || wx.getStorageSync('coupleId')) ? api.petInfo() : Promise.resolve({has_pet:false})
]);
const counts={};let failed=false;
results.forEach((result,index)=>{
const key=keys[index];
if(result.status !== 'fulfilled') { counts[key]='—';failed=true;return; }
const value=result.value;
if(key==='album') counts[key]=(Array.isArray(value)?value:[]).reduce((sum,album)=>sum+(Number(album.photo_count)||0),0);
else if(key==='diary') counts[key]=Number(value && value.total)||0;
else if(key==='checkin') counts[key]=value && value.checkin_log_total !== undefined ? Number(value.checkin_log_total)||0 : (Array.isArray(value)?value.length:(value && value.items || []).length);
else if(key==='pet') counts[key]=value && value.has_pet && value.pet ? 'Lv.'+(Number(value.pet.level)||0) : '未领养';
else counts[key]=Array.isArray(value)?value.length:0;
});
this.setData({statisticItems:this.data.statisticItems.map(item=>({...item,count:counts[item.key]===undefined?item.count:counts[item.key]})),statsLoading:false,statsError:failed});
},
openStatistic: function(e) {
const routes={timeline:'timeline',album:'albums',diary:'diary',songs:'lovewords',checkin:'punch',pet:'pet',period:'period',settings:'settings'};
const route=routes[e.currentTarget.dataset.key];
if(!route)return;
this._savePosBeforeLeave();
wx.navigateTo({url:'/pages/'+route+'/'+route});
},
// 页面跳转
manageRelationship: function() { wx.navigateTo({ url: '/pages/unbind/unbind' }); },
goToSettings: function() { this._savePosBeforeLeave(); wx.navigateTo({ url: '/pages/settings/settings' }); },
goHome: function() { this._savePosBeforeLeave(); wx.redirectTo({ url: '/pages/index/index' }); }
});
goHome: function() { this._savePosBeforeLeave(); wx.switchTab({ url: '/pages/index/index' }); }
}, {"loadData": ["coupleInfo"], "loadProfileStats": ["timelineList","albumList","diaryList","songsList","checkinList","petInfo"]}));