From 599a5de3bc3e8e553546fcf756bf6800f8f02c30 Mon Sep 17 00:00:00 2001 From: CodeST <694468528@qq.com> Date: Wed, 3 Dec 2025 14:30:02 +0800 Subject: [PATCH] 5 --- keyBoard/AppDelegate.h | 2 +- keyBoard/AppDelegate.m | 77 +++++++++-------- keyBoard/Class/Home/V/HomeHeadView.m | 5 ++ keyBoard/Class/Login/VC/KBLoginVC.m | 35 +++++++- keyBoard/Class/Manager/KBUserSessionManager.h | 2 + keyBoard/Class/Manager/KBUserSessionManager.m | 16 +++- keyBoard/Class/Me/VM/KBMyVM.m | 5 +- keyBoard/Class/Network/KBNetworkManager.h | 18 +++- keyBoard/Class/Network/KBNetworkManager.m | 86 ++++++++++++++----- 9 files changed, 180 insertions(+), 66 deletions(-) diff --git a/keyBoard/AppDelegate.h b/keyBoard/AppDelegate.h index 6b6f447..f6038a5 100644 --- a/keyBoard/AppDelegate.h +++ b/keyBoard/AppDelegate.h @@ -10,6 +10,6 @@ @interface AppDelegate : UIResponder @property (nonatomic, strong) UIWindow *window; -- (void)setupRootVC; +- (void)toMainTabbarVC; @end diff --git a/keyBoard/AppDelegate.m b/keyBoard/AppDelegate.m index 7b1b73c..f6fdbbf 100644 --- a/keyBoard/AppDelegate.m +++ b/keyBoard/AppDelegate.m @@ -94,13 +94,20 @@ [self.window makeKeyAndVisible]; // 根据当前是否已登录决定入口:有 token 进主 TabBar,否则先进入登录页 - BOOL loggedIn = [[KBUserSessionManager shared] isLoggedIn]; +// BOOL loggedIn = [[KBUserSessionManager shared] isLoggedIn]; UIViewController *rootVC = nil; - if (loggedIn) { + rootVC = [[BaseTabBarController alloc] init]; - } else { - rootVC = [[KBLoginVC alloc] init]; - } + + self.window.rootViewController = rootVC; +} + +- (void)toMainTabbarVC{ + self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + self.window.backgroundColor = [UIColor whiteColor]; + [self.window makeKeyAndVisible]; + UIViewController *rootVC = nil; + rootVC = [[BaseTabBarController alloc] init]; self.window.rootViewController = rootVC; } @@ -209,35 +216,37 @@ } - (void)goLogin{ - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - KBLoginPopView *view = [[KBLoginPopView alloc] initWithFrame:CGRectMake(0, 0, KB_SCREEN_WIDTH, KB_SCREEN_WIDTH)]; - // 创建并弹出 - LSTPopView *pop = [LSTPopView initWithCustomView:view - parentView:nil - popStyle:LSTPopStyleSmoothFromBottom - dismissStyle:LSTDismissStyleSmoothToBottom]; - pop.hemStyle = LSTHemStyleBottom; - pop.bgColor = [[UIColor blackColor] colorWithAlphaComponent:0.4]; - pop.isClickBgDismiss = YES; // 点击背景关闭 - pop.cornerRadius = 0; // 自定义 view 自处理圆角 - - __weak typeof(pop) weakPop = pop; - view.appleLoginHandler = ^{ - [weakPop dismiss]; - // 交给 VM 统一处理 Apple 登录 + 服务端登录 - [[KBLoginVM shared] signInWithAppleFromViewController:KB_CURRENT_NAV completion:^(BOOL success, NSError * _Nullable error) { - if (success) { - [KBHUD showInfo:KBLocalized(@"Signed in successfully")]; - } else { - NSString *msg = error.localizedDescription ?: KBLocalized(@"Sign-in failed"); - [KBHUD showInfo:msg]; - } - }]; - }; - view.closeHandler = ^{ [weakPop dismiss]; }; - - [pop pop]; - }); + KBLoginVC *vc = [[KBLoginVC alloc] init]; + [KB_CURRENT_NAV pushViewController:vc animated:true]; +// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ +// KBLoginPopView *view = [[KBLoginPopView alloc] initWithFrame:CGRectMake(0, 0, KB_SCREEN_WIDTH, KB_SCREEN_WIDTH)]; +// // 创建并弹出 +// LSTPopView *pop = [LSTPopView initWithCustomView:view +// parentView:nil +// popStyle:LSTPopStyleSmoothFromBottom +// dismissStyle:LSTDismissStyleSmoothToBottom]; +// pop.hemStyle = LSTHemStyleBottom; +// pop.bgColor = [[UIColor blackColor] colorWithAlphaComponent:0.4]; +// pop.isClickBgDismiss = YES; // 点击背景关闭 +// pop.cornerRadius = 0; // 自定义 view 自处理圆角 +// +// __weak typeof(pop) weakPop = pop; +// view.appleLoginHandler = ^{ +// [weakPop dismiss]; +// // 交给 VM 统一处理 Apple 登录 + 服务端登录 +// [[KBLoginVM shared] signInWithAppleFromViewController:KB_CURRENT_NAV completion:^(BOOL success, NSError * _Nullable error) { +// if (success) { +// [KBHUD showInfo:KBLocalized(@"Signed in successfully")]; +// } else { +// NSString *msg = error.localizedDescription ?: KBLocalized(@"Sign-in failed"); +// [KBHUD showInfo:msg]; +// } +// }]; +// }; +// view.closeHandler = ^{ [weakPop dismiss]; }; +// +// [pop pop]; +// }); } diff --git a/keyBoard/Class/Home/V/HomeHeadView.m b/keyBoard/Class/Home/V/HomeHeadView.m index 7883c16..9f5e896 100644 --- a/keyBoard/Class/Home/V/HomeHeadView.m +++ b/keyBoard/Class/Home/V/HomeHeadView.m @@ -164,6 +164,11 @@ #pragma mark - Actions - (void)onTapBuyAction { // if (self.onTapBuy) { self.onTapBuy(); } + // 未登录时先跳到登录页;登录后才允许进入会员购买页 + if (![KBUserSessionManager shared].isLoggedIn) { + [[KBUserSessionManager shared] goLoginVC]; + return; + } KBVipPay *vc = [[KBVipPay alloc] init]; [KB_CURRENT_NAV pushViewController:vc animated:true]; } diff --git a/keyBoard/Class/Login/VC/KBLoginVC.m b/keyBoard/Class/Login/VC/KBLoginVC.m index 5c5d3eb..7676bdf 100644 --- a/keyBoard/Class/Login/VC/KBLoginVC.m +++ b/keyBoard/Class/Login/VC/KBLoginVC.m @@ -15,6 +15,7 @@ // 背景 @property (nonatomic, strong) UIImageView *bgImageView; // 整体背景图:login_bg_icon @property (nonatomic, strong) UIImageView *topRightImageView; // 顶部右侧装饰图:login_jianp_icon +@property (nonatomic, strong) UIButton *backButton; // 顶部左侧返回按钮 // 底部白色容器(仅上圆角 26) @property (nonatomic, strong) UIView *contentContainerView; @@ -74,6 +75,14 @@ [self.bgImageView mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(self.view); }]; + + // 顶部左侧返回按钮 + [self.view addSubview:self.backButton]; + [self.backButton mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self.view).offset(16); + make.top.equalTo(self.view).offset(KB_StatusBarHeight() + 8); + make.width.height.mas_equalTo(32); + }]; // 顶部右侧装饰图 [self.view addSubview:self.topRightImageView]; @@ -165,7 +174,7 @@ id appDelegate = UIApplication.sharedApplication.delegate; if ([appDelegate respondsToSelector:@selector(setupRootVC)]) { AppDelegate *delegate = (AppDelegate *)appDelegate; - [delegate setupRootVC]; + [delegate toMainTabbarVC]; } }); } else { @@ -195,6 +204,15 @@ KBLOG(@"onTapForgotPassword"); } +- (void)onTapBack { + // 与 BaseViewController 的返回逻辑保持一致:优先 pop,其次 dismiss + if (self.navigationController && self.navigationController.viewControllers.count > 1) { + [self.navigationController popViewControllerAnimated:YES]; + } else if (self.presentingViewController) { + [self dismissViewControllerAnimated:YES completion:nil]; + } +} + #pragma mark - Lazy UI - (UIImageView *)bgImageView { @@ -223,6 +241,21 @@ return _contentContainerView; } +- (UIButton *)backButton { + if (!_backButton) { + _backButton = [UIButton buttonWithType:UIButtonTypeCustom]; + UIImage *img = [UIImage imageNamed:@"back"]; + if (!img) { img = [UIImage imageNamed:@"back_black_icon"]; } + if (img) { + [_backButton setImage:img forState:UIControlStateNormal]; + } + _backButton.adjustsImageWhenHighlighted = YES; + _backButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft; + [_backButton addTarget:self action:@selector(onTapBack) forControlEvents:UIControlEventTouchUpInside]; + } + return _backButton; +} + - (UILabel *)titleLabel { if (!_titleLabel) { _titleLabel = [UILabel new]; diff --git a/keyBoard/Class/Manager/KBUserSessionManager.h b/keyBoard/Class/Manager/KBUserSessionManager.h index b1c5e5e..5ff23b9 100644 --- a/keyBoard/Class/Manager/KBUserSessionManager.h +++ b/keyBoard/Class/Manager/KBUserSessionManager.h @@ -36,6 +36,8 @@ NS_ASSUME_NONNULL_BEGIN /// 退出登录:清 Keychain + 本地缓存 - (void)logout; +- (void)goLoginVC; + @end NS_ASSUME_NONNULL_END diff --git a/keyBoard/Class/Manager/KBUserSessionManager.m b/keyBoard/Class/Manager/KBUserSessionManager.m index 6857317..2d447a4 100644 --- a/keyBoard/Class/Manager/KBUserSessionManager.m +++ b/keyBoard/Class/Manager/KBUserSessionManager.m @@ -10,7 +10,7 @@ #import "KBUser.h" #import "KBConfig.h" #import - +#import "KBLoginVC.h" /// App Group 里的用户缓存 key static NSString * const kKBSessionUserStoreKey = @"KBSession.currentUser"; /// “本次安装是否已初始化”标记 key(用来识别卸载重装) @@ -58,14 +58,17 @@ static NSString * const kKBSessionInstallFlagKey = @"KBSession.installInitialize if (self.didBootstrap) return; self.didBootstrap = YES; - BOOL hasInitializedThisInstall = [self.defaults boolForKey:kKBSessionInstallFlagKey]; + // 使用本 App 的 standardUserDefaults 记录“本次安装是否已初始化”。 + // 这样在卸载重装后,这个标记会被系统清空,从而触发一次性清理旧的 Keychain token。 + NSUserDefaults *installDefaults = [NSUserDefaults standardUserDefaults]; + BOOL hasInitializedThisInstall = [installDefaults boolForKey:kKBSessionInstallFlagKey]; KBAuthManager *auth = [KBAuthManager shared]; // 内部会 reloadFromKeychain if (!hasInitializedThisInstall) { // 说明是“卸载重装后的第一次运行”(或者真正第一次安装) - [self.defaults setBool:YES forKey:kKBSessionInstallFlagKey]; - [self.defaults synchronize]; + [installDefaults setBool:YES forKey:kKBSessionInstallFlagKey]; + [installDefaults synchronize]; // 若此时 Keychain 里已有 token,多半是上一次安装遗留的;按你的需求清掉 if (auth.current.accessToken.length > 0) { @@ -160,4 +163,9 @@ static NSString * const kKBSessionInstallFlagKey = @"KBSession.installInitialize } } +- (void)goLoginVC{ + KBLoginVC *vc = [[KBLoginVC alloc] init]; + [KB_CURRENT_NAV pushViewController:vc animated:true]; +} + @end diff --git a/keyBoard/Class/Me/VM/KBMyVM.m b/keyBoard/Class/Me/VM/KBMyVM.m index effec0f..adc3f86 100644 --- a/keyBoard/Class/Me/VM/KBMyVM.m +++ b/keyBoard/Class/Me/VM/KBMyVM.m @@ -14,6 +14,7 @@ [[KBNetworkManager shared] GET:API_LOGOUT parameters:nil headers:nil + autoShowBusinessError:NO completion:^(NSDictionary *jsonOrData, NSURLResponse * _Nullable response, NSError * _Nullable error) { // 不管成功失败,都先把 HUD 收掉 [KBHUD dismiss]; @@ -34,9 +35,9 @@ // 回到登录 / 主界面 dispatch_async(dispatch_get_main_queue(), ^{ id appDelegate = UIApplication.sharedApplication.delegate; - if ([appDelegate respondsToSelector:@selector(setupRootVC)]) { + if ([appDelegate respondsToSelector:@selector(toMainTabbarVC)]) { AppDelegate *delegate = (AppDelegate *)appDelegate; - [delegate setupRootVC]; + [delegate toMainTabbarVC]; } }); }]; diff --git a/keyBoard/Class/Network/KBNetworkManager.h b/keyBoard/Class/Network/KBNetworkManager.h index bd3d42b..5bd617f 100644 --- a/keyBoard/Class/Network/KBNetworkManager.h +++ b/keyBoard/Class/Network/KBNetworkManager.h @@ -22,8 +22,8 @@ typedef NS_ERROR_ENUM(KBNetworkErrorDomain, KBNetworkError) { /// JSON 回调:约定服务端统一返回顶层 NSDictionary({code,message,data,...}) typedef void(^KBNetworkCompletion)(NSDictionary *_Nullable json, - NSURLResponse * _Nullable response, - NSError * _Nullable error); + NSURLResponse *_Nullable response, + NSError *_Nullable error); /// 二进制回调:用于下载 zip、图片等原始数据 typedef void(^KBNetworkDataCompletion)(NSData *_Nullable data, @@ -48,6 +48,13 @@ typedef void(^KBNetworkDataCompletion)(NSData *_Nullable data, @property (nonatomic, assign) NSTimeInterval timeout; /// GET 请求,parameters 会拼到 URL 上 +- (nullable NSURLSessionDataTask *)GET:(NSString *)path + parameters:(nullable NSDictionary *)parameters + headers:(nullable NSDictionary *)headers + autoShowBusinessError:(BOOL)autoShowBusinessError + completion:(KBNetworkCompletion)completion; + +/// 兼容旧调用:默认 autoShowBusinessError = YES - (nullable NSURLSessionDataTask *)GET:(NSString *)path parameters:(nullable NSDictionary *)parameters headers:(nullable NSDictionary *)headers @@ -60,6 +67,13 @@ typedef void(^KBNetworkDataCompletion)(NSData *_Nullable data, completion:(KBNetworkDataCompletion)completion; /// POST JSON 请求,jsonBody 会以 application/json 发送 +- (nullable NSURLSessionDataTask *)POST:(NSString *)path + jsonBody:(nullable id)jsonBody + headers:(nullable NSDictionary *)headers + autoShowBusinessError:(BOOL)autoShowBusinessError + completion:(KBNetworkCompletion)completion; + +/// 兼容旧调用:默认 autoShowBusinessError = YES - (nullable NSURLSessionDataTask *)POST:(NSString *)path jsonBody:(nullable id)jsonBody headers:(nullable NSDictionary *)headers diff --git a/keyBoard/Class/Network/KBNetworkManager.m b/keyBoard/Class/Network/KBNetworkManager.m index 35a7452..724d0c3 100644 --- a/keyBoard/Class/Network/KBNetworkManager.m +++ b/keyBoard/Class/Network/KBNetworkManager.m @@ -53,12 +53,15 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network"; return self; } +# #pragma mark - Public +// JSON GET:可控制业务错误是否由内部弹出 - (NSURLSessionDataTask *)GET:(NSString *)path - parameters:(NSDictionary *)parameters - headers:(NSDictionary *)headers - completion:(KBNetworkCompletion)completion { + parameters:(NSDictionary *)parameters + headers:(NSDictionary *)headers + autoShowBusinessError:(BOOL)autoShowBusinessError + completion:(KBNetworkCompletion)completion { NSLog(@"[KBNetworkManager] GET called, enabled=%d, path=%@", self.isEnabled, path); if (![self ensureEnabled:completion]) return nil; NSString *urlString = [self buildURLStringWithPath:path]; @@ -84,14 +87,30 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network"; req.allHTTPHeaderFields ?: @{}, paramStr); #endif - return [self startJSONTaskWithRequest:req completion:completion]; + return [self startJSONTaskWithRequest:req + autoShowBusinessError:autoShowBusinessError + completion:completion]; } +// 默认 GET:自动弹业务错误 +- (NSURLSessionDataTask *)GET:(NSString *)path + parameters:(NSDictionary *)parameters + headers:(NSDictionary *)headers + completion:(KBNetworkCompletion)completion { + return [self GET:path + parameters:parameters + headers:headers +autoShowBusinessError:YES + completion:completion]; +} + +// JSON POST:可控制业务错误是否由内部弹出 - (NSURLSessionDataTask *)POST:(NSString *)path - jsonBody:(id)jsonBody - headers:(NSDictionary *)headers - completion:(KBNetworkCompletion)completion { - NSLog(@"====="); + jsonBody:(id)jsonBody + headers:(NSDictionary *)headers + autoShowBusinessError:(BOOL)autoShowBusinessError + completion:(KBNetworkCompletion)completion { + NSLog(@"[KBNetworkManager] POST called, enabled=%d, path=%@", self.isEnabled, path); if (![self ensureEnabled:completion]) return nil; NSString *urlString = [self buildURLStringWithPath:path]; if (!urlString) { [self fail:KBNetworkErrorInvalidURL completion:completion]; return nil; } @@ -113,7 +132,21 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network"; req.allHTTPHeaderFields ?: @{}, bodyStr); #endif - return [self startJSONTaskWithRequest:req completion:completion]; + return [self startJSONTaskWithRequest:req + autoShowBusinessError:autoShowBusinessError + completion:completion]; +} + +// 默认 POST:自动弹业务错误 +- (NSURLSessionDataTask *)POST:(NSString *)path + jsonBody:(id)jsonBody + headers:(NSDictionary *)headers + completion:(KBNetworkCompletion)completion { + return [self POST:path + jsonBody:jsonBody + headers:headers + autoShowBusinessError:YES + completion:completion]; } // 原始二进制 GET,用于下载 zip、图片等 @@ -190,6 +223,7 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network"; } - (NSURLSessionDataTask *)startJSONTaskWithRequest:(NSURLRequest *)req + autoShowBusinessError:(BOOL)autoShowBusinessError completion:(KBNetworkCompletion)completion { NSLog(@"[KBNetworkManager] startAFTaskWithRequest: %@", req.URL.absoluteString); // 响应先用原始数据返回,再按 Content-Type 解析 JSON(与原实现一致) @@ -266,18 +300,23 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network"; // 统一解析业务 code:约定后端顶层包含 { code, message, data } NSInteger bizCode = KBBizCodeFromJSONObject(dict); if (bizCode != NSNotFound && bizCode != KBBizCodeSuccess) { - // 非成功业务 code:执行通用处理(如 token 失效)并通过 error 方式回调 - [self kb_handleBizCode:bizCode json:dict response:response]; - NSString *msg = KBBizMessageFromJSONObject(json) ?: KBLocalized(@"Server error"); - NSError *bizErr = [NSError errorWithDomain:KBNetworkErrorDomain - code:KBNetworkErrorBusiness - userInfo:@{ - NSLocalizedDescriptionKey : msg, - @"code" : @(bizCode) - }]; - if (completion) completion(dict, response, bizErr); - return; + // 非成功业务 code:执行通用处理(如 token 失效)并通过 error 方式回调 + BOOL handledByAuth = [self kb_handleBizCode:bizCode json:dict response:response]; + NSString *msg = KBBizMessageFromJSONObject(dict) ?: KBLocalized(@"Server error"); + if (autoShowBusinessError && !handledByAuth) { + dispatch_async(dispatch_get_main_queue(), ^{ + [KBHUD showInfo:msg]; + }); } + NSError *bizErr = [NSError errorWithDomain:KBNetworkErrorDomain + code:KBNetworkErrorBusiness + userInfo:@{ + NSLocalizedDescriptionKey : msg, + @"code" : @(bizCode) + }]; + if (completion) completion(dict, response, bizErr); + return; + } // code 缺失或为成功,按正常成功回调 if (completion) completion(dict, response, nil); } else { @@ -328,8 +367,9 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network"; #pragma mark - Private helpers -/// 处理通用业务 code(token 失效、被踢下线等) -- (void)kb_handleBizCode:(NSInteger)bizCode +/// 处理通用业务 code(token 失效、被踢下线等); +/// 返回 YES 表示该 code 已在此处消费(例如已清理登录态并提示),外部无需再提示。 +- (BOOL)kb_handleBizCode:(NSInteger)bizCode json:(id)json response:(NSURLResponse *)response { switch (bizCode) { @@ -361,10 +401,12 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network"; object:nil userInfo:info]; }); + return YES; } break; default: break; } + return NO; } - (void)fail:(KBNetworkError)code completion:(KBNetworkCompletion)completion {