init: initial commit

This commit is contained in:
2026-10-07 09:28:43 +08:00
commit a68a4ef411
251 changed files with 38279 additions and 0 deletions
+932
View File
@@ -0,0 +1,932 @@
const { getLayout } = require('../../utils/layout');
// pages/profile/profile.js - 与原网站一致的个人中心
const { api, getImageUrl } = require('../../utils/request');
const { saveScrollPosition, getScrollPosition } = require('../../utils/scroll-pos');
Page({
onResize: function() { this.setData(getLayout()); },
data: {
scrollTop: 0,
// 我的信息
myNickname: '未登录',
myDisplayId: '',
myPhone: '-',
myGender: '-',
myAge: '-',
myBirthday: '-',
myRegion: '',
myLocation: '',
myStatus: '设置恋爱信息',
myAvatarUrl: '',
myInitial: '?',
// TA的信息
hasPartner: false,
bindingKnown: false, bindingError: false,
partnerName: 'TA',
partnerDisplayId: '',
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,
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) {
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: getScrollPosition('profile')
});
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); }, 800);
}
},
onShow: function() {
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 });
this.loadData();
// 如果已绑定情侣,关闭邀请面板
if (cid && this.data.showInvitePanel) {
this.setData({ showInvitePanel: false });
if (this._pollTimer) {
clearInterval(this._pollTimer);
this._pollTimer = null;
}
}
},
onUnload: function() {
if (this._pollTimer) {
clearInterval(this._pollTimer);
this._pollTimer = null;
}
},
onPullDownRefresh: async function() {
await this.loadData();
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: '',
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 || '',
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() {});
},
// 编辑资料
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 || '?'
});
},
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 });
},
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;
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 || 'male',
editPartnerGenderText: genderText,
editPartnerAge: partnerAge,
editPartnerBirthday: d.partnerBirthday || '',
editPartnerAvatarUrl: d.partnerAvatarUrl || '',
editPartnerAvatarPath: '',
editPartnerNameInitial: d.partnerInitial || '?'
});
},
hideEditPartnerModal: function() {
this.setData({ showEditPartnerModal: 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 === 'agreed') {
wx.showToast({ title: '双方已同意解绑,正在处理中', icon: 'none' });
wx.navigateTo({ url: '/pages/unbind/unbind' });
} else {
this.doRequestUnbind();
}
},
doRequestUnbind: function() {
var self = this;
wx.showModal({
title: '💔 解除绑定',
content: '发起解绑请求后,对方需要在72小时内确认同意。\n\n对方同意后,所有情侣记录将被永久删除(记忆日、日记、相册、纪念日、情话、歌曲、打卡),仅保留双方个人资料。\n\n你也可以在对方确认前撤销请求。',
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 + ' 发起了解绑请求。\n\n同意后,所有情侣记录将被永久删除(记忆日、日记、相册、歌曲、打卡),仅保留双方个人资料。\n\n此操作不可恢复!',
cancelText: '取消',
confirmText: '同意解绑',
confirmColor: '#FF4757',
success: function(r) {
if (r.confirm) {
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);
}).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.data.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, inviteMessage } = this.data;
if (!inviteTargetId) { wx.showToast({ title: '请输入对方ID', icon: 'none' }); return; }
if (inviteTargetId.length < 6) { wx.showToast({ title: 'ID为6位数字', icon: 'none' }); return; }
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() {
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,
imageUrl: '/images/share.png'
};
},
// 从分享链接进入时处理邀请绑定
handleShareInvite: function(inviterId) {
if (!inviterId) return;
this.setData({ shareInviterId: inviterId, showShareBindModal: true, shareDisagree: false });
},
acceptShareBind: async function() {
var self = this;
try {
wx.showLoading({ title: '处理中...' });
const res = await api.acceptShareInvite({ inviter_id: this.data.shareInviterId });
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 });
},
// 页面跳转
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' }); }
});
+8
View File
@@ -0,0 +1,8 @@
{
"usingComponents": {
"app-tabbar": "/components/app-tabbar/app-tabbar",
"page-state": "/components/page-state/page-state"
},
"navigationStyle": "custom",
"enablePullDownRefresh": true
}
+160
View File
@@ -0,0 +1,160 @@
<view class="app-shell" 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-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: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>
</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">
<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><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="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>
<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>
<!-- ===== 邀请绑定面板 ===== -->
<view wx:if="{{showInvitePanel}}" class="invite-root">
<view class="invite-nav">
<view class="back-btn" bindtap="hideInvitePanel"><image class="back-icon" src="/images/back.svg" mode="aspectFit" /></view>
<text class="invite-nav-title">💕 绑定情侣</text>
<view style="width:56rpx;"></view>
</view>
<scroll-view class="invite-body" scroll-y show-scrollbar="{{false}}">
<view class="container" style="padding-top:200rpx;">
<view class="card" style="padding:32rpx;text-align:center;margin-bottom:32rpx;">
<text style="font-size:26rpx;color:#888;display:block;">你的ID</text>
<text style="font-size:64rpx;font-weight:700;color:#B74768;display:block;margin-top:8rpx;" wx:if="{{myDisplayId}}">{{myDisplayId}}</text>
<text style="font-size:64rpx;font-weight:700;color:#B74768;display:block;margin-top:8rpx;" wx:else>---</text>
<text style="font-size:22rpx;color:#888;display:block;margin-top:8rpx;">将此ID告诉TA</text>
</view>
<view class="input-group">
<label>TA的ID</label>
<input 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> 发送邀请
</button>
<view style="margin:32rpx 0;display:flex;align-items:center;gap:16rpx;">
<view style="flex:1;height:2rpx;background:#FFE4EC;"></view>
<text style="font-size:26rpx;color:#888;">或</text>
<view style="flex:1;height:2rpx;background:#FFE4EC;"></view>
</view>
<button class="btn btn-secondary" open-type="share" hover-class="none">
<text>💌</text> 分享给微信好友
</button>
<view class="invite-tabs" style="margin-top:32px;">
<view class="invite-tab {{inviteTab == 'received' ? 'active' : ''}}" bindtap="switchInviteTab" data-tab="received">收到的邀请</view>
<view class="invite-tab {{inviteTab == 'sent' ? 'active' : ''}}" bindtap="switchInviteTab" data-tab="sent">发出的邀请</view>
</view>
<view class="invite-list" wx:if="{{inviteTab == 'received'}}">
<block wx:for="{{receivedInvites}}" wx:key="id">
<view class="invite-card">
<view class="invite-card-header">
<view class="invite-avatar">{{item.from_initial}}</view>
<view class="invite-info">
<text class="invite-name">{{item.from_nickname || item.from_username}}</text>
<text class="invite-time">{{item.short_date}}</text>
</view>
<text class="invite-status {{item.status == 'accepted' ? 'accepted' : 'pending'}}">{{item.status == 'accepted' ? '已接受' : '待处理'}}</text>
</view>
<view class="invite-message" wx:if="{{item.message}}">💌 {{item.message}}</view>
<view class="invite-actions" wx:if="{{item.status == 'pending'}}">
<button class="btn btn-primary btn-sm" bindtap="acceptInvite" data-id="{{item.id}}" hover-class="none">✅ 接受</button>
<button class="btn btn-outline btn-sm" bindtap="rejectInvite" data-id="{{item.id}}" hover-class="none">❌ 拒绝</button>
</view>
</view>
</block>
<view class="empty-tip" wx:if="{{receivedInvites.length == 0}}">
<text>暂无收到的邀请 💭</text>
</view>
</view>
<view class="invite-list" wx:if="{{inviteTab == 'sent'}}">
<block wx:for="{{sentInvites}}" wx:key="id">
<view class="invite-card">
<view class="invite-card-header">
<view class="invite-avatar" style="background:linear-gradient(135deg,#82B1FF,#B388FF);">{{item.to_initial}}</view>
<view class="invite-info">
<text class="invite-name">{{item.to_nickname || item.to_username || item.to_phone}}</text>
<text class="invite-time">{{item.short_date}}</text>
</view>
<text class="invite-status {{item.status == 'accepted' ? 'accepted' : 'pending'}}">{{item.status == 'accepted' ? '已接受' : '待处理'}}</text>
</view>
</view>
</block>
<view class="empty-tip" wx:if="{{sentInvites.length == 0}}">
<text>暂无发出的邀请 💭</text>
</view>
</view>
</view>
</scroll-view>
</view>
<!-- ===== 分享绑定弹窗 ===== -->
<view wx:if="{{showShareBindModal}}" class="modal-overlay" bindtap="hideShareBindModal">
<view class="modal" catchtap="noop" style="text-align:center;">
<view class="auth-header">
<text class="auth-icon" style="font-size:128rpx;">💌</text>
<text class="auth-title">情侣邀请</text>
<text class="auth-desc" wx:if="{{!shareDisagree}}">有人想和你绑定为情侣</text>
<text class="auth-desc" wx:if="{{shareDisagree}}" style="color:#FF4757;">需要对方同意绑定后才能使用</text>
</view>
<view wx:if="{{!shareDisagree}}">
<view class="card" style="padding:48rpx 32rpx;margin-bottom:40rpx;">
<text style="font-size:36rpx;font-weight:600;display:block;color:#4A4A4A;">ID: {{shareInviterId}}</text>
<text style="font-size:26rpx;color:#888;display:block;margin-top:8rpx;">想要和你绑定为情侣 💕</text>
</view>
<button class="btn btn-primary" bindtap="acceptShareBind" hover-class="none">
<text>💕</text> 同意绑定
</button>
<button class="btn btn-outline" style="margin-top:16rpx;" bindtap="rejectShareBind" hover-class="none">
不同意
</button>
</view>
<view wx:if="{{shareDisagree}}">
<view style="padding:80rpx 0;">
<text style="font-size:80rpx;display:block;">😅</text>
<text style="font-size:30rpx;color:#888;display:block;margin-top:32rpx;">需要对方同意绑定后才能使用本应用</text>
</view>
</view>
</view>
</view>
</view>
+308
View File
@@ -0,0 +1,308 @@
/* pages/profile/profile.wxss */
@import '../../styles/common.wxss';
page { background: linear-gradient(180deg, #F4E2E3 0%, #FAF5EE 55%, #FAF5EE 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; }
/* 编辑卡全屏 */
.edit-root {
position: fixed;
top: 0; left: 0;
width: 100%; height: 100%;
z-index: 1000;
background: linear-gradient(180deg, #FFF0F5 0%, #F4E2E3 40%, #FAFAFA 100%);
display: flex;
flex-direction: column;
}
.edit-nav {
display: flex;
align-items: center;
padding: 100rpx 32rpx 24rpx;
}
.edit-body {
flex: 1;
padding: 0 56rpx;
overflow-y: auto;
}
.edit-avatar-wrap {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 24rpx;
}
.edit-avatar-img,
.edit-avatar-placeholder {
width: 120rpx; height: 120rpx;
border-radius: 50%;
border: 4rpx solid #FFB4C8;
}
.edit-avatar-placeholder {
background: linear-gradient(135deg, #FF6B81, #B388FF);
display: flex; align-items: center; justify-content: center;
font-size: 40rpx; font-weight: 700; color: white;
}
.edit-avatar-hint {
font-size: 22rpx; color: #B74768; margin-top: 8rpx;
}
.edit-id {
font-size: 24rpx; color: #bbb;
text-align: center; display: block;
margin-bottom: 24rpx;
}
.input-group { margin-bottom: 20rpx; }
.input-group label {
font-size: 28rpx; font-weight: 500; color: #555;
display: block; margin-bottom: 8rpx;
}
.input {
padding: 20rpx 28rpx; font-size: 30rpx;
background: white;
border-radius: 16rpx;
border: 2rpx solid #F0E6FF;
}
.input-group.readonly .input.readonly-text {
background: #F8F4FF;
color: #666;
border-color: #E8DCF5;
padding: 20rpx 28rpx;
font-size: 30rpx;
border-radius: 16rpx;
border: 2rpx solid #E8DCF5;
line-height: 1.5;
min-height: 44rpx;
display: flex;
align-items: center;
}
.edit-footer {
padding: 20rpx 56rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
}
/* ===== 邀请绑定面板 ===== */
.invite-root {
position: fixed;
top: 0; left: 0;
width: 100%; height: 100%;
z-index: 1000;
background: #FFF5F5;
display: flex;
flex-direction: column;
}
.invite-nav {
position: fixed;
top: 0; left: 0; right: 0;
z-index: 1001;
height: 176rpx;
display: flex;
align-items: flex-end;
justify-content: space-between;
padding-bottom: 16rpx;
background: linear-gradient(to bottom, rgba(255,255,255,0.95) 0%, rgba(255,255,255,0) 100%);
}
.invite-nav-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.invite-body {
flex: 1;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
/* 邀请页标签 */
.invite-tabs {
display: flex;
background: white;
border-radius: 100rpx;
padding: 8rpx;
margin-bottom: 48rpx;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.05);
}
.invite-tab {
flex: 1;
padding: 24rpx;
text-align: center;
border-radius: 100rpx;
font-size: 28rpx;
font-weight: 600;
transition: all 0.3s;
color: #888;
}
.invite-tab.active {
background: linear-gradient(135deg, #B74768, #B388FF);
color: white;
}
.invite-list {
display: flex;
flex-direction: column;
gap: 32rpx;
}
.invite-card {
background: white;
border-radius: 48rpx;
padding: 40rpx;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.05);
}
.invite-card-header {
display: flex;
align-items: center;
gap: 24rpx;
margin-bottom: 24rpx;
}
.invite-avatar {
width: 96rpx;
height: 96rpx;
border-radius: 50%;
background: linear-gradient(135deg, #B388FF, #82B1FF);
display: flex;
align-items: center;
justify-content: center;
font-size: 36rpx;
font-weight: 700;
color: white;
flex-shrink: 0;
}
.invite-info {
flex: 1;
}
.invite-name {
font-size: 30rpx;
font-weight: 600;
display: block;
}
.invite-time {
font-size: 26rpx;
color: #888;
display: block;
margin-top: 4rpx;
}
.invite-status {
padding: 8rpx 20rpx;
border-radius: 40rpx;
font-size: 24rpx;
flex-shrink: 0;
}
.invite-status.accepted {
background: #E8F5E9;
color: #4CAF50;
}
.invite-status.pending {
background: #FFF3E0;
color: #FF9800;
}
.invite-message {
font-size: 28rpx;
color: #4A4A4A;
padding: 24rpx;
background: #FFF8E7;
border-radius: 24rpx;
margin-bottom: 24rpx;
}
.invite-actions {
display: flex;
gap: 24rpx;
}
.invite-actions .btn {
flex: 1;
padding: 24rpx;
font-size: 28rpx;
}
.empty-tip {
text-align: center;
padding: 80rpx 0;
color: #888;
font-size: 26rpx;
}
/* 分享绑定弹窗 */
.auth-header {
text-align: center;
margin-bottom: 64rpx;
}
.auth-icon {
font-size: 112rpx;
margin-bottom: 32rpx;
display: block;
animation: bounce 2s ease-in-out infinite;
}
.auth-title {
font-size: 52rpx;
font-weight: 700;
color: #4A4A4A;
margin-bottom: 16rpx;
display: block;
background: linear-gradient(135deg, #B74768, #B388FF);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.auth-desc {
color: #888;
font-size: 28rpx;
display: block;
}
.btn-sm {
padding: 24rpx 32rpx;
font-size: 28rpx;
}
.btn-secondary {
background: white;
color: #B74768;
border: 4rpx solid #B74768;
}
.btn-secondary:active {
background: #F4E2E3;
}
.btn-outline {
background: transparent;
color: #B74768;
border: 4rpx solid #FFB4C8;
}
.btn-outline:active {
background: #F4E2E3;
}
@import '../../styles/design.wxss';
@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; }
.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; }