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

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"]}));
-1
View File
@@ -1,6 +1,5 @@
{
"usingComponents": {
"app-tabbar": "/components/app-tabbar/app-tabbar",
"page-state": "/components/page-state/page-state"
},
"navigationStyle": "custom",
+29 -20
View File
@@ -1,47 +1,56 @@
<view class="app-shell" style="{{layoutStyle}}">
<view class="app-shell j-handwritten-page" style="{{layoutStyle}}">
<launch-transition />
<!-- pages/profile/profile.wxml -->
<view wx:if="{{!showEditProfileModal && !showEditPartnerModal}}" class="j-fixed-header"><view class="j-fixed-heading" style="height:{{barHeight}}px;padding-right:{{navSide}}px;"><text class="j-eyebrow">MY CORNER</text><text class="j-page-title">我的小角落</text></view></view>
<view wx:if="{{!showEditProfileModal && !showEditPartnerModal}}" class="page-root" bindtouchstart="onTouchStart" bindtouchend="onTouchEnd">
<scroll-view class="page-bg j-main-scroll" scroll-y show-scrollbar="{{false}}" bounces="{{true}}" bindscroll="onScroll" scroll-top="{{scrollTop}}">
<view class="j-body">
<view class="j-profile-summary" bindtap="showEditProfileModal"><view class="j-profile-info"><text class="j-profile-name">{{myNickname}}</text><text class="j-caption">ID {{myDisplayId}}</text><text class="j-caption">{{myGender}} {{myAge}} {{myBirthday}}</text><text class="j-link">编辑资料 →</text></view><view class="j-avatar"><image wx:if="{{myAvatarUrl}}" src="{{myAvatarUrl}}" mode="aspectFill" /><text wx:else>{{myInitial}}</text></view></view>
<view class="j-profile-summary" bindtap="showEditProfileModal"><view class="j-profile-info"><text class="j-profile-name">{{myNickname}}</text><text class="j-caption">ID {{myDisplayId}}</text><text class="j-caption">{{myGender}} {{myAge}} {{myBirthday}}</text></view><view class="j-avatar"><image wx:if="{{myAvatarUrl}}" src="{{myAvatarUrl}}" mode="aspectFill" /><text wx:else>{{myInitial}}</text></view></view>
<view class="j-section-head"><text>我们的关系</text></view>
<view wx:if="{{bindingKnown && hasPartner}}" class="j-relation-card"><view class="j-row" bindtap="showEditPartnerModal"><view><text class="j-small-title">{{partnerName}}</text><text class="j-caption" wx:if="{{partnerDisplayId}}">ID {{partnerDisplayId}}</text><text class="j-caption">{{partnerDetail}}</text><text class="j-link">查看TA的资料 →</text></view><view class="j-avatar"><image wx:if="{{partnerAvatarUrl}}" src="{{partnerAvatarUrl}}" mode="aspectFill" /><text wx:else>{{partnerInitial}}</text></view></view><view class="j-profile-setting" bindtap="manageRelationship"><image src="/images/heart.svg" /><view><text class="j-small-title">关系管理</text><text class="j-caption">绑定信息与解绑</text></view><text class="j-link">›</text></view></view>
<view wx:if="{{bindingKnown && hasPartner}}" class="j-relation-card" bindtap="showEditPartnerModal"><view class="j-row"><view><text class="j-small-title">{{partnerName}}</text><text class="j-caption" wx:if="{{partnerDisplayId}}">ID {{partnerDisplayId}}</text><text class="j-caption">{{partnerDetail}}</text></view><view class="j-avatar"><image wx:if="{{partnerAvatarUrl}}" src="{{partnerAvatarUrl}}" mode="aspectFill" /><text wx:else>{{partnerInitial}}</text></view></view></view>
<view wx:elif="{{bindingKnown}}" class="j-invite" bindtap="showInvitePanel"><view><text class="j-small-title">另一半还没有加入</text><text class="j-caption">邀请TA,共写这本生活手账</text></view><text class="j-outline-label">绑定情侣</text></view>
<page-state wx:elif="{{bindingError}}" kind="error" title="绑定状态加载失败" action="重新加载" bind:action="loadData" /><page-state wx:else kind="loading" title="正在获取绑定状态" />
<view class="j-profile-setting" bindtap="goToSettings"><image src="/images/settings.svg" /><view><text class="j-small-title">外观与通知</text><text class="j-caption">背景、通知与账号设置</text></view><text class="j-link">›</text></view>
<view class="profile-statistics">
<view class="profile-stat-grid">
<view class="profile-stat-item" wx:for="{{statisticItems}}" wx:key="key" data-key="{{item.key}}" bindtap="openStatistic" hover-class="profile-stat-pressed">
<view class="profile-stat-icon {{item.color}}"><image src="/images/{{item.icon}}.svg" mode="aspectFit" /></view>
<view class="profile-stat-label"><text>{{item.label}}</text><text wx:if="{{item.count !== ''}}" class="profile-stat-count">{{item.count}}</text></view>
</view>
</view>
<view wx:if="{{statsError}}" class="profile-stats-retry" bindtap="loadProfileStats">部分统计未加载,点击重试</view>
</view>
</view>
</scroll-view>
</view>
<app-tabbar />
<view wx:if="{{showEditProfileModal}}" class="edit-root" bindtouchstart="onTouchStart" bindtouchend="onTouchEnd">
<view class="edit-nav"><view class="back-btn" bindtap="hideEditProfileModal"><image class="back-icon" src="/images/back.svg" mode="aspectFit" /></view></view>
<view class="edit-body" bindtouchstart="onTouchStart" bindtouchend="onTouchEnd">
<scroll-view class="edit-body" scroll-y show-scrollbar="{{false}}" bindtouchstart="onTouchStart" bindtouchend="onTouchEnd">
<view class="edit-avatar-wrap" bindtap="chooseAvatar"><image wx:if="{{editAvatarUrl}}" src="{{editAvatarUrl}}" mode="aspectFill" class="edit-avatar-img"></image><text wx:else class="edit-avatar-placeholder">{{editNicknameInitial || '?'}}</text><text class="edit-avatar-hint">点击更换头像</text></view>
<text class="edit-id">我的ID: {{myDisplayId}}</text>
<view class="input-group"><label>昵称</label><input class="input" placeholder="你的昵称" value="{{editNickname}}" bindinput="onEditNicknameInput" /></view>
<view class="input-group"><label>昵称</label><input placeholder-class="handwritten-placeholder" class="input" placeholder="你的昵称" value="{{editNickname}}" bindinput="onEditNicknameInput" /></view>
<view class="input-group"><label>性别</label><picker mode="selector" range="{{genderOptions}}" bindchange="onEditGenderChange"><view class="input">{{editGenderText || '请选择'}}</view></picker></view>
<view class="input-group"><label>生日</label><picker mode="date" value="{{editBirthday}}" bindchange="onEditBirthdayInput"><view class="input">{{editBirthday || '请选择日期'}}</view></picker><text style="font-size:24rpx;color:#B74768;margin-top:8rpx;display:block;" wx:if="{{editAge}}">自动计算:{{editAge}}岁</text></view>
<view class="input-group" wx:if="{{hasPartner}}"><label>恋爱纪念日</label><picker mode="date" value="{{tempLoveDate}}" bindchange="onLoveDateInput"><view class="input">{{tempLoveDate || '设置你们在一起的日期'}}</view></picker></view>
<view class="input-group" wx:if="{{hasPartner}}"><label>恋爱故事</label><textarea class="input" style="min-height:120rpx;" placeholder="记录你们的故事..." value="{{tempLoveStory}}" bindinput="onLoveStoryInput"></textarea></view>
</view>
<view class="input-group" wx:if="{{hasPartner}}"><label>恋爱故事</label><textarea placeholder-class="handwritten-placeholder" class="input" style="min-height:120rpx;" placeholder="记录你们的故事..." value="{{tempLoveStory}}" bindinput="onLoveStoryInput"></textarea></view>
</scroll-view>
<view class="edit-footer"><button class="btn btn-primary" bindtap="saveProfile" hover-class="none">保存</button></view>
</view>
<view wx:if="{{showEditPartnerModal}}" class="edit-root partner-view" bindtouchstart="onTouchStart" bindtouchend="onTouchEnd">
<view class="edit-nav"><view class="back-btn" bindtap="hideEditPartnerModal"><image class="back-icon" src="/images/back.svg" mode="aspectFit" /></view><text class="partner-nav-title">TA的资料</text></view>
<view class="edit-body" bindtouchstart="onTouchStart" bindtouchend="onTouchEnd">
<view class="edit-avatar-wrap"><image wx:if="{{editPartnerAvatarUrl}}" src="{{editPartnerAvatarUrl}}" mode="aspectFill" class="edit-avatar-img"></image><text wx:else class="edit-avatar-placeholder">{{editPartnerNameInitial || '?'}}</text></view>
<view wx:if="{{showEditPartnerModal}}" class="edit-root partner-edit" bindtouchstart="onTouchStart" bindtouchend="onTouchEnd">
<view class="edit-nav"><view class="back-btn" bindtap="hideEditPartnerModal"><image class="back-icon" src="/images/back.svg" mode="aspectFit" /></view><text class="partner-nav-title">编辑TA的资料</text></view>
<scroll-view class="edit-body" scroll-y show-scrollbar="{{false}}">
<view class="edit-avatar-wrap" bindtap="choosePartnerAvatar"><image wx:if="{{editPartnerAvatarUrl}}" src="{{editPartnerAvatarUrl}}" mode="aspectFill" class="edit-avatar-img" /><text wx:else class="edit-avatar-placeholder">{{editPartnerNameInitial || '?'}}</text><text class="edit-avatar-hint">{{partnerAvatarUploading ? '正在上传头像…' : '点击更换头像'}}</text></view>
<text class="edit-id" wx:if="{{partnerDisplayId}}">TA的ID: {{partnerDisplayId}}</text>
<view class="input-group readonly"><label>昵称</label><view class="input readonly-text">{{editPartnerNickname || '未设置'}}</view></view>
<view class="input-group readonly"><label>性别</label><view class="input readonly-text">{{editPartnerGenderText || '-'}}</view></view>
<view class="input-group readonly"><label>生日</label><view class="input readonly-text">{{editPartnerBirthday || '-'}}</view></view>
<view class="input-group readonly"><label>恋爱纪念日</label><view class="input readonly-text">{{tempLoveDate || '未设置'}}</view></view>
<view class="input-group readonly"><label>恋爱故事</label><view class="input readonly-text" style="min-height:60rpx;">{{tempLoveStory || '未设置'}}</view></view>
</view>
<view class="input-group"><label>昵称</label><input placeholder-class="handwritten-placeholder" class="input" value="{{editPartnerNickname}}" placeholder="TA的昵称" bindinput="onEditPartnerNicknameInput" disabled="{{partnerSaving}}" /></view>
<view class="input-group"><label>性别</label><picker mode="selector" range="{{genderOptions}}" bindchange="onEditPartnerGenderChange" disabled="{{partnerSaving}}"><view class="input">{{editPartnerGenderText || '请选择'}}</view></picker></view>
<view class="input-group"><label>生日</label><picker mode="date" value="{{editPartnerBirthday}}" bindchange="onEditPartnerBirthdayInput" disabled="{{partnerSaving}}"><view class="input">{{editPartnerBirthday || '请选择日期'}}</view></picker><text class="edit-avatar-hint" wx:if="{{editPartnerAge}}">自动计算:{{editPartnerAge}}岁</text></view>
<view class="input-group"><label>恋爱纪念日</label><picker mode="date" value="{{tempLoveDate}}" bindchange="onLoveDateInput" disabled="{{partnerSaving}}"><view class="input">{{tempLoveDate || '设置你们在一起的日期'}}</view></picker></view>
<view class="input-group"><label>恋爱故事</label><textarea placeholder-class="handwritten-placeholder" class="input" style="min-height:120rpx;" value="{{tempLoveStory}}" placeholder="记录你们的故事…" bindinput="onLoveStoryInput" disabled="{{partnerSaving}}" /></view>
</scroll-view>
<view class="edit-footer"><button class="btn btn-primary" bindtap="savePartnerProfile" loading="{{partnerSaving}}" disabled="{{partnerSaving || partnerAvatarUploading}}" hover-class="none">保存</button></view>
</view>
<!-- ===== 邀请绑定面板 ===== -->
@@ -62,7 +71,7 @@
<view class="input-group">
<label>TA的ID</label>
<input class="input" type="tel" placeholder="输入对方的6位ID" value="{{inviteTargetId}}" bindinput="onInviteTargetIdInput" maxlength="6" />
<input placeholder-class="handwritten-placeholder" class="input" type="tel" placeholder="输入对方的6位ID" value="{{inviteTargetId}}" bindinput="onInviteTargetIdInput" maxlength="6" />
</view>
<button class="btn btn-primary" bindtap="sendInvite" hover-class="none">
<text>💕</text> 发送邀请
+64 -5
View File
@@ -1,6 +1,6 @@
/* pages/profile/profile.wxss */
@import '../../styles/common.wxss';
page { background: linear-gradient(180deg, #F4E2E3 0%, #FAF5EE 55%, #FAF5EE 100%); height: 100vh; overflow: hidden; }
page { background: linear-gradient(180deg, #FDE4EC 0%, #FFF0F5 48%, #FFF7FA 100%); height: 100vh; overflow: hidden; }
.container { padding-top: 16rpx; padding-bottom: 300rpx; }
.page-root { height: 100vh; overflow: hidden; }
scroll-view.page-bg { height: 100vh; overflow-y: scroll; -webkit-overflow-scrolling: touch; }
@@ -301,8 +301,67 @@ scroll-view.page-bg { height: 100vh; overflow-y: scroll; -webkit-overflow-scroll
@import '../../styles/journal.wxss';
/* TA 的资料仅供查看,不预留编辑操作区。 */
.partner-view .edit-body { min-height: 0; padding-bottom: calc(40rpx + env(safe-area-inset-bottom)); }
.partner-view .edit-nav { position: relative; }
/* TA 资料编辑页的标题与滚动区域。 */
.partner-edit .edit-body { min-height: 0; padding-bottom: calc(40rpx + env(safe-area-inset-bottom)); }
.partner-nav-title { position: absolute; left: 120rpx; right: 120rpx; text-align: center; font-size: 32rpx; font-weight: 600; color: #342F2B; pointer-events: none; }
.partner-view .readonly-text { white-space: pre-wrap; overflow-wrap: anywhere; }
.partner-edit .readonly-text { white-space: pre-wrap; overflow-wrap: anywhere; }
/* 编辑内容独立滚动,底部保存区始终保留空间。 */
.edit-root > .edit-body { flex:1; height:0; min-height:0; box-sizing:border-box; padding-bottom:24rpx; }
.edit-root > .edit-footer { flex-shrink:0; box-sizing:border-box; }
.edit-footer > .btn { width:100%; margin:0; }
/* 个人页八宫格统计入口。 */
.profile-statistics { margin-top:32rpx; padding:28rpx 16rpx; background:#FFFDF8; border:1rpx solid #F1DCE4; border-radius:32rpx; }
.profile-stat-grid { display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:28rpx 6rpx; }
.profile-stat-item { min-width:0; display:flex; flex-direction:column; align-items:center; padding:8rpx 0; }
.profile-stat-icon { width:94rpx; height:94rpx; border-radius:30rpx; display:flex; align-items:center; justify-content:center; }
.profile-stat-icon image { width:58rpx; height:58rpx; }
.profile-stat-icon.pink { background:#FCE5ED; }.profile-stat-icon.yellow { background:#FFF3DC; }.profile-stat-icon.mint { background:#E5F5ED; }.profile-stat-icon.blue { background:#EAF0FD; }.profile-stat-icon.purple { background:#F1EAFB; }.profile-stat-icon.gray { background:#F0EFF2; }
.profile-stat-label { display:flex; flex-wrap:wrap; justify-content:center; align-items:baseline; gap:6rpx; margin-top:14rpx; font-family:'Kaiti SC','STKaiti','KaiTi',cursive; font-size:25rpx; line-height:1.5; color:#64424D; }
.profile-stat-count { font-family:-apple-system,BlinkMacSystemFont,sans-serif; font-size:19rpx; color:#A39386; }
.profile-stat-pressed { opacity:.65; }
.profile-stats-retry { text-align:center; color:#B74768; font-size:22rpx; padding:24rpx 0 0; }
/* 本人与对方资料卡共用完整样式,整卡点击编辑。 */
.j-body > .j-profile-summary,
.j-body > .j-relation-card {
height:216rpx;
box-sizing:border-box;
display:flex;
align-items:center;
padding:24rpx 30rpx;
margin:20rpx 0 24rpx;
background:#FFFDF8;
border:1rpx solid #E9DDD0;
border-radius:12rpx 40rpx 12rpx 40rpx;
box-shadow:none;
}
.j-relation-card > .j-row { width:100%; gap:30rpx; }
.j-profile-summary .j-profile-name,
.j-relation-card .j-small-title {
display:block;
font-family:'Kaiti SC','STKaiti','KaiTi',cursive;
font-size:30rpx;
font-weight:400;
font-style:normal;
line-height:1.6;
color:#64424D;
overflow:hidden;
white-space:nowrap;
text-overflow:ellipsis;
}
.j-profile-summary .j-avatar,
.j-relation-card .j-avatar {
width:96rpx;
height:96rpx;
flex-shrink:0;
box-sizing:border-box;
border:6rpx solid #FFFDF8;
font-size:32rpx;
}
@import '../../styles/button-theme.wxss';
@import '../../styles/typography.wxss';