From 40ef964b8cab6692b8ac5b9fd5bcf72c5cde5191 Mon Sep 17 00:00:00 2001 From: CodeST <694468528@qq.com> Date: Sat, 28 Feb 2026 14:50:27 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B3=A8=E9=94=80=E8=B4=A6?= =?UTF-8?q?=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Shared/KBAPI.h | 1 + .../Localization/en.lproj/Localizable.strings | 3 + .../zh-Hans.lproj/Localizable.strings | 3 + keyBoard.xcodeproj/project.pbxproj | 6 + keyBoard/Class/Me/VC/KBCancelAccountVC.h | 19 +++ keyBoard/Class/Me/VC/KBCancelAccountVC.m | 142 ++++++++++++++++++ keyBoard/Class/Me/VC/KBPersonInfoVC.m | 89 ++++------- keyBoard/Class/Me/VM/KBMyVM.h | 4 + keyBoard/Class/Me/VM/KBMyVM.m | 31 ++++ 9 files changed, 242 insertions(+), 56 deletions(-) create mode 100644 keyBoard/Class/Me/VC/KBCancelAccountVC.h create mode 100644 keyBoard/Class/Me/VC/KBCancelAccountVC.m diff --git a/Shared/KBAPI.h b/Shared/KBAPI.h index 957913d..23ed4d3 100644 --- a/Shared/KBAPI.h +++ b/Shared/KBAPI.h @@ -29,6 +29,7 @@ #define API_LOGOUT @"/user/logout" // 退出登录 #define API_USER_CANCEL_ACCOUNT @"/user/cancelAccount" // 注销账户 +#define API_CANCEL_ACCOUNT_WARNING @"/keyboardWarningMessage/byLocale" // 按locale查询注销提示信息 #define API_UPDATA_INFO @"/user/updateInfo" // 更新用户 diff --git a/Shared/Localization/en.lproj/Localizable.strings b/Shared/Localization/en.lproj/Localizable.strings index 9a33487..d590e01 100644 --- a/Shared/Localization/en.lproj/Localizable.strings +++ b/Shared/Localization/en.lproj/Localizable.strings @@ -174,6 +174,9 @@ "Log Out" = "Log Out"; "Cancel Account" = "Cancel Account"; "After cancellation, your account will be deactivated and local login data will be cleared. Continue?" = "After cancellation, your account will be deactivated and local login data will be cleared. Continue?"; +"Please enter your password" = "Please enter your password"; +"Cancel Account Notice" = "Cancel Account Notice"; +"Confirm Cancel Account" = "Confirm Cancel Account"; "Ranking List" = "Ranking List"; "Persona circle" = "Persona circle"; "Clear" = "Clear"; diff --git a/Shared/Localization/zh-Hans.lproj/Localizable.strings b/Shared/Localization/zh-Hans.lproj/Localizable.strings index ce983a2..bfec8b2 100644 --- a/Shared/Localization/zh-Hans.lproj/Localizable.strings +++ b/Shared/Localization/zh-Hans.lproj/Localizable.strings @@ -174,6 +174,9 @@ "Log Out" = "退出"; "Cancel Account" = "注销账户"; "After cancellation, your account will be deactivated and local login data will be cleared. Continue?" = "注销后账号将被停用,并清除本地登录数据,是否继续?"; +"Please enter your password" = "请输入密码"; +"Cancel Account Notice" = "注销账户须知"; +"Confirm Cancel Account" = "确认注销"; "Ranking List" = "排行榜"; "Persona circle" = "圈子"; "Clear" = "立刻清空"; diff --git a/keyBoard.xcodeproj/project.pbxproj b/keyBoard.xcodeproj/project.pbxproj index 1e0d681..7402623 100644 --- a/keyBoard.xcodeproj/project.pbxproj +++ b/keyBoard.xcodeproj/project.pbxproj @@ -60,6 +60,7 @@ 0459D1B42EBA284C00F2D189 /* KBSkinCenterVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 0459D1B32EBA284C00F2D189 /* KBSkinCenterVC.m */; }; 0459D1B72EBA287900F2D189 /* KBSkinManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0459D1B62EBA287900F2D189 /* KBSkinManager.m */; }; 0459D1B82EBA287900F2D189 /* KBSkinManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0459D1B62EBA287900F2D189 /* KBSkinManager.m */; }; + 045ED5212F52AF9200131114 /* KBCancelAccountVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 045ED5202F52AF9200131114 /* KBCancelAccountVC.m */; }; 0460866B2F18D75500757C95 /* ai_test.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 0460866A2F18D75500757C95 /* ai_test.m4a */; }; 046086752F191CC700757C95 /* AI技术分析.txt in Resources */ = {isa = PBXBuildFile; fileRef = 046086742F191CC700757C95 /* AI技术分析.txt */; }; 0460869A2F19238500757C95 /* KBAiWaveformView.m in Sources */ = {isa = PBXBuildFile; fileRef = 046086992F19238500757C95 /* KBAiWaveformView.m */; }; @@ -410,6 +411,8 @@ 0459D1B32EBA284C00F2D189 /* KBSkinCenterVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSkinCenterVC.m; sourceTree = ""; }; 0459D1B52EBA287900F2D189 /* KBSkinManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSkinManager.h; sourceTree = ""; }; 0459D1B62EBA287900F2D189 /* KBSkinManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSkinManager.m; sourceTree = ""; }; + 045ED51F2F52AF9200131114 /* KBCancelAccountVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBCancelAccountVC.h; sourceTree = ""; }; + 045ED5202F52AF9200131114 /* KBCancelAccountVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBCancelAccountVC.m; sourceTree = ""; }; 0460866A2F18D75500757C95 /* ai_test.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = ai_test.m4a; sourceTree = ""; }; 046086742F191CC700757C95 /* AI技术分析.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "AI技术分析.txt"; sourceTree = ""; }; 046086962F19238500757C95 /* KBAiRecordButton.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBAiRecordButton.h; sourceTree = ""; }; @@ -1854,6 +1857,8 @@ A1F0C1A52F1234567890ABCD /* KBConsumptionRecordVC.m */, 049FB2212EC311F900FAB05D /* KBPersonInfoVC.h */, 049FB2222EC311F900FAB05D /* KBPersonInfoVC.m */, + 045ED51F2F52AF9200131114 /* KBCancelAccountVC.h */, + 045ED5202F52AF9200131114 /* KBCancelAccountVC.m */, 04791F902ED48010004E8522 /* KBNoticeVC.h */, 04791F912ED48010004E8522 /* KBNoticeVC.m */, 04791F932ED48028004E8522 /* KBFeedBackVC.h */, @@ -2626,6 +2631,7 @@ 0498BDE42EEA885D006CC1D5 /* KBShopThemeModel.m in Sources */, 048908CD2EBE373500FABA60 /* KBSearchSectionHeader.m in Sources */, 049FB2202EC30D2700FAB05D /* HomeRankDetailPopView.m in Sources */, + 045ED5212F52AF9200131114 /* KBCancelAccountVC.m in Sources */, 048908CE2EBE373500FABA60 /* KBSkinCardCell.m in Sources */, 048908CF2EBE373500FABA60 /* KBTagCell.m in Sources */, 0477BEA22EBCF0000055D639 /* KBTopImageButton.m in Sources */, diff --git a/keyBoard/Class/Me/VC/KBCancelAccountVC.h b/keyBoard/Class/Me/VC/KBCancelAccountVC.h new file mode 100644 index 0000000..c368d9e --- /dev/null +++ b/keyBoard/Class/Me/VC/KBCancelAccountVC.h @@ -0,0 +1,19 @@ +// +// KBCancelAccountVC.h +// keyBoard +// +// 注销账户页面 +// + +#import "BaseViewController.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface KBCancelAccountVC : BaseViewController + +/// 注销协议 HTML 内容(由外部传入或内部请求) +@property (nonatomic, copy, nullable) NSString *htmlContent; + +@end + +NS_ASSUME_NONNULL_END diff --git a/keyBoard/Class/Me/VC/KBCancelAccountVC.m b/keyBoard/Class/Me/VC/KBCancelAccountVC.m new file mode 100644 index 0000000..3b4f5a6 --- /dev/null +++ b/keyBoard/Class/Me/VC/KBCancelAccountVC.m @@ -0,0 +1,142 @@ +// +// KBCancelAccountVC.m +// keyBoard +// +// 注销账户页面:顶部标题 + 中间 HTML 协议展示 + 底部注销按钮 +// + +#import "KBCancelAccountVC.h" +#import +#import +#import "KBMyVM.h" +#import "KBAlert.h" + +@interface KBCancelAccountVC () + +@property (nonatomic, strong) UILabel *titleLabel; +@property (nonatomic, strong) WKWebView *webView; +@property (nonatomic, strong) UIButton *cancelAccountBtn; +@property (nonatomic, strong) KBMyVM *myVM; + +@end + +@implementation KBCancelAccountVC + +- (void)viewDidLoad { + [super viewDidLoad]; + self.kb_titleLabel.text = KBLocalized(@"Cancel Account"); + self.view.backgroundColor = [UIColor colorWithHex:0xFFFFFF]; + + [self.view addSubview:self.titleLabel]; + [self.view addSubview:self.webView]; + [self.view addSubview:self.cancelAccountBtn]; + + [self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.top.equalTo(self.view).offset(KB_NAV_TOTAL_HEIGHT + 20); + make.left.equalTo(self.view).offset(16); + make.right.equalTo(self.view).offset(-16); + }]; + + [self.cancelAccountBtn mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self.view).offset(16); + make.right.equalTo(self.view).offset(-16); + make.bottom.equalTo(self.view.mas_safeAreaLayoutGuideBottom).offset(-12); + make.height.mas_equalTo(56); + }]; + + [self.webView mas_makeConstraints:^(MASConstraintMaker *make) { + make.top.equalTo(self.titleLabel.mas_bottom).offset(16); + make.left.equalTo(self.view).offset(16); + make.right.equalTo(self.view).offset(-16); + make.bottom.equalTo(self.cancelAccountBtn.mas_top).offset(-16); + }]; + + [self fetchAgreement]; +} + +- (void)fetchAgreement { + __weak typeof(self) weakSelf = self; + [self.myVM fetchCancelAccountWarningWithCompletion:^(NSString * _Nullable html, NSError * _Nullable error) { + if (html.length > 0) { + weakSelf.htmlContent = html; + } + [weakSelf loadHTMLContent]; + }]; +} + +- (void)loadHTMLContent { + NSString *html = self.htmlContent ?: @""; + // 包裹一层基本样式,适配移动端 + NSString *wrappedHTML = [NSString stringWithFormat: + @"" + "" + "%@", html]; + [self.webView loadHTMLString:wrappedHTML baseURL:nil]; +} + +#pragma mark - Actions + +- (void)onTapCancelAccount { + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_cancel_account_confirm_btn" + pageId:@"cancel_account" + elementId:@"cancel_account_confirm_btn" + extra:nil + completion:nil]; + KBWeakSelf; + + [KBAlert confirmTitle:KBLocalized(@"Cancel Account") message:KBLocalized(@"After cancellation, your account will be deactivated and local login data will be cleared. Continue?") ok:KBLocalized(@"Confirm") cancel:KBLocalized(@"Cancel") completion:^(BOOL ok) { + if (!ok) { return; } + [weakSelf.myVM cancelAccountWithCompletion:nil]; + }]; +} + +#pragma mark - Lazy + +- (UILabel *)titleLabel { + if (!_titleLabel) { + _titleLabel = [UILabel new]; + _titleLabel.text = KBLocalized(@"Cancel Account Notice"); + _titleLabel.textColor = [UIColor colorWithHex:KBBlackValue]; + _titleLabel.font = [KBFont bold:20]; + _titleLabel.numberOfLines = 0; + } + return _titleLabel; +} + +- (WKWebView *)webView { + if (!_webView) { + WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init]; + _webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:config]; + _webView.backgroundColor = UIColor.whiteColor; + _webView.scrollView.showsVerticalScrollIndicator = YES; + _webView.layer.cornerRadius = 12; + _webView.layer.masksToBounds = YES; + } + return _webView; +} + +- (UIButton *)cancelAccountBtn { + if (!_cancelAccountBtn) { + _cancelAccountBtn = [UIButton buttonWithType:UIButtonTypeSystem]; + [_cancelAccountBtn setTitle:KBLocalized(@"Confirm Cancel Account") forState:UIControlStateNormal]; + [_cancelAccountBtn setTitleColor:UIColor.whiteColor forState:UIControlStateNormal]; + _cancelAccountBtn.titleLabel.font = [KBFont medium:16]; + _cancelAccountBtn.backgroundColor = [UIColor colorWithHex:0xFF0000]; + _cancelAccountBtn.layer.cornerRadius = 12; + _cancelAccountBtn.layer.masksToBounds = YES; + [_cancelAccountBtn addTarget:self action:@selector(onTapCancelAccount) forControlEvents:UIControlEventTouchUpInside]; + } + return _cancelAccountBtn; +} + +- (KBMyVM *)myVM { + if (!_myVM) { + _myVM = [[KBMyVM alloc] init]; + } + return _myVM; +} + +@end diff --git a/keyBoard/Class/Me/VC/KBPersonInfoVC.m b/keyBoard/Class/Me/VC/KBPersonInfoVC.m index 98aa6c7..389b879 100644 --- a/keyBoard/Class/Me/VC/KBPersonInfoVC.m +++ b/keyBoard/Class/Me/VC/KBPersonInfoVC.m @@ -15,6 +15,7 @@ #import "KBGenderPickerPopView.h" #import "KBMyVM.h" #import "KBAlert.h" +#import "KBCancelAccountVC.h" @interface KBPersonInfoVC () // 列表 @@ -28,8 +29,6 @@ // 底部退出登录按钮 @property (nonatomic, strong) UIButton *logoutBtn; -// 底部注销账户按钮 -@property (nonatomic, strong) UIButton *cancelBtn; // 数据 @property (nonatomic, copy) NSArray *items; // {title,value,arrow,copy} @@ -70,15 +69,6 @@ // 底部退出登录按钮 [self.view addSubview:self.logoutBtn]; [self.logoutBtn mas_makeConstraints:^(MASConstraintMaker *make) { - make.left.equalTo(self.view).offset(16); - make.right.equalTo(self.view).offset(-16); - make.bottom.equalTo(self.view.mas_safeAreaLayoutGuideBottom).offset(-(12 + 56 + 10)); - make.height.mas_equalTo(56); - }]; - - // 底部注销账户按钮 - [self.view addSubview:self.cancelBtn]; - [self.cancelBtn mas_makeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(self.view).offset(16); make.right.equalTo(self.view).offset(-16); make.bottom.equalTo(self.view.mas_safeAreaLayoutGuideBottom).offset(-12); @@ -87,7 +77,7 @@ // 列表底部腾出空间,避免被按钮挡住 UIEdgeInsets inset = self.tableView.contentInset; - inset.bottom = 56 + 10 + 56 + 24; // 两个按钮高度 + 间距 + 额外间距 + inset.bottom = 56 + 24; // 按钮高度 + 额外间距 self.tableView.contentInset = inset; self.viewModel = [[KBMyVM alloc] init]; __weak typeof(self) weakSelf = self; @@ -128,14 +118,18 @@ #pragma mark - UITableView -- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.items.count; } +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 2; } +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return section == 0 ? self.items.count : 1; +} - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { return 56.0; } -- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { return 12.0; } +- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { + return section == 0 ? 12.0 : 15.0; +} - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { return [UIView new]; } - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section { return 0.01; } @@ -143,19 +137,34 @@ static NSString *cid = @"KBPersonInfoItemCell"; KBPersonInfoItemCell *cell = [tableView dequeueReusableCellWithIdentifier:cid]; if (!cell) { cell = [[KBPersonInfoItemCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cid]; } - NSDictionary *it = self.items[indexPath.row]; - BOOL isTop = (indexPath.row == 0); - BOOL isBottom = (indexPath.row == self.items.count - 1); - [cell configWithTitle:it[@"title"] - value:it[@"value"] - showArrow:[it[@"arrow"] boolValue] - showCopy:[it[@"copy"] boolValue] - isTop:isTop - isBottom:isBottom]; + + if (indexPath.section == 0) { + NSDictionary *it = self.items[indexPath.row]; + BOOL isTop = (indexPath.row == 0); + BOOL isBottom = (indexPath.row == self.items.count - 1); + [cell configWithTitle:it[@"title"] + value:it[@"value"] + showArrow:[it[@"arrow"] boolValue] + showCopy:[it[@"copy"] boolValue] + isTop:isTop + isBottom:isBottom]; + } else { + [cell configWithTitle:KBLocalized(@"Cancel Account") + value:@"" + showArrow:YES + showCopy:NO + isTop:YES + isBottom:YES]; + } return cell; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + if (indexPath.section == 1) { + KBCancelAccountVC *vc = [[KBCancelAccountVC alloc] init]; + [self.navigationController pushViewController:vc animated:YES]; + return; + } if (indexPath.row == 0) { // 昵称编辑 -> 弹窗 CGFloat width = KB_SCREEN_WIDTH; @@ -287,25 +296,6 @@ [self.myVM logout]; } -- (void)onTapCancelAccount { - [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_person_cancel_account_btn" - pageId:@"person_info" - elementId:@"cancel_account_btn" - extra:nil - completion:nil]; - KBWeakSelf; - [KBAlert confirmTitle:KBLocalized(@"Cancel Account") - message:KBLocalized(@"After cancellation, your account will be deactivated and local login data will be cleared. Continue?") - ok:KBLocalized(@"Confirm") - cancel:KBLocalized(@"Cancel") - okColor:[UIColor colorWithHex:0xFF0000] - cancelColor:nil - completion:^(BOOL ok) { - if (!ok) { return; } - [weakSelf.myVM cancelAccountWithCompletion:nil]; - }]; -} - #pragma mark - Lazy UI(懒加载) - (UITableView *)tableView { @@ -409,19 +399,6 @@ return _logoutBtn; } -- (UIButton *)cancelBtn { - if (!_cancelBtn) { - _cancelBtn = [UIButton buttonWithType:UIButtonTypeSystem]; - [_cancelBtn setTitle:KBLocalized(@"Cancel Account") forState:UIControlStateNormal]; - [_cancelBtn setTitleColor:[UIColor colorWithHex:0xFF0000] forState:UIControlStateNormal]; - _cancelBtn.titleLabel.font = [KBFont medium:16]; - _cancelBtn.backgroundColor = UIColor.whiteColor; - _cancelBtn.layer.cornerRadius = 12; _cancelBtn.layer.masksToBounds = YES; - [_cancelBtn addTarget:self action:@selector(onTapCancelAccount) forControlEvents:UIControlEventTouchUpInside]; - } - return _cancelBtn; -} - #pragma mark - Image Picker - (void)presentImagePicker { diff --git a/keyBoard/Class/Me/VM/KBMyVM.h b/keyBoard/Class/Me/VM/KBMyVM.h index c4ee0b8..cf461a3 100644 --- a/keyBoard/Class/Me/VM/KBMyVM.h +++ b/keyBoard/Class/Me/VM/KBMyVM.h @@ -32,6 +32,7 @@ typedef void(^KBMyPurchaseRecordCompletion)(NSArray *_Nul typedef void(^KBMyInviteCodeCompletion)(KBInviteCodeModel *_Nullable inviteCode, NSError *_Nullable error); typedef void(^KBMyCustomerMailCompletion)(NSString *_Nullable customerMail, NSError *_Nullable error); typedef void(^KBCancelAccountCompletion)(BOOL success, NSError *_Nullable error); +typedef void(^KBCancelAccountAgreementCompletion)(NSString *_Nullable html, NSError *_Nullable error); @interface KBMyVM : NSObject @@ -81,6 +82,9 @@ typedef void(^KBCancelAccountCompletion)(BOOL success, NSError *_Nullable error) /// 注销账号(/user/cancelAccount) - (void)cancelAccountWithCompletion:(KBCancelAccountCompletion)completion; + +/// 获取注销提示信息 HTML(GET /keyboardWarningMessage/byLocale) +- (void)fetchCancelAccountWarningWithCompletion:(KBCancelAccountAgreementCompletion)completion; @end NS_ASSUME_NONNULL_END diff --git a/keyBoard/Class/Me/VM/KBMyVM.m b/keyBoard/Class/Me/VM/KBMyVM.m index d2ba7a0..c8940c7 100644 --- a/keyBoard/Class/Me/VM/KBMyVM.m +++ b/keyBoard/Class/Me/VM/KBMyVM.m @@ -489,6 +489,37 @@ NSString * const KBUserCharacterDeletedNotification = @"KBUserCharacterDeletedNo }]; } +- (void)fetchCancelAccountWarningWithCompletion:(KBCancelAccountAgreementCompletion)completion { + KBLanguageCode langCode = [KBLocalizationManager shared].currentLanguageCode; + NSString *locale; + if ([langCode isEqualToString:KBLanguageCodeSimplifiedChinese]) { + locale = @"zh-CN"; + } else { + locale = @"en-US"; + } + + [[KBNetworkManager shared] GET:API_CANCEL_ACCOUNT_WARNING + parameters:@{@"locale": locale} + headers:nil + autoShowBusinessError:NO + completion:^(NSDictionary *jsonOrData, NSURLResponse * _Nullable response, NSError * _Nullable error) { + if (error) { + if (completion) completion(nil, error); + return; + } + + id dataObj = jsonOrData[KBData]; + NSString *html = @""; + if ([dataObj isKindOfClass:[NSDictionary class]]) { + id content = dataObj[@"content"]; + if ([content isKindOfClass:[NSString class]]) { + html = (NSString *)content; + } + } + if (completion) completion(html, nil); + }]; +} + #pragma mark - Private /// 清理本地登录态及关联缓存,并回到首页。