fix: support profile editor swipe and show account deletion entry

This commit is contained in:
2026-11-03 22:38:01 +08:00
parent 8be6d9b1ab
commit 41bcb793da
6 changed files with 59 additions and 8 deletions
+21
View File
@@ -151,6 +151,27 @@ Page(withDataCache({
} }
}, },
onEditTouchStart: function(e) {
this._editSwipe = null;
if (!e.touches || e.touches.length !== 1 || (e.target && e.target.dataset && e.target.dataset.swipeIgnore)) return;
const point = e.touches[0];
this._editSwipe = {x:point.clientX,y:point.clientY};
},
onEditTouchCancel: function() { this._editSwipe = null; },
onEditTouchEnd: function(e) {
const start = this._editSwipe;
this._editSwipe = null;
const point = e.changedTouches && e.changedTouches[0];
if (!start || !point) return;
const dx = start.x - point.clientX, dy = Math.abs(start.y - point.clientY);
if (dx < 80 || dx <= dy * 1.5) return;
if (this.data.showEditPartnerModal && (this.data.partnerSaving || this.data.partnerAvatarUploading)) return;
if (this.data.showEditProfileModal) this.hideEditProfileModal();
else if (this.data.showEditPartnerModal) this.hideEditPartnerModal();
else return;
wx.hideKeyboard();
},
_needRefresh: true, _needRefresh: true,
onScroll: function(e) { onScroll: function(e) {
+7 -7
View File
@@ -46,30 +46,30 @@
<view wx:if="{{showEditProfileModal}}" class="edit-root" bindtouchstart="onTouchStart" bindtouchend="onTouchEnd"> <view wx:if="{{showEditProfileModal}}" class="edit-root" capture-bind:touchstart="onEditTouchStart" capture-bind:touchend="onEditTouchEnd" capture-bind:touchcancel="onEditTouchCancel">
<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-nav"><view class="back-btn" bindtap="hideEditProfileModal"><image class="back-icon" src="/images/back.svg" mode="aspectFit" /></view></view>
<scroll-view class="edit-body" scroll-y show-scrollbar="{{false}}" bindtouchstart="onTouchStart" bindtouchend="onTouchEnd"> <scroll-view class="edit-body" scroll-y show-scrollbar="{{false}}">
<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> <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> <text class="edit-id">我的ID: {{myDisplayId}}</text>
<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><input data-swipe-ignore="{{true}}" 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="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"><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><picker mode="date" value="{{tempLoveDate}}" bindchange="onLoveDateInput"><view class="input">{{tempLoveDate || '设置你们在一起的日期'}}</view></picker></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> <view class="input-group" wx:if="{{hasPartner}}"><label>恋爱故事</label><textarea data-swipe-ignore="{{true}}" placeholder-class="handwritten-placeholder" class="input" style="min-height:120rpx;" placeholder="记录你们的故事..." value="{{tempLoveStory}}" bindinput="onLoveStoryInput"></textarea></view>
</scroll-view> </scroll-view>
<view class="edit-footer"><button class="btn btn-primary" bindtap="saveProfile" hover-class="none">保存</button></view> <view class="edit-footer"><button class="btn btn-primary" bindtap="saveProfile" hover-class="none">保存</button></view>
</view> </view>
<view wx:if="{{showEditPartnerModal}}" class="edit-root partner-edit" bindtouchstart="onTouchStart" bindtouchend="onTouchEnd"> <view wx:if="{{showEditPartnerModal}}" class="edit-root partner-edit" capture-bind:touchstart="onEditTouchStart" capture-bind:touchend="onEditTouchEnd" capture-bind:touchcancel="onEditTouchCancel">
<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-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}}"> <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> <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> <text class="edit-id" wx:if="{{partnerDisplayId}}">TA的ID: {{partnerDisplayId}}</text>
<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><input data-swipe-ignore="{{true}}" 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="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="{{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><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> <view class="input-group"><label>恋爱故事</label><textarea data-swipe-ignore="{{true}}" placeholder-class="handwritten-placeholder" class="input" style="min-height:120rpx;" value="{{tempLoveStory}}" placeholder="记录你们的故事…" bindinput="onLoveStoryInput" disabled="{{partnerSaving}}" /></view>
</scroll-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 class="edit-footer"><button class="btn btn-primary" bindtap="savePartnerProfile" loading="{{partnerSaving}}" disabled="{{partnerSaving || partnerAvatarUploading}}" hover-class="none">保存</button></view>
</view> </view>
+4
View File
@@ -66,6 +66,10 @@ Page({
deleteAccount: async function() { deleteAccount: async function() {
if(this._deletingAccount)return; if(this._deletingAccount)return;
if (this.data.hasPartner) {
wx.showModal({title:'暂无法注销',content:'请先完成情侣解绑后再注销账号;处于解绑申请或72小时冷静期内,也需要等待解绑完成。',showCancel:false,confirmText:'知道了'});
return;
}
this._deletingAccount=true; this._deletingAccount=true;
try { try {
const preview=await api.accountDeletionPreview(); const preview=await api.accountDeletionPreview();
+1 -1
View File
@@ -25,7 +25,7 @@
</view> </view>
<view class="section-heading">关系与账号</view> <view class="section-heading">关系与账号</view>
<view class="j-profile-setting" wx:if="{{bindingKnown && !hasPartner}}" bindtap="deleteAccount"><view class="setting-info"><view class="j-small-title" style="color:#B74768">注销账号</view><view class="j-caption">未绑定时可注销,确认后删除全部个人数据</view></view><text class="j-link">›</text></view> <view class="j-profile-setting" bindtap="deleteAccount"><view class="setting-info"><view class="j-small-title" style="color:#B74768">注销账号</view><view class="j-caption">{{hasPartner ? '请先完成情侣解绑,再注销账号' : '确认后删除账号及全部个人数据'}}</view></view><text class="j-link">›</text></view>
<!-- 操作记录 --> <!-- 操作记录 -->
<view class="j-profile-setting" bindtap="goToHistory"> <view class="j-profile-setting" bindtap="goToHistory">
<view class="setting-info"> <view class="setting-info">
+7
View File
@@ -38,3 +38,10 @@ test('concurrent homepage notice reads share a request and do not dismiss a fres
test('a hidden homepage cannot consume a once-per-login notification',async()=>{ test('a hidden homepage cannot consume a once-per-login notification',async()=>{
let done;const e=page('index/index.js',{announcement:()=>new Promise(resolve=>done=resolve)});const a=e.p.loadAnnouncement();e.p.onHide();done({mode:'login_once',enabled:true,content:'hello',revision:'v1'});await a;assert.equal(e.p.data.showAnnouncement,false);e.p._announcementActive=true;const b=e.p.loadAnnouncement();done({mode:'login_once',enabled:true,content:'hello',revision:'v1'});await b;assert.equal(e.p.data.showAnnouncement,true); let done;const e=page('index/index.js',{announcement:()=>new Promise(resolve=>done=resolve)});const a=e.p.loadAnnouncement();e.p.onHide();done({mode:'login_once',enabled:true,content:'hello',revision:'v1'});await a;assert.equal(e.p.data.showAnnouncement,false);e.p._announcementActive=true;const b=e.p.loadAnnouncement();done({mode:'login_once',enabled:true,content:'hello',revision:'v1'});await b;assert.equal(e.p.data.showAnnouncement,true);
}); });
test('bound accounts see an explanation instead of starting account deletion',async()=>{
let calls=0;const e=page('settings/settings.js',{accountDeletionPreview:async()=>{calls++;},accountDelete:async()=>{calls++;}});
e.p.data.hasPartner=true;e.p.data.bindingKnown=true;
await e.p.deleteAccount();assert.equal(calls,0);assert.equal(e.dialogs.length,1);
assert.match(e.dialogs[0].content,/请先完成情侣解绑/);assert.equal(e.dialogs[0].showCancel,false);
});
+19
View File
@@ -0,0 +1,19 @@
const test=require('node:test'),assert=require('node:assert/strict'),path=require('node:path');
function setup(partner=false){
let def;const calls=[];
const load=require('./load-module.cjs')({Page:p=>def=p,console,Date,wx:{hideKeyboard:()=>calls.push('keyboard'),navigateBack:()=>calls.push('back')}},{'../../utils/page-cache':{withDataCache:p=>p},'../../utils/request':{api:{}}});
load(path.join(__dirname,'../pages/profile/profile.js'));
const p={...def,data:{...def.data,showEditProfileModal:!partner,showEditPartnerModal:partner},setData(v){Object.assign(this.data,v)},getTabBar:()=>({setHidden:v=>calls.push(v)})};return {p,calls};
}
const start=(x,y,ignore=false)=>({touches:[{clientX:x,clientY:y}],target:{dataset:{swipeIgnore:ignore}}});
const end=(x,y)=>({changedTouches:[{clientX:x,clientY:y}]});
test('left swipe closes either editor once, restores tabs and never navigates away from profile',()=>{
for(const partner of [false,true]){const {p,calls}=setup(partner);p.onEditTouchStart(start(280,300));p.onEditTouchEnd(end(100,310));p.onEditTouchEnd(end(100,310));assert.equal(p.data.showEditProfileModal,false);assert.equal(p.data.showEditPartnerModal,false);assert.deepEqual(calls,[false,'keyboard']);}
});
test('vertical scrolling, short or rightward gestures and text selection keep the editor open',()=>{
for(const [s,e] of [[start(280,300),end(240,305)],[start(280,300),end(160,500)],[start(100,300),end(280,310)],[start(280,300,true),end(100,310)]]){const {p,calls}=setup();p.onEditTouchStart(s);p.onEditTouchEnd(e);assert.equal(p.data.showEditProfileModal,true);assert.equal(calls.length,0);}
});
test('cancelled touches and partner saving never dismiss the editor',()=>{
const {p}=setup();p.onEditTouchStart(start(280,300));p.onEditTouchCancel();p.onEditTouchEnd(end(100,300));assert.equal(p.data.showEditProfileModal,true);
const e=setup(true);e.p.data.partnerSaving=true;e.p.onEditTouchStart(start(280,300));e.p.onEditTouchEnd(end(100,300));assert.equal(e.p.data.showEditPartnerModal,true);
});