Compare commits

15 Commits
Author SHA1 Message Date
jiayangyangandClaude Opus 4.7 a6a0f0a4ce feat(sos): 应急中心页面重构,新增参与订单模型及相关接口,补充首页图标资源
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-22 14:48:45 +08:00
jiayangyangandClaude Opus 4.7 8a8a914a8c feat(sos): 应急就医群视频通话加号邀请及健康档案详情页接口对接
- 多人视频页面恢复右上角加号按钮,接入自有半屏群成员面板
- 选人后自动发起视频通话邀请并即时显示受邀成员
- IM 登录同步 setSelfInfo,解决通话成员显示 ID 问题
- 健康档案详情页接入一人一案接口,移除硬编码数据
- 新增 Pods 修改记录文档

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-20 17:56:10 +08:00
jiayangyangandClaude Opus 4.7 ee498064cc feat(sos): 集成微信SDK,实现地图卡片长按转发微信;新增急救资源详情页及群成员面板
- 手动集成微信 OpenSDK-NoPay,创建 XJWeChatManager 统一管理分享能力
- 地图卡片新增长按手势,通过代理模式实现「转发到微信」,分享含高德导航链接
- 聊天页新增群成员侧滑面板,支持查看成员列表及一键拨号
- 新增急救资源详情页(XJFirstAidResourceDetailVC)及选择联系人页
- 首页健康知识/资讯模块,含双视图列表及 H5 跳转
- 修复急救资源详情页编译报错

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-14 17:53:44 +08:00
jiayangyangandClaude Opus 4.7 6337454956 feat(home): 首页增加健康知识/资讯模块,含双视图列表及H5跳转
- 新增健康知识视图(标签切换+搜索+列表)和健康资讯视图(纯列表无搜索)
- 对接健康栏目列表和文章分页接口,资讯无子分类直接用父级ID
- 接入更多/搜索/详情H5跳转,统一用通用加密串传参
- 键盘弹起时自动避让,从H5回来刷新文章收藏数

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-29 18:46:58 +08:00
NSArray 2c2f307247 feat: HomeV2 手表数据区高度动态调整 & 绑定弹窗姓名工号布局优化
- XJHomeWatchDataView 新增 heightChangeBlock,无数据/未登录高度 104,有数据高度 200
- XJHomePageV2HeardView 初始高度改为 104,绑定回调动态更新约束带动画
- BindWatchPopView 姓名工号同行布局,工号固定右侧优先显示,姓名占满剩余空间
2026-03-19 16:50:19 +08:00
NSArray 6f9f903ca1 修改弹窗逻辑 2026-03-13 17:20:45 +08:00
NSArray 1bfe617cab 手表解绑功能 2026-03-12 18:08:53 +08:00
NSArray a47d1a56fa 积分银行 2026-03-11 17:59:56 +08:00
NSArray d0ff8c2c19 fix: 修复 WKWebView 白屏及 finishActivity JS Bridge 失效问题
- XJBaseWebViewVC: 补全 finishActivity 消息分发,基类默认实现改为 pop 返回
- XJBaseWebViewVC: 新增 decidePolicyForNavigationAction 拦截非 HTTP scheme(alipay:// 等),避免 WKWebView 加载失败导致白屏
- XJBaseWebViewVC: 新增 webViewWebContentProcessDidTerminate,进程被系统杀死后自动 reload
- XJBankWebDetailViewController: 移除重复的 finishActivity override,复用基类 pop 逻辑
- AppDelegate: 移除导致启动卡顿的 WKWebView 预热代码
- HomeV2: 将 Controllview 相关文件移入 Controllview 子目录,整理目录结构
2026-03-11 17:58:23 +08:00
NSArray b69578aa4a chore: 添加 Claude Code 项目配置文件 2026-03-09 09:43:43 +08:00
NSArray c7925e3189 chore: 版本升级至 1.0.11 并切换正式环境
- 版本号 1.0.10 → 1.0.11,Build 号 1 → 2
- API 环境切换至正式地址(api-jkglpt.iosp.ydpt.tech)
- 首页 Banner 请求增加 loading 状态显示
- 重构 QuestionViewController:移除 verifyToken 流程,改用 isLogin 判断
- 删除废弃方法(verifyTokenData、getUpDateinfo、backUp、updateHomeDate)
- 更新首页顶部图片资源(组7 → 组9)
- 清理 AppDelegate 中空的 DEBUG 编译条件块
2026-03-06 16:59:25 +08:00
NSArray 82a432f77a 首页监测自测修改 2026-03-05 18:07:03 +08:00
NSArray c5b5149bb1 首页手表监测 2026-03-04 18:07:02 +08:00
NSArray 823063653b 手表功能 2026-03-03 18:12:00 +08:00
NSArray aa34d4623e 新疆首页新需求 2026-02-05 20:24:38 +08:00
299 changed files with 18245 additions and 914 deletions
+196
View File
@@ -0,0 +1,196 @@
# Agent 行为准则 & XinjiangProject iOS 开发规范
---
## 一、Agent 行为准则
### 0. 语言强制
- 强制使用简体中文进行所有交互(代码除外)。
### 1. 抽象设计确认
- 涉及抽象设计、架构调整或新功能模块时,必须先用文字或 Mermaid 图对齐设计思路。
- 必须等待用户确认方案后,才能开始编写代码。
### 2. 任务清单确认
- 执行任何实质性任务前,必须先列出详细的任务清单。
- 必须等待用户明确回复(如"好的"、"开始")后,才能进入执行阶段。
### 3. 分步执行与确认
- 代码量较大或逻辑复杂的任务,禁止一次性完成。
- 拆分为多个步骤,每步完成后汇报进度并询问"是否可以进行下一步?"。
### 4. 所有修改必须确认
- 对代码库的任何修改(新建文件、修改文件、删除文件、执行 pod install 等)前,必须先描述变更内容。
- 必须等待用户明确回复"确认"或"同意"后,才能执行。
### 9. 代码注释语言规范
- 所有注释使用简体中文。
- 标识符(变量名、函数名、类名)仍使用英文。
---
## 二、项目技术规范
### 技术栈
- **主语言**: Objective-C
- **混编**: 允许 OC/Swift 混编,OC 为主。引入 Swift 文件前**必须征得用户确认**。
- **UI 框架**: UIKit
- **架构模式**: MVC + Manager 层
- **包管理**: CocoaPods,使用 `.xcworkspace` 打开项目
- **最低系统版本**: iOS 12.0
---
### 项目目录结构
```
XinjiangProject/
├── Project/ # 业务模块(按功能划分)
│ ├── Base/ # 基类 ViewController
│ ├── AppDelegate/
│ ├── Login_Register/
│ ├── HomeV2/
│ ├── Homepage/
│ ├── Monitoring/
│ ├── MainCenter/
│ ├── BANK/
│ ├── SOS/
│ ├── Qusetion/
│ ├── TUIChat/
│ ├── CustomTUIChat/
│ └── simulationUser(模拟用户)/
├── Manager/ # 业务逻辑管理层
│ ├── NetworkHelpManager/ # 网络层(XJPNetAPI、JKCQNetworkingWithCache
│ ├── UserManager/
│ ├── AudioManager/
│ ├── TUIManager/
│ └── RuntimeManager/
├── Categorys/ # UIKit 分类扩展
├── Utils/ # 工具类
├── Define/ # 宏定义(颜色、字体、接口地址、Key)
└── ThirdParty/ # 本地化第三方库
```
新增模块按以下结构组织:
```
Project/ModuleName/
├── Controller/
├── View/
└── Model/
```
---
### 命名规范
**类名(PascalCase):**
- 新建类统一使用前缀 `XJ`,如 `XJOrderViewController``XJUserModel`
- 历史遗留 `HQ` 前缀类(如 `HQUserInfo`)维护时保持原前缀,不得重命名
- 分类命名:`ClassName+ExtensionName`,如 `UIView+Extension`
**方法名(camelCase):**
```objc
- (void)setupNavigationBar;
- (void)loadUserDataWithType:(NSInteger)type;
```
**属性修饰符规范:**
```objc
@property (nonatomic, strong) UIView *containerView; // 对象用 strong
@property (nonatomic, copy) NSString *userName; // 字符串/Block 用 copy
@property (nonatomic, assign) NSInteger pageIndex; // 基本类型用 assign
@property (nonatomic, weak) id<Protocol> delegate; // 代理用 weak
```
**宏常量:**
```objc
#define kNavBgColor [UIColor colorWithHexString:@"#00AE68"] // 颜色用 k 前缀
#define SCREEN_WIDTH [UIScreen mainScreen].bounds.size.width // 系统级用大写
```
---
### 代码组织(#pragma mark
```objc
#pragma mark - ————— Life Cycle —————
#pragma mark - ————— Setter / Getter —————
#pragma mark - ————— Private Methods —————
#pragma mark - ————— Network —————
#pragma mark - ————— UITableViewDataSource —————
#pragma mark - ————— UITableViewDelegate —————
```
---
### 内存管理
Block 内防循环引用,使用项目全局 `kWeakSelf` 宏:
```objc
kWeakSelf(self);
[self.manager doSomethingWithCompletion:^{
[weakself updateUI];
}];
```
---
### UI 布局
frame 与 Masonry 均可使用,根据实际场景判断,不强制统一。
屏幕适配使用项目已定义宏(基于 375pt 基准):
```objc
kRealValue(x) // 尺寸等比缩放
kFontSize(x) // 字体等比缩放
```
---
### 网络层规范
项目网络层使用 `XJPNetAPI` + `JKCQNetworkingWithCache`
回调通过 `performSelector` 动态分发,**遵循以下固定签名**:
**发起请求:**
```objc
// init:tag:NeedToken: 初始化,self 作为代理,tag 区分同一 VC 内的不同请求
XJPNetAPI *api = [[XJPNetAPI alloc] init:self tag:@"USERINFO" NeedToken:token];
[api getUserInfo:paramDic];
```
**实现回调(固定方法签名,不得修改):**
```objc
// 请求成功
- (void)Sucess:(id)response tag:(NSString *)tag {
if ([tag isEqualToString:@"USERINFO"]) {
// 处理用户信息数据
}
}
// 请求失败
- (void)Failed:(NSString *)message tag:(NSString *)tag {
[self showMBProgressHUDOnlyWithString:message];
}
```
**注意事项:**
- `Sucess` 为项目约定拼写(非笔误),禁止修改方法名
- 同一 VC 内多个请求通过 `tag` 字符串区分,在回调中用 `if/else` 分支处理
- API 地址统一定义在 `StatusMacros.h`,禁止在业务代码中硬编码 URL
---
### 禁止事项
- 禁止将 API Key、SDKAPPID 等敏感信息硬编码在业务文件中(统一放 `ThirdMacros.h`
- 禁止在主线程执行网络请求、数据库读写、文件 IO 等耗时操作
- 禁止在 Category 中声明存储属性(需用 `objc_setAssociatedObject`
- 禁止修改 `Sucess:tag:` / `Failed:tag:` 的方法签名
- 禁止未经确认自行封装或绕过 `XJPNetAPI` 发起网络请求
+1 -1
View File
@@ -1,5 +1,5 @@
platform :ios, '12.0'
platform :ios, '15.0'
# 忽略引入库的所有警告
inhibit_all_warnings!
+1 -1
View File
@@ -511,6 +511,6 @@ SPEC CHECKSUMS:
YYKit: 7cda43304a8dc3696c449041e2cb3107b4e236e7
YYModel: 2a7fdd96aaa4b86a824e26d0c517de8928c04b30
PODFILE CHECKSUM: c63ef0a5edf68dd395621c435af5c0063b38442a
PODFILE CHECKSUM: 9ebe0e8991fab6e5cf3d18a72b82760099c4bc48
COCOAPODS: 1.16.2
@@ -7,6 +7,8 @@
objects = {
/* Begin PBXBuildFile section */
188C3FFF3005E07600461C07 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 188C3FFE3005E07600461C07 /* Security.framework */; };
188C40013005E08000461C07 /* CoreTelephony.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 188C40003005E08000461C07 /* CoreTelephony.framework */; };
2A9D6F4901C83B23A09A207B /* libPods-XinjiangProject.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5380821CF956D8001EF91CA7 /* libPods-XinjiangProject.a */; };
/* End PBXBuildFile section */
@@ -31,6 +33,10 @@
1809E4622E092C710034C8FC /* XinjiangProject.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = XinjiangProject.app; sourceTree = BUILT_PRODUCTS_DIR; };
1809E47D2E092C740034C8FC /* XinjiangProjectTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = XinjiangProjectTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
1809E4872E092C740034C8FC /* XinjiangProjectUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = XinjiangProjectUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
188C3FFE3005E07600461C07 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
188C40003005E08000461C07 /* CoreTelephony.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreTelephony.framework; path = System/Library/Frameworks/CoreTelephony.framework; sourceTree = SDKROOT; };
188C40023005E08B00461C07 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
188C40033005E09100461C07 /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; };
5380821CF956D8001EF91CA7 /* libPods-XinjiangProject.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-XinjiangProject.a"; sourceTree = BUILT_PRODUCTS_DIR; };
D5F062871D7E5499A760A0A9 /* Pods-XinjiangProject.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-XinjiangProject.release.xcconfig"; path = "Target Support Files/Pods-XinjiangProject/Pods-XinjiangProject.release.xcconfig"; sourceTree = "<group>"; };
F4F935FBD1765B37B37D212D /* Pods-XinjiangProject.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-XinjiangProject.debug.xcconfig"; path = "Target Support Files/Pods-XinjiangProject/Pods-XinjiangProject.debug.xcconfig"; sourceTree = "<group>"; };
@@ -72,6 +78,8 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
188C40013005E08000461C07 /* CoreTelephony.framework in Frameworks */,
188C3FFF3005E07600461C07 /* Security.framework in Frameworks */,
2A9D6F4901C83B23A09A207B /* libPods-XinjiangProject.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -118,6 +126,10 @@
5E0194FB12246C95B3FA5215 /* Frameworks */ = {
isa = PBXGroup;
children = (
188C40033005E09100461C07 /* libc++.tbd */,
188C40023005E08B00461C07 /* libz.tbd */,
188C40003005E08000461C07 /* CoreTelephony.framework */,
188C3FFE3005E07600461C07 /* Security.framework */,
5380821CF956D8001EF91CA7 /* libPods-XinjiangProject.a */,
);
name = Frameworks;
@@ -207,7 +219,7 @@
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastUpgradeCheck = 1630;
LastUpgradeCheck = 2630;
TargetAttributes = {
1809E4612E092C710034C8FC = {
CreatedOnToolsVersion = 16.3;
@@ -374,9 +386,10 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = XinjiangProject/XinjiangProject.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = A7CC5LW224;
ENABLE_MODULE_VERIFIER = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_PREFIX_HEADER = "$(SRCROOT)/XinjiangProject/Supporting Files/PrefixHeader.pch";
GENERATE_INFOPLIST_FILE = YES;
@@ -400,7 +413,8 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.8;
MARKETING_VERSION = 1.0.14;
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
PRODUCT_BUNDLE_IDENTIFIER = com.questionTools.XinjiangProject;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
@@ -418,9 +432,10 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = XinjiangProject/XinjiangProject.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = A7CC5LW224;
ENABLE_MODULE_VERIFIER = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_PREFIX_HEADER = "$(SRCROOT)/XinjiangProject/Supporting Files/PrefixHeader.pch";
GENERATE_INFOPLIST_FILE = YES;
@@ -444,7 +459,8 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.8;
MARKETING_VERSION = 1.0.14;
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
PRODUCT_BUNDLE_IDENTIFIER = com.questionTools.XinjiangProject;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
@@ -515,6 +531,7 @@
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
};
name = Debug;
};
@@ -570,6 +587,7 @@
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
VALIDATE_PRODUCT = YES;
};
name = Release;
@@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2630"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "1809E4612E092C710034C8FC"
BuildableName = "XinjiangProject.app"
BlueprintName = "XinjiangProject"
ReferencedContainer = "container:XinjiangProject.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "1809E47C2E092C740034C8FC"
BuildableName = "XinjiangProjectTests.xctest"
BlueprintName = "XinjiangProjectTests"
ReferencedContainer = "container:XinjiangProject.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "1809E4862E092C740034C8FC"
BuildableName = "XinjiangProjectUITests.xctest"
BlueprintName = "XinjiangProjectUITests"
ReferencedContainer = "container:XinjiangProject.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "1809E4612E092C710034C8FC"
BuildableName = "XinjiangProject.app"
BlueprintName = "XinjiangProject"
ReferencedContainer = "container:XinjiangProject.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "1809E4612E092C710034C8FC"
BuildableName = "XinjiangProject.app"
BlueprintName = "XinjiangProject"
ReferencedContainer = "container:XinjiangProject.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
@@ -91,7 +91,7 @@ static char edgeKey;
break;
case DIYStyleImgBottom:
NSLog(@"%f",titleX);
DLog(@"%f",titleX);
self.titleEdgeInsets = UIEdgeInsetsMake(((selfH - totalH)/2.0 - titleY),
-(titleX + titleW/2.0 - selfW/2.0),
-((selfH - totalH)/2.0 - titleY),
@@ -156,7 +156,7 @@ static char edgeKey;
break;
case DIYStyleImgBottom:
NSLog(@"%f",titleX);
DLog(@"%f",titleX);
self.titleEdgeInsets = UIEdgeInsetsMake(((selfH - totalH)/2.0 - titleY),
-(titleX + titleW/2.0 - selfW/2.0),
-((selfH - totalH)/2.0 - titleY),
@@ -220,7 +220,7 @@ static char edgeKey;
break;
case DIYStyleImgBottom:
NSLog(@"%f",titleX);
DLog(@"%f",titleX);
self.titleEdgeInsets = UIEdgeInsetsMake(((selfH - totalH)/2.0 - titleY),
-(titleX + titleW/2.0 - selfW/2.0),
-((selfH - totalH)/2.0 - titleY),
@@ -288,7 +288,7 @@ static char edgeKey;
break;
case DIYStyleImgBottom:
NSLog(@"%f",titleX);
DLog(@"%f",titleX);
self.titleEdgeInsets = UIEdgeInsetsMake(((selfH - totalH)/2.0 - titleY),
-(titleX + titleW/2.0 - selfW/2.0),
-((selfH - totalH)/2.0 - titleY),
@@ -15,13 +15,11 @@
#define KNotificationLoginIMResult @"loginIMResult"
//切换tabar
#define KNotificSelectHomePageTabbar @"SlectHomePageTabBar"
#define KNotificSelectSoSTabbar @"SlectSoSTabBar"
#define KNotificSelectQuestionTabbar @"SlectQuestionTabBar"
#define KNotificSelectSOSCenterTabbar @"SlectSoSTabBar"
#define KNotificSelectWatchTabbar @"SlectMonitoringTabBar"
#define KNotificSelectPhysicalTabbar @"SlectPhysicalTabBar"
#define KNotificSelectFileTabbar @"SlectFileTabBar"
//同意协议
@@ -63,39 +61,52 @@
#pragma mark: 网络前缀
#define ResourceAddress [NSString stringWithFormat:@"%@/file/show/",Http]
//开发环境地址 彭杰 http://192.168.1.37
//#define Http @"http://192.168.1.37"//
//#define H5DevHost @"xjgateway.mcrm.vip:8888"//
//#define IntervenH5 @"https://xjconsole.mcrm.vip:8888"
//#define BASEURL @"https://yyjk.cqygjk.com" //北边接口前缀
//#define SCHEME @"https" //
//#define Http @"http://192.168.1.23"//昊
//正式地址
//#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 @"https://api-xj.tri.icdat.cn"//
#define H5DevHost @"api-xj.tri.icdat.cn"//
#define IntervenH5 @"https://console-xj.tri.icdat.cn"
//最新新疆测试地址
//#define Http @"http://192.168.1.223"//彭杰
// 最新地址
//#define Http @"http://192.168.1.47"//王昊
#define Http @"https://xj-api.yixiong-tech.com:8081"//
#define H5DevHost @"xjxc-api.mcrm.vip:8888"//
#define IntervenH5 @"http://10.10.10.228:23006/"
#define SCHEME @"https" //
#define OtherHttp @"https://gstest.superwx.cn/xj_health" //
//新疆项目后期的调试环境由阿里云迁移至公司内部主机服务器,功能联调和测试可用以下的环境地址
//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"
// 急救宣教资源
#define kFirstAidResourceDetailPath @"/health-emergency/api/emergency/firstAid/resource/resource/queryById"
#endif /* StatusMacros_h */
@@ -10,9 +10,11 @@
#define ThirdMacros_h
//微信
#define kAppKey_Wechat @""
#define kAppKey_Wechat @"wxb33cd60beda0a212"
#define kSecret_Wechat @""
#define kSecret_Wechat @"9de1222d6c32af184538d2ae25d1f5a2"
#define kUniversalLink_Wechat @"https://www.rk-health.com"
#define UMShareAppKey @""
@@ -22,6 +24,7 @@
#define buglyAppid @""
#define SecretKey @"XJp3EHpcnwReXc1A"
//高德key
@@ -26,7 +26,7 @@
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:^(NSProgress * _Nonnull downloadProgress) {
//下载进度
progress ? progress(downloadProgress) : nil;
NSLog(@"下载进度:%.2f%%",100.0*downloadProgress.completedUnitCount/downloadProgress.totalUnitCount);
DLog(@"下载进度:%.2f%%",100.0*downloadProgress.completedUnitCount/downloadProgress.totalUnitCount);
} destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
//拼接缓存目录
@@ -35,11 +35,11 @@
//拼接文件路径
NSString *filePath = [downloadStr stringByAppendingPathComponent:fileDir];
NSLog(@"downloadStr = %@",downloadStr);
DLog(@"downloadStr = %@",downloadStr);
return [NSURL fileURLWithPath:filePath];
} completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
NSLog(@"%@---%@", filePath, filePath.absoluteString);
DLog(@"%@---%@", filePath, filePath.absoluteString);
success ? success(filePath.absoluteString /** NSURL->NSString*/) : nil;
failure && error ? failure(error) : nil;
}];
@@ -57,7 +57,7 @@
//获取路径
NSString *documentsPath = [ReaderDocument documentsPath];
//可根据路径command + shift + G查看该路径下的所有的pdf文件
NSLog(@"%@", documentsPath);
DLog(@"%@", documentsPath);
NSString *filePath = [documentsPath stringByAppendingPathComponent:fileName];
ReaderDocument *document = [ReaderDocument withDocumentFilePath:filePath password:nil];
if (document != nil)
@@ -72,7 +72,7 @@
[self downloadWithURL:url fileDir:fileName progress:^(NSProgress *progress) {
} success:^(NSString *filePath) {
NSLog(@"%@", filePath);
DLog(@"%@", filePath);
//下载完成直接打开
[self downLoadPdfFileByUrl:url fileName:fileName];
} failure:^(NSError *error) {
@@ -93,7 +93,7 @@
NSString *filePath = [path stringByAppendingPathComponent:fileName];
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL result = [fileManager fileExistsAtPath:filePath];
NSLog(@"这个文件已经存在:%@",result?@"是的":@"不存在");
DLog(@"这个文件已经存在:%@",result?@"是的":@"不存在");
return result;
}
@@ -55,4 +55,7 @@ typedef void (^FileDownloadFail)(int code, NSString * desc);
#pragma mark - 表单带参数
- (void)httpPostFormDataRequest:(NSString *)api params:(NSMutableDictionary *)params;
//外部接口
- (void)httpOtherPostRequest:(NSString *)api params:(NSMutableDictionary *)params;
@end
@@ -8,7 +8,7 @@
//
#import "JKCQNetworkingWithCache.h"
#import <CommonCrypto/CommonHMAC.h>
#define DYLog(...) NSLog(__VA_ARGS__) //如果不需要打印数据, 注释掉NSLog
@@ -20,8 +20,8 @@ typedef NS_ENUM(NSInteger, RequestType) {
RequestTypeMultiUpload,//多个上传
RequestTypeDownload,
RequestTypeUpLoadVideo,//上传视频
RequestTypeParmFormData//参数表单
RequestTypeParmFormData,//参数表单
RequestTypeOterTypePost//外部接口
};
@implementation JKCQNetworkingWithCache
@@ -65,6 +65,11 @@ typedef NS_ENUM(NSInteger, RequestType) {
[self httpRequestWithUrlStr:api params:params requestType:RequestTypePost isCache:YES cacheKey:api imageKey:nil withData:nil withDataArray:nil];
}
- (void)httpOtherPostRequest:(NSString *)api params:(NSMutableDictionary *)params
{
[self httpRequestWithUrlStr:api params:params requestType:RequestTypeOterTypePost isCache:NO cacheKey:nil imageKey:nil withData:nil withDataArray:nil];
}
#pragma mark - 上传文件方法
//上传单张图片
- (void)upLoadDataWithUrlStr:(NSString *)api params:(NSMutableDictionary *)params imageKey:(NSString *)name withData:(NSData *)data
@@ -248,6 +253,7 @@ typedef NS_ENUM(NSInteger, RequestType) {
{
NSLog(@"%@",url);
[MBProgressHUD hideHUDForView:[HQCommonUtils getCurrentVC].view animated:YES];
UIWindow *keyWindow = UIApplication.sharedApplication.windows.firstObject;
for (UIWindow *window in UIApplication.sharedApplication.windows) {
if (window.isKeyWindow) {
@@ -257,7 +263,7 @@ typedef NS_ENUM(NSInteger, RequestType) {
}
[MBProgressHUD hideHUDForView:keyWindow animated:YES];
[self showError:@"网络服务出错"];
[EasyTextView showErrorText:@"请求超时"];
}
}];
@@ -360,10 +366,74 @@ typedef NS_ENUM(NSInteger, RequestType) {
}
else {
[MBProgressHUD hideHUDForView:[HQCommonUtils getCurrentVC].view animated:YES];
UIWindow *keyWindow = UIApplication.sharedApplication.windows.firstObject;
for (UIWindow *window in UIApplication.sharedApplication.windows) {
if (window.isKeyWindow) {
keyWindow = window;
break;
}
}
[MBProgressHUD hideHUDForView:keyWindow animated:YES];
[EasyTextView showErrorText:@"请求超时"];
}
}];
}else if(requestType == RequestTypeOterTypePost) {//参数表单上传
//获取当前时间戳
NSDate *date = [NSDate date];
NSTimeInterval time = [date timeIntervalSince1970];
NSString *timeString = [NSString stringWithFormat:@"%.0f", time];
[session.requestSerializer setValue:@"application/json;charset=utf-8" forHTTPHeaderField:@"Content-Type"];
//加密
NSString * needSign = [NSString stringWithFormat:@"%@%@",timeString,params];
NSString * sign = [self signWithTimestamp:timeString body:params secretKey:SecretKey];
DLog(@"%@",sign);
[session POST:url parameters:params headers:@{@"X-Timestamp":timeString,@"X-Sign":sign} progress:^(NSProgress * _Nonnull downloadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[weakSelf dealWithResponseObject:responseObject cacheUrl:allUrl];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSString * statusCode = [error.userInfo objectForKey:@"NSLocalizedDescription"];
if ([statusCode containsString:@"401"]) {
DLog(@"%@",error.userInfo);
[HQCommonUtils keyedArchiverWithData:nil key:LoginUserInfo];
KPostNotification(KNotificationLoginStateChange, @NO);
if (![HQCommonUtils isLogin]) {
[EasyTextView showErrorText:@"请您登录"];
}
else{
[EasyTextView showErrorText:@"身份信息已过期"];
}
}
else
{
NSLog(@"%@",url);
[MBProgressHUD hideHUDForView:[HQCommonUtils getCurrentVC].view animated:YES];
UIWindow *keyWindow = UIApplication.sharedApplication.windows.firstObject;
for (UIWindow *window in UIApplication.sharedApplication.windows) {
if (window.isKeyWindow) {
keyWindow = window;
break;
}
}
[MBProgressHUD hideHUDForView:keyWindow animated:YES];
[EasyTextView showErrorText:@"请求超时"];
}
}];
}
}
@@ -572,5 +642,38 @@ typedef NS_ENUM(NSInteger, RequestType) {
self.requestDelegate=nil;
}
#pragma mark:外部加密sign
- (NSString *)signWithTimestamp:(NSString *)timestamp
body:(NSDictionary *)bodyDict
secretKey:(NSString *)secretKey {
// 1 字典转 JSON
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:bodyDict options:0 error:nil];
NSString *body = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
// 2 拼接字符串
NSString *signString = [NSString stringWithFormat:@"%@%@", timestamp, body];
// 3 HMAC-SHA256
const char *key = [secretKey UTF8String];
const char *data = [signString UTF8String];
unsigned char result[CC_SHA256_DIGEST_LENGTH];
CCHmac(kCCHmacAlgSHA256,
key,
strlen(key),
data,
strlen(data),
result);
// 4 转16进制
NSMutableString *hash = [NSMutableString string];
for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) {
[hash appendFormat:@"%02x", result[i]];
}
return hash;
}
@end
@@ -27,7 +27,28 @@
- (void)GetSoSHomePageResources:(NSMutableDictionary *)pamraDic;
- (void)PostEndSoSIMQuestion:(NSMutableDictionary *)pamraDic;
- (void)getSoSCarInfoLocationHome:(NSMutableDictionary *)parmDic;
// 急救员 - 搜索专家
- (void)getSearchExpert:(NSMutableDictionary *)parmDic;
// 急救员 - 搜索员工
- (void)getSearchEmployee:(NSMutableDictionary *)parmDic;
// 急救员 - 我参与的应急工单
- (void)getMyParticipatedOrders:(NSMutableDictionary *)parmDic;
// 急救员 - 创建群聊(不发起音视频通话)
- (void)getCreateGroupMagFirstAider:(NSMutableDictionary *)parmDic;
// 急救员 - 邀请进群
- (void)postAddGroupUser:(NSMutableDictionary *)parmDic;
// 急救员 - 移除群成员
- (void)postRemoveGroupUser:(NSMutableDictionary *)parmDic;
// 急救员 - 判断是否联络员
- (void)getIsLiaisonUser:(NSMutableDictionary *)parmDic;
// 急救员 - 群成员列表
- (void)getGroupMemberList:(NSMutableDictionary *)parmDic;
// 急救员 - 实际群成员列表(经腾讯IM验证,含isSelf字段)
- (void)getActualGroupMemberList:(NSMutableDictionary *)parmDic;
// 急救员 - 宣教资源详情
- (void)getFirstAidResourceDetail:(NSMutableDictionary *)parmDic;
// 一人一案 - 健康档案详情
- (void)getOnePersonCase:(NSMutableDictionary *)parmDic;
//首页接口
- (void)GetQuetsionRecordsList:(NSMutableDictionary *)parmDic;
@@ -151,4 +172,41 @@
//恢复用户状态
- (void)postRecoverySimulationUser:(NSMutableDictionary *)parmDic;
//消息
- (void)getUserMessageList:(NSMutableDictionary *)parmDic;
- (void)getUserMessageDetailConnet:(NSMutableDictionary *)parmDic;
- (void)getHomepageBannerList:(NSMutableDictionary *)parmDic;
//我的积分
- (void)getPointsBankHeard:(NSMutableDictionary *)parmDic;
- (void)postPointsBankRankData:(NSMutableDictionary *)parmDic;
- (void)postPointsBankSourceDetail:(NSMutableDictionary *)parmDic;
- (void)postPointsCheckInCard:(NSMutableDictionary *)parmDic;
//积分任务
- (void)postPointsTaskList:(NSMutableDictionary *)parmDic;
//图片地址
- (void)getDetailImageAddress:(NSMutableDictionary *)parmDic;
//轮播图一次性全请求
- (void)getHomeBannerAllData:(NSMutableDictionary *)parmDic;
//手表接口
- (void)getHomeWatchWearBool:(NSMutableDictionary *)parmDic;
- (void)getHomeWatchData:(NSMutableDictionary *)parmDic;
- (void)getWebNeedEncryptionKey:(NSMutableDictionary *)parmDic;
//绑定手表
- (void)getBindWatchDevice:(NSMutableDictionary *)parmDic;
//解绑手表
- (void)getUnBindWatchDevice:(NSMutableDictionary *)parmDic;
- (void)postPointsUnmberData:(NSMutableDictionary *)parmDic;
@end
@@ -69,6 +69,61 @@
}
// 急救员 - 搜索专家
- (void)getSearchExpert:(NSMutableDictionary *)parmDic {
[httpRequest httpGetRequest:[NSString stringWithFormat:@"%@/health-emergency/api/emergency/order/searchExpert",Http] params:parmDic];
}
// 急救员 - 判断是否联络员
- (void)getIsLiaisonUser:(NSMutableDictionary *)parmDic {
[httpRequest httpGetRequest:[NSString stringWithFormat:@"%@/health-emergency/emergency/LargeScreenNew/isLiaisonUser",Http] params:parmDic];
}
// 急救员 - 创建群聊(不发起音视频通话)
- (void)getCreateGroupMagFirstAider:(NSMutableDictionary *)parmDic {
[httpRequest httpGetRequest:[NSString stringWithFormat:@"%@/health-emergency/api/emergency/createGroupMagFirstAider",Http] params:parmDic];
}
// 急救员 - 我参与的应急工单
- (void)getMyParticipatedOrders:(NSMutableDictionary *)parmDic {
[httpRequest httpGetRequest:[NSString stringWithFormat:@"%@/health-emergency/api/emergency/order/myParticipatedOrders",Http] params:parmDic];
}
// 急救员 - 搜索员工
- (void)getSearchEmployee:(NSMutableDictionary *)parmDic {
[httpRequest httpGetRequest:[NSString stringWithFormat:@"%@/health-emergency/api/emergency/order/searchEmployee",Http] params:parmDic];
}
// 急救员 - 邀请进群
- (void)postAddGroupUser:(NSMutableDictionary *)parmDic {
[httpRequest httpPostRequest:[NSString stringWithFormat:@"%@/health-emergency/api/emergency/addGroupUser",Http] params:parmDic];
}
// 急救员 - 移除群成员
- (void)postRemoveGroupUser:(NSMutableDictionary *)parmDic {
[httpRequest httpPostRequest:[NSString stringWithFormat:@"%@/health-emergency/api/emergency/removeGroupUser",Http] params:parmDic];
}
// 急救员 - 群成员列表
- (void)getGroupMemberList:(NSMutableDictionary *)parmDic {
[httpRequest httpGetRequest:[NSString stringWithFormat:@"%@/health-emergency/api/emergency/order/getGroupMemberList",Http] params:parmDic];
}
// 急救员 - 实际群成员列表(经腾讯IM验证,含isSelf字段)
- (void)getActualGroupMemberList:(NSMutableDictionary *)parmDic {
[httpRequest httpGetRequest:[NSString stringWithFormat:@"%@/health-emergency/api/emergency/order/getActualGroupMemberList",Http] params:parmDic];
}
// 急救员 - 宣教资源详情
- (void)getFirstAidResourceDetail:(NSMutableDictionary *)parmDic {
[httpRequest httpGetRequest:[NSString stringWithFormat:@"%@/health-emergency/api/emergency/firstAid/resource/resource/queryById",Http] params:parmDic];
}
// 一人一案 - 健康档案详情
- (void)getOnePersonCase:(NSMutableDictionary *)parmDic {
[httpRequest httpGetRequest:[NSString stringWithFormat:@"%@/health-emergency/emergency/LargeScreenNew/onePersonCase",Http] params:parmDic];
}
- (void)getNearbyHomePageHospitalPoint:(NSMutableDictionary *)parmDic {
[httpRequest httpGetRequest:[NSString stringWithFormat:@"%@/medical-center/api/resource/nearbyResource",Http] params:parmDic];
@@ -828,4 +883,105 @@
[httpRequest httpPostRequest:[NSString stringWithFormat:@"%@/health-system/test/restore",Http] params:parmDic];
}
- (void)getUserMessageList:(NSMutableDictionary *)parmDic {
[httpRequest httpGetRequest:[NSString stringWithFormat:@"%@/health-system/app/sys/message/notice/list",Http] params:parmDic];
}
- (void)getUserMessageDetailConnet:(NSMutableDictionary *)parmDic {
[httpRequest httpGetRequest:[NSString stringWithFormat:@"%@/health-system/app/sys/message/notice/queryById",Http] params:parmDic];
}
//轮播图
- (void)getHomepageBannerList:(NSMutableDictionary *)parmDic {
[httpRequest httpGetRequest:[NSString stringWithFormat:@"%@/sys/api/banner/selectBannerList",Http] params:parmDic];
}
- (void)getWebNeedEncryptionKey:(NSMutableDictionary *)parmDic {
[httpRequest httpGetRequest:[NSString stringWithFormat:@"%@/health-system/api/sys/encryptInfo",Http] params:parmDic];
}
//积分银行
- (void)getPointsBankHeard:(NSMutableDictionary *)parmDic {
[httpRequest httpGetRequest:[NSString stringWithFormat:@"%@/health-system/bank/userPoints/app/myPoints",Http] params:parmDic];
}
//积分榜单
- (void)postPointsBankRankData:(NSMutableDictionary *)parmDic {
[httpRequest httpPostRequest:[NSString stringWithFormat:@"%@/health-system/bank/userPoints/app/pointRanking",Http] params:parmDic];
}
//积分明细
- (void)postPointsBankSourceDetail:(NSMutableDictionary *)parmDic {
[httpRequest httpPostRequest:[NSString stringWithFormat:@"%@/health-system/bank/userPoints/app/pointRecord",Http] params:parmDic];
}
//积分任务
- (void)postPointsCheckInCard:(NSMutableDictionary *)parmDic {
[httpRequest httpPostRequest:[NSString stringWithFormat:@"%@/health-system/bank/userPoints/app/pointCheckIn",Http] params:parmDic];
}
//积分任务
- (void)postPointsTaskList:(NSMutableDictionary *)parmDic {
[httpRequest httpPostRequest:[NSString stringWithFormat:@"%@/health-system/bank/userPoints/app/pointsTaskList",Http] params:parmDic];
}
//图片地址
- (void)getDetailImageAddress:(NSMutableDictionary *)parmDic {
[httpRequest httpGetRequest:[NSString stringWithFormat:@"%@/health-consultation/consultation/conSession/getPicUrl",Http] params:parmDic];
}
- (void)getHomeBannerAllData:(NSMutableDictionary *)parmDic {
[httpRequest httpGetRequest:[NSString stringWithFormat:@"%@/health-system/sys/api/banner/selectAllBannerList",Http] params:parmDic];
}
#pragma mark:手表相关
//是否佩戴手表
- (void)getHomeWatchWearBool:(NSMutableDictionary *)parmDic {
[httpRequest httpGetRequest:[NSString stringWithFormat:@"%@/health-watch/api/watchData/findWatchInfo",Http] params:parmDic];
}
//手表数据
- (void)getHomeWatchData:(NSMutableDictionary *)parmDic {
[httpRequest httpGetRequest:[NSString stringWithFormat:@"%@/health-watch/api/watchData/userWatchDataAll",Http] params:parmDic];
}
- (void)getBindWatchDevice:(NSMutableDictionary *)parmDic {
[httpRequest httpGetRequest:[NSString stringWithFormat:@"%@/health-watch/watch/watchDevice/app/bindDevice",Http] params:parmDic];
}
//解绑手表
- (void)getUnBindWatchDevice:(NSMutableDictionary *)parmDic {
[httpRequest httpGetRequest:[NSString stringWithFormat:@"%@/health-watch/watch/watchDevice/app/unbundleDevice",Http] params:parmDic];
}
//外部接口
- (void)postPointsUnmberData:(NSMutableDictionary *)parmDic {
[httpRequest httpOtherPostRequest:[NSString stringWithFormat:@"%@/api/v1/users/point-details",OtherHttp] params:parmDic];
}
@end
@@ -30,6 +30,8 @@
[TUILogin login:sdkAppid userID:userID userSig:userSig succ:^{
// KPostNotification(KNotificationLoginIMResult, @YES);
HQUserInfo *user = [HQCommonUtils keyedUnarchiverWithKey:LoginUserInfo];
[[TUICallKit createInstance] setSelfInfo:user.realname avatar:user.avatar succ:nil fail:nil];
} fail:^(int code, NSString *msg) {
@@ -103,7 +105,6 @@
}
+ (void)sendGroupVideo:(NSString *)groupID withUserIdList:(NSArray *)userList {
[[TUICallKit createInstance] groupCall:groupID userIdList:userList callMediaType:TUICallMediaTypeVideo];
}
@@ -21,6 +21,7 @@
[aCoder encodeObject:self.depart_id forKey:@"depart_id"];
[aCoder encodeObject:self.parentId forKey:@"parentId"];
[aCoder encodeObject:self.departName forKey:@"departName"];
[aCoder encodeObject:self.workNo forKey:@"workNo"];
[aCoder encodeObject:self.token forKey:@"token"];
[aCoder encodeObject:self.sex_dictText forKey:@"sex_dictText"];
@@ -43,6 +44,8 @@
self.parentId = [aDecoder decodeObjectForKey:@"parentId"];
self.departName = [aDecoder decodeObjectForKey:@"departName"];
self.token = [aDecoder decodeObjectForKey:@"token"];
self.workNo = [aDecoder decodeObjectForKey:@"workNo"];
self.sex_dictText = [aDecoder decodeObjectForKey:@"sex_dictText"];
}
@@ -74,6 +77,7 @@
self.idCard = safeValue(userInfo[@"idCard"]);
self.personType = safeValue(userInfo[@"personType"]);
self.sex_dictText = safeValue(userInfo[@"sex_dictText"]);
self.workNo = safeValue(userInfo[@"workNo"]);
NSDictionary *secondDepart = safeValue(userInfo[@"secondDepart"]);
if ([secondDepart isKindOfClass:[NSDictionary class]]) {
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AvailableLibraries</key>
<array>
<dict>
<key>BinaryPath</key>
<string>WechatOpenSDK.framework/WechatOpenSDK</string>
<key>LibraryIdentifier</key>
<string>ios-arm64</string>
<key>LibraryPath</key>
<string>WechatOpenSDK.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
</dict>
<dict>
<key>BinaryPath</key>
<string>WechatOpenSDK.framework/WechatOpenSDK</string>
<key>LibraryIdentifier</key>
<string>ios-arm64_x86_64-simulator</string>
<key>LibraryPath</key>
<string>WechatOpenSDK.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
<string>x86_64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
<key>SupportedPlatformVariant</key>
<string>simulator</string>
</dict>
</array>
<key>CFBundlePackageType</key>
<string>XFWK</string>
<key>XCFrameworkFormatVersion</key>
<string>1.0</string>
</dict>
</plist>
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
</array>
</dict>
</plist>
@@ -0,0 +1,167 @@
重要!
SDK2.0.5
1. 支持模块化集成​:XCFramework 头文件引用改为标准化格式 #import <WechatOpenSDK/WXApi.h>,解决路径冲突并支持 Swift/ObjC 混合开发
2. 修复openWXApp偶现失败的问题
SDK2.0.4
1.增加privacy manifest文件
2.修复跳微信时可能卡顿的问题
SDK2.0.2
1. 优化XCFramework打包方式
SDK2.0.1
1. SDK支持XCFramework
SDK2.0.0
1. 分享能力支持内容防篡改校验
SDK1.9.9
1. 授权登录支持关闭自动授权
2. 分享支持添加签名,防止篡改
SDK1.9.7
1. 适配CocoaPods
SDK1.9.6
1. 适配iOS 16,减少读写剪切板
SDK1.9.4
1. 修复授权登录取消/拒绝时state字段没有带回
SDK1.9.3
1. 新增发起二维码支付能力
SDK1.9.2
1. 新增发起企微客服会话能力
SDK1.9.1
1. 音乐视频分享类型增加运营H5字段
SDK1.8.9
1. 增加音乐视频分享类型
SDK1.8.8
1. 增加游戏直播消息类型
SDK1.8.7.1
1. 修复Xcode11以下编译不通过
SDK1.8.7
1. 修复iPadOS,未安装微信的情况下,因为UA问题无法授权登录
2. 修复未安装微信的情况下, 适配了UIScene的App因为UIAlertView Crash
3. 增加Universal Link检测函数
SDK1.8.6.2
1. 修改包含"UIWebView"字符的类名
SDK1.8.6.1
1.短信授权登录使用的UIWebview切换成WKWebview
SDK1.8.6
1. 支持Universal Link拉起微信以及返回App
2. SDK移除MTA库
SDK1.8.5
1. 更换MTA库:取消对剪切板的访问, 防止和其他SDK竞争导致crash
2. NSMutableArray的MTA分类方法改名,减少命名冲突
3. 不含支付功能版本移除非税支付和医保支付接口
4. 分享音乐支持填写歌词和高清封面图
SDK1.8.4
1. 调整分享图片大小限制
2. 新增openBusinessView接口
SDK1.8.3
1. SDK增加调起微信刷卡支付接口
2. SDK增加小程序订阅消息接口
3. 修复小程序订阅消息接口没有resp的问题
SDK1.8.2
1. SDK增加开发票授权 WXInvoiceAuthInsert
2. SDK增加非税接口 WXNontaxPay
3. SDK增加医保接口 WXPayInsurance
4. 更换MTA库
SDK1.8.1
1. SDK打开小程序支持指定版本(体验,开发,正式版)
2. SDK分享小程序支持指定版本(体验,开发,正式版)
3. SDK支持输出log日志
SDK1.8.0
1. SDK支持打开小程序
2. SDK分享小程序支持shareTicket
SDK1.7.9
1. SDK订阅一次性消息
SDK1.7.8
1 SDK分享小程序支持大图
SDK1.7.7
1 增加SDK分享小程序
2 增加选择发票接口
SDK1.7.6
1. 提高稳定性
1 修复mta崩溃
2 新增接口支持开发者关闭mta数据统计上报
SDK1.7.5
1. 提高稳定性
2. 加快registerApp接口启动速度
SDK1.7.4
1. 更新支持iOS启用 ATS(App Transport Security)
2. 需要在工程中链接CFNetwork.framework
3. 在工程配置中的”Other Linker Flags”中加入”-Objc -all_load”
SDK1.7.3
1. 增强稳定性,适配iOS10
2. 修复小于32K的jpg格式缩略图设置失败的问题
SDK1.7.2
1. 修复因CTTeleponyNetworkInfo引起的崩溃问题
SDK1.7.1
1. 支持兼容ipv6(提升稳定性)
2. xCode Version 7.3.1 (7D1014) 编译
SDK1.7
1. 支持兼容ipv6
2. 修复若干问题增强稳定性
SDK1.6.3
1. xCode7.2 构建的sdk包。
2. 请使用xCode7.2进行编译。
3. 需要在Build Phases中Link Security.framework
4. 修复若干小问题。
SDK1.6.2
1、xCode7.1 构建的sdk包
2、请使用xCode7.1进行编译
SDK1.6.1
1、修复armv7s下,bitcode可能编译不过
2、解决warning
SDK1.6
1、iOS 9系统策略更新,限制了http协议的访问,此外应用需要在“Info.plist”中将要使用的URL Schemes列为白名单,才可正常检查其他应用是否安装。
受此影响,当你的应用在iOS 9中需要使用微信SDK的相关能力(分享、收藏、支付、登录等)时,需要在“Info.plist”里增加如下代码:
<key>LSApplicationQueriesSchemes</key>
<array>
<string>weixin</string>
</array>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
2、开发者需要在工程中链接上 CoreTelephony.framework
3、解决bitcode编译不过问题
SDK1.5
1、废弃safeSendReq:接口,使用sendReq:即可。
2、新增+(BOOL) sendAuthReq:(SendAuthReq*) req viewController : (UIViewController*) viewController delegate:(id<WXApiDelegate>) delegate;
支持未安装微信情况下Auth,具体见WXApi.h接口描述
3、微信开放平台新增了微信模块用户统计功能,便于开发者统计微信功能模块的用户使用和活跃情况。开发者需要在工程中链接上:SystemConfiguration.framework,libz.dylib,libsqlite3.0.dylib。
@@ -0,0 +1,231 @@
//
// WXApi.h
// 所有Api接口
//
// Created by Wechat on 12-2-28.
// Copyright (c) 2012年 Tencent. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "WXApiObject.h"
NS_ASSUME_NONNULL_BEGIN
typedef BOOL(^WXGrantReadPasteBoardPermissionCompletion)(void);
#pragma mark - WXApiDelegate
/*! @brief 接收并处理来自微信终端程序的事件消息
*
* 接收并处理来自微信终端程序的事件消息,期间微信界面会切换到第三方应用程序。
* WXApiDelegate 会在handleOpenURL:delegate:中使用并触发。
*/
@protocol WXApiDelegate <NSObject>
@optional
/*! @brief 收到一个来自微信的请求,第三方应用程序处理完后调用sendResp向微信发送结果
*
* 收到一个来自微信的请求,异步处理完成后必须调用sendResp发送处理结果给微信。
* 可能收到的请求有GetMessageFromWXReq、ShowMessageFromWXReq等。
* @param req 具体请求内容,是自动释放的
*/
- (void)onReq:(BaseReq*)req;
/*! @brief 发送一个sendReq后,收到微信的回应
*
* 收到一个来自微信的处理结果。调用一次sendReq后会收到onResp。
* 可能收到的处理结果有SendMessageToWXResp、SendAuthResp等。
* @param resp具体的回应内容,是自动释放的
*/
- (void)onResp:(BaseResp*)resp;
/* ! @brief 用于在iOS16以及以上系统上,控制OpenSDK是否读取剪切板中微信传递的数据以及读取的时机
* 在iOS16以及以上系统,在SDK需要读取剪切板中微信写入的数据时,会回调该方法。没有实现默认会直接读取微信通过剪切板传递过来的数据
* 注意:
* 1. 只在iOS16以及以上的系统版本上回调;
* 2. 不实现时,OpenSDK会直接调用读取剪切板接口,读取微信传递过来的数据;
* 3. 若实现该方法:开发者需要通过调用completion(), 支持异步,通知SDK允许读取剪切板中微信传递的数据,
* 不调用completion()则代表不授权OpenSDK读取剪切板,会导致收不到onReq:, onResp:回调,无法后续业务流程。请谨慎使用
* 4. 不要长时间持有completion不释放,可能会导致内存泄漏。
*/
- (void)onNeedGrantReadPasteBoardPermissionWithURL:(nonnull NSURL *)openURL completion:(nonnull WXGrantReadPasteBoardPermissionCompletion)completion;
@end
#pragma mark - WXApiLogDelegate
@protocol WXApiLogDelegate <NSObject>
- (void)onLog:(NSString*)log logLevel:(WXLogLevel)level;
@end
#pragma mark - WXApi
/*! @brief 微信Api接口函数类
*
* 该类封装了微信终端SDK的所有接口
*/
@interface WXApi : NSObject
/*! @brief WXApi的成员函数,向微信终端程序注册第三方应用。
*
* 需要在每次启动第三方应用程序时调用。
* @attention 请保证在主线程中调用此函数
* @param appid 微信开发者ID
* @param universalLink 微信开发者Universal Link
* @return 成功返回YES,失败返回NO。
*/
+ (BOOL)registerApp:(NSString *)appid universalLink:(NSString *)universalLink;
/*! @brief 处理旧版微信通过URL启动App时传递的数据
*
* 需要在 application:openURL:sourceApplication:annotation:或者application:handleOpenURL中调用。
* @param url 微信启动第三方应用时传递过来的URL
* @param delegate WXApiDelegate对象,用来接收微信触发的消息。
* @return 成功返回YES,失败返回NO。
*/
+ (BOOL)handleOpenURL:(NSURL *)url delegate:(nullable id<WXApiDelegate>)delegate;
/*! @brief 处理微信通过Universal Link启动App时传递的数据
*
* 需要在 application:continueUserActivity:restorationHandler:中调用。
* @param userActivity 微信启动第三方应用时系统API传递过来的userActivity
* @param delegate WXApiDelegate对象,用来接收微信触发的消息。
* @return 成功返回YES,失败返回NO。
*/
+ (BOOL)handleOpenUniversalLink:(NSUserActivity *)userActivity delegate:(nullable id<WXApiDelegate>)delegate;
/*! @brief 检查微信是否已被用户安装
*
* @return 微信已安装返回YES,未安装返回NO。
*/
+ (BOOL)isWXAppInstalled;
/*! @brief 判断当前微信的版本是否支持OpenApi
*
* @return 支持返回YES,不支持返回NO。
*/
+ (BOOL)isWXAppSupportApi;
/*! @brief 判断当前微信的版本是否支持分享微信状态功能
*
* @attention 需在工程LSApplicationQueriesSchemes配置中添加weixinStateAPI
* @return 支持返回YES,不支持返回NO。
*/
+ (BOOL)isWXAppSupportStateAPI;
#ifndef BUILD_WITHOUT_PAY
/*! @brief 判断当前微信的版本是否支持二维码拉起微信支付
*
* @attention 需在工程LSApplicationQueriesSchemes配置中添加weixinQRCodePayAPI
* @return 支持返回YES,不支持返回NO。
*/
+ (BOOL)isWXAppSupportQRCodePayAPI;
#endif
/*! @brief 获取微信的itunes安装地址
*
* @return 微信的安装地址字符串。
*/
+ (NSString *)getWXAppInstallUrl;
/*! @brief 获取当前微信SDK的版本号
*
* @return 返回当前微信SDK的版本号
*/
+ (NSString *)getApiVersion;
/*! @brief 打开微信
*
* @return 成功返回YES,失败返回NO。
*/
+ (BOOL)openWXApp;
/*! @brief 发送请求到微信,等待微信返回onResp
*
* 函数调用后,会切换到微信的界面。第三方应用程序等待微信返回onResp。微信在异步处理完成后一定会调用onResp。支持以下类型
* SendAuthReq、SendMessageToWXReq、PayReq等。
* @param req 具体的发送请求。
* @param completion 调用结果回调block
*/
+ (void)sendReq:(BaseReq *)req completion:(void (^ __nullable)(BOOL success))completion;
/*! @brief 收到微信onReq的请求,发送对应的应答给微信,并切换到微信界面
*
* 函数调用后,会切换到微信的界面。第三方应用程序收到微信onReq的请求,异步处理该请求,完成后必须调用该函数。可能发送的相应有
* GetMessageFromWXResp、ShowMessageFromWXResp等。
* @param resp 具体的应答内容
* @param completion 调用结果回调block
*/
+ (void)sendResp:(BaseResp*)resp completion:(void (^ __nullable)(BOOL success))completion;
/*! @brief 发送Auth请求到微信,支持用户没安装微信,等待微信返回onResp
*
* 函数调用后,会切换到微信的界面。第三方应用程序等待微信返回onResp。微信在异步处理完成后一定会调用onResp。支持SendAuthReq类型。
* @param req 具体的发送请求。
* @param viewController 当前界面对象。
* @param delegate WXApiDelegate对象,用来接收微信触发的消息。
* @param completion 调用结果回调block
*/
+ (void)sendAuthReq:(SendAuthReq *)req viewController:(UIViewController*)viewController delegate:(nullable id<WXApiDelegate>)delegate completion:(void (^ __nullable)(BOOL success))completion;
/*! @brief 测试函数,用于排查当前App通过Universal Link方式分享到微信的流程
注意1: 调用自检函数之前必须要先调用registerApp:universalLink接口, 并确认调用成功
注意2: 自检过程中会有Log产生,可以先调用startLogByLevel函数,根据Log排查问题
注意3: 会多次回调block
注意4: 仅用于新接入SDK时调试使用,请勿在正式环境的调用
*
* 当completion回调的step为WXULCheckStepFinal时,表示检测通过,Universal Link接入成功
* @param completion 回调Block
*/
+ (void)checkUniversalLinkReady:(nonnull WXCheckULCompletion)completion;
/*! @brief WXApi的成员函数,接受微信的log信息。byBlock
注意1:SDK会强引用这个block,注意不要导致内存泄漏,注意不要导致内存泄漏
注意2:调用过一次startLog by block之后,如果再调用一次任意方式的startLoad,会释放上一次logBlock,不再回调上一个logBlock
*
* @param level 打印log的级别
* @param logBlock 打印log的回调block
*/
+ (void)startLogByLevel:(WXLogLevel)level logBlock:(WXLogBolock)logBlock;
/*! @brief WXApi的成员函数,接受微信的log信息。byDelegate
注意1:sdk会弱引用这个delegate,这里可加任意对象为代理,不需要与WXApiDelegate同一个对象
注意2:调用过一次startLog by delegate之后,再调用一次任意方式的startLoad,不会再回调上一个logDelegate对象
* @param level 打印log的级别
* @param logDelegate 打印log的回调代理,
*/
+ (void)startLogByLevel:(WXLogLevel)level logDelegate:(id<WXApiLogDelegate>)logDelegate;
/*! @brief 停止打印log,会清理block或者delegate为空,释放block
* @param
*/
+ (void)stopLog;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,68 @@
//
// WechatAuthSDK.h
// WechatAuthSDK
//
// Created by 李凯 on 13-11-29.
// Copyright (c) 2013年 Tencent. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
enum AuthErrCode {
WechatAuth_Err_Ok = 0, //Auth成功
WechatAuth_Err_NormalErr = -1, //普通错误
WechatAuth_Err_NetworkErr = -2, //网络错误
WechatAuth_Err_GetQrcodeFailed = -3, //获取二维码失败
WechatAuth_Err_Cancel = -4, //用户取消授权
WechatAuth_Err_Timeout = -5, //超时
};
@protocol WechatAuthAPIDelegate<NSObject>
@optional
- (void)onAuthGotQrcode:(UIImage *)image; //得到二维码
- (void)onQrcodeScanned; //二维码被扫描
- (void)onAuthFinish:(int)errCode AuthCode:(nullable NSString *)authCode; //成功登录
@end
@interface WechatAuthSDK : NSObject{
NSString *_sdkVersion;
__weak id<WechatAuthAPIDelegate> _delegate;
}
@property(nonatomic, weak, nullable) id<WechatAuthAPIDelegate> delegate;
@property(nonatomic, readonly) NSString *sdkVersion; //authSDK版本号
/*! @brief 发送登录请求,等待WechatAuthAPIDelegate回调
*
* @param appId 微信开发者ID
* @param nonceStr 一个随机的尽量不重复的字符串,用来使得每次的signature不同
* @param timeStamp 时间戳
* @param scope 应用授权作用域,拥有多个作用域用逗号(,)分隔
* @param signature 签名
* @param schemeData 会在扫码后拼在scheme后
* @return 成功返回YES,失败返回NO
注:该实现只保证同时只有一个Auth在运行,Auth未完成或未Stop再次调用Auth接口时会返回NO。
*/
- (BOOL)Auth:(NSString *)appId
nonceStr:(NSString *)nonceStr
timeStamp:(NSString *)timeStamp
scope:(NSString *)scope
signature:(NSString *)signature
schemeData:(nullable NSString *)schemeData;
/*! @brief 暂停登录请求
*
* @return 成功返回YES,失败返回NO。
*/
- (BOOL)StopAuth;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,9 @@
// WechatOpenSDK.h
//
// Created by Wechat.
// Copyright (c) 2012年 Tencent. All rights reserved.
//
#import <WechatOpenSDK/WXApi.h>
#import <WechatOpenSDK/WXApiObject.h>
#import <WechatOpenSDK/WechatAuthSDK.h>
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
<key>CFBundleIdentifier</key>
<string>com.tencent.WechatOpenSDK</string>
<key>CFBundleVersion</key>
<string>1.0.0</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>12.0</string>
<key>CFBundleExecutable</key>
<string>WechatOpenSDK</string>
</dict>
</plist>
@@ -0,0 +1,5 @@
framework module WechatOpenSDK {
umbrella header "WechatOpenSDK.h"
export *
link "WechatOpenSDK"
}
@@ -0,0 +1,231 @@
//
// WXApi.h
// 所有Api接口
//
// Created by Wechat on 12-2-28.
// Copyright (c) 2012年 Tencent. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "WXApiObject.h"
NS_ASSUME_NONNULL_BEGIN
typedef BOOL(^WXGrantReadPasteBoardPermissionCompletion)(void);
#pragma mark - WXApiDelegate
/*! @brief 接收并处理来自微信终端程序的事件消息
*
* 接收并处理来自微信终端程序的事件消息,期间微信界面会切换到第三方应用程序。
* WXApiDelegate 会在handleOpenURL:delegate:中使用并触发。
*/
@protocol WXApiDelegate <NSObject>
@optional
/*! @brief 收到一个来自微信的请求,第三方应用程序处理完后调用sendResp向微信发送结果
*
* 收到一个来自微信的请求,异步处理完成后必须调用sendResp发送处理结果给微信。
* 可能收到的请求有GetMessageFromWXReq、ShowMessageFromWXReq等。
* @param req 具体请求内容,是自动释放的
*/
- (void)onReq:(BaseReq*)req;
/*! @brief 发送一个sendReq后,收到微信的回应
*
* 收到一个来自微信的处理结果。调用一次sendReq后会收到onResp。
* 可能收到的处理结果有SendMessageToWXResp、SendAuthResp等。
* @param resp具体的回应内容,是自动释放的
*/
- (void)onResp:(BaseResp*)resp;
/* ! @brief 用于在iOS16以及以上系统上,控制OpenSDK是否读取剪切板中微信传递的数据以及读取的时机
* 在iOS16以及以上系统,在SDK需要读取剪切板中微信写入的数据时,会回调该方法。没有实现默认会直接读取微信通过剪切板传递过来的数据
* 注意:
* 1. 只在iOS16以及以上的系统版本上回调;
* 2. 不实现时,OpenSDK会直接调用读取剪切板接口,读取微信传递过来的数据;
* 3. 若实现该方法:开发者需要通过调用completion(), 支持异步,通知SDK允许读取剪切板中微信传递的数据,
* 不调用completion()则代表不授权OpenSDK读取剪切板,会导致收不到onReq:, onResp:回调,无法后续业务流程。请谨慎使用
* 4. 不要长时间持有completion不释放,可能会导致内存泄漏。
*/
- (void)onNeedGrantReadPasteBoardPermissionWithURL:(nonnull NSURL *)openURL completion:(nonnull WXGrantReadPasteBoardPermissionCompletion)completion;
@end
#pragma mark - WXApiLogDelegate
@protocol WXApiLogDelegate <NSObject>
- (void)onLog:(NSString*)log logLevel:(WXLogLevel)level;
@end
#pragma mark - WXApi
/*! @brief 微信Api接口函数类
*
* 该类封装了微信终端SDK的所有接口
*/
@interface WXApi : NSObject
/*! @brief WXApi的成员函数,向微信终端程序注册第三方应用。
*
* 需要在每次启动第三方应用程序时调用。
* @attention 请保证在主线程中调用此函数
* @param appid 微信开发者ID
* @param universalLink 微信开发者Universal Link
* @return 成功返回YES,失败返回NO。
*/
+ (BOOL)registerApp:(NSString *)appid universalLink:(NSString *)universalLink;
/*! @brief 处理旧版微信通过URL启动App时传递的数据
*
* 需要在 application:openURL:sourceApplication:annotation:或者application:handleOpenURL中调用。
* @param url 微信启动第三方应用时传递过来的URL
* @param delegate WXApiDelegate对象,用来接收微信触发的消息。
* @return 成功返回YES,失败返回NO。
*/
+ (BOOL)handleOpenURL:(NSURL *)url delegate:(nullable id<WXApiDelegate>)delegate;
/*! @brief 处理微信通过Universal Link启动App时传递的数据
*
* 需要在 application:continueUserActivity:restorationHandler:中调用。
* @param userActivity 微信启动第三方应用时系统API传递过来的userActivity
* @param delegate WXApiDelegate对象,用来接收微信触发的消息。
* @return 成功返回YES,失败返回NO。
*/
+ (BOOL)handleOpenUniversalLink:(NSUserActivity *)userActivity delegate:(nullable id<WXApiDelegate>)delegate;
/*! @brief 检查微信是否已被用户安装
*
* @return 微信已安装返回YES,未安装返回NO。
*/
+ (BOOL)isWXAppInstalled;
/*! @brief 判断当前微信的版本是否支持OpenApi
*
* @return 支持返回YES,不支持返回NO。
*/
+ (BOOL)isWXAppSupportApi;
/*! @brief 判断当前微信的版本是否支持分享微信状态功能
*
* @attention 需在工程LSApplicationQueriesSchemes配置中添加weixinStateAPI
* @return 支持返回YES,不支持返回NO。
*/
+ (BOOL)isWXAppSupportStateAPI;
#ifndef BUILD_WITHOUT_PAY
/*! @brief 判断当前微信的版本是否支持二维码拉起微信支付
*
* @attention 需在工程LSApplicationQueriesSchemes配置中添加weixinQRCodePayAPI
* @return 支持返回YES,不支持返回NO。
*/
+ (BOOL)isWXAppSupportQRCodePayAPI;
#endif
/*! @brief 获取微信的itunes安装地址
*
* @return 微信的安装地址字符串。
*/
+ (NSString *)getWXAppInstallUrl;
/*! @brief 获取当前微信SDK的版本号
*
* @return 返回当前微信SDK的版本号
*/
+ (NSString *)getApiVersion;
/*! @brief 打开微信
*
* @return 成功返回YES,失败返回NO。
*/
+ (BOOL)openWXApp;
/*! @brief 发送请求到微信,等待微信返回onResp
*
* 函数调用后,会切换到微信的界面。第三方应用程序等待微信返回onResp。微信在异步处理完成后一定会调用onResp。支持以下类型
* SendAuthReq、SendMessageToWXReq、PayReq等。
* @param req 具体的发送请求。
* @param completion 调用结果回调block
*/
+ (void)sendReq:(BaseReq *)req completion:(void (^ __nullable)(BOOL success))completion;
/*! @brief 收到微信onReq的请求,发送对应的应答给微信,并切换到微信界面
*
* 函数调用后,会切换到微信的界面。第三方应用程序收到微信onReq的请求,异步处理该请求,完成后必须调用该函数。可能发送的相应有
* GetMessageFromWXResp、ShowMessageFromWXResp等。
* @param resp 具体的应答内容
* @param completion 调用结果回调block
*/
+ (void)sendResp:(BaseResp*)resp completion:(void (^ __nullable)(BOOL success))completion;
/*! @brief 发送Auth请求到微信,支持用户没安装微信,等待微信返回onResp
*
* 函数调用后,会切换到微信的界面。第三方应用程序等待微信返回onResp。微信在异步处理完成后一定会调用onResp。支持SendAuthReq类型。
* @param req 具体的发送请求。
* @param viewController 当前界面对象。
* @param delegate WXApiDelegate对象,用来接收微信触发的消息。
* @param completion 调用结果回调block
*/
+ (void)sendAuthReq:(SendAuthReq *)req viewController:(UIViewController*)viewController delegate:(nullable id<WXApiDelegate>)delegate completion:(void (^ __nullable)(BOOL success))completion;
/*! @brief 测试函数,用于排查当前App通过Universal Link方式分享到微信的流程
注意1: 调用自检函数之前必须要先调用registerApp:universalLink接口, 并确认调用成功
注意2: 自检过程中会有Log产生,可以先调用startLogByLevel函数,根据Log排查问题
注意3: 会多次回调block
注意4: 仅用于新接入SDK时调试使用,请勿在正式环境的调用
*
* 当completion回调的step为WXULCheckStepFinal时,表示检测通过,Universal Link接入成功
* @param completion 回调Block
*/
+ (void)checkUniversalLinkReady:(nonnull WXCheckULCompletion)completion;
/*! @brief WXApi的成员函数,接受微信的log信息。byBlock
注意1:SDK会强引用这个block,注意不要导致内存泄漏,注意不要导致内存泄漏
注意2:调用过一次startLog by block之后,如果再调用一次任意方式的startLoad,会释放上一次logBlock,不再回调上一个logBlock
*
* @param level 打印log的级别
* @param logBlock 打印log的回调block
*/
+ (void)startLogByLevel:(WXLogLevel)level logBlock:(WXLogBolock)logBlock;
/*! @brief WXApi的成员函数,接受微信的log信息。byDelegate
注意1:sdk会弱引用这个delegate,这里可加任意对象为代理,不需要与WXApiDelegate同一个对象
注意2:调用过一次startLog by delegate之后,再调用一次任意方式的startLoad,不会再回调上一个logDelegate对象
* @param level 打印log的级别
* @param logDelegate 打印log的回调代理,
*/
+ (void)startLogByLevel:(WXLogLevel)level logDelegate:(id<WXApiLogDelegate>)logDelegate;
/*! @brief 停止打印log,会清理block或者delegate为空,释放block
* @param
*/
+ (void)stopLog;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,68 @@
//
// WechatAuthSDK.h
// WechatAuthSDK
//
// Created by 李凯 on 13-11-29.
// Copyright (c) 2013年 Tencent. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
enum AuthErrCode {
WechatAuth_Err_Ok = 0, //Auth成功
WechatAuth_Err_NormalErr = -1, //普通错误
WechatAuth_Err_NetworkErr = -2, //网络错误
WechatAuth_Err_GetQrcodeFailed = -3, //获取二维码失败
WechatAuth_Err_Cancel = -4, //用户取消授权
WechatAuth_Err_Timeout = -5, //超时
};
@protocol WechatAuthAPIDelegate<NSObject>
@optional
- (void)onAuthGotQrcode:(UIImage *)image; //得到二维码
- (void)onQrcodeScanned; //二维码被扫描
- (void)onAuthFinish:(int)errCode AuthCode:(nullable NSString *)authCode; //成功登录
@end
@interface WechatAuthSDK : NSObject{
NSString *_sdkVersion;
__weak id<WechatAuthAPIDelegate> _delegate;
}
@property(nonatomic, weak, nullable) id<WechatAuthAPIDelegate> delegate;
@property(nonatomic, readonly) NSString *sdkVersion; //authSDK版本号
/*! @brief 发送登录请求,等待WechatAuthAPIDelegate回调
*
* @param appId 微信开发者ID
* @param nonceStr 一个随机的尽量不重复的字符串,用来使得每次的signature不同
* @param timeStamp 时间戳
* @param scope 应用授权作用域,拥有多个作用域用逗号(,)分隔
* @param signature 签名
* @param schemeData 会在扫码后拼在scheme后
* @return 成功返回YES,失败返回NO
注:该实现只保证同时只有一个Auth在运行,Auth未完成或未Stop再次调用Auth接口时会返回NO。
*/
- (BOOL)Auth:(NSString *)appId
nonceStr:(NSString *)nonceStr
timeStamp:(NSString *)timeStamp
scope:(NSString *)scope
signature:(NSString *)signature
schemeData:(nullable NSString *)schemeData;
/*! @brief 暂停登录请求
*
* @return 成功返回YES,失败返回NO。
*/
- (BOOL)StopAuth;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,9 @@
// WechatOpenSDK.h
//
// Created by Wechat.
// Copyright (c) 2012年 Tencent. All rights reserved.
//
#import <WechatOpenSDK/WXApi.h>
#import <WechatOpenSDK/WXApiObject.h>
#import <WechatOpenSDK/WechatAuthSDK.h>
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
<key>CFBundleIdentifier</key>
<string>com.tencent.WechatOpenSDK</string>
<key>CFBundleVersion</key>
<string>1.0.0</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>12.0</string>
<key>CFBundleExecutable</key>
<string>WechatOpenSDK</string>
</dict>
</plist>
@@ -0,0 +1,5 @@
framework module WechatOpenSDK {
umbrella header "WechatOpenSDK.h"
export *
link "WechatOpenSDK"
}
@@ -0,0 +1,81 @@
//
// XJWeChatManager.h
// XinjiangProject
//
// 微信分享管理类(手动集成 WechatOpenSDK-NoPay
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/// 微信分享结果回调
typedef void(^XJWeChatShareCompletion)(BOOL success, NSString *_Nullable errorMsg);
@interface XJWeChatManager : NSObject
+ (instancetype)sharedManager;
#pragma mark - 注册
/// 注册微信 SDK(需在 AppDelegate didFinishLaunching 中调用)
/// @param appID 微信开放平台 AppID
/// @param universalLink 通用链接
- (BOOL)registerWithAppID:(NSString *)appID universalLink:(NSString *)universalLink;
/// 处理微信 URL 回调(AppDelegate handleOpenURL 中调用)
- (BOOL)handleOpenURL:(NSURL *)url;
/// 处理微信 Universal Link 回调(AppDelegate continueUserActivity 中调用)
- (BOOL)handleOpenUniversalLink:(NSUserActivity *)userActivity;
#pragma mark - 分享
/// 分享文字
- (void)shareText:(NSString *)text
toScene:(int)scene
completion:(XJWeChatShareCompletion _Nullable)completion;
/// 分享图片(NSData
- (void)shareImageData:(NSData *)imageData
toScene:(int)scene
completion:(XJWeChatShareCompletion _Nullable)completion;
/// 分享网页
/// @param title 标题
/// @param desc 描述
/// @param webpageUrl 网页链接
/// @param thumbImage 缩略图(不超过 32KB
/// @param scene 分享目标:WXSceneSession=好友, WXSceneTimeline=朋友圈
- (void)shareWebpageWithTitle:(NSString *)title
desc:(NSString *)desc
webpageUrl:(NSString *)webpageUrl
thumbImage:(UIImage *_Nullable)thumbImage
toScene:(int)scene
completion:(XJWeChatShareCompletion _Nullable)completion;
/// 分享小程序卡片
/// @param title 标题
/// @param desc 描述
/// @param webpageUrl 兼容低版本网页链接
/// @param userName 小程序原始IDgh_ 开头)
/// @param path 小程序页面路径
/// @param miniProgramType 小程序类型:0=正式版, 1=开发版, 2=体验版
- (void)shareMiniProgramWithTitle:(NSString *)title
desc:(NSString *)desc
webpageUrl:(NSString *)webpageUrl
userName:(NSString *)userName
path:(NSString *)path
miniProgramType:(NSUInteger)miniProgramType
thumbImage:(UIImage *_Nullable)thumbImage
completion:(XJWeChatShareCompletion _Nullable)completion;
#pragma mark - 工具
/// 是否安装微信
- (BOOL)isWeChatInstalled;
/// 微信是否支持 OpenAPI
- (BOOL)isWeChatSupportApi;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,160 @@
//
// XJWeChatManager.m
// XinjiangProject
//
// 微信分享管理类
//
#import "XJWeChatManager.h"
#import <WechatOpenSDK/WXApi.h>
@interface XJWeChatManager () <WXApiDelegate>
@property (nonatomic, copy) XJWeChatShareCompletion shareCompletion;
@end
@implementation XJWeChatManager
#pragma mark - ————— 单例 —————
+ (instancetype)sharedManager {
static XJWeChatManager *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[XJWeChatManager alloc] init];
});
return instance;
}
#pragma mark - ————— 注册 —————
- (BOOL)registerWithAppID:(NSString *)appID universalLink:(NSString *)universalLink {
return [WXApi registerApp:appID universalLink:universalLink];
}
- (BOOL)handleOpenURL:(NSURL *)url {
return [WXApi handleOpenURL:url delegate:self];
}
- (BOOL)handleOpenUniversalLink:(NSUserActivity *)userActivity {
return [WXApi handleOpenUniversalLink:userActivity delegate:self];
}
#pragma mark - ————— 分享 —————
- (void)shareText:(NSString *)text
toScene:(int)scene
completion:(XJWeChatShareCompletion)completion {
self.shareCompletion = completion;
SendMessageToWXReq *req = [[SendMessageToWXReq alloc] init];
req.bText = YES;
req.text = text;
req.scene = scene;
[WXApi sendReq:req completion:nil];
}
- (void)shareImageData:(NSData *)imageData
toScene:(int)scene
completion:(XJWeChatShareCompletion)completion {
self.shareCompletion = completion;
WXMediaMessage *message = [WXMediaMessage message];
message.thumbData = nil;
WXImageObject *imageObject = [WXImageObject object];
imageObject.imageData = imageData;
message.mediaObject = imageObject;
SendMessageToWXReq *req = [[SendMessageToWXReq alloc] init];
req.bText = NO;
req.message = message;
req.scene = scene;
[WXApi sendReq:req completion:nil];
}
- (void)shareWebpageWithTitle:(NSString *)title
desc:(NSString *)desc
webpageUrl:(NSString *)webpageUrl
thumbImage:(UIImage *)thumbImage
toScene:(int)scene
completion:(XJWeChatShareCompletion)completion {
self.shareCompletion = completion;
WXMediaMessage *message = [WXMediaMessage message];
message.title = title;
message.description = desc;
if (thumbImage) {
message.thumbData = UIImageJPEGRepresentation(thumbImage, 0.5);
}
WXWebpageObject *webpageObject = [WXWebpageObject object];
webpageObject.webpageUrl = webpageUrl;
message.mediaObject = webpageObject;
SendMessageToWXReq *req = [[SendMessageToWXReq alloc] init];
req.bText = NO;
req.message = message;
req.scene = scene;
[WXApi sendReq:req completion:nil];
}
- (void)shareMiniProgramWithTitle:(NSString *)title
desc:(NSString *)desc
webpageUrl:(NSString *)webpageUrl
userName:(NSString *)userName
path:(NSString *)path
miniProgramType:(NSUInteger)miniProgramType
thumbImage:(UIImage *)thumbImage
completion:(XJWeChatShareCompletion)completion {
self.shareCompletion = completion;
WXMediaMessage *message = [WXMediaMessage message];
message.title = title;
message.description = desc;
if (thumbImage) {
message.thumbData = UIImageJPEGRepresentation(thumbImage, 0.5);
}
WXMiniProgramObject *miniProgramObject = [WXMiniProgramObject object];
miniProgramObject.webpageUrl = webpageUrl;
miniProgramObject.userName = userName;
miniProgramObject.path = path;
miniProgramObject.miniProgramType = miniProgramType;
message.mediaObject = miniProgramObject;
SendMessageToWXReq *req = [[SendMessageToWXReq alloc] init];
req.bText = NO;
req.message = message;
req.scene = WXSceneSession;
[WXApi sendReq:req completion:nil];
}
#pragma mark - ————— 工具 —————
- (BOOL)isWeChatInstalled {
return [WXApi isWXAppInstalled];
}
- (BOOL)isWeChatSupportApi {
return [WXApi isWXAppSupportApi];
}
#pragma mark - ————— WXApiDelegate —————
- (void)onResp:(BaseResp *)resp {
if ([resp isKindOfClass:[SendMessageToWXResp class]]) {
if (self.shareCompletion) {
if (resp.errCode == WXSuccess) {
self.shareCompletion(YES, nil);
} else if (resp.errCode == WXErrCodeUserCancel) {
self.shareCompletion(NO, @"用户取消分享");
} else {
self.shareCompletion(NO, resp.errStr ?: @"分享失败");
}
self.shareCompletion = nil;
}
}
}
@end
@@ -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
@@ -34,27 +34,28 @@
object:nil];
//切换SOStabbar
//切换咨询tabbar
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(tabBarSoSChange:)
name:KNotificSelectSoSTabbar
selector:@selector(tabBarQuestionChange:)
name:KNotificSelectQuestionTabbar
object:nil];
//切换应急tabbar
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(tabBarSOSChange:)
name:KNotificSelectSOSCenterTabbar
object:nil];
//切换体检tabbar
//切换监测tabbar
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(tabBarPhyChange:)
name:KNotificSelectPhysicalTabbar
selector:@selector(tabBarMonitoringChange:)
name:KNotificSelectWatchTabbar
object:nil];
//切换档案tabbar
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(tabBarFileChange:)
name:KNotificSelectFileTabbar
object:nil];
if ([HQCommonUtils isLogin]) {
//腾讯im
#if DEBUG
// [self loginImAccount];
[self loginImAccount];
#else
[self loginImAccount];
@@ -159,6 +160,8 @@
[[NSUserDefaults standardUserDefaults] synchronize];
//更新用户授权高德SDK隐私协议状态. since 8.1.0
[MAMapView updatePrivacyAgree:AMapPrivacyAgreeStatusDidAgree];
KPostNotification(KNotificAgreeProtoocl, nil);
}];
@@ -167,6 +170,8 @@
[[NSUserDefaults standardUserDefaults] synchronize];
//更新用户授权高德SDK隐私协议状态. since 8.1.0
[MAMapView updatePrivacyAgree:AMapPrivacyAgreeStatusNotAgree];
KPostNotification(KNotificAgreeProtoocl, nil);
}];
[alert addAction:conform];
@@ -244,27 +249,24 @@
#pragma mark: 切换tabbar
- (void)tabBarSoSChange:(NSNotification *)notification {
- (void)tabBarQuestionChange:(NSNotification *)notification {
self.mainTabBar.selectedIndex = 1;
}
- (void)tabBarSOSChange:(NSNotification *)notification {
self.mainTabBar.selectedIndex = 2;
}
- (void)tabBarPhyChange:(NSNotification *)notification {
- (void)tabBarMonitoringChange:(NSNotification *)notification {
self.mainTabBar.selectedIndex = 3;
}
- (void)tabBarFileChange:(NSNotification *)notification {
self.mainTabBar.selectedIndex = 4;
}
+ (AppDelegate *)shareAppDelegate{
return (AppDelegate *)[[UIApplication sharedApplication] delegate];
@@ -7,6 +7,7 @@
#import "AppDelegate.h"
#import "SJRotationManager.h"
#import "XJWeChatManager.h"
#import <SDWebImageWebPCoder/SDWebImageWebPCoder.h>
@implementation AppDelegate
@@ -34,13 +35,9 @@
SDImageWebPCoder*webPCoder = [SDImageWebPCoder sharedCoder];
[[SDImageCodersManager sharedManager] addCoder:webPCoder];
#if DEBUG
#else
// [CheckProjectTool testAll];
#endif
// 注册微信 SDK
[[XJWeChatManager sharedManager] registerWithAppID:kAppKey_Wechat
universalLink:kUniversalLink_Wechat];
return YES;
}
@@ -62,6 +59,17 @@
}
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
if (window == self.window) {
// 遍历找到最顶层的 presented VC
UIViewController *vc = self.window.rootViewController;
while (vc.presentedViewController) { vc = vc.presentedViewController; }
// WKWebView 视频全屏(WKFullScreenViewController)或 AVPlayer 全屏时放行横屏
NSString *className = NSStringFromClass(vc.class);
if ([className containsString:@"FullScreen"] || [className containsString:@"AVPlayer"]) {
return UIInterfaceOrientationMaskAllButUpsideDown;
}
return UIInterfaceOrientationMaskPortrait;
}
return [SJRotationManager supportedInterfaceOrientationsForWindow:window];
}
@@ -80,10 +88,15 @@
}
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
return [[XJWeChatManager sharedManager] handleOpenURL:url];
}
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void(^)(NSArray<id<UIUserActivityRestoring>> * __nullable restorableObjects))restorationHandler {
if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
return [[XJWeChatManager sharedManager] handleOpenUniversalLink:userActivity];
}
return YES;
}
@@ -0,0 +1,33 @@
//
// PointsBankHModel.h
// XinjiangProject
//
// Created by Apple on 2026/2/6.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface PointsBankHModel : NSObject
@property (nonatomic, assign) double points;
@property (nonatomic, assign) double addPointsSum;
@property (nonatomic, assign) double exchangePointsSum;
//RANK
@property (nonatomic, assign) double frequency;
@property (nonatomic, assign) double ruleCategory;
@property (nonatomic, copy) NSString * ruleDesc;
@property (nonatomic, copy) NSString * icon;
@property (nonatomic, assign) double status;
@property (nonatomic, assign) double delFlag;
@property (nonatomic, copy) NSString * createUser;
@property (nonatomic, copy) NSString * createTime;
@property (nonatomic, copy) NSString * lastModifyDate;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,12 @@
//
// PointsBankHModel.m
// XinjiangProject
//
// Created by Apple on 2026/2/6.
//
#import "PointsBankHModel.h"
@implementation PointsBankHModel
@end
@@ -0,0 +1,16 @@
//
// PointsBankDetailViewController.h
// XinjiangProject
//
// Created by Apple on 2026/2/6.
//
#import "XJBaseViewController.h"
NS_ASSUME_NONNULL_BEGIN
@interface PointsBankDetailViewController : XJBaseViewController
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,263 @@
//
// PointsBankDetailViewController.m
// XinjiangProject
//
// Created by Apple on 2026/2/6.
//
#import "PointsBankDetailViewController.h"
#import "PointsBankHModel.h"
#import "PointsBankHomeTopView.h"
@interface PointsBankDetailViewController ()<UITableViewDelegate,UITableViewDataSource>
@property (nonatomic, strong) UITableView * homeTable;
@property (nonatomic, assign) NSInteger pageNo;
@property (nonatomic, strong) PointsBankHomeTopView * topView;
@property (nonatomic, strong) NSMutableArray * dataArray;
@end
@implementation PointsBankDetailViewController
- (NSMutableArray *)dataArray {
if (!_dataArray) {
_dataArray = [[NSMutableArray alloc] init];
}
return _dataArray;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = KWhiteColor;
self.pageNo = 1;
[self createData];
[self createUI];
// Do any additional setup after loading the view.
}
- (void)createUI {
self.topView= [[PointsBankHomeTopView alloc] init];
[self.view addSubview:self.topView];
[self.topView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.top.right.mas_offset(0);
}];
self.homeTable = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
if ([[UIDevice currentDevice].systemVersion floatValue] >= 11) {
self.homeTable.estimatedRowHeight = 60;
if (@available(iOS 15.0, *)) {
self.homeTable.sectionHeaderTopPadding = 0;
} else {
// Fallback on earlier versions
}
self.homeTable.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
self.homeTable.delegate = self;
self.homeTable.dataSource = self;
self.homeTable.separatorStyle = UITableViewCellSeparatorStyleSingleLine;
self.homeTable.showsVerticalScrollIndicator = NO;
self.homeTable.backgroundColor = KClearColor;
self.homeTable.rowHeight = UITableViewAutomaticDimension;
self.homeTable.estimatedRowHeight = 80;
[self.view addSubview:self.homeTable];
[self.homeTable mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(_topView.mas_bottom);
make.left.bottom.right.mas_offset(0);
}];
__weak __typeof(self) weakSelf = self;
self.homeTable.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
weakSelf.pageNo = 1;
[weakSelf.dataArray removeAllObjects];
[weakSelf createData];
}];
self.homeTable.mj_footer = [MJRefreshBackNormalFooter footerWithRefreshingBlock:^{
weakSelf.pageNo++;
[weakSelf createData];
}];
LYEmptyView *emptyView = [LYEmptyView emptyViewWithImageStr:@"noDataBox" titleStr:@"暂无数据" detailStr:@""];
//元素竖直方向的间距
emptyView.subViewMargin = 20.f;
//标题颜色
emptyView.titleLabTextColor = [UIColor blackColor];
//描述字体
emptyView.detailLabFont = [UIFont systemFontOfSize:17];
//按钮背景色
emptyView.actionBtnBackGroundColor = KWhiteColor;
emptyView.actionBtnWidth = CGFLOAT_MIN;
emptyView.actionBtnHeight = CGFLOAT_MIN;
//设置空内容占位图
self.homeTable.ly_emptyView = emptyView;
}
- (void)createData {
[self showMBProgressHUDWithString:@"加载中,请稍候..." autoHidden:false];
NSMutableDictionary * parmDic = [[NSMutableDictionary alloc] init];
[parmDic setObject:@(self.pageNo) forKey:@"pageNo"];
[parmDic setObject:@"10" forKey:@"pageSize"];
XJPNetAPI * listApi = [[XJPNetAPI alloc] init:self tag:@"LIST" NeedToken:@""];
[listApi getUserMessageList:parmDic];
}
#pragma mark - DataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.dataArray.count;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewCell.new;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
}
- (CGFloat )tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return CGFLOAT_MIN;
}
- (CGFloat )tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
{
return CGFLOAT_MIN;
}
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
return UIView.new;
}
- (void)chageDataType:(UIButton *)typeBtn {
//请求接口
if (typeBtn.tag == 0) {
[self creeateRankDataWithTyep:@"1"];
}
if (typeBtn.tag == 1) {
[self creeateRankDataWithTyep:@"2"];
}
if (typeBtn.tag == 2) {
[self creeateRankDataWithTyep:@"0"];
}
}
- (void)creeateRankDataWithTyep:(NSString *)type {
//rankType(榜单类型,0总榜,1周榜,2月榜)
[self showMBProgressHUDWithString:@"加载中,请稍候..." autoHidden:false];
NSMutableDictionary * parmDic = [[NSMutableDictionary alloc] init];
[parmDic setObject:@(self.pageNo) forKey:@"pageNo"];
[parmDic setObject:@"10" forKey:@"pageSize"];
[parmDic setObject:type forKey:@"rankType"];
XJPNetAPI * listApi = [[XJPNetAPI alloc] init:self tag:@"DETAIL" NeedToken:@""];
[listApi postPointsBankSourceDetail:parmDic];
}
- (void)Failed:(NSString*)message tag:(NSString*)tag
{
[self.homeTable.mj_header endRefreshing];
[self.homeTable.mj_footer endRefreshing];
[self hiddenAllMBProgressHUD];
}
- (void)Sucess:(id)response tag:(NSString*)tag
{
[self.homeTable.mj_header endRefreshing];
[self.homeTable.mj_footer endRefreshing];
if ([tag isEqualToString:@"DETAIL"]) {
[self hiddenAllMBProgressHUD];
NSDictionary * responseDic = response;
PointsBankHModel * model = [PointsBankHModel modelWithDictionary:responseDic];
//points 可用积分 addPointsSum 累计积分 exchangePointsSum 累计兑换
self.topView.model = model;
}
if ([tag isEqualToString:@"RANK"]) {
NSDictionary * dic = response;
if ([dic.allKeys containsObject:@"page"]) {
NSDictionary * pageDic = [dic objectForKey:@"page"];
//取到排名
if ([pageDic.allKeys containsObject:@"records"]) {
NSArray * array = [pageDic objectForKey:@"records"];
for (NSDictionary * rankDic in array) {
PointsBankHModel * mdoel = [PointsBankHModel modelWithDictionary:rankDic];
[self.dataArray addObject:mdoel];
}
//处理结束 给数据
[self.homeTable reloadData];
}
}
}
//
}
/*
#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
@@ -0,0 +1,19 @@
//
// PointsBankHomeTopView.h
// XinjiangProject
//
// Created by Apple on 2026/2/6.
//
#import <UIKit/UIKit.h>
#import "PointsBankHModel.h"
NS_ASSUME_NONNULL_BEGIN
@interface PointsBankHomeTopView : UIView
@property (nonatomic, strong) PointsBankHModel * model;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,144 @@
//
// PointsBankHomeTopView.m
// XinjiangProject
//
// Created by Apple on 2026/2/6.
//
#import "PointsBankHomeTopView.h"
@interface PointsBankHomeTopView ()
@property (nonatomic, strong) UILabel * pointsDataLabel;
@property (nonatomic, strong) UILabel * pointsAddLabel;
@property (nonatomic, strong) UILabel * exchangeLabel;
@end
@implementation PointsBankHomeTopView
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
}
*/
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self createUI];
}
return self;
}
- (void)createUI {
UILabel * haveLab = [[UILabel alloc] init];
haveLab.text = @"可用积分";
haveLab.textColor = CFontColor1;
haveLab.font = [UIFont systemFontOfSize:13 weight:UIFontWeightRegular];
[self addSubview:haveLab];
[haveLab mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_offset(15);
make.top.mas_offset(15);
}];
self.pointsDataLabel = [[UILabel alloc] init];
self.pointsDataLabel.text = @"--";
self.pointsDataLabel.textColor = CFontColor1;
self.pointsDataLabel.font = [UIFont systemFontOfSize:28 weight:UIFontWeightBold];
[self addSubview:self.pointsDataLabel];
[self.pointsDataLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(haveLab);
make.top.mas_equalTo(haveLab.mas_bottom).offset(12);
}];
UIButton * sourceBtn = [[UIButton alloc] init];
[sourceBtn setTitle:@"获取积分" forState:UIControlStateNormal];
[sourceBtn setTitleColor:KWhiteColor forState:UIControlStateNormal];
sourceBtn.titleLabel.font = SYSTEMFONT(15);
sourceBtn.layer.cornerRadius = 4;
sourceBtn.layer.masksToBounds = true;
sourceBtn.backgroundColor = UIColorHex(#21BEBE);
[self addSubview:sourceBtn];
[sourceBtn mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.mas_offset(-15);
make.width.mas_offset(80);
make.height.mas_offset(32);
make.top.mas_offset(53);
}];
UILabel * addLab = [[UILabel alloc] init];
addLab.text = @"累计积分:";
addLab.textColor = CFontColor1;
addLab.font = [UIFont systemFontOfSize:13 weight:UIFontWeightRegular];
self.pointsAddLabel = addLab;
[self addSubview:addLab];
[addLab mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(haveLab.mas_right).offset(81);
make.top.mas_equalTo(haveLab.mas_bottom).offset(11);
}];
self.exchangeLabel = [[UILabel alloc] init];
self.exchangeLabel.text = @"累计兑换:";
self.exchangeLabel.textColor = CFontColor1;
self.exchangeLabel.font = [UIFont systemFontOfSize:13 weight:UIFontWeightRegular];
[self addSubview:self.exchangeLabel];
[self.exchangeLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(haveLab.mas_right).offset(81);
make.top.mas_equalTo(addLab.mas_bottom).offset(11);
}];
UIView *dashedView = [[UIView alloc] init];
[self addSubview:dashedView];
// Masonry 布局
[dashedView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(20);
make.right.mas_equalTo(-20);
make.top.mas_equalTo(self.pointsDataLabel.mas_bottom).offset(10);
make.height.mas_equalTo(1); // 高度可以随意
make.bottom.mas_equalTo(-10);
}];
//虚线
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.strokeColor = [UIColor lightGrayColor].CGColor; // 线颜色
shapeLayer.lineWidth = 1; // 线宽
shapeLayer.lineDashPattern = @[@4, @2]; // 虚线:4pt 实线,2pt 空白
// 创建路径
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, 0, 0); // 起点
CGPathAddLineToPoint(path, NULL, [UIScreen mainScreen].bounds.size.width - 40, 0); // 终点
shapeLayer.path = path;
CGPathRelease(path);
// 添加到 view
[dashedView.layer addSublayer:shapeLayer];
}
-(void)setModel:(PointsBankHModel *)model {
_model = model;
self.pointsDataLabel.text = [NSString stringWithFormat:@"%.0f",_model.points];
self.pointsAddLabel.text = [NSString stringWithFormat:@"累计积分:%.0f",_model.addPointsSum];
self.exchangeLabel.text = [NSString stringWithFormat:@"累计兑换%.0f",_model.exchangePointsSum];
}
@end
@@ -0,0 +1,16 @@
//
// myPointsBankHomeViewController.h
// XinjiangProject
//
// Created by Apple on 2026/2/6.
//
#import "XJBaseViewController.h"
NS_ASSUME_NONNULL_BEGIN
@interface myPointsBankHomeViewController : XJBaseViewController
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,439 @@
//
// myPointsBankHomeViewController.m
// XinjiangProject
//
// Created by Apple on 2026/2/6.
//
#import "myPointsBankHomeViewController.h"
#import "PointsBankHModel.h"
#import "PointsBankHomeTopView.h"
@interface myPointsBankHomeViewController ()<UITableViewDelegate,UITableViewDataSource>
@property (nonatomic, strong) UITableView * homeTable;
@property (nonatomic, assign) NSInteger pageNo;
@property (nonatomic, strong) NSMutableArray * dataArray;
@property (nonatomic, strong) NSMutableArray * buttonArray;
@property (nonatomic, strong) PointsBankHomeTopView * topView;
@property (nonatomic, strong) UIView * lineView;
@end
@implementation myPointsBankHomeViewController
- (NSMutableArray *)dataArray {
if (!_dataArray ) {
_dataArray = [[NSMutableArray alloc] init];
}
return _dataArray;
}
- (NSMutableArray *)buttonArray {
if (!_buttonArray ) {
_buttonArray = [[NSMutableArray alloc] init];
}
return _buttonArray;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.isHidenNaviBar = false;
self.navigationItem.title = @"我的积分银行";
[self createData];
//默认总榜单
[self creeateRankDataWithTyep:@"0"];
[self createUI];
}
- (void)createUI {
//首页
self.topView = [[PointsBankHomeTopView alloc] init];
[self.view addSubview:self.topView];
[self.topView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.top.mas_offset(0);
// make.height.mas_offset(185);
}];
//功能栏
NSArray * toolsArray = @[@[@"积分兑换",@"img1"],@[@"积分明细",@"img2"],@[@"兑换记录",@"img3"]];
CGFloat itemWidth = 60;
CGFloat itemHeight = 110;
NSInteger count = toolsArray.count;
for (int i = 0; i < count; i++) {
NSArray *item = toolsArray[i];
// 1️⃣ 容器 view
UIView *itemView = [[UIView alloc] init];
itemView.tag = i;
itemView.userInteractionEnabled = YES;
[self.view addSubview:itemView];
// 2️⃣ 点击手势
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(itemTap:)];
[itemView addGestureRecognizer:tap];
// 3️⃣ 布局 container(每个 item 占 1/3 屏幕宽)
CGFloat thirdWidth = UIScreen.mainScreen.bounds.size.width / count;
[itemView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.topView.mas_bottom).offset(20);
make.width.mas_equalTo(itemWidth);
make.height.mas_equalTo(itemHeight);
make.centerX.mas_equalTo(self.view.mas_left).offset(thirdWidth * i + thirdWidth / 2);
}];
// 4️⃣ 图片
UIImageView *icon = [[UIImageView alloc] initWithImage:[UIImage imageNamed:item[1]]];
icon.contentMode = UIViewContentModeScaleAspectFit;
[itemView addSubview:icon];
[icon mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(0);
make.centerX.equalTo(itemView);
make.width.height.mas_equalTo(40);
}];
// 5️⃣ 文字
UILabel *label = [[UILabel alloc] init];
label.text = item[0];
label.font = [UIFont systemFontOfSize:14];
label.textAlignment = NSTextAlignmentCenter;
[itemView addSubview:label];
[label mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(icon.mas_bottom).offset(8);
make.left.right.equalTo(itemView);
make.height.mas_equalTo(20);
}];
}
//排行
UIView * rankView = [[UIView alloc] init];
rankView.backgroundColor = KWhiteColor;
[self.view addSubview:rankView];
UILabel * rankTitleLab = [[UILabel alloc] init];
rankTitleLab.text = @"积分排行(单位)";
rankTitleLab.textColor = CFontColor1;
[rankView addSubview:rankTitleLab];
[rankTitleLab mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_offset(15);
make.top.mas_offset(15);
}];
UILabel * detailLab = [[UILabel alloc] init];
detailLab.text = @"以员工累计获得积分进行排名";
detailLab.textColor = CFontColor1;
[rankView addSubview:detailLab];
[detailLab mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_offset(15);
make.top.mas_equalTo(rankTitleLab.mas_bottom).offset(8);
}];
//周排名 月排名 年排名
UIView * lineView = [[UIView alloc] init];
lineView.backgroundColor = UIColor.blueColor;
self.lineView = lineView;
[rankView addSubview:lineView];
[lineView mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.mas_offset(80);
make.height.mas_offset(2);
}];
NSArray * array = @[@"周排名",@"月排名",@"年排名"];
UIButton * lastBtn = nil;
for (int i= 0; i < 3; i ++) {
UIButton * btn = [[UIButton alloc] init];
[btn setTitle:array[i] forState:UIControlStateNormal];
[btn setTitleColor:UIColor.blueColor forState:UIControlStateSelected];
[btn setTitleColor:UIColor.blackColor forState:UIControlStateNormal];
btn.titleLabel.font = MEDIUMFONT(13);
btn.tag = i;
[btn addTarget:self action:@selector(chageDataType:) forControlEvents:UIControlEventTouchUpInside];
btn.selected = false;
if (i == 2) {
btn.selected = true;
lastBtn = btn;
}
[rankView addSubview:btn];
[btn mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.mas_offset(80);
make.height.mas_offset(32);
make.top.mas_equalTo(detailLab.mas_bottom).offset(10);
if (i == 0) {
make.left.mas_offset(82);
}
if (i == 1) {
make.centerX.mas_equalTo(rankView);
}
if (i == 2) {
make.right.mas_offset(-82);
}
}];
[self.buttonArray addObject:btn];
}
if (lastBtn) {
[lineView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.mas_equalTo(lastBtn);
make.top.mas_equalTo(lastBtn.mas_bottom).offset(5);
}];
}
[rankView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.mas_offset(0);
make.top.mas_equalTo(self.topView.mas_bottom).offset(15);
make.bottom.mas_equalTo(lastBtn.mas_bottom).offset(20);
}];
self.homeTable = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
if ([[UIDevice currentDevice].systemVersion floatValue] >= 11) {
self.homeTable.estimatedRowHeight = 60;
if (@available(iOS 15.0, *)) {
self.homeTable.sectionHeaderTopPadding = 0;
} else {
// Fallback on earlier versions
}
self.homeTable.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
self.homeTable.delegate = self;
self.homeTable.dataSource = self;
self.homeTable.separatorStyle = UITableViewCellSeparatorStyleSingleLine;
self.homeTable.showsVerticalScrollIndicator = NO;
self.homeTable.backgroundColor = KClearColor;
self.homeTable.rowHeight = UITableViewAutomaticDimension;
self.homeTable.estimatedRowHeight = 80;
[self.view addSubview:self.homeTable];
[self.homeTable mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(rankView.mas_bottom);
make.left.bottom.right.mas_offset(0);
}];
__weak __typeof(self) weakSelf = self;
self.homeTable.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
weakSelf.pageNo = 1;
[weakSelf.dataArray removeAllObjects];
[weakSelf createData];
}];
self.homeTable.mj_footer = [MJRefreshBackNormalFooter footerWithRefreshingBlock:^{
weakSelf.pageNo++;
[weakSelf createData];
}];
LYEmptyView *emptyView = [LYEmptyView emptyViewWithImageStr:@"noDataBox" titleStr:@"暂无数据" detailStr:@""];
//元素竖直方向的间距
emptyView.subViewMargin = 20.f;
//标题颜色
emptyView.titleLabTextColor = [UIColor blackColor];
//描述字体
emptyView.detailLabFont = [UIFont systemFontOfSize:17];
//按钮背景色
emptyView.actionBtnBackGroundColor = KWhiteColor;
emptyView.actionBtnWidth = CGFLOAT_MIN;
emptyView.actionBtnHeight = CGFLOAT_MIN;
//设置空内容占位图
self.homeTable.ly_emptyView = emptyView;
}
- (void)createData {
[self showMBProgressHUDWithString:@"加载中,请稍候..." autoHidden:false];
NSMutableDictionary * parmDic = [[NSMutableDictionary alloc] init];
XJPNetAPI * listApi = [[XJPNetAPI alloc] init:self tag:@"HEARD" NeedToken:@""];
[listApi getPointsBankHeard:parmDic];
}
#pragma mark - DataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.dataArray.count;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewCell.new;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
}
- (CGFloat )tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return CGFLOAT_MIN;
}
- (CGFloat )tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
{
return CGFLOAT_MIN;
}
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
return UIView.new;
}
- (void)chageDataType:(UIButton *)typeBtn {
for (UIButton * seleBtn in self.buttonArray) {
if (seleBtn == typeBtn) {
seleBtn.selected = true;
[self.lineView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.right.mas_equalTo(typeBtn);
make.bottom.mas_equalTo(typeBtn.mas_bottom).offset(8);
make.height.mas_offset(1);
make.centerX.mas_equalTo(typeBtn);
}];
}else
{
seleBtn.selected = false;
}
}
//请求接口
if (typeBtn.tag == 0) {
[self creeateRankDataWithTyep:@"1"];
}
if (typeBtn.tag == 1) {
[self creeateRankDataWithTyep:@"2"];
}
if (typeBtn.tag == 2) {
[self creeateRankDataWithTyep:@"0"];
}
}
- (void)creeateRankDataWithTyep:(NSString *)type {
//rankType(榜单类型,0总榜,1周榜,2月榜)
[self showMBProgressHUDWithString:@"加载中,请稍候..." autoHidden:false];
NSMutableDictionary * parmDic = [[NSMutableDictionary alloc] init];
[parmDic setObject:@(self.pageNo) forKey:@"pageNo"];
[parmDic setObject:@"10" forKey:@"pageSize"];
[parmDic setObject:type forKey:@"rankType"];
XJPNetAPI * listApi = [[XJPNetAPI alloc] init:self tag:@"RANK" NeedToken:@""];
[listApi postPointsBankRankData:parmDic];
}
- (void)Failed:(NSString*)message tag:(NSString*)tag
{
[self.homeTable.mj_header endRefreshing];
[self.homeTable.mj_footer endRefreshing];
[self hiddenAllMBProgressHUD];
}
- (void)Sucess:(id)response tag:(NSString*)tag
{
[self.homeTable.mj_header endRefreshing];
[self.homeTable.mj_footer endRefreshing];
if ([tag isEqualToString:@"HEARD"]) {
[self hiddenAllMBProgressHUD];
NSDictionary * responseDic = response;
PointsBankHModel * model = [PointsBankHModel modelWithDictionary:responseDic];
//points 可用积分 addPointsSum 累计积分 exchangePointsSum 累计兑换
self.topView.model = model;
}
if ([tag isEqualToString:@"RANK"]) {
NSDictionary * dic = response;
if ([dic.allKeys containsObject:@"page"]) {
NSDictionary * pageDic = [dic objectForKey:@"page"];
//取到排名
if ([pageDic.allKeys containsObject:@"records"]) {
NSArray * array = [pageDic objectForKey:@"records"];
for (NSDictionary * rankDic in array) {
PointsBankHModel * mdoel = [PointsBankHModel modelWithDictionary:rankDic];
[self.dataArray addObject:mdoel];
}
//处理结束 给数据
[self.homeTable reloadData];
}
}
}
//
}
- (void)itemTap:(UITapGestureRecognizer *)tap {
NSInteger index = tap.view.tag;
NSLog(@"点击了第 %ld 个", index);
if(index == 1 || index == 2) {
[EasyTextView showInfoText:@"开发者,敬请期待!"];
return;
}
}
/*
#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
@@ -14,7 +14,9 @@
#import "XJSosCenterViewController.h"
#import "MonitoringWarchViewController.h"
//干预二期
//新版首页
#import "XJHomePageV2ViewController.h"
@interface XJBaseTabBarController ()<UITabBarControllerDelegate>
@@ -39,8 +41,6 @@
- (void)viewDidLoad {
[super viewDidLoad];
self.delegate = self;
//初始化tabbar
[self setUpTabBar];
//添加子控制器
@@ -73,6 +73,11 @@
-(void)setUpAllChildViewController{
_VCS = @[].mutableCopy;
//新增首页
XJHomePageV2ViewController * homeVC = [[XJHomePageV2ViewController alloc]init];
[self setupChildViewController:homeVC title:@"首页" imageName:@"xjHomeTabBar_nomal" seleceImageName:@"xjHomeTabBar_selected"];
QuestionViewController * questionVC = [[QuestionViewController alloc]init];
[self setupChildViewController:questionVC title:@"咨询" imageName:@"questionTabBar_nomal" seleceImageName:@"questionTabBar_selected"];
@@ -141,6 +146,10 @@
return [self.selectedViewController supportedInterfaceOrientations];
}
- (void)selectedQuestion {
self.selectedIndex = 1;
}
/*
#pragma mark - Navigation
@@ -9,15 +9,42 @@
@interface XJBaseWebViewVC ()<UINavigationControllerDelegate, UIImagePickerControllerDelegate>
@property (nonatomic, strong)NSURL *originalUrl;
@property (nonatomic, copy)NSString *videoUrl;
@property (nonatomic, copy)NSString *messageType;
// 以下三个属性声明后未使用,暂时保留
//@property (nonatomic, strong)NSURL *originalUrl;
//@property (nonatomic, copy)NSString *videoUrl;
//@property (nonatomic, copy)NSString *messageType;
/// 网页加载进度条
@property (nonatomic, strong) UIProgressView *progressView;
/// 加载失败占位视图
@property (nonatomic, strong) UIView *errorView;
@end
@implementation XJBaseWebViewVC
// ── 弱引用代理 ──────────────────────────────────────────────────────────────
// WKUserContentController 对 addScriptMessageHandler 的 handler 是强引用。
// 如果直接传 selfself → wkWebView → configuration → userContentController → self
// 形成循环引用,VC 永远无法释放。
// 解决方案:用一个弱引用中间对象转发消息。
@interface XJWeakScriptMessageHandler : NSObject <WKScriptMessageHandler>
@property (nonatomic, weak) id<WKScriptMessageHandler> delegate;
+ (instancetype)handlerWithDelegate:(id<WKScriptMessageHandler>)delegate;
@end
@implementation XJWeakScriptMessageHandler
+ (instancetype)handlerWithDelegate:(id<WKScriptMessageHandler>)delegate {
XJWeakScriptMessageHandler *handler = [[XJWeakScriptMessageHandler alloc] init];
handler.delegate = delegate;
return handler;
}
- (void)userContentController:(WKUserContentController *)userContentController
didReceiveScriptMessage:(WKScriptMessage *)message {
[self.delegate userContentController:userContentController didReceiveScriptMessage:message];
}
@end
// ────────────────────────────────────────────────────────────────────────────
@implementation XJBaseWebViewVC
#pragma mark - LifeCycle
- (void)viewDidLoad {
@@ -36,6 +63,16 @@
[self.navigationController popViewControllerAnimated:YES];
}
- (void)dealloc {
// 移除进度条 KVO
[self.wkWebView removeObserver:self forKeyPath:@"estimatedProgress"];
// 移除所有 scriptMessageHandler,配合 XJWeakScriptMessageHandler 彻底避免内存泄漏
WKUserContentController *ucc = self.wkWebView.configuration.userContentController;
[ucc removeScriptMessageHandlerForName:@"JSCallback"];
[ucc removeScriptMessageHandlerForName:@"htmlBack"];
[ucc removeScriptMessageHandlerForName:@"finishActivity"];
}
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
if (_wkWebView) {
@@ -51,14 +88,14 @@
[super viewWillDisappear:animated];
_wkWebView.navigationDelegate = nil;
_wkWebView.UIDelegate = nil;
//清除cookies
NSHTTPCookie *cookie;
NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
for (cookie in [storage cookies]) {
[storage deleteCookie:cookie];
}
[[NSURLCache sharedURLCache] removeAllCachedResponses];
[self clearWebViewCache];
// ⚠️ 以下代码会清除整个 App 的所有 Cookie,影响其他模块登录状态,已注释
// NSHTTPCookie *cookie;
// NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
// for (cookie in [storage cookies]) {
// [storage deleteCookie:cookie];
// }
// [[NSURLCache sharedURLCache] removeAllCachedResponses];
// [self clearWebViewCache];
}
#pragma mark - Create UI
@@ -69,20 +106,58 @@
preferences.javaScriptCanOpenWindowsAutomatically = YES;
preferences.minimumFontSize = 10;
configuration.preferences = preferences;
[configuration.userContentController addScriptMessageHandler:self name:@"JSCallback"];
[configuration.userContentController addScriptMessageHandler:self name:@"htmlBack"];
// 允许视频内联播放,避免全屏时布局计算错误导致显示不完整
configuration.allowsInlineMediaPlayback = YES;
// ── 统一使用 configuration 自带的 userContentController ──────────────────
// Bug 修复:原先在下方又新建了 wkUController 并覆盖 configuration.userContentController
// 导致此处注册的 JSCallback / htmlBack 被丢弃,永远收不到 JS 消息。
// Bug 修复:改用 XJWeakScriptMessageHandler 代理,防止循环引用内存泄漏。
XJWeakScriptMessageHandler *weakHandler = [XJWeakScriptMessageHandler handlerWithDelegate:self];
[configuration.userContentController addScriptMessageHandler:weakHandler name:@"JSCallback"];
[configuration.userContentController addScriptMessageHandler:weakHandler name:@"htmlBack"];
[configuration.userContentController addScriptMessageHandler:weakHandler name:@"finishActivity"]; // 不带参数的调用
// 原写法(已废弃):直接传 self 会造成循环引用
// [configuration.userContentController addScriptMessageHandler:self name:@"JSCallback"];
// [configuration.userContentController addScriptMessageHandler:self name:@"htmlBack"];
// ────────────────────────────────────────────────────────────────────────
//防止搜索框自动放大 注入js代码
WKUserContentController *userController = [WKUserContentController new];
// 原代码中 userController 声明后从未使用,已移除
// WKUserContentController *userController = [WKUserContentController new];
NSString *jScript = @"var script = document.createElement('meta');script.name = 'viewport';script.content='width=device-width,user-scalable=no';document.getElementsByTagName('head')[0].appendChild(script);";
WKUserScript *wkUScript = [[WKUserScript alloc] initWithSource:jScript injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
WKUserContentController * wkUController = [[WKUserContentController alloc] init];
[wkUController addScriptMessageHandler: self name: @"finishActivity"];//不带参数的调用
[wkUController addUserScript:wkUScript];
configuration.userContentController= wkUController;
// 原写法(已废弃):新建 wkUController 并覆盖 configuration.userContentController
// 导致上方注册的 JSCallback / htmlBack 全部失效
// WKUserContentController * wkUController = [[WKUserContentController alloc] init];
// [wkUController addScriptMessageHandler: self name: @"finishActivity"];
// [wkUController addUserScript:wkUScript];
// configuration.userContentController = wkUController;
[configuration.userContentController addUserScript:wkUScript];
// 禁用网页长按文字选中和复制粘贴菜单
NSString *noSelectJS = @"document.documentElement.style.webkitUserSelect='none';"
"document.documentElement.style.webkitTouchCallout='none';";
WKUserScript *noSelectScript = [[WKUserScript alloc] initWithSource:noSelectJS
injectionTime:WKUserScriptInjectionTimeAtDocumentEnd
forMainFrameOnly:NO];
[configuration.userContentController addUserScript:noSelectScript];
self.wkWebView = [[WKWebView alloc]initWithFrame:CGRectMake(0, 0, WIDTH, MyViewHEIGHT) configuration:configuration];
if (@available(iOS 16.4, *)) {
self.wkWebView.inspectable = YES;
}
// 伪装成 Safari User-Agent,排查 H5 API 请求因 UA 差异被拒的问题
{
NSString *osVer = [UIDevice currentDevice].systemVersion;
NSString *osVerUnder = [osVer stringByReplacingOccurrencesOfString:@"." withString:@"_"];
self.wkWebView.customUserAgent = [NSString stringWithFormat:
@"Mozilla/5.0 (iPhone; CPU iPhone OS %@ like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/%@ Mobile/15E148 Safari/604.1",
osVerUnder, osVer];
}
self.wkWebView.navigationDelegate = self;
self.wkWebView.UIDelegate = self;
if (@available(iOS 11.0, *)) {
@@ -90,31 +165,104 @@
}
[self.view addSubview:self.wkWebView];
self.wkWebView.scrollView.bounces = false;//禁止滑动
// 进度条,居中偏上
self.progressView = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault];
self.progressView.trackTintColor = [UIColor clearColor];
[self.view addSubview:self.progressView];
[self.progressView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self.wkWebView);
make.centerY.equalTo(self.wkWebView).offset(-60);
make.width.mas_offset(200);
make.height.mas_offset(2);
}];
// KVO 监听加载进度
[self.wkWebView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];
// 加载失败占位视图(默认隐藏)
self.errorView = [[UIView alloc] init];
self.errorView.backgroundColor = [UIColor whiteColor];
self.errorView.hidden = YES;
[self.view addSubview:self.errorView];
[self.errorView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.wkWebView);
}];
UILabel *tipLabel = [[UILabel alloc] init];
tipLabel.text = @"网络加载失败\n请检查网络后重试";
tipLabel.textAlignment = NSTextAlignmentCenter;
tipLabel.numberOfLines = 0;
tipLabel.textColor = [UIColor colorWithRed:0.6 green:0.6 blue:0.6 alpha:1.0];
tipLabel.font = [UIFont systemFontOfSize:15];
[self.errorView addSubview:tipLabel];
[tipLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self.errorView);
make.centerY.equalTo(self.errorView).offset(-40);
make.left.greaterThanOrEqualTo(self.errorView).offset(20);
make.right.lessThanOrEqualTo(self.errorView).offset(-20);
}];
UIButton *reloadBtn = [UIButton buttonWithType:UIButtonTypeCustom];
[reloadBtn setTitle:@"重新加载" forState:UIControlStateNormal];
[reloadBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
reloadBtn.titleLabel.font = [UIFont systemFontOfSize:15];
reloadBtn.backgroundColor = [UIColor colorWithRed:0.0 green:0.68 blue:0.41 alpha:1.0];
reloadBtn.layer.cornerRadius = 20;
reloadBtn.layer.masksToBounds = YES;
[reloadBtn addTarget:self action:@selector(reloadWebView) forControlEvents:UIControlEventTouchUpInside];
[self.errorView addSubview:reloadBtn];
[reloadBtn mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(tipLabel.mas_bottom).offset(24);
make.right.equalTo(self.errorView.mas_centerX).offset(-8);
make.width.mas_offset(110);
make.height.mas_offset(40);
}];
UIButton *backBtn = [UIButton buttonWithType:UIButtonTypeCustom];
[backBtn setTitle:@"返回" forState:UIControlStateNormal];
[backBtn setTitleColor:[UIColor colorWithRed:0.0 green:0.68 blue:0.41 alpha:1.0] forState:UIControlStateNormal];
backBtn.titleLabel.font = [UIFont systemFontOfSize:15];
backBtn.backgroundColor = [UIColor whiteColor];
backBtn.layer.cornerRadius = 20;
backBtn.layer.masksToBounds = YES;
backBtn.layer.borderWidth = 1;
backBtn.layer.borderColor = [UIColor colorWithRed:0.0 green:0.68 blue:0.41 alpha:1.0].CGColor;
[backBtn addTarget:self action:@selector(backVC) forControlEvents:UIControlEventTouchUpInside];
[self.errorView addSubview:backBtn];
[backBtn mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(reloadBtn);
make.left.equalTo(self.errorView.mas_centerX).offset(8);
make.width.height.equalTo(reloadBtn);
}];
[self webViewRequest];
/*
[self.wkWebView evaluateJavaScript:@"htmlBack" completionHandler:^(id _Nullable response, NSError * _Nullable error) {
NSLog(@"response: %@ error: %@", response, error);
if (response) {
[self back];
}
}];
*/
}
- (void)finishActivity {
// H5 调用 finishActivity 的默认行为:pop 返回上一页
// 语义等同于 Android 的 finish(),即关闭当前页面
// 子类如需特殊处理(如 dismiss、发通知等),override 此方法即可
[self.navigationController popViewControllerAnimated:YES];
}
- (void)reloadWebView {
self.errorView.hidden = YES;
[self webViewRequest];
}
- (void)webViewRequest{
[self clearWebViewCache];
NSString *urlString = self.webUrl;
DLog(@"[WebView] 请求加载: %@", urlString);
[self hiddenAllMBProgressHUD];
if (urlString.length > 0) {
[self showMBProgressHUDOnlyWithString:@"加载中"];
NSURL *url = [NSURL URLWithString:urlString];
if (!url) {
DLog(@"[WebView] URL 无效: %@", urlString);
}
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
if (self.wkWebView) {
[self.wkWebView loadRequest:urlRequest];
@@ -125,6 +273,24 @@
#pragma mark - KVO
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:@"estimatedProgress"]) {
float progress = [change[NSKeyValueChangeNewKey] floatValue];
self.progressView.alpha = 1.0;
[self.progressView setProgress:progress animated:YES];
if (progress >= 1.0) {
// 加载完成后渐隐进度条
[UIView animateWithDuration:0.3 delay:0.2 options:0 animations:^{
self.progressView.alpha = 0;
} completion:^(BOOL finished) {
[self.progressView setProgress:0 animated:NO];
}];
}
}
}
#pragma mark - WKNavigationDelegate
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
if ([message.name isEqualToString:@"JSCallback"]) {
@@ -139,7 +305,7 @@
WkJSMessageType messageType = [[dic objectForKey:@"messageType"] intValue];
switch (messageType) {
case WkJSMessageTypeBack:{
// [self.navigationController popViewControllerAnimated:YES];
[self.navigationController popViewControllerAnimated:YES];
}
break;
default:
@@ -149,6 +315,12 @@
} else if ([message.name isEqualToString:@"htmlBack"]) {
[[NSNotificationCenter defaultCenter] postNotificationName:@"fromWebToReloadData" object:nil];
} else if ([message.name isEqualToString:@"finishActivity"]) {
// JS 调用 window.webkit.messageHandlers.finishActivity.postMessage({}) 时触发
// 调用 finishActivity 方法,子类可 override 实现各自的关闭逻辑
// 基类默认实现为空,不做任何操作
[self finishActivity];
}
}
@@ -162,14 +334,17 @@
}
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation{
DLog(@"[WebView] 开始加载: %@", webView.URL);
self.errorView.hidden = YES;
if (!self.webTitle) {
self.title = @"加载中...";
}
}
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation{
DLog(@"[WebView] 加载完成: %@", webView.URL);
[self hiddenAllMBProgressHUD];
self.errorView.hidden = YES;
if (!self.webTitle) {
self.title = webView.title;
}
@@ -177,7 +352,82 @@
}
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(nonnull NSError *)error{
NSLog(@"[WebView] 加载失败 URL=%@ errorCode=%ld domain=%@ desc=%@",
webView.URL, (long)error.code, error.domain, error.localizedDescription);
[self hiddenAllMBProgressHUD];
// NSURLErrorCancelled (-999) 是主动取消导航(如跳转第三方 App),不算真实网络错误
if (error.code != NSURLErrorCancelled) {
self.errorView.hidden = NO;
}
}
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error {
NSLog(@"[WebView] 导航失败 URL=%@ errorCode=%ld domain=%@ desc=%@",
webView.URL, (long)error.code, error.domain, error.localizedDescription);
[self hiddenAllMBProgressHUD];
// NSURLErrorCancelled (-999) 是主动取消导航,不算真实网络错误
if (error.code != NSURLErrorCancelled) {
self.errorView.hidden = NO;
}
}
#pragma mark - 白屏修复
/**
* 拦截所有导航请求,处理非 HTTP/HTTPS scheme。
*
* 问题根源:
* 银行 H5 点击支付/跳转按钮时,会触发 alipay:// weixin:// tel:// 等 URL Scheme。
* WKWebView 默认会尝试自己加载这些 scheme,加载失败后清空页面内容 → 永久白屏。
*
* 解决方案:
* 在此方法中拦截非 HTTP/HTTPS 的请求,交由系统(UIApplication)处理,
* WKWebView 自身取消该次导航,页面内容保持不变。
*/
- (void)webView:(WKWebView *)webView
decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
NSURL *url = navigationAction.request.URL;
NSString *scheme = url.scheme.lowercaseString;
// HTTP / HTTPS:正常加载,交给 WKWebView 处理
if ([scheme isEqualToString:@"http"] || [scheme isEqualToString:@"https"]) {
decisionHandler(WKNavigationActionPolicyAllow);
return;
}
// about:blankWKWebView 内部使用,直接放行
if ([scheme isEqualToString:@"about"]) {
decisionHandler(WKNavigationActionPolicyAllow);
return;
}
// 其他 schemealipay:// weixin:// tel:// mailto:// 等):
// 交由系统打开对应 App 或拨号,WKWebView 取消此次导航,避免白屏
if ([[UIApplication sharedApplication] canOpenURL:url]) {
[[UIApplication sharedApplication] openURL:url
options:@{}
completionHandler:nil];
}
decisionHandler(WKNavigationActionPolicyCancel);
}
/**
* WebContent 进程被系统终止时的兜底处理。
*
* 触发场景:
* 设备内存严重不足时,系统会强制 kill 掉 WKWebView 的 WebContent 进程。
* 此时 WKWebView 会立即变成空白页,且无法响应任何操作。
* 若不处理,用户只能退出页面重新进入。
*
* 解决方案:
* 检测到进程终止后,重新加载当前 URL,让用户无感知恢复。
*/
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView {
NSLog(@"[XJBaseWebViewVC] WebContent 进程被系统终止,自动 reload");
// 重新加载,恢复页面内容
[webView reload];
}
@@ -201,10 +451,13 @@
#pragma mark - 自定义
- (void)onBackClicked{
if ( self.wkWebView.canGoBack){
if (self.wkWebView.canGoBack) {
[self.wkWebView goBack];
} else {
// 原写法:只清缓存没有 pop,页面会卡住
// [self clearWebViewCache];
[self clearWebViewCache];
[self.navigationController popViewControllerAnimated:YES];
}
}
@@ -213,7 +466,7 @@
NSSet *websiteDataTypes = [WKWebsiteDataStore allWebsiteDataTypes];
NSDate *dateFrom = [NSDate dateWithTimeIntervalSince1970:0];
[[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:websiteDataTypes modifiedSince:dateFrom completionHandler:^{
NSLog(@"-----clear webView cache success------");
DLog(@"-----clear webView cache success------");
}];
} else {
NSString *libraryPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0];
@@ -221,24 +474,23 @@
NSError *errors;
[[NSFileManager defaultManager] removeItemAtPath:cookiesFolderPath error:&errors];
if (!errors) {
NSLog(@"-----clear wkWebView cache success------");
DLog(@"-----clear wkWebView cache success------");
}
}
}
- (NSString *)URLEncodedString:(NSString *)urlString{
NSString *encodePath ;
if (!IOS7) {
encodePath = [urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
}else{
NSString *encodePath;
encodePath = [urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
}return encodePath;
return encodePath;
}
#pragma mark - SETTER
- (void)setRightType:(RightNavType)rightType{
_rightType = rightType;
}
// setRightType: 与自动合成的 setter 完全相同,无需 override,保留注释备查
//- (void)setRightType:(RightNavType)rightType{
// _rightType = rightType;
//}
- (void)setWebUrl:(NSString *)webUrl{
_webUrl = webUrl;
@@ -39,6 +39,7 @@ NS_ASSUME_NONNULL_BEGIN
//地图
@property (nonatomic, assign) CGFloat Longitude;
@property (nonatomic, assign) CGFloat Latitude;
@property (nonatomic, copy) NSString * address;//地址描述
@end
@@ -134,8 +134,8 @@
}
for (MsgBody *msgBody in model.MsgBody) {
NSLog(@"MsgType: %@", msgBody.MsgType);
NSLog(@"MsgContent Data - title: %@", msgBody.MsgContent.Text);
DLog(@"MsgType: %@", msgBody.MsgType);
DLog(@"MsgContent Data - title: %@", msgBody.MsgContent.Text);
if([msgBody.MsgType isEqualToString:@"TIMTextElem"]) //文本消息
{
@@ -6,14 +6,12 @@
//
#import <TIMCommon/TUIBubbleMessageCell.h>
#import <TIMCommon/TUIMessageCell.h>
#import "CustomMapMessageCellData.h"
#import "CustomTUIHistoryModel.h"
@interface CustomMapMessageCell : UITableViewCell
@interface CustomMapMessageCell : TUIBubbleMessageCell
@property (nonatomic, strong) CustomTUIHistoryModel * model;
@end
@@ -6,224 +6,161 @@
//
#import "CustomMapMessageCell.h"
#import "XJWeChatManager.h"
#import <SDWebImage/SDImageCache.h>
#import<CommonCrypto/CommonDigest.h>
@interface CustomMapMessageCell ()
@property (nonatomic, strong) UIImageView * heardImage;
@property (nonatomic, strong) UILabel * nickNameLabel;
@property (nonatomic, strong) UILabel * myTextLabel; // 展示文本
@property (nonatomic, strong) UIImageView * mapImage; //
@property (nonatomic, strong) UIView * backView;
@property (nonatomic, strong) UILabel * myTextLabel;
@property (nonatomic, strong) UIImageView * mapImage;
@end
@implementation CustomMapMessageCell
- (void)awakeFromNib {
[super awakeFromNib];
// Initialization code
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
[self createUI];
[self createMapUI];
}
return self;
}
- (void)createUI {
self.heardImage = [[UIImageView alloc] init];
self.heardImage.backgroundColor = KWhiteColor;
self.heardImage.image = [UIImage imageNamed:@"heard_man"];
[self.contentView addSubview:self.heardImage];
self.heardImage.layer.cornerRadius = 20;
self.heardImage.layer.masksToBounds = true;
[self.heardImage mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(@15);
make.top.mas_equalTo(@8);
make.height.width.mas_equalTo(@30);
}];
self.nickNameLabel = [[UILabel alloc] init];
self.nickNameLabel.text = @"全科医生";
[self.contentView addSubview:self.nickNameLabel];
self.nickNameLabel.font = SYSTEMFONT(12);
[self.nickNameLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.heardImage.mas_right).offset(10);
make.top.mas_equalTo(@10);
make.width.mas_equalTo(@300.0);
make.height.mas_offset(13);
}];
//姓名
self.backView = [[UIView alloc] init];
self.backView.layer.cornerRadius = 10;
self.backView.backgroundColor = KWhiteColor;
[self.contentView addSubview:self.backView];
[self.backView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.heardImage.mas_right).offset(10);
make.width.mas_offset(245);
make.top.mas_equalTo(self.nickNameLabel.mas_bottom).offset(8);
make.height.mas_offset(170);
}];
- (void)createMapUI {
// 隐藏默认气泡图片,改用白色圆角
self.bubbleView.image = nil;
self.myTextLabel = [[UILabel alloc] init];
self.myTextLabel.font = SYSTEMFONT(15);
self.myTextLabel.textColor = CFontColor1;
self.myTextLabel.text = @"我的位置";
[self.backView addSubview:self.myTextLabel];
self.myTextLabel.numberOfLines = 0;
[self.container addSubview:self.myTextLabel];
[self.myTextLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_offset(10);
make.top.mas_offset(0);
make.top.mas_offset(10);
make.right.mas_offset(-10);
make.height.mas_offset(25);
}];
}];
self.mapImage = [[UIImageView alloc] init];
self.mapImage.backgroundColor = KRedColor;
[self.backView addSubview:self.mapImage];
[self.container addSubview:self.mapImage];
[self.mapImage mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.bottom.mas_offset(0);
make.top.mas_equalTo(self.myTextLabel.mas_bottom);
}];
// 长按转发到微信
// TUIChat 框架把长按手势注释掉了,这里重新加上。
// 需要让 container 上已有的 tap 手势等 longPress 失败后再触发,否则 tap 会抢走触摸。
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(onLongPress:)];
longPress.minimumPressDuration = 0.5;
[self.container addGestureRecognizer:longPress];
for (UIGestureRecognizer *g in self.container.gestureRecognizers) {
if ([g isKindOfClass:[UITapGestureRecognizer class]]) {
[g requireGestureRecognizerToFail:longPress];
}
}
}
#pragma mark - TUIChat 实时聊天回调
- (void)fillWithData:(CustomMapMessageCellData *)data {
[super fillWithData:data];
self.myTextLabel.text = data.text.length > 0 ? data.text : @"我的位置";
[self.mapImage sd_setImageWithURL:[NSURL URLWithString:data.MapPictureImg] placeholderImage:nil];
self.nameLabel.hidden = YES;
}
#pragma mark - 历史记录回调
- (void)setModel:(CustomTUIHistoryModel *)model {
_model = model;
// self.nickNameLabel.text = _model.fromAccountName;
self.myTextLabel.text = _model.address.length > 0 ? _model.address : @"我的位置";
if (_model.isSelf == true) { //头像在右
NSString * MapPictureImg = [self mapLocationImage:_model.Longitude Withlation:_model.Latitude];
[self.mapImage sd_setImageWithURL:[NSURL URLWithString:MapPictureImg] placeholderImage:nil];
[self.heardImage mas_remakeConstraints:^(MASConstraintMaker *make) {
// 历史记录:手动设置头像和名字
self.avatarView.image = [UIImage imageNamed:@"heard_man"];
self.nameLabel.text = @"全科医生";
self.nameLabel.hidden = NO;
if (_model.isSelf) {
self.avatarView.hidden = NO;
[self.avatarView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.height.width.mas_offset(40);
make.top.mas_equalTo(@10);
make.right.mas_equalTo(self.contentView.mas_right).offset(-10);
}];
self.nickNameLabel.textAlignment = NSTextAlignmentRight;
[self.nickNameLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.right.mas_equalTo(self.heardImage.mas_left).offset(-10);
self.nameLabel.textAlignment = NSTextAlignmentRight;
[self.nameLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.right.mas_equalTo(self.avatarView.mas_left).offset(-10);
make.top.mas_equalTo(@10);
make.width.mas_equalTo(@245.0);
make.height.mas_offset(13);
}];
[self.backView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.nickNameLabel.mas_bottom).offset(8);
[self.container mas_remakeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.nameLabel.mas_bottom).offset(8);
make.width.mas_offset(245);
make.right.mas_equalTo(self.heardImage.mas_left).offset(-10);
make.height.mas_equalTo(@170);
make.right.mas_equalTo(self.avatarView.mas_left).offset(-10);
make.height.mas_offset(170);
}];
}else
{
[self.heardImage mas_remakeConstraints:^(MASConstraintMaker *make) {
} else {
self.avatarView.hidden = NO;
[self.avatarView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(@15);
make.top.mas_equalTo(@8);
make.height.width.mas_equalTo(@40);
}];
self.nickNameLabel.textAlignment = NSTextAlignmentLeft;
[self.nickNameLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.heardImage.mas_right).offset(10);
self.nameLabel.textAlignment = NSTextAlignmentLeft;
[self.nameLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.avatarView.mas_right).offset(10);
make.top.mas_equalTo(@10);
make.width.mas_equalTo(@300.0);
make.height.mas_offset(13);
}];
[self.backView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.heardImage.mas_right).offset(10);
[self.container mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.avatarView.mas_right).offset(10);
make.width.mas_offset(245);
make.top.mas_equalTo(self.nickNameLabel.mas_bottom).offset(8);
make.top.mas_equalTo(self.nameLabel.mas_bottom).offset(8);
make.height.mas_offset(170);
}];
}
[self.contentView mas_updateConstraints:^(MASConstraintMaker *make) {
make.top.left.right.mas_equalTo(@0);
make.bottom.mas_equalTo(self.backView.mas_bottom).offset(10);
}];
NSString * MapPictureImg = [self mapLocationImage:_model.Longitude Withlation:_model.Latitude];
[self.mapImage sd_setImageWithURL:[NSURL URLWithString:MapPictureImg] placeholderImage:nil];
NSString * photo = [NSString stringWithFormat:@"%@%@",ResourceAddress,_model.avatar];
NSString * str1 = @"\\";
photo = [photo stringByReplacingOccurrencesOfString:str1 withString:@"/"];
// 加载头像
NSString * photo = [NSString stringWithFormat:@"%@%@", ResourceAddress, _model.avatar];
photo = [photo stringByReplacingOccurrencesOfString:@"\\" withString:@"/"];
NSString *encodedString3 = CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)photo, (CFStringRef)@"!$&'()*+,-./:;=?@_~%#[]", NULL, kCFStringEncodingUTF8));
[self.heardImage sd_setImageWithURL:[NSURL URLWithString:encodedString3] placeholderImage:PLACEHOLD_IMG];
[self.avatarView sd_setImageWithURL:[NSURL URLWithString:encodedString3] placeholderImage:PLACEHOLD_IMG];
}
#pragma mark - 地图静态图
- (NSString *)mapLocationImage:(CGFloat)longLation Withlation:(CGFloat)lation {
// 0: self.longLation,self.lation
//https://a.amap.com/jsapi_demos/static/demo-center/icons/poi-marker-default.png
NSString * staciUrl = @"https://restapi.amap.com/v3/staticmap?key=e9926badd26aaa766d13439c88a83f2d";
NSString * mapKey = @"e9926badd26aaa766d13439c88a83f2d";
NSString * loctaionUrl = @"https://a.amap.com/jsapi_demos/static/demo-center/icons/poi-marker-default.png";
NSString * needMdString = [NSString stringWithFormat:@"key=%@&location=%f,%f&markers=-1,%@,0:%f,%f&size=700*500&zoom=15124d58f7223ac9738312a8d187a21f2b",mapKey,longLation,lation,loctaionUrl,longLation,lation];
NSString * mapUrl = [NSString stringWithFormat:@"%@&location=%f,%f&markers=-1,%@,0:%f,%f&size=700*500&zoom=15&sig=%@",staciUrl,longLation,lation,loctaionUrl,longLation,lation,[self md5:needMdString]];
return mapUrl;
}
//md加密
- (NSString *)md5:(NSString *)str
{
const char *cStr = [str UTF8String];
unsigned char result[16];
CC_MD5(cStr, strlen(cStr), result); // This is the md5 call
CC_MD5(cStr, strlen(cStr), result);
return [NSString stringWithFormat:
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
result[0], result[1], result[2], result[3],
@@ -233,8 +170,83 @@
];
}
#pragma mark - 长按转发微信
- (void)onLongPress:(UILongPressGestureRecognizer *)gesture {
if (gesture.state != UIGestureRecognizerStateBegan) return;
if (![[XJWeChatManager sharedManager] isWeChatInstalled]) {
[EasyTextView showErrorText:@"未安装微信"];
return;
}
UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet];
[alert addAction:[UIAlertAction actionWithTitle:@"转发到微信" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[self shareToWeChat];
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:nil]];
UIViewController *topVC = [self topViewController];
[topVC presentViewController:alert animated:YES completion:nil];
}
- (void)shareToWeChat {
NSString *address;
CGFloat lat = 0, lng = 0;
NSString *imageKey;
// 实时聊天数据
CustomMapMessageCellData *data = (CustomMapMessageCellData *)self.messageData;
if ([data isKindOfClass:[CustomMapMessageCellData class]]) {
address = data.text.length > 0 ? data.text : @"我的位置";
lat = data.latitude;
lng = data.longitude;
imageKey = data.MapPictureImg;
}
// 历史记录数据
else if (_model) {
address = _model.address.length > 0 ? _model.address : @"我的位置";
lat = _model.Latitude;
lng = _model.Longitude;
imageKey = [self mapLocationImage:_model.Longitude Withlation:_model.Latitude];
} else {
return;
}
NSString *encodedAddress = [address stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
NSString *mapUrl = [NSString stringWithFormat:@"https://uri.amap.com/marker?position=%f,%f&name=%@",
lng, lat, encodedAddress ?: @"位置"];
UIImage *thumb = [[SDImageCache sharedImageCache] imageFromCacheForKey:imageKey];
[[XJWeChatManager sharedManager] shareWebpageWithTitle:address
desc:[NSString stringWithFormat:@"经度:%.6f 纬度:%.6f", lng, lat]
webpageUrl:mapUrl
thumbImage:thumb
toScene:0
completion:^(BOOL success, NSString * _Nullable errorMsg) {
if (!success && errorMsg.length > 0) {
[EasyTextView showErrorText:errorMsg];
}
}];
}
- (UIViewController *)topViewController {
UIViewController *rootVC = [UIApplication sharedApplication].keyWindow.rootViewController;
while (rootVC.presentedViewController) {
rootVC = rootVC.presentedViewController;
}
if ([rootVC isKindOfClass:[UINavigationController class]]) {
return [(UINavigationController *)rootVC topViewController];
}
if ([rootVC isKindOfClass:[UITabBarController class]]) {
UIViewController *selected = [(UITabBarController *)rootVC selectedViewController];
if ([selected isKindOfClass:[UINavigationController class]]) {
return [(UINavigationController *)selected topViewController];
}
return selected;
}
return rootVC;
}
@end
@@ -13,6 +13,7 @@
@property (nonatomic, copy) NSString *text;
@property (nonatomic, copy) NSString * MapPictureImg;
@property (nonatomic, assign) CGFloat latitude;
@property (nonatomic, assign) CGFloat longitude;
@end
@@ -16,6 +16,8 @@
cellData.innerMessage = message;
cellData.msgID = message.msgID;
cellData.text = message.locationElem.desc;
cellData.latitude = message.locationElem.latitude;
cellData.longitude = message.locationElem.longitude;
cellData.MapPictureImg = [self mapLocationImage:message.locationElem.longitude Withlation:message.locationElem.latitude];
cellData.reuseId = @"MapMessageCustomCell";
cellData.showAvatar = true;
@@ -0,0 +1,16 @@
//
// EmptyFunctionDevelopmentVC.h
// XinjiangProject
//
// Created by Apple on 2026/2/10.
//
#import "XJBaseViewController.h"
NS_ASSUME_NONNULL_BEGIN
@interface EmptyFunctionDevelopmentVC : XJBaseViewController
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,58 @@
//
// EmptyFunctionDevelopmentVC.m
// XinjiangProject
//
// Created by Apple on 2026/2/10.
//
#import "EmptyFunctionDevelopmentVC.h"
@interface EmptyFunctionDevelopmentVC ()
@end
@implementation EmptyFunctionDevelopmentVC
- (void)viewDidLoad {
[super viewDidLoad];
self.isHidenNaviBar = false;
self.view.backgroundColor = KWhiteColor;
UIImageView * deveImg = [[UIImageView alloc] init];
deveImg.image = [UIImage imageNamed:@"deveEmptyImg"];
[self.view addSubview:deveImg];
[deveImg mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.mas_offset(90);
make.height.mas_offset(76);
make.centerX.mas_equalTo(self.view);
make.centerY.mas_equalTo(self.view).offset(-70) ;
}];
UILabel * dataLabel = [[UILabel alloc] init];
dataLabel.text = @"~ 正在研发中,敬请期待 ~";
dataLabel.font = SYSTEMFONT(14);
dataLabel.textColor = UIColorHex(#B6B6B6);
dataLabel.textAlignment = NSTextAlignmentCenter;
[self.view addSubview:dataLabel];
[dataLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(deveImg.mas_bottom).offset(12);
make.left.right.mas_offset(0);
}];
// 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
@@ -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
@@ -0,0 +1,47 @@
//
// PhysicalWebViewController.m
// XinjiangProject
//
// Created by Apple on 2026/3/24.
//
#import "PhysicalWebViewController.h"
@interface PhysicalWebViewController ()
@end
@implementation PhysicalWebViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.isHidenNaviBar = true;
[self.wkWebView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.top.left.right.mas_offset(0);
make.bottom.mas_offset(-SafetyAreaHeight);
}];
}
#pragma mark - WKNavigationDelegate
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {
[super webView:webView didStartProvisionalNavigation:navigation];
DLog(@"[健康H5] 开始加载: %@", webView.URL.absoluteString);
}
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
[super webView:webView didFinishNavigation:navigation];
DLog(@"[健康H5] 加载完成: %@", webView.URL.absoluteString);
}
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error {
[super webView:webView didFailProvisionalNavigation:navigation withError:error];
DLog(@"[健康H5] 加载失败: %@", error.localizedDescription);
}
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error {
[super webView:webView didFailNavigation:navigation withError:error];
DLog(@"[健康H5] 导航失败: %@", error.localizedDescription);
}
@end
@@ -0,0 +1,16 @@
//
// XJBankWebDetailViewController.h
// XinjiangProject
//
// Created by Apple on 2026/3/11.
//
#import "XJBaseWebViewVC.h"
NS_ASSUME_NONNULL_BEGIN
@interface XJBankWebDetailViewController : XJBaseWebViewVC
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,28 @@
//
// XJBankWebDetailViewController.m
// XinjiangProject
//
// Created by Apple on 2026/3/11.
//
#import "XJBankWebDetailViewController.h"
@interface XJBankWebDetailViewController ()
@end
@implementation XJBankWebDetailViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.isHidenNaviBar = true;
[self.wkWebView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.left.right.mas_offset(0);
make.bottom.mas_offset(-SafetyAreaHeight);
}];
}
// finishActivity 行为与基类一致(pop 返回),无需 override
// 基类 XJBaseWebViewVC 已实现默认 pop 逻辑
@end
@@ -0,0 +1,18 @@
//
// XJHealthArchiveDetailViewController.h
// XinjiangProject
//
// 一人一案 - 健康档案查看页
//
#import "XJBaseViewController.h"
NS_ASSUME_NONNULL_BEGIN
@interface XJHealthArchiveDetailViewController : XJBaseViewController
@property (nonatomic, copy) NSString *idCard;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,16 @@
//
// XJHomePageV2ViewController.h
// XinjiangProject
//
// Created by Apple on 2026/2/9.
//
#import "XJBaseViewController.h"
NS_ASSUME_NONNULL_BEGIN
@interface XJHomePageV2ViewController : XJBaseViewController
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,25 @@
//
// HomeBannerModel.h
// XinjiangProject
//
// Created by Apple on 2026/2/10.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface HomeBannerModel : NSObject
@property (nonatomic, strong) NSString * banner_id;
@property (nonatomic, strong) NSString * tfJump;
@property (nonatomic, strong) NSString * photoUrl;
@property (nonatomic, strong) NSString * jumpUrl;
@property (nonatomic, assign) NSInteger sort;
@property (nonatomic, strong) NSString * showLocation;
@property (nonatomic, strong) NSString * showLocation_dicText;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,18 @@
//
// HomeBannerModel.m
// XinjiangProject
//
// Created by Apple on 2026/2/10.
//
#import "HomeBannerModel.h"
@implementation HomeBannerModel
+ (NSDictionary *)modelCustomPropertyMapper
{
NSDictionary * dic =@{@"banner_id" :@"id"};
return dic;
}
@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,34 @@
//
// XJHomeWatchModel.h
// XinjiangProject
//
// Created by Apple on 2026/3/2.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface XJHomeWatchModel : NSObject
@property (nonatomic, assign) CGFloat latest;
@property (nonatomic, assign) CGFloat max;
@property (nonatomic, assign) CGFloat min;
@property (nonatomic, assign) CGFloat avg;
@property (nonatomic, copy) NSString * latestTime;
@property (nonatomic, copy) NSString * watchNo;
@property (nonatomic, copy) NSString * lastUploadDate;
@property (nonatomic, copy) NSString * watchModel;
@property (nonatomic, copy) NSString * bindDate;
@property (nonatomic, assign) BOOL hasWatch;
//手表详情
@property (nonatomic, copy) NSString * type;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,12 @@
//
// XJHomeWatchModel.m
// XinjiangProject
//
// Created by Apple on 2026/3/2.
//
#import "XJHomeWatchModel.h"
@implementation XJHomeWatchModel
@end
@@ -0,0 +1,106 @@
//
// XJOnePersonCaseModel.h
// XinjiangProject
//
// 一人一案 - 健康档案数据模型
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface XJOnePersonCaseModel : NSObject
#pragma mark - 基本信息
@property (nonatomic, strong) NSString *archiveId; // 档案IDJSON: id
@property (nonatomic, strong) NSString *empId; // 员工ID
@property (nonatomic, strong) NSString *name; // 员工姓名
@property (nonatomic, assign) NSInteger age; // 年龄
@property (nonatomic, strong) NSString *sexCode; // 性别
@property (nonatomic, strong) NSString *idNo; // 身份证号
@property (nonatomic, strong) NSString *phone; // 手机号
@property (nonatomic, strong) NSString *careStatus; // 关爱状态
@property (nonatomic, strong) NSString *workUnit; // 工作单位
@property (nonatomic, strong) NSString *workUnitId; // 单位ID
@property (nonatomic, assign) NSInteger dataLevel; // 数据级别
@property (nonatomic, strong) NSString *negativeList; // 负面清单
@property (nonatomic, strong) NSString *caseInfo; // 一人一案
@property (nonatomic, strong) NSString *riskLevel; // 风险等级
@property (nonatomic, strong) NSString *year; // 年度
@property (nonatomic, strong) NSString *careLiaisonName; // 关爱联络员姓名
#pragma mark - 体重 / BMI
@property (nonatomic, assign) NSInteger flagFat; // 肥胖人员标识(1-是,2-否)
@property (nonatomic, strong) NSString *bmi; // 体检BMI
@property (nonatomic, strong) NSString *height; // 身高(cm
@property (nonatomic, strong) NSString *weight; // 体重(kg
@property (nonatomic, strong) NSString *targetWeight; // 体重管理目标
@property (nonatomic, strong) NSString *targetBmi; // 目标BMI
@property (nonatomic, strong) NSString *bmiCurrent; // 目前BMI监测值
@property (nonatomic, strong) NSString *waistNew; // 最新腰围
@property (nonatomic, strong) NSString *bodyFatPercentNew; // 最新体脂率
@property (nonatomic, strong) NSString *weightRiskType; // 体重风险类型
#pragma mark - 高血压
@property (nonatomic, assign) NSInteger hypertension; // 高血压标识(0-否,1-是)
@property (nonatomic, strong) NSString *systolicPressureAvg; // 收缩压均值
@property (nonatomic, strong) NSString *systolicPressureTarget; // 收缩压控制目标
@property (nonatomic, strong) NSString *systolicPressureCurrent; // 收缩压当前检测值
@property (nonatomic, strong) NSString *diastolicPressureAvg; // 舒张压平均值
@property (nonatomic, strong) NSString *diastolicPressureTarget; // 舒张压控制目标值
@property (nonatomic, strong) NSString *diastolicPressureCurrent; // 舒张压当前检测值
#pragma mark - 糖尿病 / 血糖
@property (nonatomic, assign) NSInteger diabetes; // 糖尿病标识(0-否,1-是)
@property (nonatomic, strong) NSString *glu; // 空腹葡萄糖(GLU
@property (nonatomic, strong) NSString *bloodSugarTarget; // 血糖控制目标值
@property (nonatomic, strong) NSString *bloodSugarCurrent; // 目前血糖监测值
#pragma mark - 血脂
@property (nonatomic, strong) NSString *ldl; // 低密度脂蛋白
@property (nonatomic, strong) NSString *ldlTarget; // 低密度脂蛋白目标控制值
@property (nonatomic, strong) NSString *ldlCurrent; // 低密度脂蛋白当前检测值
@property (nonatomic, strong) NSString *tc; // 总胆固醇
@property (nonatomic, strong) NSString *tcTarget; // 总胆固醇目标控制值
@property (nonatomic, strong) NSString *tcCurrent; // 总胆固醇目前监测值
@property (nonatomic, strong) NSString *tg; // 甘油三酯
@property (nonatomic, strong) NSString *tgTarget; // 甘油三酯目标控制值
@property (nonatomic, strong) NSString *tgCurrent; // 甘油三酯当前监测值
#pragma mark - 尿酸 / 同型半胱氨酸 / 其他指标
@property (nonatomic, strong) NSString *uricAcid; // 尿酸
@property (nonatomic, strong) NSString *uricAcidTarget; // 尿酸目标控制值
@property (nonatomic, strong) NSString *uricAcidCurrent; // 尿酸当前监测值
@property (nonatomic, strong) NSString *homocysteine; // 同型半胱氨酸
@property (nonatomic, strong) NSString *homocysteineTarget; // 同型半胱氨酸目标控制值
@property (nonatomic, strong) NSString *homocysteineCurrent; // 同型半胱氨酸当前监测值
@property (nonatomic, strong) NSString *bloodK; // 血钾
@property (nonatomic, strong) NSString *chd; // 冠心病
@property (nonatomic, strong) NSString *cvd; // 脑血管
@property (nonatomic, strong) NSString *af; // 心房颤动
@property (nonatomic, strong) NSString *ca; // 恶性肿瘤
@property (nonatomic, strong) NSString *indicatorListStr; // 检测指标列表
@property (nonatomic, strong) NSString *indicatorListStrOrigin; // 导入指标列表
@property (nonatomic, strong) NSString *screeningProjects; // 筛查项目汇总
#pragma mark - 方案与反馈
@property (nonatomic, strong) NSString *treatPlan; // 治疗方案
@property (nonatomic, strong) NSString *treatPlanResponse; // 治疗方案反馈
@property (nonatomic, strong) NSString *dietSuggestion; // 饮食建议
@property (nonatomic, strong) NSString *dietSuggestionResponse; // 饮食建议反馈
@property (nonatomic, strong) NSString *sportGuide; // 运动指导
@property (nonatomic, strong) NSString *sportGuideResponse; // 运动指导反馈
@property (nonatomic, strong) NSString *lifeManage; // 日常生活管理
@property (nonatomic, strong) NSString *lifeManageResponse; // 日常生活管理反馈
#pragma mark - 小屋 / 专家意见
@property (nonatomic, strong) NSString *cabinOpinion; // 小屋意见内容
@property (nonatomic, strong) NSString *cabinOpinionTime; // 小屋意见时间
@property (nonatomic, strong) NSString *cabinOpinionUserName; // 小屋意见员工姓名
@property (nonatomic, strong) NSString *expertOpinion; // 专家意见内容
@property (nonatomic, strong) NSString *expertOpinionTime; // 专家意见时间
@property (nonatomic, strong) NSString *expertOpinionUserName; // 专家意见员工姓名
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,16 @@
//
// XJOnePersonCaseModel.m
// XinjiangProject
//
// 一人一案 - 健康档案数据模型
//
#import "XJOnePersonCaseModel.h"
@implementation XJOnePersonCaseModel
+ (NSDictionary *)modelCustomPropertyMapper {
return @{@"archiveId" : @"id"};
}
@end
@@ -0,0 +1,29 @@
//
// BindWatchPopView.h
// XinjiangProject
//
// Created by Apple on 2026/3/12.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, BindWatchType) {
BindWatchTypeBind = 0, // 绑定
BindWatchTypeUnbind // 解绑
};
@interface BindWatchPopView : UIView
- (void)show;
@property (nonatomic, assign) BindWatchType type;
@property (nonatomic, copy) NSString *serialNumber;
@property (nonatomic, copy) void(^actionBlock)(BindWatchType type,
NSString *serialNumber);
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,364 @@
//
// BindWatchPopView.m
// XinjiangProject
//
// Created by Apple on 2026/3/12.
//
#import "BindWatchPopView.h"
@interface BindWatchPopView ()
@property (nonatomic,strong) UIView *bottomView;
@property (nonatomic,strong) UITextField *workNoField;
@property (nonatomic,strong) UITextField *nameField;
@property (nonatomic,strong) UITextField *numberField;
@property (nonatomic,strong) UIButton *actionBtn;
@property (nonatomic,strong) UIView *gestureView;
@end
@implementation BindWatchPopView
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.4];
[self createUI];
[self addKeyboardNotification];
}
return self;
}
#pragma mark - UI
- (void)createUI {
self.bottomView = [[UIView alloc] init];
self.bottomView.backgroundColor = UIColor.whiteColor;
self.bottomView.layer.cornerRadius = 16;
self.bottomView.layer.maskedCorners = kCALayerMinXMinYCorner | kCALayerMaxXMinYCorner;
self.bottomView.layer.masksToBounds = YES;
[self addSubview:self.bottomView];
[self.bottomView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.bottom.offset(0);
make.height.offset(254 + SafetyAreaHeight);
}];
// 添加透明的手势响应区域(覆盖黑色背景)
UIView *gestureView = [[UIView alloc] init];
gestureView.backgroundColor = [UIColor clearColor];
gestureView.userInteractionEnabled = YES;
self.gestureView =gestureView;
[self addSubview:gestureView];
[gestureView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.top.offset(0);
make.bottom.equalTo(self.bottomView.mas_top);
}];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(close)];
[gestureView addGestureRecognizer:tap];
/// 拖动条
UIView *indicator = [[UIView alloc] init];
indicator.backgroundColor = UIColorHex(#D8D8D8);
indicator.layer.cornerRadius = 3;
[self.bottomView addSubview:indicator];
[indicator mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.offset(0);
make.top.offset(8);
make.width.offset(40);
make.height.offset(6);
}];
UILabel *titleLab = [[UILabel alloc] init];
titleLab.text = @"健康监测工具绑定";
titleLab.font = [UIFont boldSystemFontOfSize:16];
titleLab.textAlignment = NSTextAlignmentCenter;
[self.bottomView addSubview:titleLab];
[titleLab mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.offset(0);
make.top.offset(28);
}];
HQUserInfo * user = [HQCommonUtils keyedUnarchiverWithKey:LoginUserInfo];
UILabel * nameLabel = [[UILabel alloc] init];
nameLabel.textColor = UIColorHex(#808080);
nameLabel.font= [UIFont systemFontOfSize:15 weight:UIFontWeightRegular];
nameLabel.textAlignment = NSTextAlignmentLeft;
if (!ValidStr(user.realname)) {
nameLabel.text = [NSString stringWithFormat:@"%@",user.realname];
}
[self.bottomView addSubview:nameLabel];
// workLabel 先布局,固定右侧,宽度自适应内容,不压缩
UILabel * workLabel = [[UILabel alloc] init];
workLabel.textColor = UIColorHex(#808080);
workLabel.font = [UIFont systemFontOfSize:15 weight:UIFontWeightRegular];
workLabel.textAlignment = NSTextAlignmentRight;
// 工号不压缩、不拉伸
[workLabel setContentCompressionResistancePriority:UILayoutPriorityRequired
forAxis:UILayoutConstraintAxisHorizontal];
[workLabel setContentHuggingPriority:UILayoutPriorityRequired
forAxis:UILayoutConstraintAxisHorizontal];
if (!ValidStr(user.workNo)) {
workLabel.text = [NSString stringWithFormat:@"%@", user.workNo];
}
[self.bottomView addSubview:workLabel];
[workLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.mas_offset(-21);
make.top.mas_equalTo(titleLab.mas_bottom).offset(20);
}];
// nameLabel 左侧固定,右侧距 workLabel 一个字符宽度,姓名优先占满剩余空间
[nameLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_offset(16);
make.top.mas_equalTo(titleLab.mas_bottom).offset(20);
// 右侧距 workLabel 一个字符间距(约 15pt
make.right.equalTo(workLabel.mas_left).offset(-15);
}];
// 姓名可压缩,优先级低于工号
[nameLabel setContentCompressionResistancePriority:UILayoutPriorityDefaultLow
forAxis:UILayoutConstraintAxisHorizontal];
[nameLabel setContentHuggingPriority:UILayoutPriorityDefaultLow
forAxis:UILayoutConstraintAxisHorizontal];
// self.workNoField = [self createTextField];
// self.workNoField.userInteractionEnabled = false;
// [self.bottomView addSubview:self.workNoField];
//
// [self.workNoField mas_makeConstraints:^(MASConstraintMaker *make) {
// make.left.offset(82);
// make.right.offset(-15);
// make.height.offset(52);
// make.top.equalTo(titleLab.mas_bottom).offset(20);
// }];
//
//
UILabel * watchSNLab = [[UILabel alloc] init];
watchSNLab.text = @"心脑血管监测工具SN编码:";
watchSNLab.font = MEDIUMFONT(15);
watchSNLab.textAlignment = NSTextAlignmentLeft;
[self.bottomView addSubview:watchSNLab];
[watchSNLab mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(nameLabel.mas_left);
make.right.mas_offset(-21);
make.top.mas_equalTo(nameLabel.mas_bottom).offset(15);
}];
//
//
// self.nameField = [self createTextField];
// self.nameField.enabled = false;
// [self.bottomView addSubview:self.nameField];
//
// [self.nameField mas_makeConstraints:^(MASConstraintMaker *make) {
// make.left.right.height.equalTo(self.workNoField);
// make.top.equalTo(self.workNoField.mas_bottom).offset(16);
// }];
// if (!ValidStr(user.realname)) {
// self.nameField.text = [NSString stringWithFormat:@"%@",user.realname];
// }
//
// UILabel *nameLab = [self createLabel:@"姓 名:"];
// [self.bottomView addSubview:nameLab];
//
// [nameLab mas_makeConstraints:^(MASConstraintMaker *make) {
// make.left.offset(0);
// make.right.equalTo(self.nameField.mas_left);
// make.centerY.equalTo(self.nameField);
// }];
//
//
self.numberField = [self createTextField];
NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:@"请输入SN编码" attributes:
@{NSForegroundColorAttributeName:UIColorHex(#B6B6B6),
NSFontAttributeName:[UIFont systemFontOfSize:16 weight:UIFontWeightMedium]}
];
self.numberField.attributedPlaceholder = attrString;
[self.bottomView addSubview:self.numberField];
[self.numberField mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_offset(15);
make.right.mas_offset(-15);
make.height.mas_offset(52);
make.top.equalTo(watchSNLab.mas_bottom).offset(12);
}];
UIView * lineV = [[UIView alloc] init];
lineV.backgroundColor = UIColorHex(#EAECF1);
[self.bottomView addSubview:lineV];
[lineV mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.mas_offset(0);
make.height.mas_offset(1);
make.top.mas_equalTo(self.numberField.mas_bottom).offset(12);
}];
UIButton *bindBtn = [[UIButton alloc] init];
[bindBtn setTitle:@"解绑" forState:UIControlStateNormal];
bindBtn.backgroundColor = UIColorHex(#21BEBD);
bindBtn.layer.cornerRadius = 22;
bindBtn.titleLabel.font = [UIFont boldSystemFontOfSize:16];
[bindBtn addTarget:self action:@selector(bindwatchClick:) forControlEvents:UIControlEventTouchUpInside];
self.actionBtn = bindBtn;
[self.bottomView addSubview:bindBtn];
[bindBtn mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.offset(0);
make.width.offset(215);
make.height.offset(44);
make.top.equalTo(self.numberField.mas_bottom).offset(23);
}];
}
- (void)updateUI {
if (self.type == BindWatchTypeUnbind) {
// 解绑页面:显示已绑定SN,按钮改为"绑定"并禁用(30%透明度,不可点击)
[self.actionBtn setTitle:@"绑定" forState:UIControlStateNormal];
self.actionBtn.alpha = 0.3;
self.actionBtn.userInteractionEnabled = NO;
self.numberField.text = self.serialNumber;
self.numberField.backgroundColor = UIColorHex(#F5F7FB);
self.numberField.enabled = NO;
} else {
// 绑定页面:正常可点击
[self.actionBtn setTitle:@"绑定" forState:UIControlStateNormal];
self.actionBtn.alpha = 1.0;
self.actionBtn.userInteractionEnabled = YES;
self.numberField.text = @"";
self.numberField.enabled = YES;
self.numberField.backgroundColor = KWhiteColor;
}
}
- (void)bindwatchClick:(UIButton *)btn {
NSString *number = self.numberField.text;
if (self.actionBlock) {
self.actionBlock(self.type, number);
}
[self close];
}
#pragma mark - 创建控件
- (UILabel *)createLabel:(NSString *)text {
UILabel *lab = [[UILabel alloc] init];
lab.text = text;
lab.font = [UIFont systemFontOfSize:16];
lab.textAlignment = NSTextAlignmentCenter;
return lab;
}
- (UITextField *)createTextField {
UITextField *field = [[UITextField alloc] init];
field.backgroundColor = UIColorHex(#F5F7FB);
field.layer.cornerRadius = 8;
field.layer.borderWidth = 1;
field.layer.borderColor = UIColorHex(#E6E6EA).CGColor;
UIView *leftView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 12, 52)];
field.leftView = leftView;
field.leftViewMode = UITextFieldViewModeAlways;
return field;
}
- (void)close {
[self endEditing:YES];
[UIView animateWithDuration:0.25 animations:^{
self.bottomView.transform = CGAffineTransformMakeTranslation(0, 342);
self.alpha = 0;
} completion:^(BOOL finished) {
[self removeFromSuperview];
}];
}
#pragma mark - 键盘
- (void)addKeyboardNotification {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardHide:) name:UIKeyboardWillHideNotification object:nil];
}
- (void)keyboardShow:(NSNotification *)noti {
CGRect rect = [noti.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGFloat keyboardH = rect.size.height;
[UIView animateWithDuration:0.25 animations:^{
self.bottomView.transform = CGAffineTransformMakeTranslation(0, -keyboardH);
// 同步缩小 gestureView,避免手势区域覆盖 bottomView 上的按钮
[self.gestureView mas_updateConstraints:^(MASConstraintMaker *make) {
make.bottom.equalTo(self.bottomView.mas_top).offset(-keyboardH);
}];
[self layoutIfNeeded];
}];
}
- (void)keyboardHide:(NSNotification *)noti {
[UIView animateWithDuration:0.25 animations:^{
self.bottomView.transform = CGAffineTransformIdentity;
// 恢复 gestureView 约束
[self.gestureView mas_updateConstraints:^(MASConstraintMaker *make) {
make.bottom.equalTo(self.bottomView.mas_top).offset(0);
}];
[self layoutIfNeeded];
}];
}
#pragma mark - show
- (void)show {
[self updateUI];
UIWindow *window = UIApplication.sharedApplication.keyWindow;
self.frame = window.bounds;
[window addSubview:self];
self.bottomView.transform = CGAffineTransformMakeTranslation(0, 342);
[UIView animateWithDuration:0.25 animations:^{
self.bottomView.transform = CGAffineTransformIdentity;
}];
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
@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,184 @@
//
// 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×20bottom = 图片 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 = [self dateString: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;
}
/// 截取日期部分 "2026-04-23 15:28:57" → "2026-04-23",容错处理
- (NSString *)dateString:(NSString *)datetime {
if (!datetime || ![datetime isKindOfClass:[NSString class]] || datetime.length == 0) return @"";
NSArray *parts = [datetime componentsSeparatedByString:@" "];
return parts.firstObject ?: datetime;
}
@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,434 @@
//
// 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 = UIColorHex(#14BEBE);
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.textColor = [UIColor colorWithRed:0.6 green:0.6 blue:0.6 alpha:1.0];
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,206 @@
//
// 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 = UIColorHex(#14BEBE);
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.textColor = [UIColor colorWithRed:0.6 green:0.6 blue:0.6 alpha:1.0];
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
@@ -0,0 +1,19 @@
//
// XJHomePageV2BottomTools.h
// XinjiangProject
//
// Created by Apple on 2026/2/9.
//
#import <UIKit/UIKit.h>
typedef void(^bottomToolsImgClick)(NSInteger imgIndex);
NS_ASSUME_NONNULL_BEGIN
@interface XJHomePageV2BottomTools : UIView
@property (nonatomic, strong)bottomToolsImgClick imgeBlock;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,124 @@
//
// XJHomePageV2BottomTools.m
// XinjiangProject
//
// Created by Apple on 2026/2/9.
//
#import "XJHomePageV2BottomTools.h"
@implementation XJHomePageV2BottomTools
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
}
*/
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self createUI];
}
return self;
}
- (void)createUI {
UIImageView * leftImage = [[UIImageView alloc] init];
leftImage.image = [UIImage imageNamed:@"bottomLeftImg"];
leftImage.userInteractionEnabled = true;
[self addSubview:leftImage];
[leftImage mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_offset(0);
make.top.bottom.mas_offset(0);
make.width.mas_equalTo(self.mas_width).multipliedBy(0.5).offset(10);
}];
UILabel * leftTitle = [[UILabel alloc] init];
leftTitle.text = @"智慧体重";
leftTitle.textColor = UIColorHex(#252535);
leftTitle.font = MEDIUMFONT(15);
[leftImage addSubview:leftTitle];
[leftTitle mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_offset(27);
make.top.mas_offset(25);
}];
UILabel * leftDetailTitle = [[UILabel alloc] init];
leftDetailTitle.text = @"智追体重 科学控重";
leftDetailTitle.textColor = UIColorHex(#808080);
leftDetailTitle.font = [UIFont systemFontOfSize:12 weight:UIFontWeightRegular];
[leftImage addSubview:leftDetailTitle];
[leftDetailTitle mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_offset(27);
make.top.mas_equalTo(leftTitle.mas_bottom).offset(8);
}];
UIImageView * rightImage = [[UIImageView alloc] init];
rightImage.image = [UIImage imageNamed:@"bottomRightImg"];
rightImage.userInteractionEnabled = true;
[self addSubview:rightImage];
[rightImage mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.mas_offset(0);
make.top.bottom.mas_offset(0);
make.width.mas_equalTo(self.mas_width).multipliedBy(0.5).offset(10);
}];
UILabel * rightTitle = [[UILabel alloc] init];
rightTitle.text = @"一人一案";
rightTitle.textColor = UIColorHex(#252535);
rightTitle.font = MEDIUMFONT(15);
[rightImage addSubview:rightTitle];
[rightTitle mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_offset(27);
make.top.mas_offset(25);
}];
UILabel * rightDetailTitle = [[UILabel alloc] init];
rightDetailTitle.text = @"定制专属健康方案";
rightDetailTitle.textColor = UIColorHex(#808080);
rightDetailTitle.font = [UIFont systemFontOfSize:12 weight:UIFontWeightRegular];
[rightImage addSubview:rightDetailTitle];
[rightDetailTitle mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_offset(27);
make.top.mas_equalTo(rightTitle.mas_bottom).offset(8);
}];
UITapGestureRecognizer *tap1 =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(leftImageTap:)];
[leftImage addGestureRecognizer:tap1];
UITapGestureRecognizer *tap2 =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(rightImageTap:)];
[rightImage addGestureRecognizer:tap2];
}
- (void)leftImageTap:(UITapGestureRecognizer *)tap {
if (self.imgeBlock) {
self.imgeBlock(1);
}
}
- (void)rightImageTap:(UITapGestureRecognizer *)tap {
if (self.imgeBlock) {
self.imgeBlock(2);
}
}
@end
@@ -0,0 +1,45 @@
//
// XJHomePageV2HeardView.h
// XinjiangProject
//
// Created by Apple on 2026/2/9.
//
#import <UIKit/UIKit.h>
#import "XJHomeWatchModel.h"
NS_ASSUME_NONNULL_BEGIN
@protocol homePageHeardDelegate <NSObject>
- (void)SDTitleItemClick;
- (void)heardTopToolsClick:(NSInteger)type;
- (void)heardImageClick:(NSInteger)imageIndex;
- (void)watchDetailClick;
@end
@interface XJHomePageV2HeardView : UIView
@property (nonatomic, strong) NSArray * dataArray;
@property (nonatomic, weak) id<homePageHeardDelegate> homeTopDelegate;
@property (nonatomic, strong) NSArray * bannerArray;
@property (nonatomic, strong) XJHomeWatchModel * model;
@property (nonatomic, strong) NSArray * watchArray;
@property (nonatomic, copy, nullable) void (^bindDeviceBlock)(NSString * watchNo);
@property (nonatomic, strong) NSString * totalponits;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,520 @@
//
// XJHomePageV2HeardView.m
// XinjiangProject
//
// Created by Apple on 2026/2/9.
//
#import "XJHomePageV2HeardView.h"
#import "SDCycleScrollView.h"
#import "HealthyToolsView.h"
#import "MyMessageModel.h"
#import "HomeBannerModel.h"
#import "XJHomeWatchDataView.h"
@interface XJHomePageV2HeardView ()<SDCycleScrollViewDelegate>
@property (nonatomic, strong) UIView *messageview;
@property (nonatomic, strong) SDCycleScrollView *adTitleView;
@property (nonatomic, strong) UILabel *nameLabel;
@property (nonatomic, strong) XJHomeWatchDataView *watchDataView;
@property (nonatomic, strong) UILabel * toolTitleLab;
@end
@implementation XJHomePageV2HeardView
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
}
*/
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.backgroundColor = CViewBgColor;
[self createUI];
}
return self;
}
- (void)createUI {
UIImageView * backImage = [[UIImageView alloc] init];
backImage.userInteractionEnabled = true;
backImage.image = [UIImage imageNamed:@"homePageHeardTop"];
[self addSubview:backImage];
[backImage mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.mas_offset(0);
make.height.mas_offset(kRealValue(405));
make.top.mas_offset(0);
}];
UIView * messageView = [[UIView alloc] init];
messageView.layer.cornerRadius = 8;
messageView.layer.masksToBounds = true;
messageView.userInteractionEnabled = true;
self.messageview = messageView;
[backImage addSubview:messageView];
[messageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_offset(12);
make.right.mas_offset(-12);
make.height.mas_offset(40);
make.top.mas_offset(kRealValue(145));
}];
UIImageView * messageImg = [[UIImageView alloc] init];
messageImg.image = [UIImage imageNamed:@"messageBackImg"];
messageImg.userInteractionEnabled = true;
[messageView addSubview:messageImg];
[messageImg mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.offset(0);
}];
UIImageView * labaImg = [[UIImageView alloc] init];
labaImg.image = [UIImage imageNamed:@"homeLaba"];
[messageView addSubview:labaImg];
[labaImg mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_offset(10);
make.width.height.mas_offset(17);
make.centerY.mas_equalTo(messageView);
}];
self.adTitleView = [SDCycleScrollView cycleScrollViewWithFrame:CGRectZero delegate:self placeholderImage:nil];
self.adTitleView.titleLabelTextColor = UIColorHex(#77849E);
self.adTitleView.titleLabelBackgroundColor = KClearColor;
self.adTitleView.scrollDirection = UICollectionViewScrollDirectionVertical;
self.adTitleView.onlyDisplayText = YES;
NSArray *titles = @[];
self.adTitleView.titlesGroup = [titles copy];
[self.adTitleView disableScrollGesture];
[messageView addSubview:self.adTitleView];
[self.adTitleView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(labaImg.mas_right).offset(9);
make.right.equalTo(messageView).offset(-12);
make.top.bottom.equalTo(messageView);
}];
DLog(@"%@", self.adTitleView);
if ([HQCommonUtils isLogin]) {
messageView.hidden = false;
}else
{
messageView.hidden = true;
}
self.nameLabel = [[UILabel alloc] init];
self.nameLabel.textColor = KWhiteColor;
self.nameLabel.font = BOLDSYSTEMFONT(12);
self.nameLabel.text = @"Hi~";
self.nameLabel.textAlignment = NSTextAlignmentLeft;
[backImage addSubview:self.nameLabel];
[self.nameLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_offset(24);
make.right.mas_offset(-12);
if (messageView.hidden == false) {
make.top.mas_equalTo(messageView.mas_bottom).offset(10);
}else
{
make.top.mas_offset(kRealValue(145));
}
}];
//姓名
if ([HQCommonUtils isLogin]) {
HQUserInfo * user = [HQCommonUtils keyedUnarchiverWithKey:LoginUserInfo];
self.nameLabel.text = [NSString stringWithFormat:@"Hi~%@",user.realname];
}
//
UIView * toolsView = [[UIView alloc] init];
toolsView.layer.cornerRadius = 12;
toolsView.layer.masksToBounds = true;
toolsView.alpha = 0.5;
toolsView.backgroundColor = UIColorHex(#EBFFFD);
[backImage addSubview:toolsView];
[toolsView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_offset(6);
make.right.mas_offset(-6);
make.top.mas_equalTo(self.nameLabel.mas_bottom).offset(8);
make.height.mas_offset(126);
}];
UILabel * toolTitleLab = [[UILabel alloc] init];
toolTitleLab.text = @"今日健康积分 0 | 累计积分 0";
toolTitleLab.textColor = UIColorHex(#116F6B);
toolTitleLab.font = [UIFont systemFontOfSize:12 weight:UIFontWeightRegular];
self.toolTitleLab = toolTitleLab;
[toolsView addSubview:toolTitleLab];
[toolTitleLab mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_offset(18);
make.top.mas_offset(8);
make.right.mas_offset(0);
}];
NSArray * toolsArray = @[@[@"健康档案",@"homeTools_1"],@[@"健康体检",@"homeTools_2"],@[@"应急就医",@"homeTools_3"],@[@"健康银行",@"homeTools_4"]];
UIView * customView = [[UIView alloc] init];
customView.layer.cornerRadius = 6;
customView.layer.masksToBounds = true;
customView.backgroundColor = KWhiteColor;
[backImage addSubview:customView];
[customView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_offset(12);
make.right.mas_offset(-12);
make.top.mas_equalTo(self.nameLabel.mas_bottom).offset(35);
make.height.mas_offset(93);
}];
NSInteger count = toolsArray.count;
NSMutableArray *itemViews = [NSMutableArray array];
for (int i = 0; i < count; i++) {
NSArray *item = toolsArray[i];
UIView *itemView = [[UIView alloc] init];
itemView.tag = i;
itemView.userInteractionEnabled = YES;
[customView addSubview:itemView];
[itemViews addObject:itemView];
/// 点击手势
UITapGestureRecognizer *tap =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(cenetrFunctionItemTap:)];
[itemView addGestureRecognizer:tap];
/// 上图
UIImageView *icon = [[UIImageView alloc] init];
icon.image = [UIImage imageNamed:item[1]];
icon.contentMode = UIViewContentModeScaleAspectFit;
[itemView addSubview:icon];
/// 下文
UILabel *title = [[UILabel alloc] init];
title.text = item[0];
title.font = MEDIUMFONT(14);
title.textColor = [UIColor blackColor];
title.textAlignment = NSTextAlignmentCenter;
[itemView addSubview:title];
/// icon 布局
[icon mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(10);
make.centerX.equalTo(itemView);
make.width.height.mas_equalTo(40);
}];
/// label 布局
[title mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(icon.mas_bottom).offset(6);
make.left.right.equalTo(itemView);
}];
}
[itemViews mas_distributeViewsAlongAxis:MASAxisTypeHorizontal
withFixedSpacing:0
leadSpacing:0
tailSpacing:0];
[itemViews mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.bottom.equalTo(customView);
}];
// 先用图片实现
NSArray *imageConfigs = @[
@{@"img": @"heardTopImg_1", @"height": @65},
@{@"img": @"heardTopImg_2", @"height": @60},
];
SDCycleScrollView *lastImageView = nil;
for (int i = 0; i < imageConfigs.count; i++) {
NSDictionary *config = imageConfigs[i];
SDCycleScrollView * SDCycles = [SDCycleScrollView cycleScrollViewWithFrame:CGRectZero delegate:self placeholderImage: [UIImage imageNamed:@"placeholder"]];
SDCycles.layer.cornerRadius = 12;
SDCycles.layer.masksToBounds = true;
SDCycles.backgroundColor = KWhiteColor;
// UIViewContentModeScaleToFill,
// UIViewContentModeScaleAspectFit, // contents scaled to fit with fixed aspect. remainder is transparent
// UIViewContentModeScaleAspectFill,
SDCycles.bannerImageViewContentMode = UIViewContentModeScaleToFill;
SDCycles.currentPageDotImage = [UIImage imageNamed:@"pageControlDot"];
SDCycles.pageDotImage = [UIImage imageNamed:@"pageControlNomalDot"]; SDCycles.pageDotColor = KWhiteColor;
SDCycles.tag = 50 + i; //
SDCycles.pageControlAliment = SDCycleScrollViewPageContolAlimentCenter;
SDCycles.pageControlDotSize = CGSizeMake(6, 4);
[self addSubview:SDCycles];
// UIImageView *imageView = [[UIImageView alloc] init];
// imageView.image = [UIImage imageNamed:config[@"img"]];
// imageView.userInteractionEnabled = YES;
// imageView.tag = 50 + i; // 用于区分点击的是哪一个
// imageView.contentMode = UIViewContentModeScaleAspectFill;
// imageView.layer.cornerRadius = 8;
// imageView.layer.masksToBounds = true;
// imageView.clipsToBounds = YES;
//
// [self addSubview:imageView];
/// 点击手势
// UITapGestureRecognizer *tap =
// [[UITapGestureRecognizer alloc] initWithTarget:self
// action:@selector(topImageTap:)];
//
// [SDCycles addGestureRecognizer:tap];
/// 布局
[SDCycles mas_makeConstraints:^(MASConstraintMaker *make) {
if (lastImageView) {
make.top.equalTo(lastImageView.mas_bottom).offset(12);
} else {
make.top.equalTo(toolsView.mas_bottom).offset(12);
}
make.left.mas_equalTo(12);
make.right.mas_equalTo(-12);
make.height.mas_equalTo([config[@"height"] floatValue]);
}];
lastImageView = SDCycles;
}
[self layoutIfNeeded];
// @{@"img": @"heardTopImg_3", @"height": @120}
//手表数据
self.watchDataView = [[XJHomeWatchDataView alloc] initWithFrame:CGRectZero];
// self.watchDataView.tabTitles = @[@"心脑血管", @"癌症", @"慢呼", @"糖尿病", @"亚健康"];
self.watchDataView.tabSelectBlock = ^(NSInteger index, NSString *title) {
DLog(@"选中 %ld - %@", index, title);
// 切换 collectionView 到对应 page
};
MJWeakSelf
// self.watchDataView.tabSelectBlock = ^(NSInteger index, NSString *title) {
// // tab 0 完整高度,其他收缩(header 40 + tab 52 + 内容区 68 = 160
// CGFloat height = (index == 0) ? 250 : 250;
// [weakSelf.watchDataView mas_updateConstraints:^(MASConstraintMaker *make) {
// make.height.mas_equalTo(height);
// }];
// [UIView animateWithDuration:0.25 animations:^{
// [weakSelf layoutIfNeeded];
// }];
// };
self.watchDataView.watchMoreBlock = ^{
if ([self.homeTopDelegate respondsToSelector:@selector(watchDetailClick)]) {
[self.homeTopDelegate watchDetailClick];
}
};
//绑定手表
self.watchDataView.bindDeviceBlock = ^(NSString * _Nonnull number) {
weakSelf.bindDeviceBlock(number);
};
[self addSubview:self.watchDataView];
[self.watchDataView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_offset(12);
make.right.mas_offset(-12);
make.height.mas_offset(104);
make.top.mas_equalTo(lastImageView.mas_bottom).offset(12);
}];
// 高度变化回调:无数据/未登录 104,有数据 205
self.watchDataView.heightChangeBlock = ^(CGFloat height) {
[weakSelf.watchDataView mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(height);
}];
[UIView animateWithDuration:0.25 animations:^{
[weakSelf layoutIfNeeded];
}];
};
SDCycleScrollView * questionSDCycles = [SDCycleScrollView cycleScrollViewWithFrame:CGRectZero delegate:self placeholderImage:[UIImage imageNamed:@"placeholder"]];
questionSDCycles.backgroundColor = KWhiteColor;
questionSDCycles.layer.cornerRadius = 12;
questionSDCycles.layer.masksToBounds = true;
questionSDCycles.bannerImageViewContentMode = UIViewContentModeScaleToFill;
questionSDCycles.currentPageDotImage = [UIImage imageNamed:@"pageControlDot"];
questionSDCycles.pageDotImage = [UIImage imageNamed:@"pageControlNomalDot"]; questionSDCycles.pageDotColor = KWhiteColor;
questionSDCycles.tag = 52; //
questionSDCycles.pageControlAliment = SDCycleScrollViewPageContolAlimentCenter;
questionSDCycles.pageControlDotSize = CGSizeMake(6, 4);
[self addSubview:questionSDCycles];
/// 布局
[questionSDCycles mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.watchDataView.mas_bottom).offset(12);
make.left.mas_equalTo(12);
make.right.mas_equalTo(-12);
make.height.mas_equalTo(@120);
make.bottom.mas_offset(-12);
}];
}
-(void)setTotalponits:(NSString *)totalponits {
_totalponits = totalponits;
self.toolTitleLab.text = _totalponits;
}
- (void)setModel:(XJHomeWatchModel *)model {
_model = model;
self.watchDataView.watchModel = _model;
}
- (void)setWatchArray:(NSArray *)watchArray {
_watchArray = watchArray;
self.watchDataView.dataList = _watchArray;
}
- (void)setDataArray:(NSArray *)dataArray {
_dataArray = dataArray;
NSMutableArray * titlesArray = [[NSMutableArray alloc] init];
for (MyMessageModel * model in _dataArray) {
//直接处理图片吧
[titlesArray addObject:model.title];
}
if(titlesArray.count == 0) {
self.messageview.hidden = true;
[self.nameLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.mas_offset(24);
make.right.mas_offset(-12);
make.top.mas_offset(kRealValue(145));
}];
[self layoutIfNeeded];
}else
{
self.messageview.hidden = false;
[self.nameLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.mas_offset(24);
make.right.mas_offset(-12);
make.top.mas_equalTo(self.messageview.mas_bottom).offset(10);
}];
[self layoutIfNeeded];
}
self.adTitleView.titlesGroup = titlesArray;
}
- (void)setBannerArray:(NSArray *)bannerArray {
// 1 首页体检模块。2 首页心理模块 3 健康咨询模块。4 ai随访
//处理数据
_bannerArray = bannerArray;
SDCycleScrollView * oneSDView = [self viewWithTag:50]; //体检
SDCycleScrollView * twoSDView = [self viewWithTag:51]; //EAP
SDCycleScrollView * threeSDView = [self viewWithTag:52]; //健康咨询
NSMutableArray * tijianArray = [[NSMutableArray alloc] init];
NSMutableArray * eapArray = [[NSMutableArray alloc] init];
NSMutableArray * questionArray = [[NSMutableArray alloc] init];
for (HomeBannerModel * model in _bannerArray) {
//直接处理图片吧
NSString * photo = [NSString stringWithFormat:@"%@%@",ResourceAddress,model.photoUrl];
NSString * str1 = @"\\";
photo = [ photo stringByReplacingOccurrencesOfString:str1 withString:@"/"];
NSString *encodedString3 = CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef) photo, (CFStringRef)@"!$&'()*+,-./:;=?@_~%#[]", NULL, kCFStringEncodingUTF8));
if ([model.showLocation intValue] == 1) {
[tijianArray addObject:encodedString3];
}
if ([model.showLocation intValue] == 2) {
[eapArray addObject:encodedString3];
}
if ([model.showLocation intValue] == 3) {
[questionArray addObject:encodedString3];
}
}
oneSDView.imageURLStringsGroup = tijianArray;
twoSDView.imageURLStringsGroup = eapArray;
threeSDView.imageURLStringsGroup = questionArray;
}
- (void)cycleScrollView:(SDCycleScrollView *)cycleScrollView didSelectItemAtIndex:(NSInteger)index {
DLog(@"++%ld",index);
if (cycleScrollView == self.adTitleView) {
if ([self.homeTopDelegate respondsToSelector:@selector(SDTitleItemClick)]) {
[self.homeTopDelegate SDTitleItemClick];
}
}else
{
CGFloat currentBanner = cycleScrollView.tag;
if([self.homeTopDelegate respondsToSelector:@selector(heardImageClick:)]) {
[self.homeTopDelegate heardImageClick:currentBanner];
}
}
}
- (void)cenetrFunctionItemTap:(UITapGestureRecognizer *)tap {
UIView *view = tap.view;
NSInteger index = view.tag;
if([self.homeTopDelegate respondsToSelector:@selector(heardTopToolsClick:)]) {
[self.homeTopDelegate heardTopToolsClick:index];
}
}
@end
@@ -0,0 +1,32 @@
//
// XJHomePageV2QuestionTableView.h
// XinjiangProject
//
// Created by Apple on 2026/2/9.
//
#import <UIKit/UIKit.h>
#import "HealthyQuestionHeardView.h"
#import "HomeQusetionRecordsModel.h"
NS_ASSUME_NONNULL_BEGIN
typedef void(^doctorBlock)(HomeQusetionRecordsModel * model);
typedef void(^lookMoreDoctorBlock)();
@interface XJHomePageV2QuestionTableView : UITableView
@property (nonatomic, strong) NSArray * dataArray;
@property (nonatomic, copy) doctorBlock tableBlock;
@property (nonatomic, copy) lookMoreDoctorBlock lookMoreBlock;
@property (nonatomic, weak)id <questionToolsProtocl> headDelegate;
@property (nonatomic, copy) NSString * haveSessioning;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,164 @@
//
// XJHomePageV2QuestionTableView.m
// XinjiangProject
//
// Created by Apple on 2026/2/9.
//
#import "XJHomePageV2QuestionTableView.h"
#import "HealthyQuestionTable.h"
#import "HealthyDoctorTableViewCell.h"
#import "QustionHomeRecordsCell.h"
@interface XJHomePageV2QuestionTableView ()<UITableViewDelegate,UITableViewDataSource>
@end
@implementation XJHomePageV2QuestionTableView
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style
{
if (self = [super initWithFrame:frame style:style])
{
[self createUI];
}
return self;
}
- (void)createUI
{
if (@available(iOS 11.0, *)) {
self.estimatedRowHeight = 0;
self.estimatedSectionFooterHeight = 0;
self.estimatedSectionHeaderHeight = 0;
self.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
self.delegate = self;
self.dataSource = self;
self.separatorStyle = UITableViewCellSeparatorStyleNone;
self.showsVerticalScrollIndicator = NO;
self.estimatedRowHeight = 60;
self.rowHeight = UITableViewAutomaticDimension;
}
- (void)setDataArray:(NSArray *)dataArray
{
_dataArray = dataArray;
[self reloadData];
}
#pragma mark - DataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.dataArray.count;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString * cellID = @"cellID";
QustionHomeRecordsCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];
if (!cell) {
cell = [[QustionHomeRecordsCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID];
}
cell.model = self.dataArray[indexPath.row];
cell.backgroundColor = CViewBgColor;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
HomeQusetionRecordsModel * model = self.dataArray[indexPath.row];
if (self.tableBlock) {
self.tableBlock(model);
}
}
- (CGFloat )tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return kRealValue(40);
}
- (CGFloat )tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
{
return CGFLOAT_MIN;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
UIView * sectionHeardView = [[UIView alloc] initWithFrame:CGRectMake(12, 0, kScreenWidth -24, kRealValue(40))];
sectionHeardView.backgroundColor = CViewBgColor;
UIImageView * heaedImg = [[UIImageView alloc] init];
heaedImg.image = [UIImage imageNamed:@"heaedTitleBackImg"];
[sectionHeardView addSubview:heaedImg];
[heaedImg mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_offset(0);
}];
UILabel * todayLabel = [[UILabel alloc] initWithFrame:CGRectMake(kRealValue(15), kRealValue(16), 190, 16)];
todayLabel.text = @"专家咨询记录";
todayLabel.textAlignment = NSTextAlignmentLeft;
todayLabel.textColor = CFontColor1;
todayLabel.font = BOLDSYSTEMFONT(16);
[sectionHeardView addSubview:todayLabel];
[todayLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_offset(13);
make.top.bottom.mas_offset(0);
}];
UIButton * moreBtn = [[UIButton alloc] init];
[moreBtn setImage:[UIImage imageNamed:@"wacthRightAccow"] forState:UIControlStateNormal];
[moreBtn addTarget:self action:@selector(moreClick) forControlEvents:UIControlEventTouchUpInside];
[sectionHeardView addSubview:moreBtn];
[moreBtn mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.mas_offset(-8);
make.centerY.mas_equalTo(sectionHeardView);
make.width.height.mas_offset(30);
}];
return sectionHeardView;
}
- (void)moreClick {
if (self.lookMoreBlock) {
self.lookMoreBlock();
}
}
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
return [[UIView alloc] init];
}
@end
@@ -0,0 +1,23 @@
//
// XJHomeWatchCollectionViewCell.h
// XinjiangProject
//
// Created by Apple on 2026/3/2.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface XJHomeWatchCollectionViewCell : UICollectionViewCell
/// 设置数据模型(id 兼容 HomePageV2WATACHModel
- (void)configureWithData:(nullable id)data;
/// 父 View Tab 切换时调用,更新展示类型
/// 0=心率 1=血氧 2=压力 3=体温 4=睡眠
- (void)updateTabIndex:(NSInteger)tabIndex;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,441 @@
//
// XJHomeWatchCollectionViewCell.m
// XinjiangProject
//
// Created by Apple on 2026/3/2.
//
#import "XJHomeWatchCollectionViewCell.h"
#import "XJHomeWatchModel.h"
#import <Masonry/Masonry.h>
// tabIndex 对应关系(由父 View XJHomeWatchDataView 的 Tab 传入)
// 0=心率 1=血氧 2=压力 3=体温 4=睡眠
@interface XJHomeWatchCollectionViewCell ()
@property (nonatomic, strong) UIImageView *heartImg; // 类型图标
@property (nonatomic, strong) UILabel *updateLabel; // "最新心率"
@property (nonatomic, strong) UILabel *updateTimeLabel; // 最后更新时间(图标旁)
@property (nonatomic, strong) UILabel *dataLabel; // 右侧大数值
@property (nonatomic, strong) UILabel *backLeftLabel; // 左卡标题 "心率范围"
@property (nonatomic, strong) UILabel *leftDataLabel; // 左卡数据
@property (nonatomic, strong) UILabel *backRightLabel; // 右卡标题 "平均心率"
@property (nonatomic, strong) UILabel *rightDataLabel; // 右卡数据
//@property (nonatomic, strong) UILabel *timeLabel; // 底部更新时间
@property (nonatomic, strong) UIView *emptyView;
@property (nonatomic, strong) XJHomeWatchModel *model;
@property (nonatomic, assign) NSInteger currentTabIndex;
@end
@implementation XJHomeWatchCollectionViewCell
#pragma mark - Init
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.layer.cornerRadius = 8.0f;
self.layer.masksToBounds = YES;
self.backgroundColor = KWhiteColor;
[self createUI];
}
return self;
}
#pragma mark - UI
- (void)createUI {
// ── 类型图标 ──────────────────────────────────────────
self.heartImg = [[UIImageView alloc] init];
self.heartImg.image = [UIImage imageNamed:@"homePageXinlv"];
[self.contentView addSubview:self.heartImg];
[self.heartImg mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.contentView).offset(15);
make.top.equalTo(self.contentView).offset(13);
make.size.mas_equalTo(CGSizeMake(32, 32));
}];
// ── 类型标题 "最新心率" ───────────────────────────────
self.updateLabel = [[UILabel alloc] init];
self.updateLabel.font = BOLDSYSTEMFONT(14);
self.updateLabel.textColor = UIColorHex(#252535);
self.updateLabel.text = @"最新心率";
[self.contentView addSubview:self.updateLabel];
[self.updateLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.heartImg.mas_right).offset(12);
make.top.equalTo(self.heartImg);
make.height.mas_equalTo(15);
}];
// ── 图标旁更新时间 ───────────────────────────────────
self.updateTimeLabel = [[UILabel alloc] init];
self.updateTimeLabel.font = MEDIUMFONT(12);
self.updateTimeLabel.textColor = UIColorHex(#77849E);
self.updateTimeLabel.text = @"--";
[self.contentView addSubview:self.updateTimeLabel];
[self.updateTimeLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.updateLabel);
make.top.equalTo(self.updateLabel.mas_bottom).offset(11);
make.height.mas_equalTo(11);
}];
// ── 右侧大数值 ───────────────────────────────────────
self.dataLabel = [[UILabel alloc] init];
self.dataLabel.textColor = UIColorHex(#252535);
self.dataLabel.text = @"--次/分钟";
self.dataLabel.textAlignment = NSTextAlignmentRight;
self.dataLabel.adjustsFontSizeToFitWidth = YES;
[self.contentView addSubview:self.dataLabel];
[self.dataLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.equalTo(self.contentView).offset(-15);
make.centerY.equalTo(self.heartImg);
make.width.mas_equalTo(180);
make.height.mas_equalTo(30);
}];
// ── 左卡片 ───────────────────────────────────────────
UIView *backLeft = [[UIView alloc] init];
backLeft.layer.cornerRadius = 8;
backLeft.layer.masksToBounds = YES;
backLeft.backgroundColor = UIColorHex(#F6F6F6);
[self.contentView addSubview:backLeft];
[backLeft mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.contentView).offset(15);
make.top.equalTo(self.heartImg.mas_bottom).offset(21);
make.right.equalTo(self.contentView.mas_centerX).offset(-7.5);
make.height.mas_equalTo(72);
}];
UIView *redLine = [[UIView alloc] init];
redLine.layer.cornerRadius = 2;
redLine.layer.masksToBounds = YES;
redLine.backgroundColor = CNavBgColor;
[backLeft addSubview:redLine];
[redLine mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(backLeft).offset(15);
make.top.equalTo(backLeft).offset(16);
make.size.mas_equalTo(CGSizeMake(4, 11));
}];
self.backLeftLabel = [[UILabel alloc] init];
self.backLeftLabel.font = BOLDSYSTEMFONT(14);
self.backLeftLabel.textColor = UIColorHex(#252535);
self.backLeftLabel.text = @"心率范围";
[backLeft addSubview:self.backLeftLabel];
[self.backLeftLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(redLine.mas_right).offset(8);
make.top.equalTo(backLeft).offset(13);
make.height.mas_equalTo(15);
}];
self.leftDataLabel = [[UILabel alloc] init];
self.leftDataLabel.font = BOLDSYSTEMFONT(16);
self.leftDataLabel.textColor = UIColorHex(#252535);
self.leftDataLabel.text = @"--";
self.leftDataLabel.adjustsFontSizeToFitWidth = YES;
[backLeft addSubview:self.leftDataLabel];
[self.leftDataLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(backLeft).offset(14);
make.right.equalTo(backLeft).offset(-5);
make.top.equalTo(self.backLeftLabel.mas_bottom).offset(13);
make.height.mas_equalTo(17);
}];
// ── 右卡片 ───────────────────────────────────────────
UIView *backRight = [[UIView alloc] init];
backRight.layer.cornerRadius = 8;
backRight.layer.masksToBounds = YES;
backRight.backgroundColor = UIColorHex(#F6F6F6);
[self.contentView addSubview:backRight];
[backRight mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.equalTo(self.contentView).offset(-15);
make.left.equalTo(self.contentView.mas_centerX).offset(7.5);
make.top.equalTo(backLeft);
make.height.mas_equalTo(72);
}];
UIView *blueLine = [[UIView alloc] init];
blueLine.layer.cornerRadius = 2;
blueLine.layer.masksToBounds = YES;
blueLine.backgroundColor = UIColorHex(#30A9FF);
[backRight addSubview:blueLine];
[blueLine mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(backRight).offset(15);
make.top.equalTo(backRight).offset(16);
make.size.mas_equalTo(CGSizeMake(4, 11));
}];
self.backRightLabel = [[UILabel alloc] init];
self.backRightLabel.font = BOLDSYSTEMFONT(14);
self.backRightLabel.textColor = UIColorHex(#252535);
self.backRightLabel.text = @"平均心率";
[backRight addSubview:self.backRightLabel];
[self.backRightLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(blueLine.mas_right).offset(8);
make.top.equalTo(backRight).offset(13);
make.height.mas_equalTo(15);
}];
self.rightDataLabel = [[UILabel alloc] init];
self.rightDataLabel.font = BOLDSYSTEMFONT(16);
self.rightDataLabel.textColor = UIColorHex(#252535);
self.rightDataLabel.text = @"--";
self.rightDataLabel.adjustsFontSizeToFitWidth = YES;
[backRight addSubview:self.rightDataLabel];
[self.rightDataLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(backRight).offset(14);
make.right.equalTo(backRight).offset(-5);
make.top.equalTo(self.backRightLabel.mas_bottom).offset(13);
make.height.mas_equalTo(17);
}];
// ── 底部更新时间 ─────────────────────────────────────
// self.timeLabel = [[UILabel alloc] init];
// self.timeLabel.textColor = UIColorHex(#77849E);
// self.timeLabel.textAlignment = NSTextAlignmentLeft;
// self.timeLabel.text = @"更新时间:--:--";
// self.timeLabel.font = MEDIUMFONT(12);
// [self.contentView addSubview:self.timeLabel];
//
// [self.timeLabel mas_makeConstraints:^(MASConstraintMaker *make) {
// make.top.equalTo(backLeft.mas_bottom).offset(8);
// make.left.equalTo(self.contentView).offset(15);
// make.right.equalTo(self.contentView).offset(-15);
// make.height.mas_equalTo(32);
// }];
// ── 无数据占位 ───────────────────────────────────────
self.emptyView = [[UIView alloc] init];
self.emptyView.backgroundColor = KWhiteColor;
self.emptyView.hidden = YES;
[self.contentView addSubview:self.emptyView];
[self.emptyView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.contentView);
}];
UILabel *emptyLabel = [[UILabel alloc] init];
emptyLabel.text = @"~ 无智能监测数据 ~";
emptyLabel.textColor = UIColorHex(#77849E);
emptyLabel.font = MEDIUMFONT(13);
emptyLabel.textAlignment = NSTextAlignmentCenter;
[self.emptyView addSubview:emptyLabel];
[emptyLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self.emptyView);
}];
}
#pragma mark - Public
- (void)configureWithData:(id)data {
if ([data isKindOfClass:[XJHomeWatchModel class]]) {
self.model = (XJHomeWatchModel *)data;
} else {
self.model = nil;
}
}
#pragma mark - Model Setter
- (void)setModel:(XJHomeWatchModel *)model {
_model = model;
if (!model) {
self.emptyView.hidden = NO;
return;
}
self.emptyView.hidden = YES;
[self p_updateLabelsForTabIndex:[_model.type intValue]];
[self changeDataShow:[_model.type intValue] withModel:_model];
if (!ValidStr(_model.latestTime)) {
self.updateTimeLabel.text = _model.latestTime;
// self.timeLabel.text = [NSString stringWithFormat:@"更新时间:%@", _model.latestTime];
} else {
self.updateTimeLabel.text = @"--:--";
// self.timeLabel.text = @"更新时间:--:--";
}
}
#pragma mark - Tab UI Update
/// 根据 tabIndex 切换图标和文字标题(不涉及数据)
- (void)p_updateLabelsForTabIndex:(NSInteger)tabIndex {
NSArray *icons = @[@"homePageXinlv", @"homePageXueyang", @"homePageYali", @"homePageTiwen", @"homePageShuimian"];
NSArray *latest = @[@"最新心率", @"最新血氧", @"最新压力", @"最新体温", @"睡眠总时长"];
NSArray *left = @[@"心率范围", @"血氧范围", @"压力范围", @"体温范围", @"深睡时长"];
NSArray *right = @[@"平均心率", @"平均血氧", @"平均压力", @"平均体温", @"浅睡时长"];
NSArray *units = @[@"--次/分钟", @"--%", @"--", @"--℃", @"--"];
/** 数据类型:0=心率 1=血氧 2=压力 3=体温 4=睡眠 */
if (tabIndex < 0 || tabIndex >= (NSInteger)icons.count) return;
self.heartImg.image = [UIImage imageNamed:icons[tabIndex]];
self.updateLabel.text = latest[tabIndex];
self.backLeftLabel.text = left[tabIndex];
self.backRightLabel.text = right[tabIndex];
// 无数据时显示占位
if (!_model) {
self.dataLabel.text = units[tabIndex];
self.leftDataLabel.text = @"--";
self.rightDataLabel.text = @"--";
}
}
#pragma mark - Data Display
- (void)changeDataShow:(NSInteger)isOne withModel:(XJHomeWatchModel *)model {
if (isOne == 0) { // 心率
NSString *s = [NSString stringWithFormat:@"%.0f次/分钟", model.latest];
self.dataLabel.attributedText = [self p_bigAttr:s unitLen:4];
NSString *range = [NSString stringWithFormat:@"%.0f-%.0f次/分钟", model.max, model.min];
self.leftDataLabel.attributedText = [self p_mediumAttr:range unitLen:4];
NSString *avg = [NSString stringWithFormat:@"%.0f次/分钟", model.avg];
self.rightDataLabel.attributedText = [self p_mediumAttr:avg unitLen:4];
} else if (isOne == 1) { // 血氧
NSString *s = [NSString stringWithFormat:@"%.0f%%", model.latest];
self.dataLabel.attributedText = [self p_bigAttr:s unitLen:1];
NSString *range = [NSString stringWithFormat:@"%.0f-%.0f%%", model.min, model.max];
self.leftDataLabel.attributedText = [self p_mediumAttr:range unitLen:1];
NSString *avg = [NSString stringWithFormat:@"%.0f%%", model.avg];
self.rightDataLabel.attributedText = [self p_mediumAttr:avg unitLen:1];
} else if (isOne == 2) { // 压力
NSString *s = [NSString stringWithFormat:@"%.0f", model.latest];
self.dataLabel.attributedText = [self p_plainAttr:s size:25];
NSString *range = [NSString stringWithFormat:@"%.0f-%.0f", model.min, model.max];
self.leftDataLabel.attributedText = [self p_plainAttr:range size:20];
NSString *avg = [NSString stringWithFormat:@"%.0f", model.avg];
self.rightDataLabel.attributedText = [self p_plainAttr:avg size:20];
} else if (isOne == 3) { // 体温
NSString *s = [NSString stringWithFormat:@"%.1f℃", model.latest];
self.dataLabel.attributedText = [self p_bigAttr:s unitLen:1];
NSString *range = [NSString stringWithFormat:@"%.1f-%.1f℃", model.min, model.max];
self.leftDataLabel.attributedText = [self p_mediumAttr:range unitLen:1];
NSString *avg = [NSString stringWithFormat:@"%.1f℃", model.avg];
self.rightDataLabel.attributedText = [self p_mediumAttr:avg unitLen:1];
} else if (isOne == 4) { // 睡眠
UIFont *bigFont = [UIFont systemFontOfSize:25 weight:UIFontWeightBold];
UIFont *mediumFont = [UIFont systemFontOfSize:20 weight:UIFontWeightMedium];
self.dataLabel.attributedText = [self convertMinutes:model.latest withFont:bigFont];
self.leftDataLabel.attributedText = [self convertMinutes:model.max withFont:mediumFont];
self.rightDataLabel.attributedText = [self convertMinutes:model.min withFont:mediumFont];
}
}
#pragma mark - Attributed String Helpers
/// 大数值:主体 25pt bold #252535,末尾 unitLen 个字符 14pt #77849E
- (NSMutableAttributedString *)p_bigAttr:(NSString *)str unitLen:(NSInteger)unitLen {
NSMutableAttributedString *attr = [[NSMutableAttributedString alloc] initWithString:str];
NSInteger bodyLen = MAX((NSInteger)str.length - unitLen, 0);
NSInteger tail = (NSInteger)str.length - bodyLen;
[attr addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:25 weight:UIFontWeightBold]
range:NSMakeRange(0, bodyLen)];
[attr addAttribute:NSForegroundColorAttributeName value:UIColorHex(#252535)
range:NSMakeRange(0, bodyLen)];
if (tail > 0) {
[attr addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:14 weight:UIFontWeightMedium]
range:NSMakeRange(bodyLen, tail)];
[attr addAttribute:NSForegroundColorAttributeName value:UIColorHex(#77849E)
range:NSMakeRange(bodyLen, tail)];
}
return attr;
}
/// 卡片数值:主体 20pt bold #252535,末尾 unitLen 个字符 12pt #77849E
- (NSMutableAttributedString *)p_mediumAttr:(NSString *)str unitLen:(NSInteger)unitLen {
NSMutableAttributedString *attr = [[NSMutableAttributedString alloc] initWithString:str];
NSInteger bodyLen = MAX((NSInteger)str.length - unitLen, 0);
NSInteger tail = (NSInteger)str.length - bodyLen;
[attr addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:20 weight:UIFontWeightBold]
range:NSMakeRange(0, bodyLen)];
[attr addAttribute:NSForegroundColorAttributeName value:UIColorHex(#252535)
range:NSMakeRange(0, bodyLen)];
if (tail > 0) {
[attr addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:12 weight:UIFontWeightMedium]
range:NSMakeRange(bodyLen, tail)];
[attr addAttribute:NSForegroundColorAttributeName value:UIColorHex(#77849E)
range:NSMakeRange(bodyLen, tail)];
}
return attr;
}
/// 纯数字(压力),无单位,整体 size pt bold #252535
- (NSMutableAttributedString *)p_plainAttr:(NSString *)str size:(CGFloat)size {
NSMutableAttributedString *attr = [[NSMutableAttributedString alloc] initWithString:str];
[attr addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:size weight:UIFontWeightBold]
range:NSMakeRange(0, str.length)];
[attr addAttribute:NSForegroundColorAttributeName value:UIColorHex(#252535)
range:NSMakeRange(0, str.length)];
return attr;
}
- (NSMutableAttributedString *)convertMinutes:(float)minutes withFont:(UIFont *)font {
NSString *timeString;
if (minutes == 0) {
timeString = @"0分钟";
} else if (minutes < 60) {
timeString = [NSString stringWithFormat:@"%.0f分钟", minutes];
} else {
int h = (int)(minutes / 60);
int m = (int)(minutes) % 60;
timeString = [NSString stringWithFormat:@"%d小时%d分钟", h, m];
}
NSMutableAttributedString *attr = [[NSMutableAttributedString alloc] initWithString:timeString];
[attr addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, timeString.length)];
[attr addAttribute:NSForegroundColorAttributeName value:UIColorHex(#252535)
range:NSMakeRange(0, timeString.length)];
for (NSString *unit in @[@"小时", @"分钟"]) {
NSRange r = [timeString rangeOfString:unit];
if (r.location != NSNotFound) {
[attr addAttribute:NSFontAttributeName
value:[UIFont systemFontOfSize:14 weight:UIFontWeightMedium]
range:r];
}
}
return attr;
}
@end
@@ -0,0 +1,54 @@
//
// XJHomeWatchDataView.h
// XinjiangProject
//
// Created by Apple on 2026/3/2.
//
#import <UIKit/UIKit.h>
#import "XJHomeWatchModel.h"
NS_ASSUME_NONNULL_BEGIN
/// Tab 点击回调:index 选中下标,title 选中标题
typedef void(^XJHomeWatchTabSelectBlock)(NSInteger index, NSString *title);
typedef void(^watchDataDetailBlick)();
@interface XJHomeWatchDataView : UIView
/// 设置 Tab 标题数组,赋值后自动刷新按钮
@property (nonatomic, copy) NSArray<NSString *> *tabTitles;
/// Tab 点击回调
@property (nonatomic, copy, nullable) XJHomeWatchTabSelectBlock tabSelectBlock;
@property (nonatomic, copy, nullable) watchDataDetailBlick watchMoreBlock;
/// "去绑定" 按钮点击回调
@property (nonatomic, copy, nullable) void (^bindDeviceBlock)(NSString * number);
/// 数据源,赋值后自动刷新 CollectionView,空数组时显示占位图
@property (nonatomic, copy) NSArray *dataList;
/// 底部 CollectionView(可用于外部调用 scrollToItem 等)
@property (nonatomic, strong, readonly) UICollectionView *collectionView;
/// 当前选中的 Tab 下标
@property (nonatomic, assign, readonly) NSInteger selectedIndex;
/// 手动切换 Tab(不触发 block
- (void)selectTabAtIndex:(NSInteger)index;
@property (nonatomic, strong) XJHomeWatchModel * watchModel;
@property (nonatomic, copy) void (^changeTimeClickBlock)(NSString *startDate);
/// 高度变化回调:无数据/未登录时返回 104,有数据时返回 205
@property (nonatomic, copy, nullable) void (^heightChangeBlock)(CGFloat height);
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,722 @@
//
// XJHomeWatchDataView.m
// XinjiangProject
//
// Created by Apple on 2026/3/2.
//
#import "XJHomeWatchDataView.h"
#import "XJHomeWatchCollectionViewCell.h"
#import <Masonry/Masonry.h>
// ── 布局常量 ──────────────────────────────────────────────
static CGFloat const kHeaderHeight = 92.0; // 头部保留高度(外部自行填充)
static CGFloat const kTabScrollH = 52.0; // Tab ScrollView 高度
static CGFloat const kButtonH = 30.0; // 按钮高度
static CGFloat const kButtonPadX = 10.0; // 按钮左右内边距
static CGFloat const kButtonGap = 10.0; // 按钮间距
// ── Private ───────────────────────────────────────────────
@interface XJHomeWatchDataView ()<UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
@property (nonatomic, strong) YYLabel *watchNumberLabel; //
//@property (nonatomic, strong) UIScrollView *tabScrollView;
@property (nonatomic, strong) UIView *tabContentView;
@property (nonatomic, strong) NSMutableArray<UIButton *> *tabButtons;
@property (nonatomic, strong) UICollectionView *collectionView;
@property (nonatomic, strong) UILabel *emptyLabel; // 无数据占位
@property (nonatomic, strong) UIView *emptyView; // 空状态占位整体容器
@property (nonatomic, strong) UIView *devView; // 紧急开发中占位
@property (nonatomic, assign) NSInteger selectedIndex; // 当前选中的 Tab 下标
@property (nonatomic, assign) NSInteger carouselPage; // collectionView 当前轮播页码(与 Tab 无关)
@property (nonatomic, strong) NSTimer *autoScrollTimer; // 轮播定时器
@property (nonatomic, strong) UIView *
timeView;
@property (nonatomic, strong) UIButton *
preBtn;
@property (nonatomic, strong) UIButton *
nextBtn;
@property (nonatomic, strong) UILabel *
weekLabel;
@property (nonatomic, strong) NSDate * currentDayDate; // 当前选中的日
@end
// ─────────────────────────────────────────────────────────
@implementation XJHomeWatchDataView
#pragma mark - Init
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
self.backgroundColor = KWhiteColor;
self.layer.cornerRadius = 12;
self.layer.masksToBounds = true;
if (self) [self p_setupUI];
return self;
}
#pragma mark - UI Setup
- (void)p_setupUI {
_selectedIndex = 0;
_tabButtons = [NSMutableArray array];
self.currentDayDate = [NSDate date];
UIImageView * heaedImg = [[UIImageView alloc] init];
heaedImg.image = [UIImage imageNamed:@"heaedTitleBackImg"];
heaedImg.userInteractionEnabled = true;
[self addSubview:heaedImg];
[heaedImg mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.top.mas_offset(0);
make.height.mas_offset(40);
}];
UILabel * todayLabel = [[UILabel alloc] init];
todayLabel.text = @"健康监测";
todayLabel.textAlignment = NSTextAlignmentLeft;
todayLabel.textColor = CFontColor1;
todayLabel.font = BOLDSYSTEMFONT(16);
[heaedImg addSubview:todayLabel];
[todayLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_offset(13);
make.top.bottom.mas_offset(0);
}];
UIButton * moreBtn = [[UIButton alloc] init];
[moreBtn setImage:[UIImage imageNamed:@"wacthRightAccow"] forState:UIControlStateNormal];
[moreBtn addTarget:self action:@selector(moreClick) forControlEvents:UIControlEventTouchUpInside];
[heaedImg addSubview:moreBtn];
[moreBtn mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.mas_offset(-8);
make.centerY.mas_equalTo(heaedImg);
make.width.height.mas_offset(30);
}];
// YYLabel * morelabel = [[YYLabel alloc] init];
// morelabel.text = @">";
// morelabel.textAlignment = NSTextAlignmentRight;
// morelabel.textColor = UIColorHex(#999999);
// morelabel.font = SYSTEMFONT(14);
// MJWeakSelf
// [morelabel setTextTapAction:^(UIView * _Nonnull containerView, NSAttributedString * _Nonnull text, NSRange range, CGRect rect) {
// if (self.watchMoreBlock) {
// self.watchMoreBlock();
// }
// }];
// [heaedImg addSubview:morelabel];
//
// [morelabel mas_makeConstraints:^(MASConstraintMaker *make) {
// make.right.mas_offset(-13);
// make.centerY.mas_equalTo(heaedImg);
// make.width.height.mas_offset(20);
// }];
//
self.watchNumberLabel = [[YYLabel alloc] init];
self.watchNumberLabel.text = @"";
self.watchNumberLabel.textAlignment = NSTextAlignmentLeft;
self.watchNumberLabel.textColor = UIColorHex(#14BEBE);
self.watchNumberLabel.font = MEDIUMFONT(13);
[heaedImg addSubview:self.watchNumberLabel];
[self.watchNumberLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.mas_equalTo(moreBtn.mas_left);
make.top.mas_offset(0);
make.height.mas_offset(40);
}];
MJWeakSelf
[self.watchNumberLabel setTextTapAction:^(UIView * _Nonnull containerView, NSAttributedString * _Nonnull text, NSRange range, CGRect rect) {
if (weakSelf.bindDeviceBlock) {
weakSelf.bindDeviceBlock(weakSelf.watchModel.watchNo);
}
}];
//日期切换
// self.timeView = [[UIView alloc] init];
// self.timeView.backgroundColor = KWhiteColor;
// [self addSubview:self.timeView];
//
// [self.timeView mas_makeConstraints:^(MASConstraintMaker *make) {
// make.left.right.mas_offset(0);
// make.top.mas_equalTo(heaedImg.mas_bottom);
// make.height.mas_offset(52);
// }];
//
// UIButton *preBtn = [[UIButton alloc] init];
// [preBtn setTitle:@"上一天" forState:UIControlStateNormal];
// [preBtn setImage:[UIImage imageNamed:@"blackLeftAccow"] forState:UIControlStateNormal];
// [preBtn setTitleColor:UIColorHex(#333333) forState:UIControlStateNormal];
// preBtn.titleLabel.font = MEDIUMFONT(12);
// preBtn.titleEdgeInsets = UIEdgeInsetsMake(0, -8, 0, 8); // 左边多 4px 间距
// self.preBtn = preBtn;
// [preBtn addTarget:self action:@selector(previousWeekAction) forControlEvents:UIControlEventTouchUpInside];
// [self.timeView addSubview:preBtn];
//
// [preBtn mas_makeConstraints:^(MASConstraintMaker *make) {
// make.left.mas_equalTo(@46);
// make.width.mas_offset(60); // 注意宽度要能容纳文字+图片
// make.centerY.mas_equalTo(self.timeView);
// }];
// preBtn.semanticContentAttribute = UISemanticContentAttributeForceRightToLeft;
//
//
// UIButton *rightBtn = [[UIButton alloc] init];
// [rightBtn setImage:[UIImage imageNamed:@"blackRightAccow"] forState:UIControlStateNormal];
// [rightBtn setTitle:@"下一天" forState:UIControlStateNormal];
// [rightBtn setTitleColor:UIColorHex(#333333) forState:UIControlStateNormal];
// rightBtn.titleLabel.font = MEDIUMFONT(12);
// rightBtn.titleEdgeInsets = UIEdgeInsetsMake(0, 8, 0, 0); // 左边多 4px 间距
// self.nextBtn = rightBtn;
// [rightBtn addTarget:self action:@selector(nextWeekAction) forControlEvents:UIControlEventTouchUpInside];
// [self.timeView addSubview:rightBtn];
// [rightBtn mas_makeConstraints:^(MASConstraintMaker *make) {
// make.right.mas_equalTo(@-46);
// make.width.mas_offset(60); // 记得宽度要能容下文字+图片
// make.centerY.mas_equalTo(self.timeView);
// }];
//
// self.weekLabel = [[UILabel alloc] init];
// self.weekLabel.textAlignment = NSTextAlignmentCenter;
// self.weekLabel.textColor = UIColorHex(#333333);
// self.weekLabel.font = MEDIUMFONT(12);
// [self.timeView addSubview: self.weekLabel];
//
// [self.weekLabel mas_makeConstraints:^(MASConstraintMaker *make) {
// make.right.mas_equalTo(rightBtn.mas_left);
// make.left.mas_equalTo(preBtn.mas_right);
// make.centerY.mas_equalTo(self.timeView);
// make.top.mas_offset(0);
// }];
//
// [self updateWeekLabelWithType];
//
// ── Tab ScrollView ───────────────────────────────────
// _tabScrollView = [[UIScrollView alloc] init];
// _tabScrollView.showsHorizontalScrollIndicator = NO;
// _tabScrollView.showsVerticalScrollIndicator = NO;
// [self addSubview:_tabScrollView];
//
// [_tabScrollView mas_makeConstraints:^(MASConstraintMaker *make) {
// make.top.equalTo(heaedImg.mas_bottom);
// make.left.right.equalTo(self);
// make.height.mas_equalTo(kTabScrollH);
// }];
// scrollView 内的容器 viewMasonry 驱动 contentSize
// _tabContentView = [[UIView alloc] init];
// [_tabScrollView addSubview:_tabContentView];
//
// [_tabContentView mas_makeConstraints:^(MASConstraintMaker *make) {
// make.edges.equalTo(_tabScrollView); // 四边贴 scrollView
// make.height.equalTo(_tabScrollView); // 固定高度 = scrollView,只横向滚动
// }];
// ── 底部 CollectionView ──────────────────────────────
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
layout.minimumLineSpacing = 0;
layout.minimumInteritemSpacing = 0;
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero
collectionViewLayout:layout];
_collectionView.backgroundColor = UIColor.clearColor;
_collectionView.showsHorizontalScrollIndicator = NO;
_collectionView.pagingEnabled = YES;
_collectionView.delegate = self;
_collectionView.dataSource = self;
[_collectionView registerClass:[XJHomeWatchCollectionViewCell class]
forCellWithReuseIdentifier:NSStringFromClass([XJHomeWatchCollectionViewCell class])];
[self addSubview:_collectionView];
[_collectionView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(@40);
make.left.right.bottom.equalTo(self);
}];
// ── 未登录占位 Label ─────────────────────────────────────────
_emptyLabel = [[UILabel alloc] init];
_emptyLabel.text = @" 暂未登录 ";
_emptyLabel.textAlignment = NSTextAlignmentCenter;
_emptyLabel.textColor = [UIColor colorWithRed:0.6 green:0.6 blue:0.6 alpha:1.0];
_emptyLabel.font = [UIFont systemFontOfSize:15.0];
_emptyLabel.hidden = YES;
[self.collectionView addSubview:_emptyLabel];
[_emptyLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self.collectionView);
}];
// ── 无设备占位 View(已登录但未绑定设备时显示)────────────────
_emptyView = [[UIView alloc] init];
_emptyView.backgroundColor = KWhiteColor;
_emptyView.hidden = YES;
[self addSubview:_emptyView];
[_emptyView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(@40);
make.left.right.bottom.equalTo(self);
}];
// // 空状态图标,宽高 59,居中,距 Tab 切换 View 底部 16
// UIImageView *emptyImageView = [[UIImageView alloc] init];
// emptyImageView.image = [UIImage imageNamed:@"ic_watch_empty"];
// emptyImageView.contentMode = UIViewContentModeScaleAspectFit;
// [_emptyView addSubview:emptyImageView];
//
// [emptyImageView mas_makeConstraints:^(MASConstraintMaker *make) {
// make.top.mas_offset(16);
// make.centerX.equalTo(_emptyView);
// make.width.height.mas_offset(59);
// }];
//
// // 提示文字:~ 暂无数据 ~,字号 11 regular,颜色 #B6B6B6
// UILabel *emptyHintLabel = [[UILabel alloc] init];
// emptyHintLabel.text = @"~ 暂无数据 ~";
// emptyHintLabel.textAlignment = NSTextAlignmentCenter;
// emptyHintLabel.textColor = UIColorHex(#B6B6B6);
// emptyHintLabel.font = [UIFont systemFontOfSize:11];
// [_emptyView addSubview:emptyHintLabel];
//
// [emptyHintLabel mas_makeConstraints:^(MASConstraintMaker *make) {
// make.top.equalTo(emptyImageView.mas_bottom).offset(8);
// make.centerX.equalTo(_emptyView);
// }];
// 去绑定按钮:宽 80 高 24medium 13,颜色 #21BEBD,圆角 12,白底边框 1
UIButton *bindBtn = [UIButton buttonWithType:UIButtonTypeCustom];
[bindBtn setTitle:@"去绑定" forState:UIControlStateNormal];
[bindBtn setTitleColor:UIColorHex(#21BEBD) forState:UIControlStateNormal];
bindBtn.titleLabel.font = MEDIUMFONT(13);
bindBtn.backgroundColor = KWhiteColor;
bindBtn.layer.cornerRadius = 12;
bindBtn.layer.borderWidth = 1;
bindBtn.layer.borderColor = UIColorHex(#21BEBD).CGColor;
bindBtn.layer.masksToBounds = YES;
[bindBtn addTarget:self action:@selector(bindButtonClick) forControlEvents:UIControlEventTouchUpInside];
[_emptyView addSubview:bindBtn];
[bindBtn mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_offset(20);
make.centerX.equalTo(_emptyView);
make.width.mas_offset(82);
make.height.mas_offset(24);
}];
if ([HQCommonUtils isLogin]) {
// 已登录,等待数据回调
} else {
// 未登录:显示"暂未登录"文字,emptyView 保持隐藏
_emptyLabel.hidden = NO;
_collectionView.hidden = NO;
}
}
- (void)bindButtonClick {
if (self.bindDeviceBlock) {
self.bindDeviceBlock(@"");
}
}
- (void)moreClick {
if (![HQCommonUtils isLogin]) {
KPostNotification(KNotificationLoginStateChange, @NO);
return;
}
if (self.watchModel.hasWatch == true) {
if (self.watchMoreBlock) {
self.watchMoreBlock();
}
}else {
if (self.bindDeviceBlock) {
self.bindDeviceBlock(@"");
}
}
}
- (void)setWatchModel:(XJHomeWatchModel *)watchModel {
_watchModel = watchModel;
if (_watchModel.hasWatch == true) {
//有手表
self.watchNumberLabel.text = [NSString stringWithFormat:@"监测工具编码:%@",_watchModel.watchNo];
self.watchNumberLabel.textColor = UIColorHex(#14BEBE);
}else
{
self.watchNumberLabel.text = @"监测工具编码:暂无信息";
self.watchNumberLabel.textColor = UIColorHex(#808080);
self.dataList = @[];
}
}
#pragma mark - Tab Titles Setter
- (void)setTabTitles:(NSArray<NSString *> *)tabTitles {
_tabTitles = [tabTitles copy];
_selectedIndex = 0;
// 清除旧按钮
for (UIButton *btn in _tabButtons) [btn removeFromSuperview];
[_tabButtons removeAllObjects];
UIFont *font = [UIFont systemFontOfSize:14 weight:UIFontWeightBold];
UIButton *prevBtn = nil;
for (NSInteger i = 0; i < (NSInteger)tabTitles.count; i++) {
NSString *title = tabTitles[i];
BOOL isSelected = (i == 0);
// 计算文字宽度,得到按钮宽度
CGFloat textW = ceil([title boundingRectWithSize:CGSizeMake(MAXFLOAT, kButtonH)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:@{NSFontAttributeName: font}
context:nil].size.width);
CGFloat btnW = textW + kRealValue(kButtonPadX) * 2;
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
btn.tag = i;
btn.titleLabel.font = font;
btn.layer.cornerRadius = kButtonH / 2.0;
btn.clipsToBounds = YES;
btn.backgroundColor = isSelected ? UIColorHex(#14BEBE) : UIColorHex(#F6F7FB);
[btn setTitle:title forState:UIControlStateNormal];
btn.titleLabel.font = isSelected ? BOLDSYSTEMFONT(15) : [UIFont systemFontOfSize:14 weight:UIFontWeightRegular];
[btn setTitleColor:isSelected ? KWhiteColor : UIColorHex(#808080)
forState:UIControlStateNormal];
[btn addTarget:self
action:@selector(p_tabButtonTapped:)
forControlEvents:UIControlEventTouchUpInside];
[_tabContentView addSubview:btn];
[btn mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(_tabContentView);
make.height.mas_equalTo(kButtonH);
make.width.mas_equalTo(btnW);
if (prevBtn) {
make.left.equalTo(prevBtn.mas_right).offset(kButtonGap);
} else {
make.left.equalTo(_tabContentView).offset(kButtonGap);
}
// 最后一个按钮右边贴 contentView(撑开 scrollView contentSize
if (i == (NSInteger)tabTitles.count - 1) {
make.right.equalTo(_tabContentView).offset(-kButtonGap);
}
}];
[_tabButtons addObject:btn];
prevBtn = btn;
}
}
#pragma mark - Button Tap
- (void)p_tabButtonTapped:(UIButton *)sender {
NSInteger index = sender.tag;
[self selectTabAtIndex:index];
// 用户主动点击,重置轮播计时器
[self p_restartAutoScroll];
}
#pragma mark - Public: Select Tab
//- (void)selectTabAtIndex:(NSInteger)index {
// if (index < 0 || index >= (NSInteger)_tabButtons.count) return;
// if (![HQCommonUtils isLogin]) {
// KPostNotification(KNotificationLoginStateChange, @NO);
// return;
// }
//
// // 恢复旧 Tab 样式
// UIButton *oldBtn = _tabButtons[_selectedIndex];
// oldBtn.backgroundColor = UIColorHex(#F6F7FB);
// oldBtn.titleLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightRegular];
// [oldBtn setTitleColor:UIColorHex(#808080) forState:UIControlStateNormal];
//
// // 设置新 Tab 样式
// _selectedIndex = index;
// UIButton *newBtn = _tabButtons[index];
// newBtn.backgroundColor = UIColorHex(#14BEBE);
// newBtn.titleLabel.font = BOLDSYSTEMFONT(14);
// [newBtn setTitleColor:KWhiteColor forState:UIControlStateNormal];
//
// [self p_scrollToButton:newBtn];
//
// // tab 0:显示 CollectionView 并恢复轮播;其他:显示"紧急开发中"并暂停轮播
// BOOL isFirst = (index == 0);
// _collectionView.hidden = !isFirst;
// _devView.hidden = isFirst;
//
// if (isFirst) {
// // 切换回第一个 Tab,重新根据数据状态显示 emptyView
// BOOL isEmpty = (_dataList.count == 0);
// _emptyView.hidden = !isEmpty;
// [self p_restartAutoScroll];
// } else {
// // 切换到其他 Tab,隐藏 emptyView
// _emptyView.hidden = YES;
// [self p_stopAutoScroll];
// }
//
// // 通知外部高度变化
// if (self.tabSelectBlock) {
// self.tabSelectBlock(index, _tabTitles[index]);
// }
//}
///// 若按钮中心超过可视区一半,则滚动将其居中
//- (void)p_scrollToButton:(UIButton *)btn {
// // layoutIfNeeded 保证 frame 已计算
// [_tabScrollView layoutIfNeeded];
//
// CGFloat scrollW = _tabScrollView.bounds.size.width;
// CGFloat contentW = _tabScrollView.contentSize.width;
// CGFloat btnCenterX = CGRectGetMidX(btn.frame);
//
// CGFloat offsetX = 0;
// if (btnCenterX > scrollW / 2.0) {
// offsetX = btnCenterX - scrollW / 2.0;
// offsetX = MIN(offsetX, MAX(contentW - scrollW, 0));
// }
//
// [_tabScrollView setContentOffset:CGPointMake(offsetX, 0) animated:YES];
//}
#pragma mark - DataList Setter
- (void)setDataList:(NSArray *)dataList {
_dataList = [dataList copy];
// 未登录:显示 emptyLabel,隐藏 emptyView 及内容区
if (![HQCommonUtils isLogin]) {
_emptyLabel.text = @" 暂未登录 ";
_emptyLabel.hidden = NO;
_emptyView.hidden = YES;
_collectionView.hidden = YES;
return;
}
// 已登录:隐藏 emptyLabel,显示内容区
_emptyLabel.hidden = YES;
_collectionView.hidden = NO;
// 无设备数据:显示 emptyView(图标 + 提示 + 去绑定按钮)
BOOL isEmpty = (_dataList.count == 0);
_emptyView.hidden = !isEmpty;
[_collectionView reloadData];
// 有多条数据才启动轮播
if (_dataList.count > 1) {
[self p_startAutoScroll];
} else {
[self p_stopAutoScroll];
}
// 通知外部更新高度:无数据/未登录 104,有数据 205
BOOL needSmall = (isEmpty || ![HQCommonUtils isLogin]);
CGFloat newHeight = needSmall ? 104.0 : 200.0;
if (self.heightChangeBlock) {
self.heightChangeBlock(newHeight);
}
}
#pragma mark - UICollectionViewDataSource
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return self.dataList.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath {
XJHomeWatchCollectionViewCell *cell =
[collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass([XJHomeWatchCollectionViewCell class])
forIndexPath:indexPath];
// 用 dataList 里对应位置的 model 填充
if (indexPath.item < (NSInteger)_dataList.count) {
[cell configureWithData:_dataList[indexPath.item]];
}
// // item 下标即数据类型:0=心率 1=血氧 2=压力 3=体温
// [cell updateTabIndex:indexPath.item];
return cell;
}
#pragma mark - UICollectionViewDelegateFlowLayout
- (CGSize)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
// cell 宽 = 整个 view 宽;cell 高 = collectionView 自身高度
return CGSizeMake(self.bounds.size.width, collectionView.bounds.size.height);
}
#pragma mark - UICollectionViewDelegate
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
// 预留:外部如需监听 cell 点击可在此通过 block 回调
}
#pragma mark - UIScrollViewDelegate(手动滑动同步 Tab
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
if (scrollView != _collectionView) return;
// 更新轮播页码(与 Tab selectedIndex 无关)
_carouselPage = (NSInteger)(scrollView.contentOffset.x / MAX(scrollView.bounds.size.width, 1));
[self p_restartAutoScroll];
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
if (scrollView != _collectionView) return;
// 手指触碰时暂停自动轮播,避免与手势冲突
[self p_stopAutoScroll];
}
#pragma mark - 轮播
/// 启动自动轮播(3 秒一次)
- (void)p_startAutoScroll {
[self p_stopAutoScroll];
if (_dataList.count <= 1) return;
_autoScrollTimer = [NSTimer scheduledTimerWithTimeInterval:5.0
target:self
selector:@selector(p_autoScrollToNext)
userInfo:nil
repeats:YES];
// 加入 NSRunLoopCommonModes,防止父 ScrollView 滚动时定时器暂停
[[NSRunLoop mainRunLoop] addTimer:_autoScrollTimer forMode:NSRunLoopCommonModes];
}
/// 停止自动轮播
- (void)p_stopAutoScroll {
[_autoScrollTimer invalidate];
_autoScrollTimer = nil;
}
/// 重置定时器(用户交互后调用)
- (void)p_restartAutoScroll {
if (_dataList.count > 1) [self p_startAutoScroll];
}
/// 滚到下一页,最后一页→第一页不用动画,避免明显跳动
- (void)p_autoScrollToNext {
if (_dataList.count == 0) return;
NSInteger next = (_carouselPage + 1) % _dataList.count;
BOOL animated = (next != 0);
[self p_scrollCollectionToIndex:next animated:animated];
}
/// 滚动 collectionView 到指定页,只更新 carouselPage,不影响 Tab
- (void)p_scrollCollectionToIndex:(NSInteger)index animated:(BOOL)animated {
if (index < 0 || index >= (NSInteger)_dataList.count) return;
_carouselPage = index;
NSIndexPath *ip = [NSIndexPath indexPathForItem:index inSection:0];
[_collectionView scrollToItemAtIndexPath:ip
atScrollPosition:UICollectionViewScrollPositionLeft
animated:animated];
}
/// 离开父视图时停止轮播,防止 timer 持有 self 造成内存泄漏
- (void)willMoveToSuperview:(UIView *)newSuperview {
[super willMoveToSuperview:newSuperview];
if (!newSuperview) [self p_stopAutoScroll];
}
#pragma mark - 上一日/上一周、下一日/下一周
// 上一日/上一周
- (void)previousWeekAction {
self.currentDayDate = [self dateByAddingDays:-1 toDate:self.currentDayDate];
[self updateWeekLabelWithType];
[self checkNextButtonState];
}
// 下一日/下一周
- (void)nextWeekAction {
self.currentDayDate = [self dateByAddingDays:1 toDate:self.currentDayDate];
[self updateWeekLabelWithType];
[self checkNextButtonState];
}
- (void)updateWeekLabelWithType {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"yyyy-MM-dd";
self.weekLabel.text = [formatter stringFromDate:self.currentDayDate];
if (self.changeTimeClickBlock) {
NSString *dayYMD = [self formatDateToYMDString:self.currentDayDate];
self.changeTimeClickBlock(dayYMD); //
}
}
// 日期加减
- (NSDate *)dateByAddingDays:(NSInteger)days toDate:(NSDate *)date {
return [date dateByAddingTimeInterval:days*24*60*60];
}
- (NSString *)formatDateToYMDString:(NSDate *)date {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyy-MM-dd"];
return [formatter stringFromDate:date];
}
#pragma mark - 检查下一天/下一周按钮状态
- (void)checkNextButtonState {
NSCalendar *calendar = [NSCalendar currentCalendar];
[calendar setFirstWeekday:2]; // 周一为第一天
NSDate *today = [NSDate date];
NSDate *currentOnly = [self dateByRemovingTime:self.currentDayDate];
NSDate *todayOnly = [self dateByRemovingTime:today];
self.nextBtn.enabled = ([currentOnly compare:todayOnly] == NSOrderedAscending);
}
// 去掉时分秒
- (NSDate *)dateByRemovingTime:(NSDate *)date {
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *components = [calendar components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay
fromDate:date];
return [calendar dateFromComponents:components];
}
@end
@@ -0,0 +1,16 @@
//
// MessageListViewController.h
// XinjiangProject
//
// Created by Apple on 2026/2/5.
//
#import "XJBaseViewController.h"
NS_ASSUME_NONNULL_BEGIN
@interface MessageListViewController : XJBaseViewController
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,31 @@
//
// MessageListViewController.m
// XinjiangProject
//
// Created by Apple on 2026/2/5.
//
#import "MessageListViewController.h"
@interface MessageListViewController ()
@end
@implementation MessageListViewController
- (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
@@ -176,17 +176,21 @@
//1月21日新需求
self.topLoginTitleLab = [[UILabel alloc] initWithFrame:CGRectMake(0, kRealValue(40), kScreenWidth, 20)];;
self.topLoginTitleLab = [[UILabel alloc] init];;
self.topLoginTitleLab.text = @"平台统一认证";
self.topLoginTitleLab.font = [UIFont systemFontOfSize:20 weight:UIFontWeightBold];
self.topLoginTitleLab.textColor = UIColorHex(#2EB8B2);
self.topLoginTitleLab.textAlignment = NSTextAlignmentCenter;
[self.loginView addSubview:self.topLoginTitleLab];
[self.topLoginTitleLab mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.mas_offset(0);
make.top.mas_offset(40);
make.height.mas_offset(kRealValue(40));
}];
UITextField *accountField = [[UITextField alloc]init];
accountField.frame = CGRectMake(kRealValue(25),self.topLoginTitleLab.bottom + kRealValue(24), loginView.width - kRealValue(50), 48);
accountField.placeholder = @"请输入员工编号或用户名";
accountField.font = BOLDSYSTEMFONT(14);
accountField.textColor = CFontColor1;
@@ -203,9 +207,17 @@
self.userNameField.autocapitalizationType = UITextAutocapitalizationTypeNone;
[loginView addSubview:accountField ];
//
[self.userNameField mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_offset(25);
make.right.mas_offset(-25);
make.height.mas_offset(48);
make.top.mas_equalTo(self.topLoginTitleLab.mas_bottom).offset(kRealValue(24));
}];
//
MQTextField * passwordField = [[MQTextField alloc]init];
passwordField.frame = CGRectMake(kRealValue(25), accountField.bottom + kRealValue(19), accountField.width, accountField.height);
// passwordField.frame = CGRectMake(kRealValue(25), accountField.bottom + kRealValue(19), accountField.width, accountField.height);
passwordField.placeholder = @"请输入密码";
passwordField.font = BOLDSYSTEMFONT(14);
passwordField.textColor = CFontColor1;
@@ -239,10 +251,16 @@
[loginView addSubview:passwordField];
[self.passwordField mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_offset(25);
make.right.mas_offset(-25);
make.height.mas_offset(48);
make.top.mas_equalTo(self.userNameField.mas_bottom).offset(kRealValue(19));
}];
UIButton *loginBtn = [UIButton buttonWithType:UIButtonTypeCustom];
loginBtn.frame = CGRectMake(kRealValue(25), self.passwordField.bottom + kRealValue(20), WIDTH - kRealValue(50), 48);
// loginBtn.frame = CGRectMake(kRealValue(25), self.passwordField.bottom + kRealValue(20), WIDTH - kRealValue(50), 48);
[loginBtn setTitle:@"登录" forState:UIControlStateNormal];
[loginBtn.titleLabel setFont:BOLDSYSTEMFONT(16)];
loginBtn.backgroundColor = UIColorHex(#2EB8B2);
@@ -252,8 +270,18 @@
[loginBtn addTarget:self action:@selector(toClickFootBtn:) forControlEvents:UIControlEventTouchUpInside];
[loginView addSubview:loginBtn];
[loginBtn mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_offset(kRealValue(25));
make.right.mas_offset(-kRealValue(25));
make.height.mas_offset(48);
make.top.mas_equalTo(self.passwordField.mas_bottom).offset(kRealValue(20));
}];
UIButton * foundPasswordBtn = [UIButton buttonWithType:UIButtonTypeCustom];
foundPasswordBtn.frame = CGRectMake(loginBtn.right - 52, loginBtn.bottom + kRealValue(22), 52, 12);
// foundPasswordBtn.frame = CGRectMake(loginBtn.right - 60, loginBtn.bottom + kRealValue(22), 60, 12);
[foundPasswordBtn setTitle:@"找回密码" forState:UIControlStateNormal];
foundPasswordBtn.titleLabel.font = [UIFont systemFontOfSize:13 weight:UIFontWeightRegular];
[foundPasswordBtn setTitleColor:UIColorHex(#B6B6B6) forState:UIControlStateNormal];
@@ -262,6 +290,12 @@
[loginView addSubview:foundPasswordBtn];
[foundPasswordBtn mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.mas_equalTo(loginBtn.mas_right);
make.height.mas_offset(kRealValue(20));
make.top.mas_equalTo(loginBtn.mas_bottom).offset(18);
}];
[[NSUserDefaults standardUserDefaults] setObject:@"1" forKey:@"LOGINAgree"];
[[NSUserDefaults standardUserDefaults] synchronize];
@@ -283,7 +317,7 @@
{
self.agreenBtn.selected = false;
}
[self.view addSubview:self.agreenBtn];
[self.loginView addSubview:self.agreenBtn];
[self.agreenBtn mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_offset(25);
@@ -189,7 +189,7 @@
{
[EasyTextView showErrorText:message];
NSLog(@"apiError=%@",message);
DLog(@"apiError=%@",message);
// [self showMessage:message];
}

Some files were not shown because too many files have changed in this diff Show More