From 633745495633f16b0949c70373f5baf1db5c7da4 Mon Sep 17 00:00:00 2001 From: jiayangyang <534955398@qq.com> Date: Mon, 29 Jun 2026 18:46:58 +0800 Subject: [PATCH] =?UTF-8?q?feat(home):=20=E9=A6=96=E9=A1=B5=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E5=81=A5=E5=BA=B7=E7=9F=A5=E8=AF=86/=E8=B5=84?= =?UTF-8?q?=E8=AE=AF=E6=A8=A1=E5=9D=97=EF=BC=8C=E5=90=AB=E5=8F=8C=E8=A7=86?= =?UTF-8?q?=E5=9B=BE=E5=88=97=E8=A1=A8=E5=8F=8AH5=E8=B7=B3=E8=BD=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增健康知识视图(标签切换+搜索+列表)和健康资讯视图(纯列表无搜索) - 对接健康栏目列表和文章分页接口,资讯无子分类直接用父级ID - 接入更多/搜索/详情H5跳转,统一用通用加密串传参 - 键盘弹起时自动避让,从H5回来刷新文章收藏数 Co-Authored-By: Claude Opus 4.7 --- .../XinjiangProject.xcodeproj/project.pbxproj | 8 +- .../XinjiangProject/Define/StatusMacros.h | 59 +- .../Manager/XJHealthKnowledgeManager.h | 47 ++ .../Manager/XJHealthKnowledgeManager.m | 222 +++++++ .../Controllview/PhysicalWebViewController.h | 16 + .../Controllview/PhysicalWebViewController.m | 31 + .../Controllview/XJHomePageV2ViewController.m | 585 ++++++++++++++++-- .../HomeV2/Model/XJHealthArticleModel.h | 28 + .../HomeV2/Model/XJHealthArticleModel.m | 12 + .../HomeV2/Model/XJHealthCategoryModel.h | 20 + .../HomeV2/Model/XJHealthCategoryModel.m | 16 + .../HomeV2/Model/XJHealthKnowledgeModel.h | 24 + .../HomeV2/Model/XJHealthKnowledgeModel.m | 12 + .../HomeV2/View/XJHealthKnowledgeCell.h | 20 + .../HomeV2/View/XJHealthKnowledgeCell.m | 177 ++++++ .../HomeV2/View/XJHealthKnowledgeView.h | 42 ++ .../HomeV2/View/XJHealthKnowledgeView.m | 433 +++++++++++++ .../Project/HomeV2/View/XJHealthNewsCell.h | 20 + .../Project/HomeV2/View/XJHealthNewsCell.m | 115 ++++ .../Project/HomeV2/View/XJHealthNewsView.h | 31 + .../Project/HomeV2/View/XJHealthNewsView.m | 205 ++++++ .../article_classIcon.imageset/Contents.json | 21 + .../article_classIcon.png | Bin 0 -> 1349 bytes .../Contents.json | 21 + .../article_placholdIMg.png | Bin 0 -> 4253 bytes 25 files changed, 2080 insertions(+), 85 deletions(-) create mode 100644 XinjiangProject/XinjiangProject/Manager/XJHealthKnowledgeManager.h create mode 100644 XinjiangProject/XinjiangProject/Manager/XJHealthKnowledgeManager.m create mode 100644 XinjiangProject/XinjiangProject/Project/HomeV2/Controllview/PhysicalWebViewController.h create mode 100644 XinjiangProject/XinjiangProject/Project/HomeV2/Controllview/PhysicalWebViewController.m create mode 100644 XinjiangProject/XinjiangProject/Project/HomeV2/Model/XJHealthArticleModel.h create mode 100644 XinjiangProject/XinjiangProject/Project/HomeV2/Model/XJHealthArticleModel.m create mode 100644 XinjiangProject/XinjiangProject/Project/HomeV2/Model/XJHealthCategoryModel.h create mode 100644 XinjiangProject/XinjiangProject/Project/HomeV2/Model/XJHealthCategoryModel.m create mode 100644 XinjiangProject/XinjiangProject/Project/HomeV2/Model/XJHealthKnowledgeModel.h create mode 100644 XinjiangProject/XinjiangProject/Project/HomeV2/Model/XJHealthKnowledgeModel.m create mode 100644 XinjiangProject/XinjiangProject/Project/HomeV2/View/XJHealthKnowledgeCell.h create mode 100644 XinjiangProject/XinjiangProject/Project/HomeV2/View/XJHealthKnowledgeCell.m create mode 100644 XinjiangProject/XinjiangProject/Project/HomeV2/View/XJHealthKnowledgeView.h create mode 100644 XinjiangProject/XinjiangProject/Project/HomeV2/View/XJHealthKnowledgeView.m create mode 100644 XinjiangProject/XinjiangProject/Project/HomeV2/View/XJHealthNewsCell.h create mode 100644 XinjiangProject/XinjiangProject/Project/HomeV2/View/XJHealthNewsCell.m create mode 100644 XinjiangProject/XinjiangProject/Project/HomeV2/View/XJHealthNewsView.h create mode 100644 XinjiangProject/XinjiangProject/Project/HomeV2/View/XJHealthNewsView.m create mode 100644 XinjiangProject/XinjiangProject/Supporting Files/Assets.xcassets/HOMEV2/article_classIcon.imageset/Contents.json create mode 100644 XinjiangProject/XinjiangProject/Supporting Files/Assets.xcassets/HOMEV2/article_classIcon.imageset/article_classIcon.png create mode 100644 XinjiangProject/XinjiangProject/Supporting Files/Assets.xcassets/HOMEV2/article_placholdIMg.imageset/Contents.json create mode 100644 XinjiangProject/XinjiangProject/Supporting Files/Assets.xcassets/HOMEV2/article_placholdIMg.imageset/article_placholdIMg.png diff --git a/XinjiangProject/XinjiangProject.xcodeproj/project.pbxproj b/XinjiangProject/XinjiangProject.xcodeproj/project.pbxproj index 26fa44c..687d18b 100644 --- a/XinjiangProject/XinjiangProject.xcodeproj/project.pbxproj +++ b/XinjiangProject/XinjiangProject.xcodeproj/project.pbxproj @@ -375,7 +375,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = A7CC5LW224; ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_PREFIX_HEADER = "$(SRCROOT)/XinjiangProject/Supporting Files/PrefixHeader.pch"; @@ -400,7 +400,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.11; + MARKETING_VERSION = 1.0.13; PRODUCT_BUNDLE_IDENTIFIER = com.questionTools.XinjiangProject; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; @@ -419,7 +419,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = A7CC5LW224; ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_PREFIX_HEADER = "$(SRCROOT)/XinjiangProject/Supporting Files/PrefixHeader.pch"; @@ -444,7 +444,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.11; + MARKETING_VERSION = 1.0.13; PRODUCT_BUNDLE_IDENTIFIER = com.questionTools.XinjiangProject; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; diff --git a/XinjiangProject/XinjiangProject/Define/StatusMacros.h b/XinjiangProject/XinjiangProject/Define/StatusMacros.h index af9b4bd..ab81e3b 100755 --- a/XinjiangProject/XinjiangProject/Define/StatusMacros.h +++ b/XinjiangProject/XinjiangProject/Define/StatusMacros.h @@ -61,40 +61,49 @@ #pragma mark: 网络前缀 #define ResourceAddress [NSString stringWithFormat:@"%@/file/show/",Http] -////开发环境地址 彭杰 -//#define Http @"http://192.168.1.47"//昊 -//#define H5DevHost @"xj-admin.mcrm.vip:8888"// -//#define IntervenH5 @"https://console-xj-admin.mcrm.vip:8888" -//#define SCHEME @"https" // //正式地址 -//#define Http @"https://api-jkglpt.iosp.ydpt.tech"// -//#define H5DevHost @"api-jkglpt.iosp.ydpt.tech"// -//#define IntervenH5 @"https://console-jkglpt.iosp.ydpt.tech" -//#define SCHEME @"https" // +#define Http @"https://api-jkglpt.iosp.ydpt.tech"// +#define H5DevHost @"api-jkglpt.iosp.ydpt.tech"// +#define IntervenH5 @"https://console-jkglpt.iosp.ydpt.tech" +#define SCHEME @"https" // +#define OtherHttp @"https://api-jkglpt.iosp.ydpt.tech/hb" // + -//卫鑫本地 -//#define Http @"https://api-jkglpt.iosp.ydpt.tech"// -//#define H5DevHost @"api-jkglpt.iosp.ydpt.tech"// -//#define IntervenH5 @"http://192.168.1.26:9876" -//#define SCHEME @"https" // //最新新疆测试地址 //#define Http @"http://192.168.1.223"//彭杰 // 最新地址 //#define Http @"http://192.168.1.47"//王昊 +//#define Http @"https://xjxc-api.mcrm.vip:8888"// +//#define H5DevHost @"xjxc-api.mcrm.vip:8888"// +//#define IntervenH5 @"https://xj-admin.mcrm.vip:8888" +//#define SCHEME @"https" // +//#define OtherHttp @"https://gstest.superwx.cn/xj_health" // -#define Http @"https://xj-api.mcrm.vip:8888"// -//#define Http @"http://192.168.1.22"//王昊 -#define H5DevHost @"xj-api.mcrm.vip:8888"// -#define IntervenH5 @"https://xj-admin.mcrm.vip:8888" -#define SCHEME @"https" // -//新疆项目后期的调试环境由阿里云迁移至公司内部主机服务器,功能联调和测试可用以下的环境地址 -//https://api-xj.tri.icdat.cn -//https://console-xj.tri.icdat.cn/ -//接口: http://api-xj.tri.dt.io/ -//后台: http://console-xj.tri.dt.io/ -//大屏: http://screen-xj.tri.dt.io/home +#pragma mark - ——————— 健康知识/资讯 ———————— + +// H5 页面地址 +#define kHealthKnowMoreH5 @"/healthnewsweb/healthKnow.html" +#define kHealthNewsMoreH5 @"/healthnewsweb/healthNews.html" +#define kHealthDetailH5 @"/healthnewsweb/healthNewsDetail.html" +#define kHealthSearchH5 @"/healthnewsweb/healthSecrch.html" + +// HMAC-SHA256 签名密钥 +#define kHealthKnowledgeSecretKey @"XJp3EHpcnwReXc1A" + +//测试 健康知识+资讯 +//#define kHealthKnowledgeBaseURL @"https://gstest.superwx.cn/healthcheck" +//#define kHealthKnowledgeH5Host @"https://gstest.superwx.cn" +//正式 健康知识+资讯 +#define kHealthKnowledgeBaseURL @"https://api-jkglpt.iosp.ydpt.tech/hb" +#define kHealthKnowledgeH5Host @"https://console-jkglpt.iosp.ydpt.tech/hb" + +// API 路径 +#define kHealthKnowledgeTokenPath @"/pe/frontend/init" +#define kHealthKnowledgeCategoryPath @"/cms/frontend/category/list" +#define kHealthKnowledgeArticleListPath @"/cms/frontend/articleList" + #endif /* StatusMacros_h */ diff --git a/XinjiangProject/XinjiangProject/Manager/XJHealthKnowledgeManager.h b/XinjiangProject/XinjiangProject/Manager/XJHealthKnowledgeManager.h new file mode 100644 index 0000000..011b509 --- /dev/null +++ b/XinjiangProject/XinjiangProject/Manager/XJHealthKnowledgeManager.h @@ -0,0 +1,47 @@ +// +// XJHealthKnowledgeManager.h +// XinjiangProject +// +// Created by Apple on 2026/6/29. +// + +#import +#import "XJHealthCategoryModel.h" +#import "XJHealthArticleModel.h" + +NS_ASSUME_NONNULL_BEGIN + +/// 健康知识网络管理器 +@interface XJHealthKnowledgeManager : NSObject + ++ (instancetype)sharedManager; + +/// 当前 token(获取后缓存) +@property (nonatomic, copy, readonly, nullable) NSString * cachedToken; + +/// 清除缓存的 token(退出登录时调用) +- (void)clearToken; + +/// 获取 token +- (void)getTokenWithUserId:(NSString *)userId + workNo:(NSString *)workNo + success:(void (^)(NSString * token))success + failure:(void (^)(NSString * errorMsg))failure; + +/// 获取栏目列表 +- (void)getCategoryListWithToken:(NSString *)token + success:(void (^)(NSArray * categories))success + failure:(void (^)(NSString * errorMsg))failure; + +/// 分页查询文章(showAppHomepage 固定 Y) +- (void)getArticleListWithToken:(NSString *)token + categoryId:(NSInteger)categoryId + articleType:(nullable NSString *)articleType + pageNum:(NSInteger)pageNum + pageSize:(NSInteger)pageSize + success:(void (^)(NSArray * articles, NSInteger total))success + failure:(void (^)(NSString * errorMsg))failure; + +@end + +NS_ASSUME_NONNULL_END diff --git a/XinjiangProject/XinjiangProject/Manager/XJHealthKnowledgeManager.m b/XinjiangProject/XinjiangProject/Manager/XJHealthKnowledgeManager.m new file mode 100644 index 0000000..60df127 --- /dev/null +++ b/XinjiangProject/XinjiangProject/Manager/XJHealthKnowledgeManager.m @@ -0,0 +1,222 @@ +// +// XJHealthKnowledgeManager.m +// XinjiangProject +// +// Created by Apple on 2026/6/29. +// + +#import "XJHealthKnowledgeManager.h" +#import + +#pragma mark - ————— Manager ————— + +@interface XJHealthKnowledgeManager () + +@property (nonatomic, copy) NSString * cachedToken; + +@end + +@implementation XJHealthKnowledgeManager + ++ (instancetype)sharedManager { + static XJHealthKnowledgeManager * instance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [[XJHealthKnowledgeManager alloc] init]; + }); + return instance; +} + +#pragma mark - ————— HMAC-SHA256 ————— + +- (void)clearToken { + self.cachedToken = nil; + DLog(@"[健康知识] Token 已清除"); +} + ++ (NSString *)hmacSHA256:(NSString *)data key:(NSString *)key { + const char * cKey = [key cStringUsingEncoding:NSUTF8StringEncoding]; + const char * cData = [data cStringUsingEncoding:NSUTF8StringEncoding]; + unsigned char result[CC_SHA256_DIGEST_LENGTH]; + CCHmac(kCCHmacAlgSHA256, cKey, strlen(cKey), cData, strlen(cData), result); + + NSMutableString * hash = [[NSMutableString alloc] initWithCapacity:CC_SHA256_DIGEST_LENGTH * 2]; + for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) { + [hash appendFormat:@"%02x", result[i]]; + } + return hash; +} + +#pragma mark - ————— 获取 Token ————— + +- (void)getTokenWithUserId:(NSString *)userId + workNo:(NSString *)workNo + success:(void (^)(NSString *))success + failure:(void (^)(NSString *))failure { + + NSString * urlString = [NSString stringWithFormat:@"%@%@", kHealthKnowledgeBaseURL, kHealthKnowledgeTokenPath]; + + // 构建请求体 + NSDictionary * bodyDict = @{ @"userId": userId ?: @"", + @"workNo": workNo ?: @"" }; + NSData * bodyData = [NSJSONSerialization dataWithJSONObject:bodyDict options:0 error:nil]; + NSString * bodyString = [[NSString alloc] initWithData:bodyData encoding:NSUTF8StringEncoding]; + + // 签名 + NSString * timestamp = [NSString stringWithFormat:@"%.0f", [[NSDate date] timeIntervalSince1970]]; + NSString * stringToSign = [NSString stringWithFormat:@"%@%@", timestamp, bodyString]; + NSString * sign = [[self class] hmacSHA256:stringToSign key:kHealthKnowledgeSecretKey]; + + NSMutableURLRequest * request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:urlString]]; + request.HTTPMethod = @"POST"; + [request setValue:@"application/json; charset=utf-8" forHTTPHeaderField:@"Content-Type"]; + [request setValue:timestamp forHTTPHeaderField:@"X-Timestamp"]; + [request setValue:sign forHTTPHeaderField:@"X-Sign"]; + request.HTTPBody = bodyData; + + DLog(@"[健康知识] 请求 Token URL: %@", urlString); + DLog(@"[健康知识] 签名串: %@", stringToSign); + DLog(@"[健康知识] X-Sign: %@", sign); + + [self sendRequest:request completion:^(NSDictionary * json) { + DLog(@"[健康知识] Token 响应: %@", json); + if ([json[@"code"] integerValue] == 200) { + NSString * token = json[@"data"][@"token"]; + if (!ValidStr(token)) { + self.cachedToken = token; + DLog(@"[健康知识] Token 获取成功: %@", token); + if (success) success(token); + } else { + DLog(@"[健康知识] Token 为空"); + if (failure) failure(@"token 为空"); + } + } else { + DLog(@"[健康知识] Token 失败: %@", json[@"msg"]); + if (failure) failure(json[@"msg"] ?: @"获取 token 失败"); + } + } failure:^(NSString * errorMsg) { + DLog(@"[健康知识] Token 请求异常: %@", errorMsg); + if (failure) failure(errorMsg); + }]; +} + +#pragma mark - ————— 栏目列表 ————— + +- (void)getCategoryListWithToken:(NSString *)token + success:(void (^)(NSArray *))success + failure:(void (^)(NSString *))failure { + + NSString * urlString = [NSString stringWithFormat:@"%@%@", kHealthKnowledgeBaseURL, kHealthKnowledgeCategoryPath]; + + NSMutableURLRequest * request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:urlString]]; + request.HTTPMethod = @"GET"; + [request setValue:token forHTTPHeaderField:@"token"]; + + [self sendRequest:request completion:^(NSDictionary * json) { + if ([json[@"code"] integerValue] == 200) { + NSArray * dataArray = json[@"data"]; + NSMutableArray * categories = [[NSMutableArray alloc] init]; + if ([dataArray isKindOfClass:[NSArray class]]) { + for (NSDictionary * dict in dataArray) { + XJHealthCategoryModel * m = [XJHealthCategoryModel modelWithDictionary:dict]; + [categories addObject:m]; + } + } + if (success) success(categories); + } else { + if (failure) failure(json[@"msg"] ?: @"获取栏目列表失败"); + } + } failure:^(NSString * errorMsg) { + if (failure) failure(errorMsg); + }]; +} + +#pragma mark - ————— 分页查询文章 ————— + +- (void)getArticleListWithToken:(NSString *)token + categoryId:(NSInteger)categoryId + articleType:(NSString *)articleType + pageNum:(NSInteger)pageNum + pageSize:(NSInteger)pageSize + success:(void (^)(NSArray *, NSInteger))success + failure:(void (^)(NSString *))failure { + + NSMutableString * urlString = [NSMutableString stringWithFormat:@"%@%@", kHealthKnowledgeBaseURL, kHealthKnowledgeArticleListPath]; + [urlString appendFormat:@"?categoryId=%ld&pageNum=%ld&pageSize=%ld&showAppHomepage=Y", + (long)categoryId, (long)pageNum, (long)pageSize]; + + if (!ValidStr(articleType)) { + [urlString appendFormat:@"&articleType=%@", articleType]; + } + + NSMutableURLRequest * request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:urlString]]; + request.HTTPMethod = @"GET"; + [request setValue:token forHTTPHeaderField:@"token"]; + + [self sendRequest:request completion:^(NSDictionary * json) { + if ([json[@"code"] integerValue] == 200) { + NSArray * rows = json[@"rows"]; + NSInteger total = [json[@"total"] integerValue]; + NSMutableArray * articles = [[NSMutableArray alloc] init]; + if ([rows isKindOfClass:[NSArray class]]) { + for (NSDictionary * dict in rows) { + XJHealthArticleModel * m = [XJHealthArticleModel modelWithDictionary:dict]; + [articles addObject:m]; + } + } + if (success) success(articles, total); + } else { + if (failure) failure(json[@"msg"] ?: @"获取文章列表失败"); + } + } failure:^(NSString * errorMsg) { + if (failure) failure(errorMsg); + }]; +} + +#pragma mark - ————— 通用请求 ————— + +- (void)sendRequest:(NSURLRequest *)request + completion:(void (^)(NSDictionary * json))completion + failure:(void (^)(NSString * errorMsg))failure { + + DLog(@"[健康知识] >>> 请求: %@ %@", request.HTTPMethod, request.URL.absoluteString); + + NSURLSession * session = [NSURLSession sharedSession]; + NSURLSessionDataTask * task = [session dataTaskWithRequest:request + completionHandler:^(NSData * _Nullable data, + NSURLResponse * _Nullable response, + NSError * _Nullable error) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (error) { + DLog(@"[健康知识] <<< 网络错误: %@", error.localizedDescription); + if (failure) failure(error.localizedDescription); + return; + } + + NSHTTPURLResponse * httpResp = (NSHTTPURLResponse *)response; + NSInteger statusCode = httpResp.statusCode; + DLog(@"[健康知识] <<< HTTP %ld", (long)statusCode); + + if (statusCode != 200) { + NSString * msg = [NSString stringWithFormat:@"HTTP %ld", (long)statusCode]; + DLog(@"[健康知识] <<< 请求失败: %@", msg); + if (failure) failure(msg); + return; + } + + NSError * jsonError = nil; + NSDictionary * json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; + if (jsonError || ![json isKindOfClass:[NSDictionary class]]) { + DLog(@"[健康知识] <<< JSON 解析失败"); + if (failure) failure(@"数据解析失败"); + return; + } + + DLog(@"[健康知识] <<< 响应: %@", json); + if (completion) completion(json); + }); + }]; + [task resume]; +} + +@end diff --git a/XinjiangProject/XinjiangProject/Project/HomeV2/Controllview/PhysicalWebViewController.h b/XinjiangProject/XinjiangProject/Project/HomeV2/Controllview/PhysicalWebViewController.h new file mode 100644 index 0000000..16eaf27 --- /dev/null +++ b/XinjiangProject/XinjiangProject/Project/HomeV2/Controllview/PhysicalWebViewController.h @@ -0,0 +1,16 @@ +// +// PhysicalWebViewController.h +// XinjiangProject +// +// Created by Apple on 2026/3/24. +// + +#import "XJBaseWebViewVC.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface PhysicalWebViewController : XJBaseWebViewVC + +@end + +NS_ASSUME_NONNULL_END diff --git a/XinjiangProject/XinjiangProject/Project/HomeV2/Controllview/PhysicalWebViewController.m b/XinjiangProject/XinjiangProject/Project/HomeV2/Controllview/PhysicalWebViewController.m new file mode 100644 index 0000000..8ed43e5 --- /dev/null +++ b/XinjiangProject/XinjiangProject/Project/HomeV2/Controllview/PhysicalWebViewController.m @@ -0,0 +1,31 @@ +// +// PhysicalWebViewController.m +// XinjiangProject +// +// Created by Apple on 2026/3/24. +// + +#import "PhysicalWebViewController.h" + +@interface PhysicalWebViewController () + +@end + +@implementation PhysicalWebViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + // Do any additional setup after loading the view. +} + +/* +#pragma mark - Navigation + +// In a storyboard-based application, you will often want to do a little preparation before navigation +- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { + // Get the new view controller using [segue destinationViewController]. + // Pass the selected object to the new view controller. +} +*/ + +@end diff --git a/XinjiangProject/XinjiangProject/Project/HomeV2/Controllview/XJHomePageV2ViewController.m b/XinjiangProject/XinjiangProject/Project/HomeV2/Controllview/XJHomePageV2ViewController.m index d9f001b..dc5f670 100644 --- a/XinjiangProject/XinjiangProject/Project/HomeV2/Controllview/XJHomePageV2ViewController.m +++ b/XinjiangProject/XinjiangProject/Project/HomeV2/Controllview/XJHomePageV2ViewController.m @@ -37,6 +37,11 @@ #import "SDCycleScrollView.h" #import "BRPickerView.h" #import "BindWatchPopView.h" +#import "PhysicalWebViewController.h" +#import "XJHealthKnowledgeView.h" +#import "XJHealthNewsView.h" +#import "XJHealthKnowledgeModel.h" +#import "XJHealthKnowledgeManager.h" static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半 @@ -77,6 +82,17 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半 @property (nonatomic, strong) SDCycleScrollView * AIBannerImage; +@property (nonatomic, strong) XJHealthKnowledgeView * healthKnowledgeView; +@property (nonatomic, strong) MASConstraint * knowledgeHeightConstraint; + +@property (nonatomic, strong) XJHealthNewsView * healthNewsView; +@property (nonatomic, strong) MASConstraint * healthNewsHeightConstraint; + +@property (nonatomic, strong) NSArray * healthCategories; +@property (nonatomic, strong) XJHealthCategoryModel * currentHealthCategory; +@property (nonatomic, assign) NSInteger healthNewsCategoryId; +@property (nonatomic, assign) BOOL healthDataLoaded; + //加密后的字符串 @property (nonatomic, copy) NSString * secretString; @@ -123,10 +139,27 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半 // Request the desired authorization [self.userlocationManager requestWhenInUseAuthorization]; self.isHidenNaviBar = true; + //每次进入首页清除旧 token,重新获取(避免环境切换残留) + self.healthDataLoaded = NO; + [[XJHealthKnowledgeManager sharedManager] clearToken]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateHomeDate) name:KNotificAgreeProtoocl object:nil]; + //监听退出登录,清除健康知识 token + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(handleLogoutClearToken:) + name:KNotificationLoginStateChange + object:nil]; + //键盘通知 + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(keyboardWillShow:) + name:UIKeyboardWillShowNotification + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(keyboardWillHide:) + name:UIKeyboardWillHideNotification + object:nil]; // [self createUI]; [self updateV2UI]; [self getUpDateinfo]; @@ -182,13 +215,28 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半 //请求token获取数据 [self verifyTokenData]; + //健康知识/资讯:首次全量加载,从 H5 回来只刷新文章列表(收藏数等会变) + if ([HQCommonUtils isLogin]) { + if (!self.healthDataLoaded) { + [self loadHealthKnowledgeData]; + } else { + [self refreshHealthArticles]; + } + } else { + [self.healthKnowledgeView showNotLoggedInPlaceholder]; + [self updateHealthKnowledgeViewHeight]; + [self.healthNewsView showNotLoggedInPlaceholder]; + [self updateHealthNewsViewHeight]; + } + //登录了那就走轮询 if ([HQCommonUtils isLogin]) { //请求消息列表 [self getMessageListData]; [self getUserWatchData]; [self getWebKeySecret]; - + //请求首页积分接口 + [self getHomePagePointsNumber]; } } @@ -306,18 +354,62 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半 } - (void)updateHomeDate { - - + + if (self.homeTable) { [self verifyTokenData]; //token [self getBannerListData]; //banner }else { - + } } +- (void)handleLogoutClearToken:(NSNotification *)noti { + // @NO = 退出登录,@YES = 登录成功 + if ([noti.object boolValue] == NO) { + self.healthDataLoaded = NO; + [[XJHealthKnowledgeManager sharedManager] clearToken]; + [self.healthKnowledgeView showNotLoggedInPlaceholder]; + [self updateHealthKnowledgeViewHeight]; + [self.healthNewsView showNotLoggedInPlaceholder]; + [self updateHealthNewsViewHeight]; + } +} + +#pragma mark - ————— 键盘避让 ————— + +- (void)keyboardWillShow:(NSNotification *)noti { + NSDictionary * info = noti.userInfo; + CGFloat keyboardH = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height; + CGFloat duration = [info[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; + + [UIView animateWithDuration:duration animations:^{ + self.mainScrollView.contentInset = UIEdgeInsetsMake(0, 0, keyboardH, 0); + }]; + + // 只算搜索框区域(头部40 + 间距10 + 搜索36 ≈ 头部下方86pt) + CGRect searchArea = CGRectMake(0, 0, self.healthKnowledgeView.bounds.size.width, 86); + CGRect searchRect = [self.healthKnowledgeView convertRect:searchArea toView:nil]; + CGFloat searchBottom = CGRectGetMaxY(searchRect); + CGFloat visibleH = [UIScreen mainScreen].bounds.size.height - keyboardH; + + if (searchBottom > visibleH) { + CGFloat gap = searchBottom - visibleH; + CGPoint offset = self.mainScrollView.contentOffset; + offset.y += gap; + [self.mainScrollView setContentOffset:offset animated:YES]; + } +} + +- (void)keyboardWillHide:(NSNotification *)noti { + CGFloat duration = [noti.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; + + [UIView animateWithDuration:duration animations:^{ + self.mainScrollView.contentInset = UIEdgeInsetsZero; + }]; +} - (void)getData { @@ -357,6 +449,22 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半 [searchApi getHomeWatchData:parmDic]; } +#pragma mark:请求积分 +- (void)getHomePagePointsNumber { + + NSMutableDictionary * parmDic = [[NSMutableDictionary alloc] init]; + NSDate * localDate = [NSDate date]; + NSString * str = [NSDate br_stringFromDate:localDate dateFormat:@"yyyy-MM-dd"]; + [parmDic setObject:User_ID forKey:@"userId"]; + + XJPNetAPI * searchApi = [[XJPNetAPI alloc] init:self tag:@"POINT" NeedToken:@""]; + + [searchApi postPointsUnmberData:parmDic]; + + +} + + #pragma mark: 请求key的加密 - (void)getWebKeySecret { @@ -365,7 +473,8 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半 NSString * host; #if DEBUG - host = @"xj-api.mcrm.vip"; + //xj-api.mcrm.vip + host = @"api-jkglpt.iosp.ydpt.tech"; #else @@ -384,8 +493,8 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半 XJPNetAPI * searchApi = [[XJPNetAPI alloc] init:self tag:@"SECRET" NeedToken:@""]; [searchApi getWebNeedEncryptionKey:parmDic]; - } + #pragma mark:绑定手表 - (void)getBindWatchDataWithSerialNum:(NSString *)numNo { [self showMBProgressHUDWithString:@"绑定中,请稍候..." autoHidden:false]; @@ -395,16 +504,6 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半 [searchApi getBindWatchDevice:parmDic]; } -#pragma mark:解除绑定手表 -- (void)getUnBindWatchDataWithSerialNum:(NSString *)numNo { - - [self showMBProgressHUDWithString:@"解绑中,请稍候..." autoHidden:false]; - NSMutableDictionary * parmDic = [[NSMutableDictionary alloc] init]; - [parmDic setObject:numNo forKey:@"watchNo"]; - XJPNetAPI * searchApi = [[XJPNetAPI alloc] init:self tag:@"UNBINDWATCH" NeedToken:@""]; - - [searchApi getUnBindWatchDevice:parmDic]; -} - (void)Failed:(NSString*)message tag:(NSString*)tag { @@ -430,7 +529,7 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半 self.topView.watchArray = @[]; } - + [self showMBProgressHUDOnlyWithString:message]; [self hiddenAllMBProgressHUD]; @@ -639,10 +738,9 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半 if ([tag isEqualToString:@"SECRET"]) { DLog(@"%@",response) NSDictionary * responseDic = response; - + if ([responseDic.allKeys containsObject:@"encryptData"]) { self.secretString = responseDic[@"encryptData"]; - } } if ([tag isEqualToString:@"BINDWATCH"]) { @@ -651,14 +749,25 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半 [EasyTextView showSuccessText:@"绑定成功"]; [self getUserWatchData]; } - if ([tag isEqualToString:@"UNBINDWATCH"]) { - [self hiddenAllMBProgressHUD]; - [EasyTextView showSuccessText:@"解绑成功"]; - - - DLog(@"%@",response); - [self getUserWatchData]; + if([tag isEqualToString:@"POINT"]) { + NSDictionary * responseDic = response; + if (ValidDict(responseDic)) { + NSString * todayPointStr = @"今日健康积分 0"; + NSString * totalPointStr = @"累计积分 0"; + + if ([responseDic.allKeys containsObject:@"totalPoints"]) { + + totalPointStr = [NSString stringWithFormat:@"累计积分 %.2f",[responseDic[@"totalPoints"] floatValue]]; + + } + if ([responseDic.allKeys containsObject:@"todayPoints"]) { + + todayPointStr = [NSString stringWithFormat:@"今日健康积分 %.2f",[responseDic[@"todayPoints"] floatValue]]; + } + self.topView.totalponits = [NSString stringWithFormat:@"%@ | %@",todayPointStr,totalPointStr]; + } + DLog(@"--------%@",responseDic); } } @@ -856,12 +965,21 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半 self.mainScrollView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{ self.page = 1; [self getBannerListData]; + //健康知识:未登录占位,已登录刷新 if ([HQCommonUtils isLogin]) { - //请求消息列表 - [self getUserWatchData]; - + [self loadHealthKnowledgeData]; + } else { + [self.healthKnowledgeView showNotLoggedInPlaceholder]; + [self updateHealthKnowledgeViewHeight]; + [self.healthNewsView showNotLoggedInPlaceholder]; + [self updateHealthNewsViewHeight]; } - + //请求消息列表 + if ([HQCommonUtils isLogin]) { + [self getUserWatchData]; + [self getHomePagePointsNumber]; + } + [self verifyTokenData]; }]; @@ -876,26 +994,14 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半 self.topView.bindDeviceBlock = ^(NSString * _Nonnull watchNo) { + // 已绑定手表时不响应 + if (!ValidStr(watchNo)) return; + BindWatchPopView *pop = [[BindWatchPopView alloc] init]; - - if (ValidStr(watchNo)) { - pop.type = BindWatchTypeBind; - }else - { - pop.serialNumber = watchNo; - pop.type = BindWatchTypeUnbind; - - } + pop.type = BindWatchTypeBind; pop.actionBlock = ^(BindWatchType type, NSString *serialNumber) { - if (type == BindWatchTypeBind) { //绑定 - [weakSelf getBindWatchDataWithSerialNum:serialNumber]; - - }else //解绑 - { - [weakSelf getUnBindWatchDataWithSerialNum:serialNumber]; - - } + [weakSelf getBindWatchDataWithSerialNum:serialNumber]; }; [pop show]; @@ -1026,12 +1132,69 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半 [self.navigationController pushViewController:vc animated:YES]; }; + // 健康知识模块 + XJHealthKnowledgeView * healthKnowledgeView = [[XJHealthKnowledgeView alloc] init]; + self.healthKnowledgeView = healthKnowledgeView; + [self.contentView addSubview:healthKnowledgeView]; + + [healthKnowledgeView mas_makeConstraints:^(MASConstraintMaker *make) { + make.top.mas_equalTo(bottomTools.mas_bottom).offset(0); + make.left.mas_offset(12); + make.right.mas_offset(-12); + weakself.knowledgeHeightConstraint = make.height.mas_offset(200); + }]; + + healthKnowledgeView.moreBlock = ^{ + [weakself ensureTokenThenExecute:^{ + [weakself openHealthKnowledgeMorePage]; + }]; + }; + + healthKnowledgeView.searchBlock = ^(NSString * _Nonnull keyword) { + [weakself ensureTokenThenExecute:^{ + [weakself openHealthKnowledgeSearchPage:keyword]; + }]; + }; + + healthKnowledgeView.tabSelectBlock = ^(NSInteger index) { + if (index < weakself.healthCategories.count) { + weakself.currentHealthCategory = weakself.healthCategories[index]; + [weakself fetchHealthArticlesForCategoryId:weakself.currentHealthCategory.categoryId]; + } + }; + + healthKnowledgeView.cellSelectBlock = ^(XJHealthKnowledgeModel * _Nonnull model) { + [weakself openHealthKnowledgeDetailPage:model]; + }; + + // 健康资讯模块 + XJHealthNewsView * healthNewsView = [[XJHealthNewsView alloc] init]; + self.healthNewsView = healthNewsView; + [self.contentView addSubview:healthNewsView]; + + [healthNewsView mas_makeConstraints:^(MASConstraintMaker *make) { + make.top.mas_equalTo(healthKnowledgeView.mas_bottom).offset(12); + make.left.mas_offset(12); + make.right.mas_offset(-12); + weakself.healthNewsHeightConstraint = make.height.mas_offset(130); + }]; + + healthNewsView.moreBlock = ^{ + [weakself ensureTokenThenExecute:^{ + [weakself openHealthNewsMorePage]; + }]; + }; + + healthNewsView.cellSelectBlock = ^(XJHealthKnowledgeModel * _Nonnull model) { + [weakself openHealthKnowledgeDetailPage:model]; + }; + //最下面的一张图 // self.aiImage = [[UIImageView alloc] init]; //// self.aiImage.image = [UIImage imageNamed:@"bottomAIImg"]; // self.aiImage.userInteractionEnabled = true; // [self.contentView addSubview:self.aiImage]; - + self.AIBannerImage = [SDCycleScrollView cycleScrollViewWithFrame:CGRectZero delegate:self placeholderImage:[UIImage imageNamed:@"placeholder"]]; self.AIBannerImage.backgroundColor = KClearColor; self.AIBannerImage.placeholderImage = nil; @@ -1050,7 +1213,7 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半 [self.AIBannerImage mas_makeConstraints:^(MASConstraintMaker *make) { make.left.mas_offset(12); make.right.mas_offset(-12); - make.top.mas_equalTo(bottomTools.mas_bottom).offset(2); + make.top.mas_equalTo(healthNewsView.mas_bottom).offset(12); make.height.mas_offset(120); make.bottom.equalTo(self.contentView.mas_bottom).offset(-18); }]; @@ -1135,9 +1298,33 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半 } if (type == 1) { - EmptyFunctionDevelopmentVC * vc = [[EmptyFunctionDevelopmentVC alloc] init]; - vc.navigationItem.title = @"健康体检"; - [self.navigationController pushViewController:vc animated:YES]; +// EmptyFunctionDevelopmentVC * vc = [[EmptyFunctionDevelopmentVC alloc] init]; +// vc.navigationItem.title = @"健康体检"; +// [self.navigationController pushViewController:vc animated:YES]; + + if(![HQCommonUtils isLogin]) { + KPostNotification(KNotificationLoginStateChange, @NO); + return; + } + if (ValidStr(self.secretString)) { + [self showMBProgressHUDOnlyWithString:@"网络异常请稍候再试~"]; + [self getWebKeySecret]; + return; + } + PhysicalWebViewController * phyVC = [[PhysicalWebViewController alloc] init]; + + NSString * urlString; +#if DEBUG + urlString = @"https://gstest.superwx.cn/healthcheckweb/index.html"; + + +#else + urlString = @"https://console-jkglpt.iosp.ydpt.tech/hb/healthcheckweb/index.html"; + +#endif + phyVC.webUrl = [NSString stringWithFormat:@"%@?param=%@",urlString,self.secretString]; + [self.navigationController pushViewController:phyVC animated:YES]; + } if (type == 2) { @@ -1186,9 +1373,31 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半 if (imageIndex == 50) { - EmptyFunctionDevelopmentVC * vc = [[EmptyFunctionDevelopmentVC alloc] init]; - vc.navigationItem.title = @"体检套餐"; - [self.navigationController pushViewController:vc animated:YES]; +// EmptyFunctionDevelopmentVC * vc = [[EmptyFunctionDevelopmentVC alloc] init]; +// vc.navigationItem.title = @"体检套餐"; +// [self.navigationController pushViewController:vc animated:YES]; + if(![HQCommonUtils isLogin]) { + KPostNotification(KNotificationLoginStateChange, @NO); + return; + } + if (ValidStr(self.secretString)) { + [self showMBProgressHUDOnlyWithString:@"网络异常请稍候再试~"]; + [self getWebKeySecret]; + return; + } + PhysicalWebViewController * phyVC = [[PhysicalWebViewController alloc] init]; + + NSString * urlString; +#if DEBUG + urlString = @"https://console-jkglpt.iosp.ydpt.tech/hb/healthcheckweb/index.html"; + + +#else + urlString = @"https://console-jkglpt.iosp.ydpt.tech/hb/healthcheckweb/index.html"; + +#endif + phyVC.webUrl = [NSString stringWithFormat:@"%@?param=%@",urlString,self.secretString]; + [self.navigationController pushViewController:phyVC animated:YES]; } if (imageIndex == 51) { @@ -1206,7 +1415,31 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半 } #pragma mark:点击 - (void)aiImageTap { - + // +// +// +// if(![HQCommonUtils isLogin]) { +// KPostNotification(KNotificationLoginStateChange, @NO); +// return; +// } +// if (ValidStr(self.secretString)) { +// [self showMBProgressHUDOnlyWithString:@"网络异常请稍候再试~"]; +// [self getWebKeySecret]; +// return; +// } +// PhysicalWebViewController * phyVC = [[PhysicalWebViewController alloc] init]; +// +// NSString * urlString; +//#if DEBUG +// urlString = @"https://console-jkglpt.iosp.ydpt.tech/hb/healthcheckweb/index.html"; +// +// +//#else +// urlString = @"https://console-jkglpt.iosp.ydpt.tech/hb/healthcheckweb/index.html"; +// +//#endif +// phyVC.webUrl = [NSString stringWithFormat:@"%@?param=%@",urlString,self.secretString]; +// [self.navigationController pushViewController:phyVC animated:YES]; EmptyFunctionDevelopmentVC * vc = [[EmptyFunctionDevelopmentVC alloc] init]; vc.navigationItem.title = @"AI随访"; [self.navigationController pushViewController:vc animated:YES]; @@ -1292,5 +1525,245 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半 // dispatch_resume(self.timer); //} +#pragma mark - ————— 健康知识 ————— + +- (void)loadHealthKnowledgeData { + kWeakSelf(self); + + DLog(@"[健康知识] 开始加载,缓存 token: %@", [XJHealthKnowledgeManager sharedManager].cachedToken ?: @"无"); + + // 先尝试用缓存 token 加载 + NSString * token = [XJHealthKnowledgeManager sharedManager].cachedToken; + if (!ValidStr(token)) { + DLog(@"[健康知识] 使用缓存 token 加载栏目"); + [self loadHealthCategoriesWithToken:token]; + return; + } + + NSString * userId = User_ID; + if (ValidStr(userId)) { + DLog(@"[健康知识] User_ID 为空,跳过获取 token"); + return; + } + + DLog(@"[健康知识] 无缓存 token,先获取 token,userId=%@ workNo=%@", userId, userId); + + // 首次需要获取 token + [[XJHealthKnowledgeManager sharedManager] getTokenWithUserId:userId + workNo:userId + success:^(NSString * t) { + [weakself loadHealthCategoriesWithToken:t]; + } failure:^(NSString * errorMsg) { + DLog(@"健康知识 token 获取失败: %@", errorMsg); + }]; +} + +- (void)loadHealthCategoriesWithToken:(NSString *)token { + kWeakSelf(self); + [[XJHealthKnowledgeManager sharedManager] getCategoryListWithToken:token + success:^(NSArray * categories) { + DLog(@"[健康知识] 栏目解析成功,共 %lu 个一级栏目", (unsigned long)categories.count); + // 找到"健康知识"栏目,取其 children 作为标签 + XJHealthCategoryModel * knowledgeCategory = nil; + XJHealthCategoryModel * newsCategory = nil; + for (XJHealthCategoryModel * cat in categories) { + DLog(@"[健康知识] 一级栏目: %@ (id=%ld, children=%lu)", cat.categoryName, (long)cat.categoryId, (unsigned long)cat.children.count); + if ([cat.categoryName isEqualToString:@"健康知识"]) { + knowledgeCategory = cat; + } else if ([cat.categoryName isEqualToString:@"健康资讯"]) { + newsCategory = cat; + } + } + + dispatch_async(dispatch_get_main_queue(), ^{ + weakself.healthDataLoaded = YES; + + // 健康知识 + if (knowledgeCategory && knowledgeCategory.children.count > 0) { + weakself.healthCategories = knowledgeCategory.children; + + NSMutableArray * titles = [[NSMutableArray alloc] init]; + for (XJHealthCategoryModel * child in knowledgeCategory.children) { + [titles addObject:child.categoryName ?: @""]; + } + DLog(@"[健康知识] 标签: %@", titles); + + weakself.healthKnowledgeView.tabTitles = titles; + weakself.currentHealthCategory = knowledgeCategory.children.firstObject; + DLog(@"[健康知识] 默认选中: %@ (categoryId=%ld)", weakself.currentHealthCategory.categoryName, (long)weakself.currentHealthCategory.categoryId); + [weakself fetchHealthArticlesForCategoryId:weakself.currentHealthCategory.categoryId isNews:NO]; + } else { + DLog(@"[健康知识] 栏目不存在或无子分类"); + weakself.healthKnowledgeView.dataArray = @[]; + [weakself updateHealthKnowledgeViewHeight]; + } + + // 健康资讯:无子分类,直接用 categoryId 查文章 + if (newsCategory) { + weakself.healthNewsCategoryId = newsCategory.categoryId; + DLog(@"[健康资讯] 找到栏目 categoryId=%ld, children=%lu", (long)newsCategory.categoryId, (unsigned long)newsCategory.children.count); + [weakself fetchHealthArticlesForCategoryId:newsCategory.categoryId isNews:YES]; + } else { + DLog(@"[健康资讯] 未找到栏目"); + weakself.healthNewsView.dataArray = @[]; + [weakself updateHealthNewsViewHeight]; + } + }); + } failure:^(NSString * errorMsg) { + DLog(@"[健康知识] 栏目列表获取失败: %@", errorMsg); + }]; +} + +- (void)fetchHealthArticlesForCategoryId:(NSInteger)categoryId { + [self fetchHealthArticlesForCategoryId:categoryId isNews:NO]; +} + +- (void)fetchHealthArticlesForCategoryId:(NSInteger)categoryId isNews:(BOOL)isNews { + kWeakSelf(self); + NSString * token = [XJHealthKnowledgeManager sharedManager].cachedToken; + if (ValidStr(token)) { + DLog(@"[健康知识] fetchArticles token 无效,跳过"); + return; + } + + NSInteger pageSize = isNews ? 2 : 3; + NSString * tag = isNews ? @"健康资讯" : @"健康知识"; + DLog(@"[%@] 请求文章列表 categoryId=%ld", tag, (long)categoryId); + + [[XJHealthKnowledgeManager sharedManager] getArticleListWithToken:token + categoryId:categoryId + articleType:nil + pageNum:1 + pageSize:pageSize + success:^(NSArray * articles, NSInteger total) { + DLog(@"[%@] 文章列表返回 %lu 条,total=%ld", tag, (unsigned long)articles.count, (long)total); + NSMutableArray * dataArray = [[NSMutableArray alloc] init]; + for (XJHealthArticleModel * article in articles) { + XJHealthKnowledgeModel * model = [[XJHealthKnowledgeModel alloc] init]; + model.knowledgeId = [NSString stringWithFormat:@"%ld", (long)article.articleId]; + model.title = article.articleTitle; + model.content = !ValidStr(article.articleExcerpt) ? article.articleExcerpt : article.articleSubtitle; + model.imageUrl = article.articleHeaderimage; + model.tags = article.articleTag; + model.createTime = article.createTime; + model.favoriteCount = article.favoriteCount; + [dataArray addObject:model]; + } + + DLog(@"[%@] 转换后 dataArray 共 %lu 条", tag, (unsigned long)dataArray.count); + dispatch_async(dispatch_get_main_queue(), ^{ + if (isNews) { + weakself.healthNewsView.dataArray = dataArray; + [weakself updateHealthNewsViewHeight]; + } else { + weakself.healthKnowledgeView.dataArray = dataArray; + [weakself updateHealthKnowledgeViewHeight]; + } + }); + } failure:^(NSString * errorMsg) { + DLog(@"[%@] 文章列表获取失败: %@", tag, errorMsg); + }]; +} + +- (void)updateHealthKnowledgeViewHeight { + CGFloat height = [self.healthKnowledgeView totalHeight]; + [self.knowledgeHeightConstraint uninstall]; + kWeakSelf(self); + [self.healthKnowledgeView mas_makeConstraints:^(MASConstraintMaker *make) { + weakself.knowledgeHeightConstraint = make.height.mas_offset(height); + }]; +} + +- (void)updateHealthNewsViewHeight { + CGFloat height = [self.healthNewsView totalHeight]; + [self.healthNewsHeightConstraint uninstall]; + kWeakSelf(self); + [self.healthNewsView mas_makeConstraints:^(MASConstraintMaker *make) { + weakself.healthNewsHeightConstraint = make.height.mas_offset(height); + }]; +} + +#pragma mark - ————— Token 校验 ————— + +- (void)ensureTokenThenExecute:(void(^)(void))action { + NSString * token = [XJHealthKnowledgeManager sharedManager].cachedToken; + // token 有效直接执行 + if (!ValidStr(token)) { + if (action) action(); + return; + } + + // token 无效,重新获取 + NSString * userId = User_ID; + if (ValidStr(userId)) { + [EasyTextView showErrorText:@"服务繁忙请稍后再试"]; + return; + } + + kWeakSelf(self); + [self showMBProgressHUDWithString:nil autoHidden:false]; + [[XJHealthKnowledgeManager sharedManager] getTokenWithUserId:userId + workNo:userId + success:^(NSString * t) { + [weakself hiddenAllMBProgressHUD]; + if (action) action(); + } failure:^(NSString * errorMsg) { + [weakself hiddenAllMBProgressHUD]; + [EasyTextView showErrorText:@"服务繁忙请稍后再试"]; + }]; +} + +/// 从 H5 回来时刷新文章列表(收藏数等可能变化) +- (void)refreshHealthArticles { + NSString * token = [XJHealthKnowledgeManager sharedManager].cachedToken; + if (ValidStr(token)) return; + + if (self.currentHealthCategory.categoryId > 0) { + [self fetchHealthArticlesForCategoryId:self.currentHealthCategory.categoryId isNews:NO]; + } + if (self.healthNewsCategoryId > 0) { + [self fetchHealthArticlesForCategoryId:self.healthNewsCategoryId isNews:YES]; + } +} + +#pragma mark - ————— H5 跳转 ————— + +- (void)openHealthKnowledgeMorePage { + NSString * param = !ValidStr(self.secretString) ? self.secretString : @""; + NSString * url = [NSString stringWithFormat:@"%@%@?param=%@", + kHealthKnowledgeH5Host, kHealthKnowMoreH5, param]; + [self pushHealthWebViewController:url title:@"健康知识"]; +} + +- (void)openHealthNewsMorePage { + NSString * param = !ValidStr(self.secretString) ? self.secretString : @""; + NSString * url = [NSString stringWithFormat:@"%@%@?param=%@", + kHealthKnowledgeH5Host, kHealthNewsMoreH5, param]; + [self pushHealthWebViewController:url title:@"健康资讯"]; +} + +- (void)openHealthKnowledgeSearchPage:(NSString *)keyword { + NSString * param = !ValidStr(self.secretString) ? self.secretString : @""; + NSString * encodedKeyword = [keyword stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]] ?: @""; + NSString * url = [NSString stringWithFormat:@"%@%@?param=%@&secrchVal=%@", + kHealthKnowledgeH5Host, kHealthSearchH5, param, encodedKeyword]; + [self pushHealthWebViewController:url title:@"搜索"]; +} + +- (void)openHealthKnowledgeDetailPage:(XJHealthKnowledgeModel *)model { + NSString * param = !ValidStr(self.secretString) ? self.secretString : @""; + NSString * url = [NSString stringWithFormat:@"%@%@?param=%@&articleId=%@", + kHealthKnowledgeH5Host, kHealthDetailH5, param, model.knowledgeId]; + [self pushHealthWebViewController:url title:model.title]; +} + +- (void)pushHealthWebViewController:(NSString *)urlString title:(NSString *)title { + DLog(@"[健康H5] 跳转 URL: %@", urlString); + PhysicalWebViewController * webVC = [[PhysicalWebViewController alloc] init]; + webVC.webUrl = urlString; + webVC.navigationItem.title = title; + [self.navigationController pushViewController:webVC animated:YES]; +} + @end diff --git a/XinjiangProject/XinjiangProject/Project/HomeV2/Model/XJHealthArticleModel.h b/XinjiangProject/XinjiangProject/Project/HomeV2/Model/XJHealthArticleModel.h new file mode 100644 index 0000000..8997119 --- /dev/null +++ b/XinjiangProject/XinjiangProject/Project/HomeV2/Model/XJHealthArticleModel.h @@ -0,0 +1,28 @@ +// +// XJHealthArticleModel.h +// XinjiangProject +// +// Created by Apple on 2026/6/29. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface XJHealthArticleModel : NSObject + +@property (nonatomic, assign) NSInteger articleId; +@property (nonatomic, copy) NSString * articleTitle; +@property (nonatomic, copy) NSString * articleSubtitle; +@property (nonatomic, copy) NSString * articleExcerpt; +@property (nonatomic, copy) NSString * articleTag; +@property (nonatomic, copy) NSString * articleHeaderimage; +@property (nonatomic, copy) NSString * articleType; +@property (nonatomic, copy) NSString * createTime; +@property (nonatomic, assign) NSInteger viewCount; +@property (nonatomic, assign) NSInteger likeCount; +@property (nonatomic, assign) NSInteger favoriteCount; + +@end + +NS_ASSUME_NONNULL_END diff --git a/XinjiangProject/XinjiangProject/Project/HomeV2/Model/XJHealthArticleModel.m b/XinjiangProject/XinjiangProject/Project/HomeV2/Model/XJHealthArticleModel.m new file mode 100644 index 0000000..88f91f1 --- /dev/null +++ b/XinjiangProject/XinjiangProject/Project/HomeV2/Model/XJHealthArticleModel.m @@ -0,0 +1,12 @@ +// +// XJHealthArticleModel.m +// XinjiangProject +// +// Created by Apple on 2026/6/29. +// + +#import "XJHealthArticleModel.h" + +@implementation XJHealthArticleModel + +@end diff --git a/XinjiangProject/XinjiangProject/Project/HomeV2/Model/XJHealthCategoryModel.h b/XinjiangProject/XinjiangProject/Project/HomeV2/Model/XJHealthCategoryModel.h new file mode 100644 index 0000000..d320076 --- /dev/null +++ b/XinjiangProject/XinjiangProject/Project/HomeV2/Model/XJHealthCategoryModel.h @@ -0,0 +1,20 @@ +// +// XJHealthCategoryModel.h +// XinjiangProject +// +// Created by Apple on 2026/6/29. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface XJHealthCategoryModel : NSObject + +@property (nonatomic, assign) NSInteger categoryId; +@property (nonatomic, copy) NSString * categoryName; +@property (nonatomic, strong) NSArray * children; + +@end + +NS_ASSUME_NONNULL_END diff --git a/XinjiangProject/XinjiangProject/Project/HomeV2/Model/XJHealthCategoryModel.m b/XinjiangProject/XinjiangProject/Project/HomeV2/Model/XJHealthCategoryModel.m new file mode 100644 index 0000000..79ca672 --- /dev/null +++ b/XinjiangProject/XinjiangProject/Project/HomeV2/Model/XJHealthCategoryModel.m @@ -0,0 +1,16 @@ +// +// XJHealthCategoryModel.m +// XinjiangProject +// +// Created by Apple on 2026/6/29. +// + +#import "XJHealthCategoryModel.h" + +@implementation XJHealthCategoryModel + ++ (NSDictionary *)modelContainerPropertyGenericClass { + return @{ @"children": [XJHealthCategoryModel class] }; +} + +@end diff --git a/XinjiangProject/XinjiangProject/Project/HomeV2/Model/XJHealthKnowledgeModel.h b/XinjiangProject/XinjiangProject/Project/HomeV2/Model/XJHealthKnowledgeModel.h new file mode 100644 index 0000000..c153cb2 --- /dev/null +++ b/XinjiangProject/XinjiangProject/Project/HomeV2/Model/XJHealthKnowledgeModel.h @@ -0,0 +1,24 @@ +// +// XJHealthKnowledgeModel.h +// XinjiangProject +// +// Created by Apple on 2026/6/29. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface XJHealthKnowledgeModel : NSObject + +@property (nonatomic, copy) NSString * knowledgeId; +@property (nonatomic, copy) NSString * title; +@property (nonatomic, copy) NSString * content; +@property (nonatomic, copy) NSString * imageUrl; +@property (nonatomic, copy) NSString * tags; +@property (nonatomic, copy) NSString * createTime; +@property (nonatomic, assign) NSInteger favoriteCount; + +@end + +NS_ASSUME_NONNULL_END diff --git a/XinjiangProject/XinjiangProject/Project/HomeV2/Model/XJHealthKnowledgeModel.m b/XinjiangProject/XinjiangProject/Project/HomeV2/Model/XJHealthKnowledgeModel.m new file mode 100644 index 0000000..c601f25 --- /dev/null +++ b/XinjiangProject/XinjiangProject/Project/HomeV2/Model/XJHealthKnowledgeModel.m @@ -0,0 +1,12 @@ +// +// XJHealthKnowledgeModel.m +// XinjiangProject +// +// Created by Apple on 2026/6/29. +// + +#import "XJHealthKnowledgeModel.h" + +@implementation XJHealthKnowledgeModel + +@end diff --git a/XinjiangProject/XinjiangProject/Project/HomeV2/View/XJHealthKnowledgeCell.h b/XinjiangProject/XinjiangProject/Project/HomeV2/View/XJHealthKnowledgeCell.h new file mode 100644 index 0000000..c60362d --- /dev/null +++ b/XinjiangProject/XinjiangProject/Project/HomeV2/View/XJHealthKnowledgeCell.h @@ -0,0 +1,20 @@ +// +// XJHealthKnowledgeCell.h +// XinjiangProject +// +// Created by Apple on 2026/6/29. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class XJHealthKnowledgeModel; + +@interface XJHealthKnowledgeCell : UITableViewCell + +@property (nonatomic, strong) XJHealthKnowledgeModel * model; + +@end + +NS_ASSUME_NONNULL_END diff --git a/XinjiangProject/XinjiangProject/Project/HomeV2/View/XJHealthKnowledgeCell.m b/XinjiangProject/XinjiangProject/Project/HomeV2/View/XJHealthKnowledgeCell.m new file mode 100644 index 0000000..c2aade3 --- /dev/null +++ b/XinjiangProject/XinjiangProject/Project/HomeV2/View/XJHealthKnowledgeCell.m @@ -0,0 +1,177 @@ +// +// XJHealthKnowledgeCell.m +// XinjiangProject +// +// Created by Apple on 2026/6/29. +// +// ┌──────────────────────────────────────────────┐ +// │ │ ← cell 高 130 +// │ ┌──────────┐ ┌────────────────────────┐ │ +// │ │ 图片 │ │ 标题 #252535 Bold 15 │ │ ← 标题 top = 图片 top + 5 +// │ │ 100×100 │ │ │ │ +// │ │ 圆角15 │ ├────────────────────────┤ │ +// │ │ 上下居中 │ │ 描述 #77849E 13 Medium │ │ ← 描述 top = 标题 bottom + 15 +// │ │ │ │ 最多 2 行 │ │ +// │ │ │ ├────────────────────────┤ │ +// │ │ │ │ [图20] 标签|标签 │ │ ← 图标 bottom = 图片 bottom - 5 +// │ └──────────┘ │ 时间 → │ │ ← 时间 bottom = 图片 bottom - 9 +// │ └────────────────────────┘ │ +// │ ──────────────────────────────────────── │ ← 分割线 left=标题, right=-15 +// └──────────────────────────────────────────────┘ +// + +#import "XJHealthKnowledgeCell.h" +#import "XJHealthKnowledgeModel.h" + +@interface XJHealthKnowledgeCell () + +@property (nonatomic, strong) UIImageView * leftImageView; +@property (nonatomic, strong) UILabel * titleLabel; +@property (nonatomic, strong) UILabel * descLabel; +@property (nonatomic, strong) UIImageView * tagIcon; +@property (nonatomic, strong) UILabel * tagLabel; +@property (nonatomic, strong) UILabel * timeLabel; +@property (nonatomic, strong) UIView * separatorLine; + +@end + +@implementation XJHealthKnowledgeCell + +- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { + if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) { + self.selectionStyle = UITableViewCellSelectionStyleNone; + [self createUI]; + } + return self; +} + +- (void)createUI { + + // 左侧图片 100×100 圆角 15 + self.leftImageView = [[UIImageView alloc] init]; + self.leftImageView.layer.cornerRadius = 15; + self.leftImageView.layer.masksToBounds = YES; + self.leftImageView.contentMode = UIViewContentModeScaleAspectFill; + [self.contentView addSubview:self.leftImageView]; + + [self.leftImageView mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.mas_offset(15); + make.centerY.mas_equalTo(self.contentView); + make.width.height.mas_offset(100); + }]; + + // 标题 #252535 Bold 15,右间距 -22 + self.titleLabel = [[UILabel alloc] init]; + self.titleLabel.textColor = UIColorHex(#252535); + self.titleLabel.font = BOLDSYSTEMFONT(15); + self.titleLabel.textAlignment = NSTextAlignmentLeft; + self.titleLabel.numberOfLines = 1; + [self.contentView addSubview:self.titleLabel]; + + [self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.mas_equalTo(self.leftImageView.mas_right).offset(15); + make.top.mas_equalTo(self.leftImageView.mas_top).offset(5); + make.right.mas_offset(-22); + }]; + + // 描述 #77849E 13 Medium,最多 2 行,距标题底部 15 + self.descLabel = [[UILabel alloc] init]; + self.descLabel.textColor = UIColorHex(#77849E); + self.descLabel.font = MEDIUMFONT(13); + self.descLabel.textAlignment = NSTextAlignmentLeft; + self.descLabel.numberOfLines = 2; + [self.contentView addSubview:self.descLabel]; + + [self.descLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.mas_equalTo(self.titleLabel.mas_left); + make.top.mas_equalTo(self.titleLabel.mas_bottom).offset(15); + make.right.mas_offset(-22); + }]; + + // 时间 #77849E 11 Medium,右对齐,bottom = 图片 bottom - 9(必须放在 tagLabel 前面,tagLabel 引用了它) + self.timeLabel = [[UILabel alloc] init]; + self.timeLabel.textColor = UIColorHex(#77849E); + self.timeLabel.font = MEDIUMFONT(11); + self.timeLabel.textAlignment = NSTextAlignmentRight; + self.timeLabel.numberOfLines = 1; + [self.contentView addSubview:self.timeLabel]; + + [self.timeLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.right.mas_offset(-15); + make.bottom.mas_equalTo(self.leftImageView.mas_bottom).offset(-9); + }]; + [self.timeLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal]; + + // 标签图标 20×20,bottom = 图片 bottom - 5 + self.tagIcon = [[UIImageView alloc] init]; + self.tagIcon.image = [UIImage imageNamed:@"article_classIcon"]; + self.tagIcon.contentMode = UIViewContentModeScaleAspectFit; + [self.contentView addSubview:self.tagIcon]; + + [self.tagIcon mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.mas_equalTo(self.titleLabel.mas_left); + make.bottom.mas_equalTo(self.leftImageView.mas_bottom).offset(-5); + make.width.height.mas_offset(20); + }]; + + // 标签文字 #14BEBE 11 Medium(引用 timeLabel,已在前方创建) + self.tagLabel = [[UILabel alloc] init]; + self.tagLabel.textColor = UIColorHex(#14BEBE); + self.tagLabel.font = MEDIUMFONT(11); + self.tagLabel.textAlignment = NSTextAlignmentLeft; + self.tagLabel.numberOfLines = 1; + [self.contentView addSubview:self.tagLabel]; + + [self.tagLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.mas_equalTo(self.tagIcon.mas_right).offset(5); + make.centerY.mas_equalTo(self.tagIcon); + make.right.mas_lessThanOrEqualTo(self.timeLabel.mas_left).offset(-8); + }]; + + // 分割线 #EAECF1 h1,左对齐标题,右 -15 + self.separatorLine = [[UIView alloc] init]; + self.separatorLine.backgroundColor = UIColorHex(#EAECF1); + [self.contentView addSubview:self.separatorLine]; + + [self.separatorLine mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.mas_equalTo(self.titleLabel.mas_left); + make.right.mas_offset(-15); + make.bottom.mas_offset(-1); + make.height.mas_offset(1); + }]; +} + +- (void)setModel:(XJHealthKnowledgeModel *)model { + _model = model; + + self.titleLabel.text = model.title; + self.descLabel.text = model.content; + self.timeLabel.text = model.createTime; + + // 标签 #健康资讯#肝癌 → 健康资讯 | 肝癌 + self.tagLabel.text = [self formatTagString:model.tags]; + + // 图片 URL 是完整地址,直接加载 + if (!ValidStr(model.imageUrl)) { + NSString * photo = model.imageUrl; + // http → https + if ([photo hasPrefix:@"http://"]) { + photo = [photo stringByReplacingOccurrencesOfString:@"http://" withString:@"https://"]; + } + [self.leftImageView sd_setImageWithURL:[NSURL URLWithString:photo] placeholderImage:[UIImage imageNamed:@"article_placholdIMg"]]; + } +} + +/// #健康资讯#肝癌 → 健康资讯 | 肝癌 +- (NSString *)formatTagString:(NSString *)tagStr { + if (ValidStr(tagStr)) return tagStr ?: @""; + NSString * result = tagStr; + // 去掉首尾的 # + if ([result hasPrefix:@"#"]) result = [result substringFromIndex:1]; + if ([result hasSuffix:@"#"]) result = [result substringToIndex:result.length - 1]; + // 中间的 # 替换为 | + result = [result stringByReplacingOccurrencesOfString:@"#" withString:@" | "]; + return result; +} + +@end diff --git a/XinjiangProject/XinjiangProject/Project/HomeV2/View/XJHealthKnowledgeView.h b/XinjiangProject/XinjiangProject/Project/HomeV2/View/XJHealthKnowledgeView.h new file mode 100644 index 0000000..0448bb2 --- /dev/null +++ b/XinjiangProject/XinjiangProject/Project/HomeV2/View/XJHealthKnowledgeView.h @@ -0,0 +1,42 @@ +// +// XJHealthKnowledgeView.h +// XinjiangProject +// +// Created by Apple on 2026/6/29. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class XJHealthKnowledgeModel; + +@interface XJHealthKnowledgeView : UIView + +/// 数据源 +@property (nonatomic, strong) NSArray * dataArray; +/// 标签标题数组 +@property (nonatomic, strong) NSArray * tabTitles; +/// 当前选中标签索引 +@property (nonatomic, assign) NSInteger selectedTabIndex; + +/// 点击"更多" +@property (nonatomic, copy) void (^moreBlock)(void); +/// 点击搜索按钮 +@property (nonatomic, copy) void (^searchBlock)(NSString * keyword); +/// 搜索框开始编辑(用于键盘避让) +@property (nonatomic, copy) void (^searchBeginBlock)(void); +/// 标签切换回调 +@property (nonatomic, copy) void (^tabSelectBlock)(NSInteger index); +/// 点击 cell +@property (nonatomic, copy) void (^cellSelectBlock)(XJHealthKnowledgeModel * model); + +/// 计算当前总高度 +- (CGFloat)totalHeight; + +/// 显示未登录占位 +- (void)showNotLoggedInPlaceholder; + +@end + +NS_ASSUME_NONNULL_END diff --git a/XinjiangProject/XinjiangProject/Project/HomeV2/View/XJHealthKnowledgeView.m b/XinjiangProject/XinjiangProject/Project/HomeV2/View/XJHealthKnowledgeView.m new file mode 100644 index 0000000..0ef7f5d --- /dev/null +++ b/XinjiangProject/XinjiangProject/Project/HomeV2/View/XJHealthKnowledgeView.m @@ -0,0 +1,433 @@ +// +// XJHealthKnowledgeView.m +// XinjiangProject +// +// Created by Apple on 2026/6/29. +// + +#import "XJHealthKnowledgeView.h" +#import "XJHealthKnowledgeModel.h" +#import "XJHealthKnowledgeCell.h" + +static CGFloat const kHeaderHeight = 40; +static CGFloat const kSearchBoxHeight = 36; +static CGFloat const kCellHeight = 130; +static CGFloat const kMaxCellCount = 3; +static CGFloat const kTabButtonGap = 43; +static CGFloat const kDefaultEmptyHeight = 200; + +// 标签区:文字 14pt(≈17) + 指示间距6 + 指示2 + 分割间距9 + 分割1 = ~35 +static CGFloat const kTabAreaHeight = 48; + +@interface XJHealthKnowledgeView () + +@property (nonatomic, strong) UIView * headerView; +@property (nonatomic, strong) UIImageView * headerBgImg; +@property (nonatomic, strong) UILabel * titleLabel; +@property (nonatomic, strong) UIButton * moreBtn; + +@property (nonatomic, strong) UIView * searchContainer; +@property (nonatomic, strong) UIImageView * searchIcon; +@property (nonatomic, strong) UITextField * searchTextField; +@property (nonatomic, strong) UIButton * searchBtn; + +@property (nonatomic, strong) UIScrollView * tabScrollView; +@property (nonatomic, strong) NSMutableArray * tabButtons; +@property (nonatomic, strong) UIView * indicatorView; +@property (nonatomic, strong) UIView * tabSeparator; + +@property (nonatomic, strong) UITableView * listTableView; +@property (nonatomic, strong) UILabel * emptyLabel; + +@end + +@implementation XJHealthKnowledgeView + +- (instancetype)initWithFrame:(CGRect)frame { + if (self = [super initWithFrame:frame]) { + self.backgroundColor = KWhiteColor; + self.layer.cornerRadius = 12; + self.layer.masksToBounds = YES; + self.tabButtons = [[NSMutableArray alloc] init]; + [self createUI]; + } + return self; +} + +#pragma mark - ————— UI 搭建 ————— + +- (void)createUI { + + // 头部 + self.headerView = [[UIView alloc] init]; + [self addSubview:self.headerView]; + [self.headerView mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.right.top.mas_offset(0); + make.height.mas_offset(kRealValue(kHeaderHeight)); + }]; + + self.headerBgImg = [[UIImageView alloc] init]; + self.headerBgImg.image = [UIImage imageNamed:@"heaedTitleBackImg"]; + [self.headerView addSubview:self.headerBgImg]; + [self.headerBgImg mas_makeConstraints:^(MASConstraintMaker *make) { + make.edges.mas_offset(0); + }]; + + self.titleLabel = [[UILabel alloc] init]; + self.titleLabel.text = @"健康知识"; + self.titleLabel.textColor = CFontColor1; + self.titleLabel.font = BOLDSYSTEMFONT(16); + [self.headerView addSubview:self.titleLabel]; + [self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.mas_offset(13); + make.centerY.mas_equalTo(self.headerView); + }]; + + self.moreBtn = [[UIButton alloc] init]; + [self.moreBtn setImage:[UIImage imageNamed:@"wacthRightAccow"] forState:UIControlStateNormal]; + [self.moreBtn addTarget:self action:@selector(moreAction) forControlEvents:UIControlEventTouchUpInside]; + [self.headerView addSubview:self.moreBtn]; + [self.moreBtn mas_makeConstraints:^(MASConstraintMaker *make) { + make.right.mas_offset(-8); + make.centerY.mas_equalTo(self.headerView); + make.width.height.mas_offset(30); + }]; + + // 搜索框 - 圆角 18 背景 #F5F7FB + self.searchContainer = [[UIView alloc] init]; + self.searchContainer.backgroundColor = UIColorHex(#F5F7FB); + self.searchContainer.layer.cornerRadius = 18; + self.searchContainer.layer.masksToBounds = YES; + self.searchContainer.hidden = YES; // 初始隐藏,等数据来了再显示 + [self addSubview:self.searchContainer]; + [self.searchContainer mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.mas_offset(15); + make.right.mas_offset(-15); + make.top.mas_equalTo(self.headerView.mas_bottom).offset(10); + make.height.mas_offset(kSearchBoxHeight); + }]; + + self.searchIcon = [[UIImageView alloc] init]; + self.searchIcon.image = [UIImage imageNamed:@"searchLeftIcon"]; + self.searchIcon.contentMode = UIViewContentModeScaleAspectFit; + [self.searchContainer addSubview:self.searchIcon]; + [self.searchIcon mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.mas_offset(15); + make.centerY.mas_equalTo(self.searchContainer); + make.width.height.mas_offset(14); + }]; + + self.searchBtn = [[UIButton alloc] init]; + self.searchBtn.backgroundColor = UIColorHex(#14BEBE); + self.searchBtn.layer.cornerRadius = 14; + self.searchBtn.layer.masksToBounds = YES; + [self.searchBtn setTitle:@"搜索" forState:UIControlStateNormal]; + [self.searchBtn setTitleColor:KWhiteColor forState:UIControlStateNormal]; + self.searchBtn.titleLabel.font = MEDIUMFONT(14); + [self.searchBtn addTarget:self action:@selector(searchAction) forControlEvents:UIControlEventTouchUpInside]; + [self.searchContainer addSubview:self.searchBtn]; + [self.searchBtn mas_makeConstraints:^(MASConstraintMaker *make) { + make.right.mas_offset(0); + make.centerY.mas_equalTo(self.searchContainer); + make.width.mas_offset(55); + make.height.mas_offset(28); + }]; + + self.searchTextField = [[UITextField alloc] init]; + self.searchTextField.placeholder = @"请输入关键字进行搜索"; + self.searchTextField.font = [UIFont systemFontOfSize:13 weight:UIFontWeightRegular]; + self.searchTextField.textColor = UIColorHex(#252535); + // placeholder 颜色 + self.searchTextField.attributedPlaceholder = [[NSAttributedString alloc] + initWithString:@"请输入关键字进行搜索" + attributes:@{NSForegroundColorAttributeName: UIColorHex(#B6B6B6)}]; + self.searchTextField.delegate = self; + [self.searchContainer addSubview:self.searchTextField]; + [self.searchTextField mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.mas_equalTo(self.searchIcon.mas_right).offset(8); + make.right.mas_equalTo(self.searchBtn.mas_left).offset(-8); + make.centerY.mas_equalTo(self.searchContainer); + make.height.mas_equalTo(self.searchContainer); + }]; + + // 标签切换区 + self.tabScrollView = [[UIScrollView alloc] init]; + self.tabScrollView.showsHorizontalScrollIndicator = NO; + self.tabScrollView.showsVerticalScrollIndicator = NO; + self.tabScrollView.hidden = YES; // 初始隐藏 + [self addSubview:self.tabScrollView]; + [self.tabScrollView mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.right.mas_offset(0); + make.top.mas_equalTo(self.searchContainer.mas_bottom).offset(12); + make.height.mas_offset(kTabAreaHeight); + }]; + + // 指示条 13×2 + self.indicatorView = [[UIView alloc] init]; + self.indicatorView.backgroundColor = UIColorHex(#14BEBE); + self.indicatorView.layer.cornerRadius = 1; + self.indicatorView.layer.masksToBounds = YES; + self.indicatorView.hidden = YES; + [self.tabScrollView addSubview:self.indicatorView]; + + // 分割线 在指示条底部 +9 + self.tabSeparator = [[UIView alloc] init]; + self.tabSeparator.backgroundColor = UIColorHex(#EAECF1); + [self.tabScrollView addSubview:self.tabSeparator]; + + // 列表 + self.listTableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; + self.listTableView.delegate = self; + self.listTableView.dataSource = self; + self.listTableView.separatorStyle = UITableViewCellSeparatorStyleNone; + self.listTableView.scrollEnabled = NO; + self.listTableView.rowHeight = kCellHeight; + self.listTableView.backgroundColor = KWhiteColor; + self.listTableView.hidden = YES; // 初始隐藏 + if (@available(iOS 15.0, *)) { + self.listTableView.sectionHeaderTopPadding = 0; + } + [self addSubview:self.listTableView]; + [self.listTableView mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.right.mas_offset(0); + make.top.mas_equalTo(self.tabScrollView.mas_bottom); + make.height.mas_offset(0); + }]; + + // 空状态 - 放在 tabScrollView 下方 + self.emptyLabel = [[UILabel alloc] init]; + self.emptyLabel.text = @"暂无健康知识"; + self.emptyLabel.textColor = [UIColor colorWithRed:0.6 green:0.6 blue:0.6 alpha:1.0]; + self.emptyLabel.font = [UIFont systemFontOfSize:15]; + self.emptyLabel.textAlignment = NSTextAlignmentCenter; + self.emptyLabel.hidden = YES; + [self addSubview:self.emptyLabel]; + [self.emptyLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.centerX.mas_equalTo(self); + make.top.mas_equalTo(self.tabScrollView.mas_bottom).offset(40); + }]; +} + +#pragma mark - ————— Setter ————— + +- (void)setDataArray:(NSArray *)dataArray { + _dataArray = dataArray; + + BOOL hasData = dataArray.count > 0; + + self.listTableView.hidden = !hasData; + self.emptyLabel.hidden = hasData; + + if (hasData) { + [self.listTableView reloadData]; + CGFloat tableH = MIN(dataArray.count, kMaxCellCount) * kCellHeight; + [self.listTableView mas_remakeConstraints:^(MASConstraintMaker *make) { + make.left.right.mas_offset(0); + make.top.mas_equalTo(self.tabScrollView.mas_bottom); + make.height.mas_offset(tableH); + }]; + } else { + [self.listTableView mas_remakeConstraints:^(MASConstraintMaker *make) { + make.left.right.mas_offset(0); + make.top.mas_equalTo(self.tabScrollView.mas_bottom); + make.height.mas_offset(0); + }]; + } +} + +- (void)setTabTitles:(NSArray *)tabTitles { + _tabTitles = tabTitles; + + for (UIButton * btn in self.tabButtons) { + [btn removeFromSuperview]; + } + [self.tabButtons removeAllObjects]; + + self.tabScrollView.hidden = (tabTitles.count == 0); + self.searchContainer.hidden = (tabTitles.count == 0); + + if (tabTitles.count == 0) { + self.indicatorView.hidden = YES; + return; + } + + self.indicatorView.hidden = NO; + + // 按钮高度给满 kTabAreaHeight 让点击范围大 + CGFloat btnHeight = kTabAreaHeight - 2 - 6 - 9 - 1; // 文字区域 ≈30pt + CGFloat btnTop = 0; + + CGFloat leftOffset = 15; + for (NSInteger i = 0; i < tabTitles.count; i++) { + NSString * title = tabTitles[i]; + + CGSize textSize = [title sizeWithAttributes:@{NSFontAttributeName: BOLDSYSTEMFONT(14)}]; + CGFloat btnWidth = ceil(textSize.width) + 4; // 左右各留 2pt + + // 透明大按钮包住文字区域 + UIButton * btn = [UIButton buttonWithType:UIButtonTypeCustom]; + [btn setTitle:title forState:UIControlStateNormal]; + btn.titleLabel.font = (i == self.selectedTabIndex) ? BOLDSYSTEMFONT(14) : [UIFont systemFontOfSize:14 weight:UIFontWeightRegular]; + [btn setTitleColor:(i == self.selectedTabIndex) ? UIColorHex(#14BEBE) : UIColorHex(#808080) forState:UIControlStateNormal]; + btn.backgroundColor = KClearColor; + btn.tag = i; + [btn addTarget:self action:@selector(tabButtonTapped:) forControlEvents:UIControlEventTouchUpInside]; + btn.frame = CGRectMake(leftOffset, btnTop, btnWidth, btnHeight); + [self.tabScrollView addSubview:btn]; + [self.tabButtons addObject:btn]; + + leftOffset += btnWidth + kTabButtonGap; + } + + leftOffset += 15 - kTabButtonGap; + self.tabScrollView.contentSize = CGSizeMake(leftOffset, kTabAreaHeight); + + // 更新分割线位置:在标签区底部 + self.tabSeparator.frame = CGRectMake(0, kTabAreaHeight - 1, MAX(leftOffset, self.tabScrollView.bounds.size.width), 1); + + // 强制布局,确保按钮 titleLabel.frame 计算正确 + [self.tabScrollView layoutIfNeeded]; + + if (self.tabButtons.count > 0) { + [self updateIndicatorPosition:self.selectedTabIndex animated:NO]; + } +} + +- (void)setSelectedTabIndex:(NSInteger)selectedTabIndex { + if (selectedTabIndex < 0 || selectedTabIndex >= self.tabButtons.count) return; + _selectedTabIndex = selectedTabIndex; + + for (NSInteger i = 0; i < self.tabButtons.count; i++) { + UIButton * btn = self.tabButtons[i]; + if (i == selectedTabIndex) { + [btn setTitleColor:UIColorHex(#14BEBE) forState:UIControlStateNormal]; + btn.titleLabel.font = BOLDSYSTEMFONT(14); + } else { + [btn setTitleColor:UIColorHex(#808080) forState:UIControlStateNormal]; + btn.titleLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightRegular]; + } + } + [self.tabScrollView layoutIfNeeded]; + [self updateIndicatorPosition:selectedTabIndex animated:YES]; +} + +- (void)showNotLoggedInPlaceholder { + self.dataArray = @[]; + self.tabTitles = nil; + self.emptyLabel.text = @"暂未登录"; + self.emptyLabel.hidden = NO; + // 未登录时在头部下方区域上下居中 + [self.emptyLabel mas_remakeConstraints:^(MASConstraintMaker *make) { + make.centerX.mas_equalTo(self); + make.top.mas_equalTo(self.headerView.mas_bottom); + make.bottom.mas_equalTo(self); + }]; +} + +#pragma mark - ————— 高度计算 ————— + +- (CGFloat)totalHeight { + BOOL hasTabs = self.tabTitles.count > 0; + BOOL hasData = self.dataArray.count > 0; + + if (!hasTabs) { + return kDefaultEmptyHeight; + } + + // 头部(40) + 10 + 搜索(36) + 12 + 标签(48) + 列表/空区域 + CGFloat base = kRealValue(kHeaderHeight) + 10 + kSearchBoxHeight + 12 + kTabAreaHeight; + if (hasData) { + CGFloat tableH = MIN(self.dataArray.count, kMaxCellCount) * kCellHeight; + return base + tableH; + } else { + return base + 80; + } +} + +#pragma mark - ————— 私有 ————— + +- (void)updateIndicatorPosition:(NSInteger)index animated:(BOOL)animated { + if (index < 0 || index >= self.tabButtons.count) return; + + UIButton * selectedBtn = self.tabButtons[index]; + + CGFloat indicatorW = 13; + CGFloat indicatorH = 2; + CGFloat indicatorX = selectedBtn.center.x - indicatorW / 2; + // 文字底部 +6 + CGFloat textBottom = CGRectGetMaxY(selectedBtn.titleLabel.frame); + // 兜底:14pt 字体行高 ≈ 17,垂直居中 + if (textBottom <= 0) { + CGFloat lineH = 17; + textBottom = (selectedBtn.frame.size.height - lineH) / 2.0 + lineH; + } + CGFloat indicatorY = textBottom + 6; + + void (^updateBlock)(void) = ^{ + self.indicatorView.frame = CGRectMake(indicatorX, indicatorY, indicatorW, indicatorH); + }; + + if (animated) { + [UIView animateWithDuration:0.25 animations:updateBlock]; + } else { + updateBlock(); + } +} + +#pragma mark - ————— 事件 ————— + +- (void)moreAction { + if (self.moreBlock) self.moreBlock(); +} + +- (void)searchAction { + if (self.searchBlock) self.searchBlock(self.searchTextField.text ?: @""); +} + +- (void)tabButtonTapped:(UIButton *)sender { + if (sender.tag == self.selectedTabIndex) return; + self.selectedTabIndex = sender.tag; + if (self.tabSelectBlock) self.tabSelectBlock(sender.tag); +} + +#pragma mark - ————— UITextFieldDelegate ————— + +- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField { + return YES; +} + +- (void)textFieldDidBeginEditing:(UITextField *)textField { + if (self.searchBeginBlock) self.searchBeginBlock(); +} + +- (BOOL)textFieldShouldReturn:(UITextField *)textField { + [textField resignFirstResponder]; + if (self.searchBlock) self.searchBlock(textField.text ?: @""); + return YES; +} + +#pragma mark - ————— UITableViewDataSource ————— + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return MIN(self.dataArray.count, kMaxCellCount); +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + static NSString * cellID = @"XJHealthKnowledgeCell"; + XJHealthKnowledgeCell * cell = [tableView dequeueReusableCellWithIdentifier:cellID]; + if (!cell) { + cell = [[XJHealthKnowledgeCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID]; + } + cell.model = self.dataArray[indexPath.row]; + return cell; +} + +#pragma mark - ————— UITableViewDelegate ————— + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + if (self.cellSelectBlock && indexPath.row < self.dataArray.count) { + self.cellSelectBlock(self.dataArray[indexPath.row]); + } +} + +@end diff --git a/XinjiangProject/XinjiangProject/Project/HomeV2/View/XJHealthNewsCell.h b/XinjiangProject/XinjiangProject/Project/HomeV2/View/XJHealthNewsCell.h new file mode 100644 index 0000000..7c7f5b6 --- /dev/null +++ b/XinjiangProject/XinjiangProject/Project/HomeV2/View/XJHealthNewsCell.h @@ -0,0 +1,20 @@ +// +// XJHealthNewsCell.h +// XinjiangProject +// +// Created by Apple on 2026/6/29. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class XJHealthKnowledgeModel; + +@interface XJHealthNewsCell : UITableViewCell + +@property (nonatomic, strong) XJHealthKnowledgeModel * model; + +@end + +NS_ASSUME_NONNULL_END diff --git a/XinjiangProject/XinjiangProject/Project/HomeV2/View/XJHealthNewsCell.m b/XinjiangProject/XinjiangProject/Project/HomeV2/View/XJHealthNewsCell.m new file mode 100644 index 0000000..322f595 --- /dev/null +++ b/XinjiangProject/XinjiangProject/Project/HomeV2/View/XJHealthNewsCell.m @@ -0,0 +1,115 @@ +// +// XJHealthNewsCell.m +// XinjiangProject +// +// Created by Apple on 2026/6/29. +// +// ┌─────────────────────────────────────────────┐ +// │ │ ← cell 高 90 +// │ ┌──────────────────────┐ ┌────────────┐ │ +// │ │ 标题 #111225 15 │ │ 图片 │ │ ← 图片 80×60 垂直居中 +// │ │ Medium 最多2行 │ │ 80×60 │ │ 右间距 -15 +// │ │ 左+15 右距图-15 │ │ │ │ +// │ ├──────────────────────┤ │ │ │ +// │ │ 收藏1865 │ │ │ │ ← 底部-15,左对齐标题 +// │ └──────────────────────┘ └────────────┘ │ +// │ ─────────────────────────────────────── │ ← 分割线 +// └─────────────────────────────────────────────┘ +// + +#import "XJHealthNewsCell.h" +#import "XJHealthKnowledgeModel.h" + +@interface XJHealthNewsCell () + +@property (nonatomic, strong) UIImageView * rightImageView; +@property (nonatomic, strong) UILabel * titleLabel; +@property (nonatomic, strong) UILabel * favoriteLabel; +@property (nonatomic, strong) UIView * separatorLine; + +@end + +@implementation XJHealthNewsCell + +- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { + if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) { + self.selectionStyle = UITableViewCellSelectionStyleNone; + [self createUI]; + } + return self; +} + +- (void)createUI { + + // 右侧图片 80×60,右间距 -15,垂直居中 + self.rightImageView = [[UIImageView alloc] init]; + self.rightImageView.contentMode = UIViewContentModeScaleAspectFill; + self.rightImageView.layer.cornerRadius = 4; + self.rightImageView.layer.masksToBounds = YES; + [self.contentView addSubview:self.rightImageView]; + + [self.rightImageView mas_makeConstraints:^(MASConstraintMaker *make) { + make.right.mas_offset(-15); + make.centerY.mas_equalTo(self.contentView); + make.width.mas_offset(80); + make.height.mas_offset(60); + }]; + + // 标题 #111225 15 Medium,最多2行,左+15,右距图片左 -15 + self.titleLabel = [[UILabel alloc] init]; + self.titleLabel.textColor = UIColorHex(#111225); + self.titleLabel.font = MEDIUMFONT(15); + self.titleLabel.textAlignment = NSTextAlignmentLeft; + self.titleLabel.numberOfLines = 2; + [self.contentView addSubview:self.titleLabel]; + + [self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.mas_offset(15); + make.top.mas_offset(14); + make.right.mas_equalTo(self.rightImageView.mas_left).offset(-15); + }]; + + // 收藏数 #77849E 11 Medium,底部-15,左对齐标题 + self.favoriteLabel = [[UILabel alloc] init]; + self.favoriteLabel.textColor = UIColorHex(#77849E); + self.favoriteLabel.font = MEDIUMFONT(11); + self.favoriteLabel.textAlignment = NSTextAlignmentLeft; + [self.contentView addSubview:self.favoriteLabel]; + + [self.favoriteLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.mas_equalTo(self.titleLabel.mas_left); + make.bottom.mas_offset(-15); + }]; + + // 分割线 #EAECF1 h1,左对齐标题,右 -15 + self.separatorLine = [[UIView alloc] init]; + self.separatorLine.backgroundColor = UIColorHex(#EAECF1); + [self.contentView addSubview:self.separatorLine]; + + [self.separatorLine mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.mas_equalTo(self.titleLabel.mas_left); + make.right.mas_offset(-15); + make.bottom.mas_offset(0); + make.height.mas_offset(1); + }]; +} + +- (void)setModel:(XJHealthKnowledgeModel *)model { + _model = model; + + self.titleLabel.text = model.title; + + // 图片 + if (!ValidStr(model.imageUrl)) { + NSString * photo = model.imageUrl; + if ([photo hasPrefix:@"http://"]) { + photo = [photo stringByReplacingOccurrencesOfString:@"http://" withString:@"https://"]; + } + [self.rightImageView sd_setImageWithURL:[NSURL URLWithString:photo] placeholderImage:[UIImage imageNamed:@"article_placholdIMg"]]; + } + + // 收藏数 + self.favoriteLabel.text = [NSString stringWithFormat:@"收藏%ld", (long)model.favoriteCount]; +} + +@end diff --git a/XinjiangProject/XinjiangProject/Project/HomeV2/View/XJHealthNewsView.h b/XinjiangProject/XinjiangProject/Project/HomeV2/View/XJHealthNewsView.h new file mode 100644 index 0000000..328fd34 --- /dev/null +++ b/XinjiangProject/XinjiangProject/Project/HomeV2/View/XJHealthNewsView.h @@ -0,0 +1,31 @@ +// +// XJHealthNewsView.h +// XinjiangProject +// +// Created by Apple on 2026/6/29. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class XJHealthKnowledgeModel; + +@interface XJHealthNewsView : UIView + +/// 数据源 +@property (nonatomic, strong) NSArray * dataArray; +/// 点击"更多" +@property (nonatomic, copy) void (^moreBlock)(void); +/// 点击 cell +@property (nonatomic, copy) void (^cellSelectBlock)(XJHealthKnowledgeModel * model); + +/// 计算当前总高度 +- (CGFloat)totalHeight; + +/// 显示未登录占位 +- (void)showNotLoggedInPlaceholder; + +@end + +NS_ASSUME_NONNULL_END diff --git a/XinjiangProject/XinjiangProject/Project/HomeV2/View/XJHealthNewsView.m b/XinjiangProject/XinjiangProject/Project/HomeV2/View/XJHealthNewsView.m new file mode 100644 index 0000000..cd6bf4a --- /dev/null +++ b/XinjiangProject/XinjiangProject/Project/HomeV2/View/XJHealthNewsView.m @@ -0,0 +1,205 @@ +// +// XJHealthNewsView.m +// XinjiangProject +// +// Created by Apple on 2026/6/29. +// +// ┌─────────────────────────────┐ +// │ 健康资讯 [>] │ ← header 40 +// ├─────────────────────────────┤ +// │ Cell 1 │ ← cell 90 (最多2条) +// ├─────────────────────────────┤ +// │ Cell 2 │ +// ├─────────────────────────────┤ +// │ 暂无健康资讯(空状态) │ +// └─────────────────────────────┘ +// + +#import "XJHealthNewsView.h" +#import "XJHealthNewsCell.h" +#import "XJHealthKnowledgeModel.h" + +static CGFloat const kHeaderHeight = 40; +static CGFloat const kCellHeight = 90; +static CGFloat const kMaxCellCount = 2; +static CGFloat const kDefaultEmptyHeight = 130; + +@interface XJHealthNewsView () + +@property (nonatomic, strong) UIView * headerView; +@property (nonatomic, strong) UIImageView * headerBgImg; +@property (nonatomic, strong) UILabel * titleLabel; +@property (nonatomic, strong) UIButton * moreBtn; + +@property (nonatomic, strong) UITableView * listTableView; +@property (nonatomic, strong) UILabel * emptyLabel; + +@end + +@implementation XJHealthNewsView + +- (instancetype)initWithFrame:(CGRect)frame { + if (self = [super initWithFrame:frame]) { + self.backgroundColor = KWhiteColor; + self.layer.cornerRadius = 12; + self.layer.masksToBounds = YES; + [self createUI]; + } + return self; +} + +#pragma mark - ————— UI 搭建 ————— + +- (void)createUI { + + // 头部 + self.headerView = [[UIView alloc] init]; + [self addSubview:self.headerView]; + [self.headerView mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.right.top.mas_offset(0); + make.height.mas_offset(kRealValue(kHeaderHeight)); + }]; + + self.headerBgImg = [[UIImageView alloc] init]; + self.headerBgImg.image = [UIImage imageNamed:@"heaedTitleBackImg"]; + [self.headerView addSubview:self.headerBgImg]; + [self.headerBgImg mas_makeConstraints:^(MASConstraintMaker *make) { + make.edges.mas_offset(0); + }]; + + self.titleLabel = [[UILabel alloc] init]; + self.titleLabel.text = @"健康资讯"; + self.titleLabel.textColor = CFontColor1; + self.titleLabel.font = BOLDSYSTEMFONT(16); + [self.headerView addSubview:self.titleLabel]; + [self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.mas_offset(13); + make.centerY.mas_equalTo(self.headerView); + }]; + + self.moreBtn = [[UIButton alloc] init]; + [self.moreBtn setImage:[UIImage imageNamed:@"wacthRightAccow"] forState:UIControlStateNormal]; + [self.moreBtn addTarget:self action:@selector(moreAction) forControlEvents:UIControlEventTouchUpInside]; + [self.headerView addSubview:self.moreBtn]; + [self.moreBtn mas_makeConstraints:^(MASConstraintMaker *make) { + make.right.mas_offset(-8); + make.centerY.mas_equalTo(self.headerView); + make.width.height.mas_offset(30); + }]; + + // 列表(直接跟在 header 下方,无搜索框无标签区) + self.listTableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; + self.listTableView.delegate = self; + self.listTableView.dataSource = self; + self.listTableView.separatorStyle = UITableViewCellSeparatorStyleNone; + self.listTableView.scrollEnabled = NO; + self.listTableView.rowHeight = kCellHeight; + self.listTableView.backgroundColor = KWhiteColor; + self.listTableView.hidden = YES; + if (@available(iOS 15.0, *)) { + self.listTableView.sectionHeaderTopPadding = 0; + } + [self addSubview:self.listTableView]; + [self.listTableView mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.right.mas_offset(0); + make.top.mas_equalTo(self.headerView.mas_bottom); + make.height.mas_offset(0); + }]; + + // 空状态 + self.emptyLabel = [[UILabel alloc] init]; + self.emptyLabel.text = @"暂无健康资讯"; + self.emptyLabel.textColor = [UIColor colorWithRed:0.6 green:0.6 blue:0.6 alpha:1.0]; + self.emptyLabel.font = [UIFont systemFontOfSize:15]; + self.emptyLabel.textAlignment = NSTextAlignmentCenter; + self.emptyLabel.hidden = YES; + [self addSubview:self.emptyLabel]; + [self.emptyLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.centerX.mas_equalTo(self); + make.top.mas_equalTo(self.headerView.mas_bottom).offset(30); + }]; +} + +#pragma mark - ————— Setter ————— + +- (void)setDataArray:(NSArray *)dataArray { + _dataArray = dataArray; + + BOOL hasData = dataArray.count > 0; + + self.listTableView.hidden = !hasData; + self.emptyLabel.hidden = hasData; + + if (hasData) { + [self.listTableView reloadData]; + CGFloat tableH = MIN(dataArray.count, kMaxCellCount) * kCellHeight; + [self.listTableView mas_remakeConstraints:^(MASConstraintMaker *make) { + make.left.right.mas_offset(0); + make.top.mas_equalTo(self.headerView.mas_bottom); + make.height.mas_offset(tableH); + }]; + } else { + [self.listTableView mas_remakeConstraints:^(MASConstraintMaker *make) { + make.left.right.mas_offset(0); + make.top.mas_equalTo(self.headerView.mas_bottom); + make.height.mas_offset(0); + }]; + } +} + +- (void)showNotLoggedInPlaceholder { + self.dataArray = @[]; + self.emptyLabel.text = @"暂未登录"; + self.emptyLabel.hidden = NO; + [self.emptyLabel mas_remakeConstraints:^(MASConstraintMaker *make) { + make.centerX.mas_equalTo(self); + make.top.mas_equalTo(self.headerView.mas_bottom); + make.bottom.mas_equalTo(self); + }]; +} + +#pragma mark - ————— 高度计算 ————— + +- (CGFloat)totalHeight { + BOOL hasData = self.dataArray.count > 0; + + CGFloat base = kRealValue(kHeaderHeight); + if (hasData) { + CGFloat tableH = MIN(self.dataArray.count, kMaxCellCount) * kCellHeight; + return base + tableH; + } else { + return base + (self.emptyLabel.hidden ? kDefaultEmptyHeight - kHeaderHeight : 80); + } +} + +#pragma mark - ————— 事件 ————— + +- (void)moreAction { + if (self.moreBlock) self.moreBlock(); +} + +#pragma mark - ————— UITableViewDataSource ————— + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return MIN(self.dataArray.count, kMaxCellCount); +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + static NSString * cellID = @"XJHealthNewsCell"; + XJHealthNewsCell * cell = [tableView dequeueReusableCellWithIdentifier:cellID]; + if (!cell) { + cell = [[XJHealthNewsCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID]; + } + cell.model = self.dataArray[indexPath.row]; + return cell; +} + +#pragma mark - ————— UITableViewDelegate ————— + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + if (self.cellSelectBlock && indexPath.row < self.dataArray.count) { + self.cellSelectBlock(self.dataArray[indexPath.row]); + } +} + +@end diff --git a/XinjiangProject/XinjiangProject/Supporting Files/Assets.xcassets/HOMEV2/article_classIcon.imageset/Contents.json b/XinjiangProject/XinjiangProject/Supporting Files/Assets.xcassets/HOMEV2/article_classIcon.imageset/Contents.json new file mode 100644 index 0000000..4628948 --- /dev/null +++ b/XinjiangProject/XinjiangProject/Supporting Files/Assets.xcassets/HOMEV2/article_classIcon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "article_classIcon.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/XinjiangProject/XinjiangProject/Supporting Files/Assets.xcassets/HOMEV2/article_classIcon.imageset/article_classIcon.png b/XinjiangProject/XinjiangProject/Supporting Files/Assets.xcassets/HOMEV2/article_classIcon.imageset/article_classIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..3c2bcab1969a64dcd2134950e8ce39b495823d45 GIT binary patch literal 1349 zcmV-L1-kl)P)lcAB2 z7=aiQiO7Q*A}RifOcR#5ViZ0zzEO} z^a7WG9>84gM4@E@#(~4YYruwO&NT#l3v>a)K`#ml7y~{A4gwX+1S?h+_!>9?O!*lH z0el5<*K7VKSg|I7BfxiFMv6zD27F-;@UY>q$Gm9Ip_MW!<-v#z_TmbF$@CMj4@ebv z$ULY47bqVb_FNZ`cv1#<8rZYaL=x?>=Ppn&;6C6`S#ab!1iYHxkpB+GfIe0xAeN(P zpaYnILJywg(Zd1M0iQTO91k`EH-S~ME55&bhi^tlNG+L!LJ$vE@=3D-_SGtqw0H8R#lDLSkzonI_Gm=fKatG_0R-5g0nhkZWi5-%)z!4<5lqYC*Xb#y4Fdp0 z0_rri=}}i7z|cZEDBu-%GH&e-=fDIZ@ z?PrxWO?Jj&Y>ZZ6Sr$Kxk27w2FsiCtni!`)m1N1X7)T|Fhbwb{VMTGJp((>)IFlh> zS?M=*b;*5Lz6{Iag*Ekb)(J0Nota_Meh0&f!j*f|T)8)$vwzx--ms$3tm`C_Zjar{ zq=NvW0dt6TxTK=0%JD>BLEEv$CIBW4gU>rQ@!Vr|0KDJd$F=#noL#FPkphkfi{v>C z81n;6nBg8}Tm)dw{?1pb$i7N-doW}Ao`ReM zOlKd}g{b(Y+7(+vR8@1IT1{hHt%&H|^=k|IR%;r2>mM(;et$y)HJX;Ywnf)@ecgI5 zxU9`oP?K~4@A}0%sEohBHh+ZtG;O|6~t?dZ-D!YH)hUiCZ=YHb>|AzP%hQyaB99!_N00000NkvXX Hu0mjfajXU2ui3ykPH$G-+_)!Vb>U4g+|#rPzTY?)2IQ}DA-4WdJAZ1ofrtPL$Bo&Kvfzqm*_Vd z5-oxH5XR~=5zTuoRRIM7EZr|k{nHp8MoD5J)FBisd^8rRkW^pod~OOFAM!5Oh1*Ym z0oo_kVE{xh;Tl6s(DW|+a?8~3&+MnkmsG*L1bP#wO{w`UR9DY-$)oBVAw574ErEw2 z=2d|Rw)2vOwfa;$^Ts`za6HOq;!#d)YY2cST5r1_m&tce$8XM${9=Lv>Z)(HHR&Jf zTMR_y!x&}}gFZt$ip6rHz;EDG0Jd)!B3m!QCKXKW0s*~4M2HH6fxb>|#cQc-Nalrh zRG8(Tw)!E6f}`;VW=B!4{z$1oWFUdmC3ggT0CmkxV77x`W+-F#5Qk5pBxC2Nr@7W9rEGmPs$KYSP zwY}gLeJPXVU>ILitxZnP!E_4uv;nZv{MCOhyVSuf%uSfL$!OHX(B_Fw`Fx)^C2hVQ zuyXvU0Z$85F4CzpgLY_}ZHKb#30*`0jb{Q_sY{1i0y89Fs=^%~fqCueX;0pXXO-1U z1Qc|b7*`9=EgB!6|BVWPcf0L-dhQ+La3HvtOg40}7$rOO^CugAgUe8@V93eJ=Bd{+ zZ8JvdQvKKLZ)RG~=^8rQOIVr)MFou0hgC%{`EV_YW*RESK)3X8QfK2RJrzRr^J~NS zar<(8!N{UU!v_O8j*_2r+|ejmeN$7=bRG6xhzj3z2gLL0jXeki!eu)KOF{>y3dg_I z{7NLsao(s&)hh!WUP@w9`hxaXwMK9Yi~2IHZGA^SrMaMkZFl6yD$>`%WPm+INV~7d zY$N$Wvd7^XQY0l|y4Kh1^TCJOq(8(}8QsA_qvRC%!l#O4_tJDEq!jsCrtgPPYut3B zIJ>8$Rq5Ntxhn30ef91_RDi$G25!T6bj<>s9!&;RJLcPVL+lna8Wr@UdBs=p=IoE9 zDVxwea4M(a0mL`RH7nGvX2n-EQR*aUgg)-Yu1ER9YR)gpt=~8Pz>r=2XTx`yv}qI6 z>K1+5Hn_63_s+&~RqFmVr;}rXtp}=3{%^QYy_&h!_#2HGGs-7lb)tF$0=?5lY8Ils z4y;O0P2GF!&)}h9CA}+3O~VS?rEoG}gQ$|jqemC0jtQiB&&{)kznbFyiRwMW;PBQD zk|yr!rV3Ze@~wI?=y8bfEocJW?nISfL<9oFzHUYI{0s%BDxKjKHw(o17W7M=1k9^ygWK3RAO1%Z?I%TN;n>@3X~DmOOjQktwXDKZ0dwv=op z%{AOvuWxN$Wbz=Cwz6_{ookQkoSj1xIy#zME(JqFfmmjcG{U=L z0sZP0wN{^ou>>3CzZS?Q{Xj#D3C#Mxn{LIUZsEs52uJ1f`Ip5TUNbEt15+%#*2+oZ z)ta~QV=7Orgxa%Um-E(vo9BnWOvMr2(4FswDP{P(Gt^y}*2b2!tH4V2N3MmS;l|_E z&+UBMf0?%bQnC+$F+*SgvP;|Pj@BJk_~#@~n6CU~=|f1QLw)PkB_QO;HR{nt;j@Fs zu76f<6@_=Er$GOhNx*7k?#h`s9nN;@+3C_a-R#U;Dq{5VhR)2JfI~;JylrmEaIa>V z9;6O!U{w~mnC`GA$RJ=AFUxJ0=AZewHFvHu{xC|qt14}A+CbiZ%f*w*T7Ac=A(`p> zjBBFnEvD|d0r_mtWO5S!MDu)zsf&W44%r05s>GB5F_Hv{p_6K@ zG?|GGKJu29(GT!Kc(6etVa+aQbpMAmnEs^aY}l)|KG+ktLFx@6{VB<8E*?!_iT-%5 zrq!V&FOXs}rH2O5#az%cpJvP(W-JEgfMQxg%Rbw+l{huFrz>af62JQnb?RXx`z(+D zR!k=CrH`6JF-U&i)LzZLcUOVJx#GClWR_@EE>>KNRczgw z=FvZlU4dnyZa8^`18jx?gq1gb>I_Lna1CUTN4iq0IlSu1c;%^41|5W}zdU|PA`^g| zsq`@y!TYOg+0$*hnVk;HeWt)R{=Y{*xxW>1FtnQ6Gvn!|5?hc6_1?&+AW}v4&_-Ku zVDeIzQm(o|8_)m^`jCBH;ZJX=4B1l$fd|LDi#}G`xtz0k;}Sf)N%>n^&~#CmS-wr( z=K*s1^CdZMvNaao@Bv-NA9i^H8KFHR82^yhF3H)Ie#Mb1M3G5C<*23j_?^?w#g3G# zV!u7wmX@PJ6SZWt;S;0iZTgnid5Y=ofy+rX{8LO`p_V(_m+Fa9mH}YD-Mj5dZ_>#8 z^%SAdaP3|lmReZGMoO8;M#4soUOw|IUBz>LPG*W#e)`y;K}|nDViIh#5T>vw06ln>zPar zQ3#~em%`V3g$*FoI~iy*n!=QopDI3e<(I2cHl$WqtpCp`GJ4G}zOUde&$j#~SviCU zbjS>7Xlxmp8+hI6uL`F3_l6s+Z97#wsuQB50OxP&?W3(0W?-|lN0*Xi&?+DE+zVw` zYcLn=!;F8f6V~-l6c^@fuxWn ztToJhh&{u-KU&bHAp3^`NOHKR#S8gnSB;#GMS33^em+4)ecNh(rZdXyh?B=*hT+o4PMGiT&`1Czc0VMtT+{Fv5gJu zzxyyJM??l21(Em8>sPi(z2yl!y+x#WT3)kD+VdCo3flw}ct3Biv9X1|`$1`QMuN#; zXvB7$w8fOXmEFSfVn|-ECZ;w`A~1DjVAAkfSOHo4aHK%j=wwiARl%7h1W9xoIq<;A z4_9H&MQRBA$K+uAjf~{jtbX#o|Fn;r!%?W(XMf^aGFc3KFFdYO?-rw)hVO?&k;_!4 zLt)v5(b++3$m&`x88%sIW_;h0`Hkn(dpSD$=K;rsS$>5FMf#lj^3=ll`_4^rW7Wy= zX0JU#&g<9yJQqRIHG?w{_~TBVW`Q)r+_DcUgPH~o9^5!2RgPK zsTR1Ixs^rY`1+rK>Ks*-rrCKD-)Elh1S5T1+JwyZoywKXGWE)4=Jo4OgCfy`yyUjd z=FR@|#{tg%?IBksb3+r}lX5_M&}nNa=p{nit7Erde#5M0iLkFXNe)!)50g!o8HGUh z#~^Fx5Rg#8Lz-=f;XBsDJ|w~qhfVy&QVdXEpM(qWK>(Wz*Mmw zRp8W?!*KsDg^p#Y1+i1Np!P4;Inpj>x?@3l07!E?H(T8~-An+KA#4$Aprx|4xlE+u z?GARxh7m^Yr_sQvgu?^ybd4-umpTzzD(j(nMtM&X&RX{>$^SsdRw7K_X}e=;>a_dJ z)%u1gd|%5QfUKX!$Kr{`D@o(P^V^4ZyY6g9@9?NAk*~e{{bI7z^HkAK%`;-5zbT|B z45I=$m7lrO6OfDy>__Pb{$hL&E)sh_0@c0U6$KZM9P>g+iO-ECECDVmgprMjTdVXC zKp4eZysE1bRF#&_-tx%dhyShE*HJhDo==eSlX_-WUrO9Gb#IllKe?Ja_m2h83*0a-ecybK>6en&LIIL`3-WZ`(R>Y(~T=gq!-p>Qh|@a!HVn YWf{Gcvk9W)`v!o%wux5leTRtu0))F2O8@`> literal 0 HcmV?d00001