feat(home): 首页增加健康知识/资讯模块,含双视图列表及H5跳转
- 新增健康知识视图(标签切换+搜索+列表)和健康资讯视图(纯列表无搜索) - 对接健康栏目列表和文章分页接口,资讯无子分类直接用父级ID - 接入更多/搜索/详情H5跳转,统一用通用加密串传参 - 键盘弹起时自动避让,从H5回来刷新文章收藏数 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
2c2f307247
commit
6337454956
@@ -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";
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// XJHealthKnowledgeManager.h
|
||||
// XinjiangProject
|
||||
//
|
||||
// Created by Apple on 2026/6/29.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#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<XJHealthCategoryModel *> * 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<XJHealthArticleModel *> * articles, NSInteger total))success
|
||||
failure:(void (^)(NSString * errorMsg))failure;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,222 @@
|
||||
//
|
||||
// XJHealthKnowledgeManager.m
|
||||
// XinjiangProject
|
||||
//
|
||||
// Created by Apple on 2026/6/29.
|
||||
//
|
||||
|
||||
#import "XJHealthKnowledgeManager.h"
|
||||
#import <CommonCrypto/CommonHMAC.h>
|
||||
|
||||
#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<XJHealthCategoryModel *> *))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<XJHealthArticleModel *> *, 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
|
||||
+16
@@ -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
|
||||
+31
@@ -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
|
||||
+516
-43
@@ -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<XJHealthCategoryModel *> * 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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,6 +366,50 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半
|
||||
}
|
||||
}
|
||||
|
||||
- (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
|
||||
{
|
||||
@@ -642,7 +741,6 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半
|
||||
|
||||
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:@"解绑成功"];
|
||||
if([tag isEqualToString:@"POINT"]) {
|
||||
NSDictionary * responseDic = response;
|
||||
|
||||
if (ValidDict(responseDic)) {
|
||||
NSString * todayPointStr = @"今日健康积分 0";
|
||||
NSString * totalPointStr = @"累计积分 0";
|
||||
|
||||
DLog(@"%@",response);
|
||||
[self getUserWatchData];
|
||||
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,10 +965,19 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半
|
||||
self.mainScrollView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
|
||||
self.page = 1;
|
||||
[self getBannerListData];
|
||||
//健康知识:未登录占位,已登录刷新
|
||||
if ([HQCommonUtils isLogin]) {
|
||||
[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.actionBlock = ^(BindWatchType type, NSString *serialNumber) {
|
||||
if (type == BindWatchTypeBind) { //绑定
|
||||
[weakSelf getBindWatchDataWithSerialNum:serialNumber];
|
||||
|
||||
}else //解绑
|
||||
{
|
||||
[weakSelf getUnBindWatchDataWithSerialNum:serialNumber];
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
[pop show];
|
||||
@@ -1026,6 +1132,63 @@ 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"];
|
||||
@@ -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<XJHealthCategoryModel *> * 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<XJHealthArticleModel *> * 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
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// XJHealthArticleModel.h
|
||||
// XinjiangProject
|
||||
//
|
||||
// Created by Apple on 2026/6/29.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
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
|
||||
@@ -0,0 +1,12 @@
|
||||
//
|
||||
// XJHealthArticleModel.m
|
||||
// XinjiangProject
|
||||
//
|
||||
// Created by Apple on 2026/6/29.
|
||||
//
|
||||
|
||||
#import "XJHealthArticleModel.h"
|
||||
|
||||
@implementation XJHealthArticleModel
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// XJHealthCategoryModel.h
|
||||
// XinjiangProject
|
||||
//
|
||||
// Created by Apple on 2026/6/29.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface XJHealthCategoryModel : NSObject
|
||||
|
||||
@property (nonatomic, assign) NSInteger categoryId;
|
||||
@property (nonatomic, copy) NSString * categoryName;
|
||||
@property (nonatomic, strong) NSArray<XJHealthCategoryModel *> * children;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -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
|
||||
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// XJHealthKnowledgeModel.h
|
||||
// XinjiangProject
|
||||
//
|
||||
// Created by Apple on 2026/6/29.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
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
|
||||
@@ -0,0 +1,12 @@
|
||||
//
|
||||
// XJHealthKnowledgeModel.m
|
||||
// XinjiangProject
|
||||
//
|
||||
// Created by Apple on 2026/6/29.
|
||||
//
|
||||
|
||||
#import "XJHealthKnowledgeModel.h"
|
||||
|
||||
@implementation XJHealthKnowledgeModel
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// XJHealthKnowledgeCell.h
|
||||
// XinjiangProject
|
||||
//
|
||||
// Created by Apple on 2026/6/29.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class XJHealthKnowledgeModel;
|
||||
|
||||
@interface XJHealthKnowledgeCell : UITableViewCell
|
||||
|
||||
@property (nonatomic, strong) XJHealthKnowledgeModel * model;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -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
|
||||
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// XJHealthKnowledgeView.h
|
||||
// XinjiangProject
|
||||
//
|
||||
// Created by Apple on 2026/6/29.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class XJHealthKnowledgeModel;
|
||||
|
||||
@interface XJHealthKnowledgeView : UIView
|
||||
|
||||
/// 数据源
|
||||
@property (nonatomic, strong) NSArray<XJHealthKnowledgeModel *> * dataArray;
|
||||
/// 标签标题数组
|
||||
@property (nonatomic, strong) NSArray<NSString *> * 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
|
||||
@@ -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 () <UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate>
|
||||
|
||||
@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<UIButton *> * 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<XJHealthKnowledgeModel *> *)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<NSString *> *)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
|
||||
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// XJHealthNewsCell.h
|
||||
// XinjiangProject
|
||||
//
|
||||
// Created by Apple on 2026/6/29.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class XJHealthKnowledgeModel;
|
||||
|
||||
@interface XJHealthNewsCell : UITableViewCell
|
||||
|
||||
@property (nonatomic, strong) XJHealthKnowledgeModel * model;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -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
|
||||
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// XJHealthNewsView.h
|
||||
// XinjiangProject
|
||||
//
|
||||
// Created by Apple on 2026/6/29.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class XJHealthKnowledgeModel;
|
||||
|
||||
@interface XJHealthNewsView : UIView
|
||||
|
||||
/// 数据源
|
||||
@property (nonatomic, strong) NSArray<XJHealthKnowledgeModel *> * dataArray;
|
||||
/// 点击"更多"
|
||||
@property (nonatomic, copy) void (^moreBlock)(void);
|
||||
/// 点击 cell
|
||||
@property (nonatomic, copy) void (^cellSelectBlock)(XJHealthKnowledgeModel * model);
|
||||
|
||||
/// 计算当前总高度
|
||||
- (CGFloat)totalHeight;
|
||||
|
||||
/// 显示未登录占位
|
||||
- (void)showNotLoggedInPlaceholder;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -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 () <UITableViewDelegate, UITableViewDataSource>
|
||||
|
||||
@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<XJHealthKnowledgeModel *> *)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
|
||||
+21
@@ -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
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
+21
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "article_placholdIMg.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
Reference in New Issue
Block a user