const { itemsOf, totalOf } = require('../../utils/list-pagination'); const { withDataCache } = require('../../utils/page-cache'); const { getLayout } = require('../../utils/layout'); // pages/profile/profile.js - 与原网站一致的个人中心 const { api, getImageUrl } = require('../../utils/request'); const { saveScrollPosition, resetScrollOnTabShow } = require('../../utils/scroll-pos'); const { requestSubscribe } = require('../../utils/subscribe'); 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: '未登录', myDisplayId: '', myPhone: '-', myGender: '-', myAge: '-', myBirthday: '-', myRegion: '', myLocation: '', myStatus: '设置恋爱信息', myAvatarUrl: '', myInitial: '?', // TA的信息 hasPartner: false, bindingKnown: false, bindingError: false, partnerName: 'TA', partnerDisplayId: '', partnerId: 0, partnerDetail: '', partnerAvatarUrl: '', partnerInitial: '?', partnerBirthday: '', partnerGenderRaw: '', // 恋爱信息 anniversaryDateText: '点击设置你们的纪念日', loveStoryText: '记录你们的故事', loveStartDate: '', // 编辑资料弹窗 showEditProfileModal: false, editNickname: '', editGender: 'male', editGenderText: '男', editAge: '', editBirthday: '', editRegion: '', editAvatarUrl: '', editAvatarPath: '', editNicknameInitial: '?', // 设置恋爱日期弹窗 showSetDateModal: false, tempLoveDate: '', tempLoveStory: '', // 编辑TA资料弹窗 showEditPartnerModal: false, partnerSaving: false, partnerAvatarUploading: false, editPartnerNickname: '', editPartnerGender: 'male', editPartnerGenderText: '男', editPartnerAge: '', editPartnerBirthday: '', editPartnerAvatarUrl: '', editPartnerAvatarPath: '', editPartnerNameInitial: '?', genderOptions: ['男', '女'], // 解除绑定验证 showUnbindVerifyModal: false, unbindQuestion: '', unbindAnswer: 0, unbindUserInput: '', // 解绑状态 unbindStatus: '', unbindIsRequester: false, unbindRequesterName: '', unbindRemaining: 0, // TA详情弹窗 showPartnerDetailModal: false, partnerDetailNickname: '-', partnerDetailGender: '-', partnerDetailAge: '-', partnerDetailBirthday: '-', partnerDetailAvatarUrl: '', partnerNameInitial: '?', // ===== 邀请绑定相关 ===== showInvitePanel: false, inviteTab: 'received', receivedInvites: [], sentInvites: [], inviteTargetId: '', inviteMessage: '', // 分享绑定 showShareBindModal: false, shareInviterId: '', shareInviterName: '', shareDisagree: false }, // 左滑返回机制 touchStartX: 0, touchStartY: 0, onTouchStart: function(e) { this.touchStartX = e.touches[0].clientX; this.touchStartY = e.touches[0].clientY; }, onTouchEnd: function(e) { var dx = this.touchStartX - e.changedTouches[0].clientX; var dy = Math.abs(this.touchStartY - e.changedTouches[0].clientY); // 短距离/垂直滑动不处理(交给按钮tap事件) if (dx < 20 && dy < 20) return; // 只有水平且左滑超过80px才处理 if (Math.abs(dx) > dy && dx > 80) { if (this.data.showEditProfileModal) { this.hideEditProfileModal(); } else if (this.data.showEditPartnerModal) { this.hideEditPartnerModal(); } else { wx.navigateBack(); } } }, _needRefresh: true, onScroll: function(e) { this._scrollTop = e.detail.scrollTop; saveScrollPosition('profile', e.detail.scrollTop); }, onLoad: function(options) { this.setData(getLayout()); this._needRefresh = true; this._openBindOnLoad = !!(options && options.bind); this.setData({ userId: wx.getStorageSync('userId'), coupleId: wx.getStorageSync('coupleId'), scrollTop: 0 }); this.loadData(); this.loadUnbindStatus(); this.fetchAndSaveLocation(); // 从首页点击头像跳转过来,自动打开编辑 if (options.edit === 'self') { var self = this; setTimeout(function() { self.showEditProfileModal(); }, 500); } else if (options.edit === 'partner') { var self = this; setTimeout(function() { self.showEditPartnerModal(); }, 500); } // 从分享邀请链接进入 if (options && options.inviter) { var self = this; 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'); 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 }); if (this._pollTimer) { clearInterval(this._pollTimer); this._pollTimer = null; } } }, onHide: function() { if (this._pollTimer) { clearInterval(this._pollTimer); this._pollTimer = null; } }, onUnload: function() { if (this._pollTimer) { clearInterval(this._pollTimer); this._pollTimer = null; } }, onPullDownRefresh: async function() { await Promise.all([this.loadData(),this.loadProfileStats()]); wx.stopPullDownRefresh(); }, // 比较新旧值,只返回变化的字段(避免全量 setData 导致闪屏) _diffUpdate: function(newData) { var updates = {}; for (var key in newData) { if (this.data[key] !== newData[key]) { updates[key] = newData[key]; } } if (Object.keys(updates).length > 0) { this.setData(updates); } }, loadData: async function() { this.setData({bindingError:false}); var requestId = (this._coupleInfoRequestId || 0) + 1; this._coupleInfoRequestId = requestId; try { // 获取情侣信息 const info = await api.coupleInfo(); this.setData({userId: wx.getStorageSync('userId')}); if (requestId !== this._coupleInfoRequestId) return; // 未绑定情侣时从后端获取用户最新资料 if (info.not_bound) { wx.removeStorageSync('coupleId'); var myInfo = info.partner1 || {}; var genderText = myInfo.gender === 'male' ? '男' : (myInfo.gender === 'female' ? '女' : '-'); var region = myInfo.region || ''; var location = myInfo.location || ''; if (/^-?\d+(\.\d+)?,-?\d+(\.\d+)?$/.test(location)) location = ''; wx.setStorageSync('region', region); this._diffUpdate({ bindingKnown: true, coupleId: null, hasPartner: false, partnerName: 'TA', partnerDisplayId: '', partnerId: 0, partnerDetail: '', partnerAvatarUrl: '', partnerInitial: '?', partnerBirthday: '', partnerGenderRaw: '', myStatus: '设置恋爱信息', anniversaryDateText: '点击设置你们的纪念日', loveStoryText: '记录你们的故事', loveStartDate: '', tempLoveDate: '', tempLoveStory: '', myNickname: myInfo.nickname || myInfo.username || '未登录', myDisplayId: myInfo.user_id || wx.getStorageSync('displayId') || '', myPhone: myInfo.phone || myInfo.username || '-', myGender: genderText, myGenderRaw: myInfo.gender || '', myAge: myInfo.age > 0 ? myInfo.age + '岁' : '-', myAgeText: myInfo.age > 0 ? myInfo.age : '', myBirthday: myInfo.birthday || '-', myBirthdayText: myInfo.birthday || '', myRegion: region, myLocation: location, myAvatarUrl: getImageUrl(myInfo.avatar), myInitial: (myInfo.nickname || myInfo.username || '?')[0], editNickname: myInfo.nickname || '', editAge: myInfo.age > 0 ? myInfo.age : '', editBirthday: myInfo.birthday || '', editRegion: region, editNicknameInitial: (myInfo.nickname || myInfo.username || '?')[0] }); if (this._openBindOnLoad) { this._openBindOnLoad = false; this.showInvitePanel(); } return; } // 同步情侣ID到本地存储 if (info.id) { wx.setStorageSync('coupleId', info.id); if (this.data.coupleId !== info.id) { this.setData({ coupleId: info.id }); } } const myUid = this.data.userId; const p1 = info.partner1 || {}; const p2 = info.partner2 || {}; const me = p1.id == myUid ? p1 : (p2.id == myUid ? p2 : null); const partner = p1.id == myUid ? p2 : (p2.id == myUid ? p1 : null); // 我的信息 — 只更新变化的字段 if (me) { const genderText = me.gender === 'male' ? '男' : (me.gender === 'female' ? '女' : '-'); const storedDisplayId = wx.getStorageSync('displayId') || ''; var region = me.region || ''; var location = me.location || ''; if (/^-?\d+(\.\d+)?,-?\d+(\.\d+)?$/.test(location)) location = ''; wx.setStorageSync('region', region); this._diffUpdate({ myNickname: me.nickname || me.username || '未登录', myDisplayId: me.user_id || storedDisplayId, myPhone: me.phone || me.username || '-', myGender: genderText, myGenderRaw: me.gender || '', myAge: me.age > 0 ? me.age + '岁' : '-', myAgeText: me.age > 0 ? me.age : '', myBirthday: me.birthday || '-', myBirthdayText: me.birthday || '', myRegion: region, myLocation: location, myAvatarUrl: getImageUrl(me.avatar), myInitial: (me.nickname || me.username || '?')[0], editNickname: me.nickname || '', editAge: me.age > 0 ? me.age : '', editBirthday: me.birthday || '', editRegion: region, editNicknameInitial: (me.nickname || me.username || '?')[0] }); } // TA的信息 — 只更新变化的字段 if (partner) { const genderText = partner.gender === 'male' ? '男' : (partner.gender === 'female' ? '女' : '-'); let details = []; if (genderText !== '-') details.push(genderText); if (partner.age > 0) details.push(partner.age + '岁'); if (partner.birthday) details.push('🎂 ' + partner.birthday); this._diffUpdate({ bindingKnown: true, 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], partnerBirthday: partner.birthday || '', partnerGenderRaw: partner.gender || '', partnerDetailNickname: partner.nickname || partner.username || '-', partnerDetailGender: genderText, partnerDetailAge: partner.age > 0 ? partner.age + '岁' : '-', partnerDetailBirthday: partner.birthday || '-', partnerDetailAvatarUrl: getImageUrl(partner.avatar), partnerNameInitial: (partner.nickname || partner.username || '?')[0] }); } // 恋爱天数和状态 — 只更新变化的字段 let daysCount = 0; if (info.love_start_date) { const start = new Date(info.love_start_date); daysCount = Math.floor((Date.now() - start) / 86400000); const partnerName = partner ? (partner.nickname || partner.username || 'TA') : 'TA'; this._diffUpdate({ anniversaryDateText: info.love_start_date + ' (已设置)', loveStoryText: info.love_story ? (info.love_story.length > 20 ? info.love_story.substring(0, 20) + '...' : info.love_story) : '记录你们的故事', myStatus: '和' + partnerName + '在一起 ' + daysCount + ' 天 💕', loveStartDate: info.love_start_date, tempLoveDate: info.love_start_date || '', tempLoveStory: info.love_story || '' }); } else { this._diffUpdate({ anniversaryDateText: '点击设置你们的纪念日', loveStoryText: info.love_story ? (info.love_story.length > 20 ? info.love_story.substring(0, 20) + '...' : info.love_story) : '记录你们的故事', myStatus: '设置恋爱信息', loveStartDate: '', tempLoveDate: '', tempLoveStory: '' }); } } catch (e) { if (requestId !== this._coupleInfoRequestId) return; console.error(e); this.setData({ bindingKnown: false, bindingError:true }); } }, // 获取定位并保存到数据库(使用模糊定位,getLocation 与 getFuzzyLocation 互斥,统一用模糊定位) fetchAndSaveLocation: function() { var self = this; self._fuzzyLocation(); }, // 模糊定位兜底 _fuzzyLocation: function() { var self = this; if (!wx.getFuzzyLocation) return; wx.getFuzzyLocation({ type: 'gcj02', success: function(res) { self._reportLocation(res.latitude, res.longitude); }, fail: function() {} }); }, // 上报经纬度到后端并更新城市地址显示 _reportLocation: function(lat, lng) { var self = this; if (!lat || !lng) return; // 保留更多小数位,减少坐标截断误差 var location = lat.toFixed(6) + ',' + lng.toFixed(6); api.updateLocation(location).then(function(resp) { // 后端返回的地址;若为空或经纬度(说明逆地理编码失败),一律不采用,绝不显示经纬度 var city = (resp && resp.location) ? resp.location : ''; if (city === '' || /^-?\d+(\.\d+)?,-?\d+(\.\d+)?$/.test(city)) { return; } // 转换成功后展示接口返回的城市地址 if (self.data.myLocation !== city) { self.setData({ myLocation: city }); } }).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; var rawGender = d.myGenderRaw || ''; var genderText = rawGender === 'female' ? '女' : '男'; this.setData({ showEditProfileModal: true, editNickname: d.myNickname === '未设置' ? '' : d.myNickname, editGender: rawGender || 'male', editGenderText: genderText, editAge: d.myAgeText ? String(parseInt(d.myAgeText) || '') : '', editBirthday: d.myBirthdayText || '', editRegion: d.myRegion || '', editAvatarUrl: d.myAvatarUrl || '', editNicknameInitial: d.myInitial || '?' }); this._syncProfileTabBar(); }, onEditGenderChange: function(e) { var idx = e.detail.value; var texts = ['男', '女']; var vals = ['male', 'female']; this.setData({ editGender: vals[idx], editGenderText: texts[idx] }); }, hideEditProfileModal: function() { this.setData({ showEditProfileModal: false }); this._syncProfileTabBar(); }, onEditNicknameInput: function(e) { this.setData({ editNickname: e.detail.value }); }, onEditBirthdayInput: function(e) { var birthday = e.detail.value; var age = ''; if (birthday) { var birthDate = new Date(birthday); var today = new Date(); var calcAge = today.getFullYear() - birthDate.getFullYear(); var m = today.getMonth() - birthDate.getMonth(); if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) { calcAge--; } age = calcAge > 0 ? calcAge : 1; } this.setData({ editBirthday: birthday, editAge: String(age) }); }, chooseAvatar: function() { wx.chooseImage({ count: 1, sizeType: ['compressed'], sourceType: ['album', 'camera'], success: async (res) => { const filePath = res.tempFiles[0].path; this.setData({ editAvatarUrl: filePath }); // 上传头像 try { const data = await api.uploadAvatar(filePath); this.setData({ editAvatarUrl: getImageUrl(data.path), editAvatarPath: data.path }); wx.showToast({ title: '头像上传成功 ✨', icon: 'success' }); } catch (e) { wx.showToast({ title: e.error || '上传失败', icon: 'none' }); } } }); }, saveProfile: async function() { const { editNickname, editGender, editAge, editBirthday, editRegion, editAvatarPath, tempLoveDate, tempLoveStory, coupleId } = this.data; if (!editNickname) { wx.showToast({ title: '请输入昵称', icon: 'none' }); return; } try { await api.updateProfile({ nickname: editNickname, gender: editGender, age: parseInt(editAge) || 0, birthday: editBirthday, region: editRegion, avatar: editAvatarPath }); // 更新本地存储的昵称,确保未绑定情侣时也能正确显示 wx.setStorageSync('nickname', editNickname); if (editAvatarPath) { wx.setStorageSync('avatar', editAvatarPath); } // 如果有恋爱日期且已绑定情侣,也保存 if (tempLoveDate && coupleId) { await api.updateCoupleInfo({ love_start_date: tempLoveDate, love_story: tempLoveStory || '' }); } wx.showToast({ title: '资料更新成功 💕', icon: 'success' }); this.hideEditProfileModal(); this.loadData(); } catch (e) { wx.showToast({ title: e.error || '保存失败', icon: 'none' }); } }, // 设置恋爱日期 showSetDateModal: function() { this.setData({ showSetDateModal: true }); }, hideSetDateModal: function() { this.setData({ showSetDateModal: false }); }, onLoveDateInput: function(e) { this.setData({ tempLoveDate: e.detail.value }); }, onLoveStoryInput: function(e) { this.setData({ tempLoveStory: e.detail.value }); }, // 显示恋爱故事 showCoupleStory: function() { const { tempLoveStory } = this.data; if (!tempLoveStory) { wx.showToast({ title: '还没有恋爱故事,先设置纪念日吧 ✨', icon: 'none' }); this.showSetDateModal(); } else { wx.showModal({ title: '💕 恋爱故事', content: tempLoveStory, showCancel: false }); } }, saveLoveDate: async function() { const { tempLoveDate, tempLoveStory } = this.data; if (!tempLoveDate) { wx.showToast({ title: '请选择日期', icon: 'none' }); return; } try { await api.updateCoupleInfo({ love_start_date: tempLoveDate, love_story: tempLoveStory }); wx.showToast({ title: '纪念日设置成功 💕', icon: 'success' }); this.hideSetDateModal(); this.loadData(); } catch (e) { wx.showToast({ title: e.error || '保存失败', icon: 'none' }); } }, // TA详情 showPartnerDetail: function() { this.setData({ showPartnerDetailModal: true }); }, hidePartnerDetailModal: function() { this.setData({ showPartnerDetailModal: false }); }, // 编辑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岁) var ageMatch = d.partnerDetail.match(/(\d+)岁/); var partnerAge = ageMatch ? ageMatch[1] : ''; this.setData({ showEditPartnerModal: true, editPartnerNickname: d.partnerName === 'TA' ? '' : d.partnerName, editPartnerGender: rawGender || '', editPartnerGenderText: genderText, editPartnerAge: partnerAge, editPartnerBirthday: d.partnerBirthday || '', editPartnerAvatarUrl: d.partnerAvatarUrl || '', 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; api.unbindStatus().then(function(res) { if (res && res.status && res.status !== 'none') { self.setData({ unbindStatus: res.status, unbindIsRequester: !!res.is_requester, unbindRequesterName: res.requester_name || '对方', unbindRemaining: res.remaining_seconds || 0 }); } else { self.setData({ unbindStatus: '', unbindIsRequester: false, unbindRequesterName: '' }); } }).catch(function() {}); }, // 解除绑定 — 根据状态显示不同操作 showUnbindVerify: function() { var status = this.data.unbindStatus; var isRequester = this.data.unbindIsRequester; if (status === 'pending') { if (isRequester) { this.doCancelUnbind(); } else { this.doRespondUnbind(); } } else if (status === 'cooling') { wx.showToast({ title: '正在72小时冷静期', icon: 'none' }); wx.navigateTo({ url: '/pages/unbind/unbind' }); } else { this.doRequestUnbind(); } }, doRequestUnbind: function() { var self = this; wx.showModal({ title: '💔 解除绑定', content: '对方同意后进入72小时冷静期,期间任一方可取消。到期自动解绑并永久删除共同数据;个人抽烟记录保留。', cancelText: '取消', confirmText: '发起解绑', confirmColor: '#FF4757', success: function(res) { if (res.confirm) { wx.showLoading({ title: '发送请求...' }); api.unbindRequest().then(function() { wx.hideLoading(); wx.showToast({ title: '解绑请求已发送', icon: 'success' }); self.loadUnbindStatus(); wx.navigateTo({ url: '/pages/unbind/unbind' }); }).catch(function(err) { wx.hideLoading(); wx.showToast({ title: err.error || '操作失败', icon: 'none' }); }); } } }); }, doCancelUnbind: function() { var self = this; wx.showModal({ title: '↩️ 撤销解绑', content: '取消后关系继续保留。确定取消解绑吗?', cancelText: '取消', confirmText: '撤销请求', confirmColor: '#FF6B9D', success: function(res) { if (res.confirm) { wx.showLoading({ title: '撤销中...' }); api.unbindCancel().then(function() { wx.hideLoading(); wx.showToast({ title: '已撤销解绑请求', icon: 'success' }); self.loadUnbindStatus(); }).catch(function(err) { wx.hideLoading(); wx.showToast({ title: err.error || '操作失败', icon: 'none' }); }); } } }); }, doRespondUnbind: function() { var self = this; var name = this.data.unbindRequesterName; wx.showActionSheet({ itemList: ['✅ 同意解绑', '❌ 拒绝解绑'], success: function(res) { if (res.tapIndex === 0) { wx.showModal({ title: '⚠️ 确认同意', content: name + ' 发起了解绑请求。同意后进入72小时冷静期,期间可取消;到期自动解绑并永久删除共同数据,个人抽烟记录保留。', cancelText: '取消', confirmText: '同意解绑', confirmColor: '#FF4757', success: function(r) { if (r.confirm) { wx.showLoading({ title: '处理中...' }); api.unbindRespond({ agree: true }).then(function() { wx.hideLoading(); wx.showToast({ title: '已进入72小时冷静期', icon: 'none' }); self.loadUnbindStatus(); }).catch(function(err) { wx.hideLoading(); wx.showToast({ title: err.error || '操作失败', icon: 'none' }); }); } } }); } else if (res.tapIndex === 1) { wx.showModal({ title: '拒绝解绑', content: '确定要拒绝 ' + name + ' 的解绑请求吗?', cancelText: '取消', confirmText: '拒绝', confirmColor: '#FF6B9D', success: function(r) { if (r.confirm) { wx.showLoading({ title: '处理中...' }); api.unbindRespond({ agree: false }).then(function() { wx.hideLoading(); wx.showToast({ title: '已拒绝解绑请求', icon: 'success' }); self.loadUnbindStatus(); }).catch(function(err) { wx.hideLoading(); wx.showToast({ title: err.error || '操作失败', icon: 'none' }); }); } } }); } } }); }, // 空操作(用于 catchtap 阻止冒泡) noop: function() {}, // 退出登录 logout: function() { wx.showModal({ title: '确认退出', content: '确定要退出登录吗?', success: (res) => { if (res.confirm) { api.logout().catch(() => {}); wx.clearStorageSync(); wx.reLaunch({ url: '/pages/index/index' }); } } }); }, // 跳转前保存当前滚动位置 _savePosBeforeLeave: function() { saveScrollPosition('profile', this._scrollTop == null ? this.data.scrollTop : this._scrollTop); }, // ===== 邀请绑定 ===== showInvitePanel: function() { var displayId = wx.getStorageSync('displayId') || ''; this.setData({ showInvitePanel: true, inviteTab: 'received', inviteTargetId: '', inviteMessage: '', myDisplayId: displayId }); this.loadInvites(); this.startPollCoupleStatus(); }, hideInvitePanel: function() { this.setData({ showInvitePanel: false }); if (this._pollTimer) { clearInterval(this._pollTimer); this._pollTimer = null; } }, onInviteTargetIdInput: function(e) { this.setData({ inviteTargetId: e.detail.value }); }, onInviteMessageInput: function(e) { this.setData({ inviteMessage: e.detail.value }); }, // 轮询检查情侣关系是否已恢复 startPollCoupleStatus: function() { var self = this; if (self._pollTimer) clearInterval(self._pollTimer); self._pollTimer = setInterval(function() { api.myInvitations().then(function(data) { if (data && data.couple_id) { clearInterval(self._pollTimer); self._pollTimer = null; wx.setStorageSync('coupleId', data.couple_id); wx.showToast({ title: '情侣关系已恢复 💕', icon: 'success' }); setTimeout(function() { self.setData({ showInvitePanel: false }); self.loadData(); }, 1500); } }).catch(function() {}); }, 10000); }, sendInvite: async function() { const inviteTargetId = String(this.data.inviteTargetId || '').trim(); const { inviteMessage } = this.data; if (!inviteTargetId) { wx.showToast({ title: '请输入对方ID', 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 }); wx.hideLoading(); wx.showToast({ title: res.msg || '邀请已发送,等待对方同意 💕', icon: 'success' }); this.setData({ inviteTargetId: '', inviteTab: 'sent' }); this.loadInvites(); } catch (err) { wx.hideLoading(); wx.showToast({ title: err.error || '发送失败', icon: 'none' }); } }, loadInvites: async function() { try { const data = await api.myInvitations(); const received = (data.received || []).map(function(item) { item.from_initial = (item.from_nickname || item.from_username || '?')[0]; item.short_date = item.created_at ? item.created_at.substring(0, 16).replace('T', ' ') : ''; return item; }); const sent = (data.sent || []).map(function(item) { item.to_initial = (item.to_nickname || item.to_username || '?')[0]; item.short_date = item.created_at ? item.created_at.substring(0, 16).replace('T', ' ') : ''; return item; }); this.setData({ receivedInvites: received, sentInvites: sent }); if (data.couple_id) { wx.setStorageSync('coupleId', data.couple_id); wx.showToast({ title: '情侣关系已恢复 💕', icon: 'success' }); setTimeout(() => { this.setData({ showInvitePanel: false }); this.loadData(); }, 1500); } } catch (e) { console.error(e); } }, switchInviteTab: function(e) { const tab = e.currentTarget.dataset.tab; this.setData({ inviteTab: tab }); }, acceptInvite: async function(e) { const id = e.currentTarget.dataset.id; try { wx.showLoading({ title: '处理中...' }); const res = await api.respondInvitation({ invitation_id: id, accept: 1 }); wx.hideLoading(); if (res.couple_id) { wx.setStorageSync('coupleId', res.couple_id); wx.showToast({ title: '绑定成功 💕', icon: 'success' }); setTimeout(() => { this.setData({ showInvitePanel: false }); this.loadData(); }, 1000); } } catch (err) { wx.hideLoading(); wx.showToast({ title: err.error || '操作失败', icon: 'none' }); } }, rejectInvite: async function(e) { const id = e.currentTarget.dataset.id; try { wx.showLoading({ title: '处理中...' }); await api.respondInvitation({ invitation_id: id, accept: 0 }); wx.hideLoading(); wx.showToast({ title: '已拒绝', icon: 'success' }); this.loadInvites(); } catch (err) { wx.hideLoading(); wx.showToast({ title: err.error || '操作失败', icon: 'none' }); } }, // ===== 分享绑定 ===== 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) { wx.showToast({ title: '请先完善资料', icon: 'none' }); return {}; } return { title: nick + ' 邀请你绑定情侣 💕', path: '/pages/profile/profile?inviter=' + displayId + '&invite_code=' + encodeURIComponent(this.data.shareOutgoingCode || ''), imageUrl: '/images/share.png' }; }, // 从分享链接进入时处理邀请绑定 handleShareInvite: function(inviterId, code) { if (!inviterId) return; 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, invite_code: this.data.shareInviteCode }); wx.hideLoading(); if (res.couple_id) { wx.setStorageSync('coupleId', res.couple_id); wx.showToast({ title: '绑定成功 💕', icon: 'success' }); this.setData({ showShareBindModal: false }); setTimeout(() => { this.loadData(); }, 1000); } } catch (err) { wx.hideLoading(); wx.showToast({ title: err.error || '操作失败', icon: 'none' }); } }, rejectShareBind: function() { this.setData({ shareDisagree: true, shareInviterName: 'TA' }); }, hideShareBindModal: function() { 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]=value && value.photo_total != null ? Number(value.photo_total) : itemsOf(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]=totalOf(value); }); 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.switchTab({ url: '/pages/index/index' }); } }, {"loadData": ["coupleInfo"], "loadProfileStats": ["timelineList","albumList","diaryList","songsList","checkinList","petInfo"]}));