RONG 2 yıl önce
ebeveyn
işleme
1d28fc0d80
100 değiştirilmiş dosya ile 6061 ekleme ve 0 silme
  1. 22 0
      jiaPei/Modules/AppleThirldLogin/Other/YostarKeychain.h
  2. 65 0
      jiaPei/Modules/AppleThirldLogin/Other/YostarKeychain.m
  3. 23 0
      jiaPei/Modules/AppleThirldLogin/View/SignInApple.h
  4. 154 0
      jiaPei/Modules/AppleThirldLogin/View/SignInApple.m
  5. 73 0
      jiaPei/Modules/BaseModule/BaseClass/Model/RQBaseModel.h
  6. 192 0
      jiaPei/Modules/BaseModule/BaseClass/Model/RQBaseModel.m
  7. 114 0
      jiaPei/Modules/BaseModule/BaseClass/Model/RQEXTRuntimeExtensions.h
  8. 210 0
      jiaPei/Modules/BaseModule/BaseClass/Model/RQEXTRuntimeExtensions.m
  9. 31 0
      jiaPei/Modules/BaseModule/BaseClass/Model/RQReflection.h
  10. 51 0
      jiaPei/Modules/BaseModule/BaseClass/Model/RQReflection.m
  11. 45 0
      jiaPei/Modules/BaseModule/BaseClass/RQBaseClass.h
  12. 17 0
      jiaPei/Modules/BaseModule/BaseClass/View/RQCollectionView.h
  13. 21 0
      jiaPei/Modules/BaseModule/BaseClass/View/RQCollectionView.m
  14. 17 0
      jiaPei/Modules/BaseModule/BaseClass/View/RQTabBar.h
  15. 46 0
      jiaPei/Modules/BaseModule/BaseClass/View/RQTabBar.m
  16. 13 0
      jiaPei/Modules/BaseModule/BaseClass/View/RQTableView.h
  17. 26 0
      jiaPei/Modules/BaseModule/BaseClass/View/RQTableView.m
  18. 16 0
      jiaPei/Modules/BaseModule/BaseClass/ViewController/RQBaseNavigationController.h
  19. 214 0
      jiaPei/Modules/BaseModule/BaseClass/ViewController/RQBaseNavigationController.m
  20. 42 0
      jiaPei/Modules/BaseModule/BaseClass/ViewController/RQBaseViewController.h
  21. 136 0
      jiaPei/Modules/BaseModule/BaseClass/ViewController/RQBaseViewController.m
  22. 31 0
      jiaPei/Modules/BaseModule/BaseClass/ViewController/RQCollectionViewController.h
  23. 350 0
      jiaPei/Modules/BaseModule/BaseClass/ViewController/RQCollectionViewController.m
  24. 37 0
      jiaPei/Modules/BaseModule/BaseClass/ViewController/RQNavigationControllerStack.h
  25. 116 0
      jiaPei/Modules/BaseModule/BaseClass/ViewController/RQNavigationControllerStack.m
  26. 18 0
      jiaPei/Modules/BaseModule/BaseClass/ViewController/RQTabBarController.h
  27. 50 0
      jiaPei/Modules/BaseModule/BaseClass/ViewController/RQTabBarController.m
  28. 30 0
      jiaPei/Modules/BaseModule/BaseClass/ViewController/RQTableViewController.h
  29. 304 0
      jiaPei/Modules/BaseModule/BaseClass/ViewController/RQTableViewController.m
  30. 22 0
      jiaPei/Modules/BaseModule/BaseClass/ViewController/RQWebViewViewController.h
  31. 366 0
      jiaPei/Modules/BaseModule/BaseClass/ViewController/RQWebViewViewController.m
  32. 94 0
      jiaPei/Modules/BaseModule/BaseClass/ViewModel/RQBaseViewModel.h
  33. 86 0
      jiaPei/Modules/BaseModule/BaseClass/ViewModel/RQBaseViewModel.m
  34. 57 0
      jiaPei/Modules/BaseModule/BaseClass/ViewModel/RQCollectionViewModel.h
  35. 56 0
      jiaPei/Modules/BaseModule/BaseClass/ViewModel/RQCollectionViewModel.m
  36. 53 0
      jiaPei/Modules/BaseModule/BaseClass/ViewModel/RQNavigationProtocol.h
  37. 13 0
      jiaPei/Modules/BaseModule/BaseClass/ViewModel/RQTabBarViewModel.h
  38. 13 0
      jiaPei/Modules/BaseModule/BaseClass/ViewModel/RQTabBarViewModel.m
  39. 60 0
      jiaPei/Modules/BaseModule/BaseClass/ViewModel/RQTableViewModel.h
  40. 57 0
      jiaPei/Modules/BaseModule/BaseClass/ViewModel/RQTableViewModel.m
  41. 18 0
      jiaPei/Modules/BaseModule/BaseClass/ViewModel/RQViewModelServices.h
  42. 15 0
      jiaPei/Modules/BaseModule/BaseClass/ViewModel/RQViewModelServicesImpl.h
  43. 35 0
      jiaPei/Modules/BaseModule/BaseClass/ViewModel/RQViewModelServicesImpl.m
  44. 38 0
      jiaPei/Modules/BaseModule/BaseClass/ViewModel/RQWebViewModel.h
  45. 29 0
      jiaPei/Modules/BaseModule/BaseClass/ViewModel/RQWebViewModel.m
  46. 15 0
      jiaPei/Modules/BaseModule/BaseModule.h
  47. 45 0
      jiaPei/Modules/BaseModule/Common/Common.h
  48. 33 0
      jiaPei/Modules/BaseModule/Common/Model/RQCommonGroupModel.h
  49. 13 0
      jiaPei/Modules/BaseModule/Common/Model/RQCommonGroupModel.m
  50. 20 0
      jiaPei/Modules/BaseModule/Common/View/RQCommonCell.h
  51. 277 0
      jiaPei/Modules/BaseModule/Common/View/RQCommonCell.m
  52. 17 0
      jiaPei/Modules/BaseModule/Common/View/RQCommonCollectionViewCell.h
  53. 142 0
      jiaPei/Modules/BaseModule/Common/View/RQCommonCollectionViewCell.m
  54. 15 0
      jiaPei/Modules/BaseModule/Common/View/RQCommonFooterView.h
  55. 111 0
      jiaPei/Modules/BaseModule/Common/View/RQCommonFooterView.m
  56. 16 0
      jiaPei/Modules/BaseModule/Common/View/RQCommonHeaderView.h
  57. 108 0
      jiaPei/Modules/BaseModule/Common/View/RQCommonHeaderView.m
  58. 22 0
      jiaPei/Modules/BaseModule/Common/View/RQCommonReusableView.h
  59. 182 0
      jiaPei/Modules/BaseModule/Common/View/RQCommonReusableView.m
  60. 17 0
      jiaPei/Modules/BaseModule/Common/View/RQDebugTouchView.h
  61. 251 0
      jiaPei/Modules/BaseModule/Common/View/RQDebugTouchView.m
  62. 14 0
      jiaPei/Modules/BaseModule/Common/ViewController/RQCommonCollectionViewController.h
  63. 107 0
      jiaPei/Modules/BaseModule/Common/ViewController/RQCommonCollectionViewController.m
  64. 14 0
      jiaPei/Modules/BaseModule/Common/ViewController/RQCommonViewController.h
  65. 101 0
      jiaPei/Modules/BaseModule/Common/ViewController/RQCommonViewController.m
  66. 18 0
      jiaPei/Modules/BaseModule/Common/ViewModel/CommonProfileHeaderItemViewModel.h
  67. 24 0
      jiaPei/Modules/BaseModule/Common/ViewModel/CommonProfileHeaderItemViewModel.m
  68. 13 0
      jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonArrowItemViewModel.h
  69. 13 0
      jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonArrowItemViewModel.m
  70. 14 0
      jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonAvatarItemViewModel.h
  71. 13 0
      jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonAvatarItemViewModel.m
  72. 42 0
      jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonCollectionItemViewModel.h
  73. 31 0
      jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonCollectionItemViewModel.m
  74. 15 0
      jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonCollectionViewModel.h
  75. 35 0
      jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonCollectionViewModel.m
  76. 28 0
      jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonCollecttionItemViewModel.h
  77. 27 0
      jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonCollecttionItemViewModel.m
  78. 15 0
      jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonCollecttionViewModel.h
  79. 35 0
      jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonCollecttionViewModel.m
  80. 31 0
      jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonGroupViewModel.h
  81. 40 0
      jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonGroupViewModel.m
  82. 52 0
      jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonItemViewModel.h
  83. 37 0
      jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonItemViewModel.m
  84. 15 0
      jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonLabelItemViewModel.h
  85. 13 0
      jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonLabelItemViewModel.m
  86. 13 0
      jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonQRCodeItemViewModel.h
  87. 13 0
      jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonQRCodeItemViewModel.m
  88. 15 0
      jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonSwitchItemViewModel.h
  89. 21 0
      jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonSwitchItemViewModel.m
  90. 14 0
      jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonValueItemViewModel.h
  91. 12 0
      jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonValueItemViewModel.m
  92. 15 0
      jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonViewModel.h
  93. 37 0
      jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonViewModel.m
  94. 21 0
      jiaPei/Modules/ChangeSchool/Controller/StudentChangeSchoolVC.h
  95. 201 0
      jiaPei/Modules/ChangeSchool/Controller/StudentChangeSchoolVC.m
  96. 77 0
      jiaPei/Modules/ChangeSchool/Controller/StudentChangeSchoolVC.xib
  97. 51 0
      jiaPei/Modules/ChangeSchool/Model/StudentChangeSchoolModel.h
  98. 15 0
      jiaPei/Modules/ChangeSchool/Model/StudentChangeSchoolModel.m
  99. 17 0
      jiaPei/Modules/DiscoverModule/Controller/RQDiscoverViewController.h
  100. 32 0
      jiaPei/Modules/DiscoverModule/Controller/RQDiscoverViewController.m

+ 22 - 0
jiaPei/Modules/AppleThirldLogin/Other/YostarKeychain.h

@@ -0,0 +1,22 @@
+//
+//  YostarKeychain.h
+//  YostarUtilits
+//
+//  Created by Yostar on 2018/6/21.
+//  Copyright © 2018年 Yostar. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface YostarKeychain : NSObject
+
+// save ... to keychain
++ (void)save:(NSString *)service data:(id)data;
+
+// take out ... from keychain
++ (id)load:(NSString *)service;
+
+// delete ... from keychain
++ (void)delete:(NSString *)service;
+
+@end

+ 65 - 0
jiaPei/Modules/AppleThirldLogin/Other/YostarKeychain.m

@@ -0,0 +1,65 @@
+//
+//  YostarKeychain.m
+//  YostarUtilits
+//
+//  Created by Yostar on 2018/6/21.
+//  Copyright © 2018年 Yostar. All rights reserved.
+//
+
+#import "YostarKeychain.h"
+#import <Security/Security.h>
+
+@implementation YostarKeychain
+
++ (NSMutableDictionary *)getKeychainQuery:(NSString *)service{
+    return [NSMutableDictionary dictionaryWithObjectsAndKeys:
+            (id)kSecClassGenericPassword, (id)kSecClass,
+            service, (id)kSecAttrService,
+            service, (id)kSecAttrAccount,
+            (id)kSecAttrAccessibleAfterFirstUnlock, (id)kSecAttrAccessible, nil];
+}
+
+#pragma mark - 写入
++ (void)save:(NSString *)service data:(id)data{
+    //Get search dictionary
+    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
+    //Delete old item before add new item
+    SecItemDelete((CFDictionaryRef)keychainQuery);
+    //Add new object to search dictionary(Attention:the data format)
+    [keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(id)kSecValueData];
+    //Add item to keychain with the search dictionary
+    SecItemAdd((CFDictionaryRef)keychainQuery, NULL);
+}
+
+#pragma mark - 读取
++ (id)load:(NSString *)service{
+    id result = nil;
+    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
+    //Configure the search setting
+    //Since in our simple case we are expecting only a single attribute to be returned (the password) we can set the attribute kSecReturnData to kCFBooleanTrue
+    [keychainQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
+    [keychainQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
+    CFDataRef keyData = NULL;
+    if (SecItemCopyMatching((CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData) == noErr) {
+        @try {
+            result = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge NSData *)keyData];
+//            result = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge_transfer NSData *)keyData];
+        } @catch (NSException *exception) {
+            NSLog(@"Unarchive of %@ failed: %@", service, exception);
+        } @finally {
+            
+        }
+    }
+    if (keyData) {
+        CFRelease(keyData);
+    }
+    return result;
+}
+
+#pragma mark - 删除
++ (void)delete:(NSString *)service{
+    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
+    SecItemDelete((CFDictionaryRef)keychainQuery);
+}
+
+@end

+ 23 - 0
jiaPei/Modules/AppleThirldLogin/View/SignInApple.h

@@ -0,0 +1,23 @@
+//
+//  SignInApple.h
+//  SignInAppleDemo
+//
+//  Created by Yostar on 2019/11/25.
+//  Copyright © 2019 Yostar. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface SignInApple : NSObject
+
+// 处理授权
+- (void)handleAuthorizationAppleIDButtonPress;
+
+// 如果存在iCloud Keychain 凭证或者AppleID 凭证提示用户
+- (void)perfomExistingAccountSetupFlows;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 154 - 0
jiaPei/Modules/AppleThirldLogin/View/SignInApple.m

@@ -0,0 +1,154 @@
+//
+//  SignInApple.m
+//  SignInAppleDemo
+//
+//  Created by Yostar on 2019/11/25.
+//  Copyright © 2019 Yostar. All rights reserved.
+//
+
+#import "SignInApple.h"
+#import <AuthenticationServices/AuthenticationServices.h>
+#import "YostarKeychain.h"
+
+#define KEYCHAIN_IDENTIFIER(a) ([NSString stringWithFormat:@"%@_%@",[[NSBundle mainBundle] bundleIdentifier],a])
+
+@interface SignInApple () <ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding>
+
+@end
+
+@implementation SignInApple
+
+// 处理授权
+- (void)handleAuthorizationAppleIDButtonPress{
+    NSLog(@"////////");
+    
+    if (@available(iOS 13.0, *)) {
+        // 基于用户的Apple ID授权用户,生成用户授权请求的一种机制
+        ASAuthorizationAppleIDProvider *appleIDProvider = [[ASAuthorizationAppleIDProvider alloc] init];
+        // 创建新的AppleID 授权请求
+        ASAuthorizationAppleIDRequest *appleIDRequest = [appleIDProvider createRequest];
+        // 在用户授权期间请求的联系信息
+        appleIDRequest.requestedScopes = @[ASAuthorizationScopeFullName, ASAuthorizationScopeEmail];
+        // 由ASAuthorizationAppleIDProvider创建的授权请求 管理授权请求的控制器
+        ASAuthorizationController *authorizationController = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[appleIDRequest]];
+        // 设置授权控制器通知授权请求的成功与失败的代理
+        authorizationController.delegate = self;
+        // 设置提供 展示上下文的代理,在这个上下文中 系统可以展示授权界面给用户
+        authorizationController.presentationContextProvider = self;
+        // 在控制器初始化期间启动授权流
+        [authorizationController performRequests];
+    }else{
+        // 处理不支持系统版本
+        NSLog(@"该系统版本不可用Apple登录");
+    }
+}
+
+// 如果存在iCloud Keychain 凭证或者AppleID 凭证提示用户
+- (void)perfomExistingAccountSetupFlows{
+    NSLog(@"///已经认证过了/////");
+    
+    if (@available(iOS 13.0, *)) {
+        // 基于用户的Apple ID授权用户,生成用户授权请求的一种机制
+        ASAuthorizationAppleIDProvider *appleIDProvider = [[ASAuthorizationAppleIDProvider alloc] init];
+        // 授权请求AppleID
+        ASAuthorizationAppleIDRequest *appleIDRequest = [appleIDProvider createRequest];
+        // 为了执行钥匙串凭证分享生成请求的一种机制
+        ASAuthorizationPasswordProvider *passwordProvider = [[ASAuthorizationPasswordProvider alloc] init];
+        ASAuthorizationPasswordRequest *passwordRequest = [passwordProvider createRequest];
+        // 由ASAuthorizationAppleIDProvider创建的授权请求 管理授权请求的控制器
+        ASAuthorizationController *authorizationController = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[appleIDRequest, passwordRequest]];
+        // 设置授权控制器通知授权请求的成功与失败的代理
+        authorizationController.delegate = self;
+        // 设置提供 展示上下文的代理,在这个上下文中 系统可以展示授权界面给用户
+        authorizationController.presentationContextProvider = self;
+        // 在控制器初始化期间启动授权流
+        [authorizationController performRequests];
+    }else{
+        // 处理不支持系统版本
+        NSLog(@"该系统版本不可用Apple登录");
+    }
+}
+
+#pragma mark - delegate
+//@optional 授权成功地回调
+- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0)){
+    NSLog(@"授权完成:::%@", authorization.credential);
+    NSLog(@"%s", __FUNCTION__);
+    NSLog(@"%@", controller);
+    NSLog(@"%@", authorization);
+    
+    if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) {
+        // 用户登录使用ASAuthorizationAppleIDCredential
+        ASAuthorizationAppleIDCredential *appleIDCredential = (ASAuthorizationAppleIDCredential *)authorization.credential;
+        NSString *user = appleIDCredential.user;
+        // 使用过授权的,可能获取不到以下三个参数
+//        NSString *familyName = appleIDCredential.fullName.familyName;
+//        NSString *givenName = appleIDCredential.fullName.givenName;
+//        NSString *email = appleIDCredential.email;
+//		NSLog(@"%@-%@-%@",familyName,givenName,email);
+//        NSData *identityToken = appleIDCredential.identityToken;
+//        NSData *authorizationCode = appleIDCredential.authorizationCode;
+        
+        // 服务器验证需要使用的参数
+//        NSString *identityTokenStr = [[NSString alloc] initWithData:identityToken encoding:NSUTF8StringEncoding];
+//        NSString *authorizationCodeStr = [[NSString alloc] initWithData:authorizationCode encoding:NSUTF8StringEncoding];
+//        NSLog(@"%@\n\n%@", identityTokenStr, authorizationCodeStr);
+        
+        // Create an account in your system.
+        // For the purpose of this demo app, store the userIdentifier in the keychain.
+        //  需要使用钥匙串的方式保存用户的唯一信息
+        [YostarKeychain save:KEYCHAIN_IDENTIFIER(@"userIdentifier") data:user];
+        
+    }else if ([authorization.credential isKindOfClass:[ASPasswordCredential class]]){
+        // 这个获取的是iCloud记录的账号密码,需要输入框支持iOS 12 记录账号密码的新特性,如果不支持,可以忽略
+        // Sign in using an existing iCloud Keychain credential.
+        // 用户登录使用现有的密码凭证
+//        ASPasswordCredential *passwordCredential = (ASPasswordCredential *)authorization.credential;
+        // 密码凭证对象的用户标识 用户的唯一标识
+//        NSString *user = passwordCredential.user;
+//        // 密码凭证对象的密码
+//        NSString *password = passwordCredential.password;
+//		NSLog(@"%@-%@",user,password);
+    }else{
+        NSLog(@"授权信息均不符");
+        
+    }
+}
+
+// 授权失败的回调
+- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error API_AVAILABLE(ios(13.0)){
+    // Handle error.
+    NSLog(@"Handle error:%@", error);
+    NSString *errorMsg = nil;
+    switch (error.code) {
+        case ASAuthorizationErrorCanceled:
+            errorMsg = @"用户取消了授权请求";
+            break;
+        case ASAuthorizationErrorFailed:
+            errorMsg = @"授权请求失败";
+            break;
+        case ASAuthorizationErrorInvalidResponse:
+            errorMsg = @"授权请求响应无效";
+            break;
+        case ASAuthorizationErrorNotHandled:
+            errorMsg = @"未能处理授权请求";
+            break;
+        case ASAuthorizationErrorUnknown:
+            errorMsg = @"授权请求失败未知原因";
+            break;
+            
+        default:
+            break;
+    }
+    
+    NSLog(@"%@", errorMsg);
+}
+
+// 告诉代理应该在哪个window 展示内容给用户
+- (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller API_AVAILABLE(ios(13.0)){
+    NSLog(@"88888888888");
+    // 返回window
+    return [UIApplication sharedApplication].windows.lastObject;
+}
+
+@end

+ 73 - 0
jiaPei/Modules/BaseModule/BaseClass/Model/RQBaseModel.h

@@ -0,0 +1,73 @@
+//
+//  RQBaseModel.h
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/14.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import <YYModel/YYModel.h>
+
+@interface RQBaseModel : NSObject <YYModel,NSCopying,NSCoding>
+/// YYModel - API
+/// 将 Json (NSData,NSString,NSDictionary) 转换为 Model
++ (instancetype)modelWithJSON:(id)json;
+/// 字典转模型
++ (instancetype)modelWithDictionary:(NSDictionary *)dictionary;
+/// json-array 转换为 模型数组
++ (NSArray *)modelArrayWithJSON:(id)json;
+
+
+/// 将 Model 转换为 JSON 对象
+- (id)toJSONObject;
+/// 将 Model 转换为 NSData
+- (NSData *)toJSONData;
+/// 将 Model 转换为 JSONString
+- (NSString *)toJSONString;
+
+// Returns the keys for all @property declarations, except for `readonly`
+// properties without ivars, or properties on RQBaseModel itself.
+/// 返回所有@property声明的属性,除了只读属性,以及属性列表不包括成员变量
++ (NSSet *)propertyKeys;
+
+// A dictionary representing the properties of the receiver.
+//
+// The default implementation combines the values corresponding to all
+// +propertyKeys into a dictionary, with any nil values represented by NSNull.
+// This property must never be nil.
+@property (nonatomic, copy, readonly) NSDictionary *dictionaryValue;
+@property (nonatomic, assign, readwrite) NSInteger total;
+
+
+// Merges the value of the given key on the receiver with the value of the same
+// key from the given model object, giving precedence to the other model object.
+//
+// By default, this method looks for a `-merge<Key>FromModel:` method on the
+// receiver, and invokes it if found. If not found, and `model` is not nil, the
+// value for the given key is taken from `model`.
+- (void)mergeValueForKey:(NSString *)key fromModel:(RQBaseModel *)model;
+
+// Merges the values of the given model object into the receiver, using
+// -mergeValueForKey:fromModel: for each key in +propertyKeys.
+//
+// `model` must be an instance of the receiver's class or a subclass thereof.
+- (void)mergeValuesForKeysFromModel:(RQBaseModel *)model;
+@end
+
+
+// Implements validation logic for RQBaseModel.
+@interface RQBaseModel (Validation)
+
+// Validates the model.
+//
+// The default implementation simply invokes -validateValue:forKey:error: with
+// all +propertyKeys and their current value. If -validateValue:forKey:error:
+// returns a new value, the property is set to that new value.
+//
+// error - If not NULL, this may be set to any error that occurs during
+//         validation
+//
+// Returns YES if the model is valid, or NO if the validation failed.
+- (BOOL)validate:(NSError **)error;
+@end

+ 192 - 0
jiaPei/Modules/BaseModule/BaseClass/Model/RQBaseModel.m

@@ -0,0 +1,192 @@
+//
+//  RQBaseModel.m
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/14.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import "RQBaseModel.h"
+#import "RQReflection.h"
+#import "RQEXTRuntimeExtensions.h"
+
+// Used to cache the reflection performed in +propertyKeys.
+static void *RQObjectCachedPropertyKeysKey = &RQObjectCachedPropertyKeysKey;
+
+// Validates a value for an object and sets it if necessary.
+//
+// obj         - The object for which the value is being validated. This value
+//               must not be nil.
+// key         - The name of one of `obj`s properties. This value must not be
+//               nil.
+// value       - The new value for the property identified by `key`.
+// forceUpdate - If set to `YES`, the value is being updated even if validating
+//               it did not change it.
+// error       - If not NULL, this may be set to any error that occurs during
+//               validation
+//
+// Returns YES if `value` could be validated and set, or NO if an error
+// occurred.
+static BOOL SBValidateAndSetValue(id obj, NSString *key, id value, BOOL forceUpdate, NSError **error) {
+	// Mark this as being autoreleased, because validateValue may return
+	// a new object to be stored in this variable (and we don't want ARC to
+	// double-free or leak the old or new values).
+	__autoreleasing id validatedValue = value;
+	
+	@try {
+		if (![obj validateValue:&validatedValue forKey:key error:error]) return NO;
+		
+		if (forceUpdate || value != validatedValue) {
+			[obj setValue:validatedValue forKey:key];
+		}
+		
+		return YES;
+	} @catch (NSException *ex) {
+		NSLog(@"*** Caught exception setting key \"%@\" : %@", key, ex);
+		
+		// Fail fast in Debug builds.
+#if DEBUG
+		@throw ex;
+#else
+		if (error != NULL) {
+			*error = [NSError rq_modelErrorWithException:ex];
+		}
+		
+		return NO;
+#endif
+	}
+}
+
+@implementation RQBaseModel
+/// 将 JSON (NSData,NSString,NSDictionary) 转换为 Model
++ (instancetype)modelWithJSON:(id)json { return [self yy_modelWithJSON:json]; }
+
+/// json-array 转 模型-数组
++ (NSArray *)modelArrayWithJSON:(id)json {
+	return [NSArray yy_modelArrayWithClass:[self class] json:json];
+}
+
+/// 字典转模型
++ (instancetype)modelWithDictionary:(NSDictionary *)dictionary{
+	return [self yy_modelWithDictionary:dictionary];
+}
+
+- (id)toJSONObject { return [self yy_modelToJSONObject]; }
+- (NSData *)toJSONData { return [self yy_modelToJSONData]; }
+- (NSString *)toJSONString { return [self yy_modelToJSONString]; }
+
+
+
+/// Coding/Copying/hash/equal
+- (void)encodeWithCoder:(NSCoder *)aCoder { [self yy_modelEncodeWithCoder:aCoder]; }
+- (id)initWithCoder:(NSCoder *)aDecoder { return [self yy_modelInitWithCoder:aDecoder]; }
+- (id)copyWithZone:(NSZone *)zone { return [self yy_modelCopy]; }
+- (NSUInteger)hash { return [self yy_modelHash]; }
+- (BOOL)isEqual:(id)object { return [self yy_modelIsEqual:object]; }
+
+/// Properties optional
+- (void)setValue:(id)value forUndefinedKey:(NSString *)key { }
+
+/// desc
+- (NSString *)description { return [self yy_modelDescription]; }
+
+
+
+
+
++ (NSSet *)propertyKeys {
+	NSSet *cachedKeys = objc_getAssociatedObject(self, RQObjectCachedPropertyKeysKey);
+	if (cachedKeys != nil) return cachedKeys;
+	
+	NSMutableSet *keys = [NSMutableSet set];
+	
+	[self enumeratePropertiesUsingBlock:^(objc_property_t property, BOOL *stop) {
+		rq_propertyAttributes *attributes = rq_copyPropertyAttributes(property);
+		@onExit {
+			free(attributes);
+		};
+		
+		if (attributes->readonly && attributes->ivar == NULL) return;
+		
+		NSString *key = @(property_getName(property));
+		[keys addObject:key];
+	}];
+	
+	// It doesn't really matter if we replace another thread's work, since we do
+	// it atomically and the result should be the same.
+	objc_setAssociatedObject(self, RQObjectCachedPropertyKeysKey, keys, OBJC_ASSOCIATION_COPY);
+	
+	return keys;
+}
+
+- (NSDictionary *)dictionaryValue {
+	return [self dictionaryWithValuesForKeys:self.class.propertyKeys.allObjects];
+}
+
+
+
+#pragma mark Reflection
++ (void)enumeratePropertiesUsingBlock:(void (^)(objc_property_t property, BOOL *stop))block {
+	Class cls = self;
+	BOOL stop = NO;
+	
+	while (!stop && ![cls isEqual:RQBaseModel.class]) {
+		unsigned count = 0;
+		objc_property_t *properties = class_copyPropertyList(cls, &count);
+		
+		cls = cls.superclass;
+		if (properties == NULL) continue;
+		
+		@onExit {
+			free(properties);
+		};
+		
+		for (unsigned i = 0; i < count; i++) {
+			block(properties[i], &stop);
+			if (stop) break;
+		}
+	}
+}
+
+#pragma mark Merging
+- (void)mergeValueForKey:(NSString *)key fromModel:(RQBaseModel *)model {
+	NSParameterAssert(key != nil);
+	
+	SEL selector = RQSelectorWithCapitalizedKeyPattern("merge", key, "FromModel:");
+	if (![self respondsToSelector:selector]) {
+		if (model != nil) {
+			[self setValue:[model valueForKey:key] forKey:key];
+		}
+		return;
+	}
+	
+	NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:selector]];
+	invocation.target = self;
+	invocation.selector = selector;
+	
+	[invocation setArgument:&model atIndex:2];
+	[invocation invoke];
+}
+
+- (void)mergeValuesForKeysFromModel:(RQBaseModel *)model {
+	NSSet *propertyKeys = model.class.propertyKeys;
+	for (NSString *key in self.class.propertyKeys) {
+		if (![propertyKeys containsObject:key]) continue;
+		
+		[self mergeValueForKey:key fromModel:model];
+	}
+}
+
+
+#pragma mark Validation
+
+- (BOOL)validate:(NSError **)error {
+	for (NSString *key in self.class.propertyKeys) {
+		id value = [self valueForKey:key];
+		BOOL success = SBValidateAndSetValue(self, key, value, NO, error);
+		if (!success) return NO;
+	}
+	return YES;
+}
+
+@end

+ 114 - 0
jiaPei/Modules/BaseModule/BaseClass/Model/RQEXTRuntimeExtensions.h

@@ -0,0 +1,114 @@
+//
+//  RQEXTRuntimeExtensions.h
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/16.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import <objc/runtime.h>
+
+/**
+ * Describes the memory management policy of a property.
+ */
+typedef enum {
+	/**
+	 * The value is assigned.
+	 */
+	rq_propertyMemoryManagementPolicyAssign = 0,
+	
+	/**
+	 * The value is retained.
+	 */
+	rq_propertyMemoryManagementPolicyRetain,
+	
+	/**
+	 * The value is copied.
+	 */
+	rq_propertyMemoryManagementPolicyCopy
+} rq_propertyMemoryManagementPolicy;
+
+/**
+ * Describes the attributes and type information of a property.
+ */
+typedef struct {
+	/**
+	 * Whether this property was declared with the \c readonly attribute.
+	 */
+	BOOL readonly;
+	
+	/**
+	 * Whether this property was declared with the \c nonatomic attribute.
+	 */
+	BOOL nonatomic;
+	
+	/**
+	 * Whether the property is a weak reference.
+	 */
+	BOOL weak;
+	
+	/**
+	 * Whether the property is eligible for garbage collection.
+	 */
+	BOOL canBeCollected;
+	
+	/**
+	 * Whether this property is defined with \c \@dynamic.
+	 */
+	BOOL dynamic;
+	
+	/**
+	 * The memory management policy for this property. This will always be
+	 * #rq_propertyMemoryManagementPolicyAssign if #readonly is \c YES.
+	 */
+	rq_propertyMemoryManagementPolicy memoryManagementPolicy;
+	
+	/**
+	 * The selector for the getter of this property. This will reflect any
+	 * custom \c getter= attribute provided in the property declaration, or the
+	 * inferred getter name otherwise.
+	 */
+	SEL getter;
+	
+	/**
+	 * The selector for the setter of this property. This will reflect any
+	 * custom \c setter= attribute provided in the property declaration, or the
+	 * inferred setter name otherwise.
+	 *
+	 * @note If #readonly is \c YES, this value will represent what the setter
+	 * \e would be, if the property were writable.
+	 */
+	SEL setter;
+	
+	/**
+	 * The backing instance variable for this property, or \c NULL if \c
+	 * \c @synthesize was not used, and therefore no instance variable exists. This
+	 * would also be the case if the property is implemented dynamically.
+	 */
+	const char *ivar;
+	
+	/**
+	 * If this property is defined as being an instance of a specific class,
+	 * this will be the class object representing it.
+	 *
+	 * This will be \c nil if the property was defined as type \c id, if the
+	 * property is not of an object type, or if the class could not be found at
+	 * runtime.
+	 */
+	Class objectClass;
+	
+	/**
+	 * The type encoding for the value of this property. This is the type as it
+	 * would be returned by the \c \@encode() directive.
+	 */
+	char type[];
+} rq_propertyAttributes;
+
+/**
+ * Returns a pointer to a structure containing information about \a property.
+ * You must \c free() the returned pointer. Returns \c NULL if there is an error
+ * obtaining information from \a property.
+ */
+rq_propertyAttributes *rq_copyPropertyAttributes (objc_property_t property);
+
+

+ 210 - 0
jiaPei/Modules/BaseModule/BaseClass/Model/RQEXTRuntimeExtensions.m

@@ -0,0 +1,210 @@
+//
+//  RQEXTRuntimeExtensions.m
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/16.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import "RQEXTRuntimeExtensions.h"
+#import <Foundation/Foundation.h>
+
+rq_propertyAttributes *rq_copyPropertyAttributes (objc_property_t property) {
+	const char * const attrString = property_getAttributes(property);
+	if (!attrString) {
+		fprintf(stderr, "ERROR: Could not get attribute string from property %s\n", property_getName(property));
+		return NULL;
+	}
+	
+	if (attrString[0] != 'T') {
+		fprintf(stderr, "ERROR: Expected attribute string \"%s\" for property %s to start with 'T'\n", attrString, property_getName(property));
+		return NULL;
+	}
+	
+	const char *typeString = attrString + 1;
+	const char *next = NSGetSizeAndAlignment(typeString, NULL, NULL);
+	if (!next) {
+		fprintf(stderr, "ERROR: Could not read past type in attribute string \"%s\" for property %s\n", attrString, property_getName(property));
+		return NULL;
+	}
+	
+	size_t typeLength = next - typeString;
+	if (!typeLength) {
+		fprintf(stderr, "ERROR: Invalid type in attribute string \"%s\" for property %s\n", attrString, property_getName(property));
+		return NULL;
+	}
+	
+	// allocate enough space for the structure and the type string (plus a NUL)
+	rq_propertyAttributes *attributes = calloc(1, sizeof(rq_propertyAttributes) + typeLength + 1);
+	if (!attributes) {
+		fprintf(stderr, "ERROR: Could not allocate rq_propertyAttributes structure for attribute string \"%s\" for property %s\n", attrString, property_getName(property));
+		return NULL;
+	}
+	
+	// copy the type string
+	strncpy(attributes->type, typeString, typeLength);
+	attributes->type[typeLength] = '\0';
+	
+	// if this is an object type, and immediately followed by a quoted string...
+	if (typeString[0] == *(@encode(id)) && typeString[1] == '"') {
+		// we should be able to extract a class name
+		const char *className = typeString + 2;
+		next = strchr(className, '"');
+		
+		if (!next) {
+			fprintf(stderr, "ERROR: Could not read class name in attribute string \"%s\" for property %s\n", attrString, property_getName(property));
+			return NULL;
+		}
+		
+		if (className != next) {
+			size_t classNameLength = next - className;
+			char trimmedName[classNameLength + 1];
+			
+			strncpy(trimmedName, className, classNameLength);
+			trimmedName[classNameLength] = '\0';
+			
+			// attempt to look up the class in the runtime
+			attributes->objectClass = objc_getClass(trimmedName);
+		}
+	}
+	
+	if (*next != '\0') {
+		// skip past any junk before the first flag
+		next = strchr(next, ',');
+	}
+	
+	while (next && *next == ',') {
+		char flag = next[1];
+		next += 2;
+		
+		switch (flag) {
+			case '\0':
+				break;
+				
+			case 'R':
+				attributes->readonly = YES;
+				break;
+				
+			case 'C':
+				attributes->memoryManagementPolicy = rq_propertyMemoryManagementPolicyCopy;
+				break;
+				
+			case '&':
+				attributes->memoryManagementPolicy = rq_propertyMemoryManagementPolicyRetain;
+				break;
+				
+			case 'N':
+				attributes->nonatomic = YES;
+				break;
+				
+			case 'G':
+			case 'S':
+			{
+				const char *nextFlag = strchr(next, ',');
+				SEL name = NULL;
+				
+				if (!nextFlag) {
+					// assume that the rest of the string is the selector
+					const char *selectorString = next;
+					next = "";
+					
+					name = sel_registerName(selectorString);
+				} else {
+					size_t selectorLength = nextFlag - next;
+					if (!selectorLength) {
+						fprintf(stderr, "ERROR: Found zero length selector name in attribute string \"%s\" for property %s\n", attrString, property_getName(property));
+						goto errorOut;
+					}
+					
+					char selectorString[selectorLength + 1];
+					
+					strncpy(selectorString, next, selectorLength);
+					selectorString[selectorLength] = '\0';
+					
+					name = sel_registerName(selectorString);
+					next = nextFlag;
+				}
+				
+				if (flag == 'G')
+					attributes->getter = name;
+				else
+					attributes->setter = name;
+			}
+				
+				break;
+				
+			case 'D':
+				attributes->dynamic = YES;
+				attributes->ivar = NULL;
+				break;
+				
+			case 'V':
+				// assume that the rest of the string (if present) is the ivar name
+				if (*next == '\0') {
+					// if there's nothing there, let's assume this is dynamic
+					attributes->ivar = NULL;
+				} else {
+					attributes->ivar = next;
+					next = "";
+				}
+				
+				break;
+				
+			case 'W':
+				attributes->weak = YES;
+				break;
+				
+			case 'P':
+				attributes->canBeCollected = YES;
+				break;
+				
+			case 't':
+				fprintf(stderr, "ERROR: Old-style type encoding is unsupported in attribute string \"%s\" for property %s\n", attrString, property_getName(property));
+				
+				// skip over this type encoding
+				while (*next != ',' && *next != '\0')
+					++next;
+				
+				break;
+				
+			default:
+				fprintf(stderr, "ERROR: Unrecognized attribute string flag '%c' in attribute string \"%s\" for property %s\n", flag, attrString, property_getName(property));
+		}
+	}
+	
+	if (next && *next != '\0') {
+		fprintf(stderr, "Warning: Unparsed data \"%s\" in attribute string \"%s\" for property %s\n", next, attrString, property_getName(property));
+	}
+	
+	if (!attributes->getter) {
+		// use the property name as the getter by default
+		attributes->getter = sel_registerName(property_getName(property));
+	}
+	
+	if (!attributes->setter) {
+		const char *propertyName = property_getName(property);
+		size_t propertyNameLength = strlen(propertyName);
+		
+		// we want to transform the name to setProperty: style
+		size_t setterLength = propertyNameLength + 4;
+		
+		char setterName[setterLength + 1];
+		strncpy(setterName, "set", 3);
+		strncpy(setterName + 3, propertyName, propertyNameLength);
+		
+		// capitalize property name for the setter
+		setterName[3] = (char)toupper(setterName[3]);
+		
+		setterName[setterLength - 1] = ':';
+		setterName[setterLength] = '\0';
+		
+		attributes->setter = sel_registerName(setterName);
+	}
+	
+	return attributes;
+	
+errorOut:
+	free(attributes);
+	return NULL;
+}
+

+ 31 - 0
jiaPei/Modules/BaseModule/BaseClass/Model/RQReflection.h

@@ -0,0 +1,31 @@
+//
+//  RQReflection.h
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/16.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+// Creates a selector from a key and a constant string.
+//
+// key    - The key to insert into the generated selector. This key should be in
+//          its natural case.
+// suffix - A string to append to the key as part of the selector.
+//
+// Returns a selector, or NULL if the input strings cannot form a valid
+// selector.
+SEL RQSelectorWithKeyPattern(NSString *key, const char *suffix) __attribute__((pure, nonnull(1, 2)));
+
+// Creates a selector from a key and a constant prefix and suffix.
+//
+// prefix - A string to prepend to the key as part of the selector.
+// key    - The key to insert into the generated selector. This key should be in
+//          its natural case, and will have its first letter capitalized when
+//          inserted.
+// suffix - A string to append to the key as part of the selector.
+//
+// Returns a selector, or NULL if the input strings cannot form a valid
+// selector.
+SEL RQSelectorWithCapitalizedKeyPattern(const char *prefix, NSString *key, const char *suffix) __attribute__((pure, nonnull(1, 2, 3)));

+ 51 - 0
jiaPei/Modules/BaseModule/BaseClass/Model/RQReflection.m

@@ -0,0 +1,51 @@
+//
+//  RQReflection.m
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/16.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import "RQReflection.h"
+#import <objc/runtime.h>
+
+SEL RQSelectorWithKeyPattern(NSString *key, const char *suffix) {
+	NSUInteger keyLength = [key maximumLengthOfBytesUsingEncoding:NSUTF8StringEncoding];
+	NSUInteger suffixLength = strlen(suffix);
+	
+	char selector[keyLength + suffixLength + 1];
+	
+	BOOL success = [key getBytes:selector maxLength:keyLength usedLength:&keyLength encoding:NSUTF8StringEncoding options:0 range:NSMakeRange(0, key.length) remainingRange:NULL];
+	if (!success) return NULL;
+	
+	memcpy(selector + keyLength, suffix, suffixLength);
+	selector[keyLength + suffixLength] = '\0';
+	
+	return sel_registerName(selector);
+}
+
+SEL RQSelectorWithCapitalizedKeyPattern(const char *prefix, NSString *key, const char *suffix) {
+	NSUInteger prefixLength = strlen(prefix);
+	NSUInteger suffixLength = strlen(suffix);
+	
+	NSString *initial = [key substringToIndex:1].uppercaseString;
+	NSUInteger initialLength = [initial maximumLengthOfBytesUsingEncoding:NSUTF8StringEncoding];
+	
+	NSString *rest = [key substringFromIndex:1];
+	NSUInteger restLength = [rest maximumLengthOfBytesUsingEncoding:NSUTF8StringEncoding];
+	
+	char selector[prefixLength + initialLength + restLength + suffixLength + 1];
+	memcpy(selector, prefix, prefixLength);
+	
+	BOOL success = [initial getBytes:selector + prefixLength maxLength:initialLength usedLength:&initialLength encoding:NSUTF8StringEncoding options:0 range:NSMakeRange(0, initial.length) remainingRange:NULL];
+	if (!success) return NULL;
+	
+	success = [rest getBytes:selector + prefixLength + initialLength maxLength:restLength usedLength:&restLength encoding:NSUTF8StringEncoding options:0 range:NSMakeRange(0, rest.length) remainingRange:NULL];
+	if (!success) return NULL;
+	
+	memcpy(selector + prefixLength + initialLength + restLength, suffix, suffixLength);
+	selector[prefixLength + initialLength + restLength + suffixLength] = '\0';
+	
+	return sel_registerName(selector);
+}
+

+ 45 - 0
jiaPei/Modules/BaseModule/BaseClass/RQBaseClass.h

@@ -0,0 +1,45 @@
+//
+//  RQBaseClass.h
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/13.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#ifndef RQBaseClass_h
+#define RQBaseClass_h
+
+
+#pragma mark - ViewController
+#import "RQBaseViewController.h"
+#import "RQBaseNavigationController.h"
+#import "RQTabBarController.h"
+#import "RQTableViewController.h"
+#import "RQWebViewViewController.h"
+#import "RQNavigationControllerStack.h"
+#import "RQCollectionViewController.h"
+
+#pragma mark - ViewModel
+#import "RQBaseViewModel.h"
+#import "RQTableViewModel.h"
+#import "RQTabBarViewModel.h"
+#import "RQWebViewModel.h"
+#import "RQViewModelServicesImpl.h"
+#import "RQNavigationProtocol.h"
+#import "RQViewModelServices.h"
+#import "RQCollectionViewModel.h"
+
+#pragma mark - View
+#import "RQTabBar.h"
+#import "RQTableView.h"
+#import "RQCollectionView.h"
+
+#pragma mark - Model
+#import "RQBaseModel.h"
+#import "RQEXTRuntimeExtensions.h"
+#import "RQReflection.h"
+
+
+
+
+#endif /* RQBaseClass_h */

+ 17 - 0
jiaPei/Modules/BaseModule/BaseClass/View/RQCollectionView.h

@@ -0,0 +1,17 @@
+//
+//  RQCollectionView.h
+//  YueXueChe
+//
+//  Created by 张嵘 on 2018/12/18.
+//  Copyright © 2018 lee. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface RQCollectionView : UICollectionView
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 21 - 0
jiaPei/Modules/BaseModule/BaseClass/View/RQCollectionView.m

@@ -0,0 +1,21 @@
+//
+//  RQCollectionView.m
+//  YueXueChe
+//
+//  Created by 张嵘 on 2018/12/18.
+//  Copyright © 2018 lee. All rights reserved.
+//
+
+#import "RQCollectionView.h"
+
+@implementation RQCollectionView
+
+/*
+// Only override drawRect: if you perform custom drawing.
+// An empty implementation adversely affects performance during animation.
+- (void)drawRect:(CGRect)rect {
+    // Drawing code
+}
+*/
+
+@end

+ 17 - 0
jiaPei/Modules/BaseModule/BaseClass/View/RQTabBar.h

@@ -0,0 +1,17 @@
+//
+//  RQTabBar.h
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/14.
+//  Copyright © 2018 张嵘. All rights reserved.
+//	自定义系统的TabBar,解决TabBar顶部的细线高度问题
+
+#import <UIKit/UIKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface RQTabBar : UITabBar
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 46 - 0
jiaPei/Modules/BaseModule/BaseClass/View/RQTabBar.m

@@ -0,0 +1,46 @@
+//
+//  RQTabBar.m
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/14.
+//  Copyright © 2018 张嵘. All rights reserved.
+//	自定义系统的TabBar,解决TabBar顶部的细线高度问题
+
+#import "RQTabBar.h"
+
+@interface RQTabBar ()
+/// divider
+@property (nonatomic, readwrite, weak) UIView *divider;
+@end
+
+@implementation RQTabBar
+- (instancetype)initWithFrame:(CGRect)frame {
+	self = [super initWithFrame:frame];
+	if (self) {
+		/// 去掉tabBar的分割线,以及背景图片
+		[self setShadowImage:[UIImage new]];
+		[self setBackgroundImage:[UIImage yy_imageWithColor:UIColor.whiteColor]];
+        self.backgroundColor = UIColor.whiteColor;
+		/// 添加细线,
+		UIView *divider = [[UIView alloc] init];
+		divider.backgroundColor = RQColor(167.0f, 167.0f, 170.0f);
+		[self addSubview:divider];
+		self.divider = divider;
+        
+        if (@available(iOS 13.0, *)) {
+            self.tintColor = RQ_MAIN_COLOR;
+        } else {
+            self.tintColor = [UIColor whiteColor];
+        }
+	}
+	return self;
+}
+#pragma mark - 布局
+- (void)layoutSubviews {
+	[super layoutSubviews];
+	
+	[self bringSubviewToFront:self.divider];
+	self.divider.rq_height = RQGlobalBottomLineHeight;
+	self.divider.rq_width = RQ_SCREEN_WIDTH;
+}
+@end

+ 13 - 0
jiaPei/Modules/BaseModule/BaseClass/View/RQTableView.h

@@ -0,0 +1,13 @@
+//
+//  RQTableView.h
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/23.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@interface RQTableView : UITableView
+
+@end

+ 26 - 0
jiaPei/Modules/BaseModule/BaseClass/View/RQTableView.m

@@ -0,0 +1,26 @@
+//
+//  RQTableView.m
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/23.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import "RQTableView.h"
+
+@implementation RQTableView
+
+- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
+	
+	/// 处理popView
+	[RQMomentHelper hideAllPopViewWithAnimated:YES];
+	
+	/// 全局
+	[super touchesBegan:touches withEvent:event];
+}
+
+- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
+	UIView * hitView = [super hitTest:point withEvent:event];
+	return hitView;
+}
+@end

+ 16 - 0
jiaPei/Modules/BaseModule/BaseClass/ViewController/RQBaseNavigationController.h

@@ -0,0 +1,16 @@
+//
+//  RQBaseNavigationController.h
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/13.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@interface RQBaseNavigationController : QMUINavigationController
+/// 显示导航栏的细线
+- (void)rq_showNavigationBottomLine;
+/// 隐藏导航栏的细线
+- (void)rq_hideNavigationBottomLine;
+@end

+ 214 - 0
jiaPei/Modules/BaseModule/BaseClass/ViewController/RQBaseNavigationController.m

@@ -0,0 +1,214 @@
+//
+//  RQBaseNavigationController.m
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/13.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import "RQBaseNavigationController.h"
+//#import "RQBaseViewController.h"
+
+@interface RQBaseNavigationController ()
+@property (nonatomic , weak , readwrite) UIImageView * navigationBottomLine;// 导航栏分隔线
+@end
+
+@implementation RQBaseNavigationController
+// 第一次使用这个类调用一次
++ (void)initialize {
+	// 2.设置UINavigationBar的主题
+//	[self _setupNavigationBarTheme];
+	
+	// 3.设置UIBarButtonItem的主题
+//	[self _setupBarButtonItemTheme];
+}
+
+- (void)viewDidLoad {
+	[super viewDidLoad];
+	// 初始化
+//	[self _setup];
+}
+
+#pragma mark - 初始化
+- (void) _setup {
+	[self _setupNavigationBarBottomLine];
+    if (@available(iOS 15.0, *)) {
+        UINavigationBarAppearance *appearance = [UINavigationBarAppearance new];
+        [appearance configureWithOpaqueBackground];
+        /// 设置导航栏背景色
+        appearance.backgroundColor = RQ_MAIN_BACKGROUNDCOLOR;
+        /// 设置文字属性
+        NSMutableDictionary *textAttrs = [NSMutableDictionary dictionary];
+        textAttrs[NSFontAttributeName] = RQMediumFont(15.0f);
+        textAttrs[NSForegroundColorAttributeName] = RQ_MAIN_TEXT_COLOR_1;
+        appearance.titleTextAttributes = textAttrs;
+        /// 设置导航栏下边界分割线透明
+        appearance.shadowImage = [UIImage imageWithColor:UIColor.clearColor];
+        self.navigationBar.standardAppearance = appearance;
+        self.navigationBar.scrollEdgeAppearance = self.navigationBar.standardAppearance;
+    } else {
+        // Fallback on earlier versions
+    }
+}
+
+// 查询最后一条数据
+- (UIImageView *)_findHairlineImageViewUnder:(UIView *)view {
+	if ([view isKindOfClass:UIImageView.class] && view.bounds.size.height <= 1.0) {
+		return (UIImageView *)view;
+	}
+	for (UIView *subview in view.subviews){
+		UIImageView *imageView = [self _findHairlineImageViewUnder:subview];
+		if (imageView){ return imageView; }
+	}
+	return nil;
+}
+
+#pragma mark - 设置导航栏的分割线
+- (void)_setupNavigationBarBottomLine {
+	//!!!:这里之前设置系统的 navigationBarBottomLine.image = xxx;无效 Why? 隐藏了系统的 自己添加了一个分割线
+	// 隐藏系统的导航栏分割线
+	UIImageView *navigationBarBottomLine = [self _findHairlineImageViewUnder:self.navigationBar];
+	navigationBarBottomLine.hidden = YES;
+	// 添加自己的分割线
+	CGFloat navSystemLineH = .5f;
+	UIImageView *navSystemLine = [[UIImageView alloc] initWithFrame:CGRectMake(0, self.navigationBar.rq_height - navSystemLineH, RQ_SCREEN_WIDTH, navSystemLineH)];
+	navSystemLine.backgroundColor = RQ_MAIN_LINE_COLOR_1;
+	[self.navigationBar addSubview:navSystemLine];
+	self.navigationBottomLine = navSystemLine;
+}
+
+/**
+ *  设置UINavigationBarTheme的主题
+ */
++ (void)_setupNavigationBarTheme {
+	UINavigationBar *appearance = [UINavigationBar appearance];
+	
+	/// 设置背景
+	//!!!: 必须设置为透明  不然布局有问题 ios8以下  会崩溃/ 如果iOS8以下  请再_setup里面 设置透明 self.navigationBar.translucent = YES;
+	[appearance setTranslucent:YES]; /// 必须设置YES
+	
+	// 设置导航栏的样式
+	[appearance setBarStyle:UIBarStyleDefault];
+	//设置导航栏文字按钮的渲染色
+	[appearance setTintColor:RQ_MAIN_TEXT_COLOR_1];
+	// 设置导航栏的背景渲染色
+	[appearance setBarTintColor:RQ_MAIN_BACKGROUNDCOLOR];
+	// 设置文字属性
+	NSMutableDictionary *textAttrs = [NSMutableDictionary dictionary];
+	textAttrs[NSFontAttributeName] = RQMediumFont(17.0f);
+	textAttrs[NSForegroundColorAttributeName] = RQ_MAIN_TEXT_COLOR_1;
+	
+	// UIOffsetZero是结构体, 只要包装成NSValue对象, 才能放进字典\数组中
+	NSShadow *shadow = [[NSShadow alloc] init];
+	shadow.shadowOffset =  CGSizeZero;
+	textAttrs[NSShadowAttributeName] = shadow;
+	[appearance setTitleTextAttributes:textAttrs];
+	
+	/// 去掉导航栏的阴影图片
+	[appearance setShadowImage:[UIImage new]];
+}
+
+/**
+ *  设置UIBarButtonItem的主题
+ */
++ (void)_setupBarButtonItemTheme{
+	// 通过appearance对象能修改整个项目中所有UIBarButtonItem的样式
+	UIBarButtonItem *appearance = [UIBarButtonItem appearance];
+	
+	CGFloat fontSize = 16.0f;
+	
+	/**设置文字属性**/
+	// 设置普通状态的文字属性
+	NSMutableDictionary *textAttrs = [NSMutableDictionary dictionary];
+	textAttrs[NSForegroundColorAttributeName] = [UIColor whiteColor];
+	textAttrs[NSFontAttributeName] = RQRegularFont(fontSize);
+	NSShadow *shadow = [[NSShadow alloc] init];
+	shadow.shadowOffset =  CGSizeZero;
+	textAttrs[NSShadowAttributeName] = shadow;
+	[appearance setTitleTextAttributes:textAttrs forState:UIControlStateNormal];
+	
+	
+	// 设置高亮状态的文字属性
+	NSMutableDictionary *highTextAttrs = [NSMutableDictionary dictionaryWithDictionary:textAttrs];
+	highTextAttrs[NSForegroundColorAttributeName] = [[UIColor whiteColor] colorWithAlphaComponent:.5f];
+	[appearance setTitleTextAttributes:highTextAttrs forState:UIControlStateHighlighted];
+	
+	// 设置不可用状态(disable)的文字属性
+	NSMutableDictionary *disableTextAttrs = [NSMutableDictionary dictionaryWithDictionary:textAttrs];
+	disableTextAttrs[NSForegroundColorAttributeName] = [[UIColor whiteColor] colorWithAlphaComponent:.5f];
+	[appearance setTitleTextAttributes:disableTextAttrs forState:UIControlStateDisabled];
+}
+
+#pragma mark - Publi Method
+/// 显示导航栏的细线
+- (void)rq_showNavigationBottomLine {
+	self.navigationBottomLine.hidden = NO;
+}
+/// 隐藏导航栏的细线
+- (void)rq_hideNavigationBottomLine {
+	self.navigationBottomLine.hidden = YES;
+}
+
+/// 能拦截所有push进来的子控制器
+- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated{
+	// 如果现在push的不是栈底控制器(最先push进来的那个控制器)
+	if (self.viewControllers.count > 0){
+		/// 隐藏底部tabbar
+		viewController.hidesBottomBarWhenPushed = YES;
+		
+		NSString *title = @"";
+		
+		/// eg: [A push B]
+		/// 1.取出当前的控制器的title , 也就是取出 A.title
+		/// RQ Fixed: [[self topViewController] navigationItem].title 这样来获取title 而不是[[self topViewController] title]
+		title = [[self topViewController] navigationItem].title? : @"";
+		
+		/// 2.判断要被Push的控制器(B)是否是 RQViewController ,
+		if ([viewController isKindOfClass:[RQBaseViewController class]]) {
+			
+			RQBaseViewModel *viewModel = [(RQBaseViewController *)viewController viewModel];
+			
+			/// 3. 查看backTitle 是否有值
+			title = viewModel.backTitle?:title;
+			
+			
+		}
+		
+		// 4.这里可以设置导航栏的左右按钮 统一管理方法
+//		viewController.navigationItem.leftBarButtonItem = [UIBarButtonItem rq_backItemWithTitle:@"" imageName:@"backIcon" target:self action:@selector(rq_back)];
+        UIBarButtonItem *backItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"backIcon.png"] style:UIBarButtonItemStylePlain target:self action:@selector(rq_back)];
+//        [backItem setTintColor:RQ_MAIN_COLOR];
+        viewController.navigationItem.leftBarButtonItem = backItem;
+       
+	}
+    
+    if ([self.viewControllers.lastObject isKindOfClass:[viewController class]]) {
+        return;
+    }
+    
+	// push
+	[super pushViewController:viewController animated:animated];
+}
+/// 事件处理
+- (void)rq_back {
+	[self popViewControllerAnimated:YES];
+}
+
+#pragma mark - Override
+//- (BOOL)shouldAutorotate {
+//	return self.topViewController.shouldAutorotate;
+//}
+//
+//- (UIInterfaceOrientationMask)supportedInterfaceOrientations{
+//	return self.topViewController.supportedInterfaceOrientations;
+//}
+//
+//- (UIStatusBarStyle)preferredStatusBarStyle{
+//	return self.topViewController.preferredStatusBarStyle;
+//}
+//
+//- (BOOL)prefersStatusBarHidden{
+//	return self.topViewController.prefersStatusBarHidden;
+//}
+
+@end

+ 42 - 0
jiaPei/Modules/BaseModule/BaseClass/ViewController/RQBaseViewController.h

@@ -0,0 +1,42 @@
+//
+//  RQBaseViewController.h
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/13.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+#import "RQBaseViewModel.h"
+
+static inline UIEdgeInsets rq_safeAreaInset(UIView *view) {
+	if (@available(iOS 11.0, *)) {
+		return view.safeAreaInsets;
+	}
+	return UIEdgeInsetsZero;
+}
+
+@interface RQBaseViewController : QMUICommonViewController
+/// The `viewModel` parameter in `-initWithViewModel:` method.
+@property (nonatomic, readonly, strong) RQBaseViewModel *viewModel;
+/// 截图(Push/Pop Present/Dismiss 过度过程中的缩略图)
+@property (nonatomic, readwrite, strong) UIView *snapshot;
+/**
+ 统一使用该方法初始化,子类中直接声明对于的'readonly' 的 'viewModel'属性,
+ 并在@implementation内部加上关键词 '@dynamic viewModel;'
+ @dynamic A相当于告诉编译器:“参数A的getter和setter方法并不在此处,
+ 而在其他地方实现了或者生成了,当你程序运行的时候你就知道了,
+ 所以别警告我了”这样程序在运行的时候,
+ 对应参数的getter和setter方法就会在其他地方去寻找,比如父类。
+ */
+/// Initialization method. This is the preferred way to create a new view.
+///
+/// viewModel - corresponding view model
+///
+/// Returns a new view.
+- (instancetype)initWithViewModel:(RQBaseViewModel *)viewModel;
+
+/// Binds the corresponding view model to the view.(绑定数据模型)
+- (void)bindViewModel;
+@end
+

+ 136 - 0
jiaPei/Modules/BaseModule/BaseClass/ViewController/RQBaseViewController.m

@@ -0,0 +1,136 @@
+//
+//  RQBaseViewController.m
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/13.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import "RQBaseViewController.h"
+
+@interface RQBaseViewController ()
+@property (nonatomic, readwrite, strong) RQBaseViewModel *viewModel;
+@end
+
+@implementation RQBaseViewController
+
+
+- (void)dealloc {
+	/// 销毁时保存数据
+	//    [SBPhotoManager configureSelectOriginalPhoto:_isSelectOriginalPhoto];
+	RQDealloc;
+}
+
+// when `BaseViewController ` created and call `viewDidLoad` method , execute `bindViewModel` method
++ (instancetype)allocWithZone:(struct _NSZone *)zone {
+	RQBaseViewController *viewController = [super allocWithZone:zone];
+	@weakify(viewController)
+	[[viewController
+	  rac_signalForSelector:@selector(viewDidLoad)]
+	 subscribeNext:^(id x) {
+		 @strongify(viewController)
+		 [viewController bindViewModel];
+	 }];
+	return viewController;
+}
+
+- (instancetype)initWithViewModel:(RQBaseViewModel *)viewModel {
+	self = [super init];
+	if (self) {
+		self.viewModel = viewModel;
+	}
+	return self;
+}
+
+- (void)viewWillAppear:(BOOL)animated{
+	[super viewWillAppear:animated];
+	/// 隐藏导航栏细线
+	self.viewModel.prefersNavigationBarBottomLineHidden?[(RQBaseNavigationController *)self.navigationController rq_hideNavigationBottomLine]:[(RQBaseNavigationController *)self.navigationController rq_showNavigationBottomLine];
+	
+    /// 配置键盘
+    IQKeyboardManager.sharedManager.enable = self.viewModel.keyboardEnable;
+    IQKeyboardManager.sharedManager.shouldResignOnTouchOutside = self.viewModel.shouldResignOnTouchOutside;
+    IQKeyboardManager.sharedManager.keyboardDistanceFromTextField = self.viewModel.keyboardDistanceFromTextField;
+	/// 这里做友盟统计
+	//    [MobClick beginLogPageView:SBPageName(self)];
+}
+
+- (void)viewWillDisappear:(BOOL)animated {
+	[super viewWillDisappear:animated];
+	
+	[self.viewModel.willDisappearSignal sendNext:nil];
+	
+	// Being popped, take a snapshot
+	if ([self isMovingFromParentViewController]) {
+		self.snapshot = [self.navigationController.view snapshotViewAfterScreenUpdates:NO];
+	}
+	
+	/// 这里做友盟统计
+	//    [MobClick endLogPageView:SBPageName(self)];
+}
+
+
+- (void)viewDidLoad {
+	[super viewDidLoad];
+	/// ignore adjust auto scroll 64
+	/// RQ: 适配 iOS 11.0 ,iOS11以后,控制器的automaticallyAdjustsScrollViewInsets已经废弃,所以默认就会是YES
+	/// iOS 11新增:adjustContentInset 和 contentInsetAdjustmentBehavior 来处理滚动区域
+	///
+	if (@available(iOS 11.0, *)) {
+		self.automaticallyAdjustsScrollViewInsets = YES;
+	}else{
+		self.automaticallyAdjustsScrollViewInsets = NO;
+	}
+	
+	self.extendedLayoutIncludesOpaqueBars = YES;
+	/// backgroundColor
+	self.view.backgroundColor = RQ_MAIN_BACKGROUNDCOLOR;
+	
+	/// 导航栏隐藏 只能在ViewDidLoad里面加载,无法动态
+	self.fd_prefersNavigationBarHidden = self.viewModel.prefersNavigationBarHidden;
+	
+	/// pop手势
+	self.fd_interactivePopDisabled = self.viewModel.interactivePopDisabled;
+	
+	/// 先记录
+	//    self.isSelectOriginalPhoto = [SBPhotoManager isSelectOriginalPhoto];
+	
+	/// 后重置
+	//    [SBPhotoManager configureSelectOriginalPhoto:NO];
+}
+
+
+// bind the viewModel
+- (void)bindViewModel{
+	/// set navgation title
+	/// RQ Fixed: 这里只是单纯设置导航栏的title。 不然以免self.title同时设置了navigatiItem.title, 同时又设置了tabBarItem.title
+	
+	NSLog(@"--- %@" , self.viewModel.title);
+	@weakify(self);
+	
+    RAC(self.titleView , title) = [RACObserve(self, viewModel.title) deliverOnMainThread];
+	/// 绑定错误信息
+	[self.viewModel.errors subscribeNext:^(NSError *error) {
+		/// 这里可以统一处理某个错误,例如用户授权失效的的操作
+		NSLog(@"...错误...");
+	}];
+	
+	/// 动态改变
+	[[[RACObserve(self.viewModel, interactivePopDisabled) distinctUntilChanged] deliverOnMainThread] subscribeNext:^(NSNumber * x) {
+		@strongify(self);
+		self.fd_interactivePopDisabled = x.boolValue;
+	}];
+}
+
+#pragma mark - Orientation
+- (UIInterfaceOrientationMask)supportedInterfaceOrientations {return UIInterfaceOrientationMaskPortrait;}
+- (BOOL)shouldAutorotate {return YES;}
+- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {return UIInterfaceOrientationPortrait;}
+
+#pragma mark - Status bar
+//- (BOOL)prefersStatusBarHidden { return NO; }
+//- (UIStatusBarStyle)preferredStatusBarStyle { return UIStatusBarStyleDefault; }
+//- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation { return UIStatusBarAnimationFade; }
+
+
+@end

+ 31 - 0
jiaPei/Modules/BaseModule/BaseClass/ViewController/RQCollectionViewController.h

@@ -0,0 +1,31 @@
+//
+//  RQCollectionViewController.h
+//  YueXueChe
+//
+//  Created by 张嵘 on 2018/12/18.
+//  Copyright © 2018 lee. All rights reserved.
+//
+
+#import "RQBaseViewController.h"
+#import "RQCollectionViewModel.h"
+#import "RQCollectionView.h"
+
+@interface RQCollectionViewController : RQBaseViewController <UICollectionViewDelegate , UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
+/// The collection view for collectionView controller.
+/// collectionView
+@property (nonatomic, readonly, weak) RQCollectionView *collectionView;
+
+@property (nonatomic, readonly, strong) UICollectionViewFlowLayout *flowLayout;
+
+/// `collectionView` 的内容缩进,default is UIEdgeInsetsMake(64,0,0,0),you can override it
+@property (nonatomic, readonly, assign) UIEdgeInsets contentInset;
+
+/// reload collectionView data , sub class can override
+- (void)reloadData;
+
+/// dequeueReusableCell
+- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath;
+
+/// configure cell data
+- (void)configureCell:(UICollectionViewCell *)cell atIndexPath:(NSIndexPath *)indexPath withObject:(id)object;
+@end

+ 350 - 0
jiaPei/Modules/BaseModule/BaseClass/ViewController/RQCollectionViewController.m

@@ -0,0 +1,350 @@
+//
+//  RQCollectionViewController.m
+//  YueXueChe
+//
+//  Created by 张嵘 on 2018/12/18.
+//  Copyright © 2018 lee. All rights reserved.
+//
+
+#import "RQCollectionViewController.h"
+
+@interface RQCollectionViewController ()
+/// collectionView
+@property (nonatomic, readwrite, weak) RQCollectionView *collectionView;
+/// contentInset defaul is (64 , 0 , 0 , 0)
+@property (nonatomic, readwrite, assign) UIEdgeInsets contentInset;
+/// 视图模型
+@property (nonatomic, readonly, strong) RQCollectionViewModel *viewModel;
+
+@property (nonatomic, readwrite, strong) UICollectionViewFlowLayout *flowLayout;
+@end
+
+@implementation RQCollectionViewController
+@dynamic viewModel;
+
+- (void)dealloc {
+	// set nil
+	_collectionView.dataSource = nil;
+	_collectionView.delegate = nil;
+}
+
+/// init
+- (instancetype)initWithViewModel:(RQCollectionViewModel *)viewModel {
+	self = [super initWithViewModel:viewModel];
+	if (self) {
+		if ([viewModel shouldRequestRemoteDataOnViewDidLoad]) {
+			@weakify(self)
+			[[self rac_signalForSelector:@selector(viewDidLoad)] subscribeNext:^(id x) {
+				@strongify(self)
+				/// 请求第一页的网络数据
+				[self.viewModel.requestRemoteDataCommand execute:@1];
+			}];
+		}
+	}
+	return self;
+}
+
+
+- (void)viewDidLoad {
+	[super viewDidLoad];
+	// 设置子控件
+	[self _su_setupSubViews];
+	
+}
+
+/// override
+- (void)bindViewModel {
+	[super bindViewModel];
+	
+	/// observe viewModel's dataSource
+	@weakify(self)
+	NSLog(@"%@", self.viewModel.dataSource);
+	[[RACObserve(self.viewModel, dataSource)
+	  deliverOnMainThread]
+	 subscribeNext:^(id x) {
+		 @strongify(self)
+		 // 刷新数据
+		 [self reloadData];
+	 }];
+	
+	/// 隐藏emptyView
+	[self.viewModel.requestRemoteDataCommand.executing subscribeNext:^(NSNumber *executing) {
+		@strongify(self)
+		UIView *emptyDataSetView = [self.collectionView.subviews.rac_sequence objectPassingTest:^(UIView *view) {
+			return [NSStringFromClass(view.class) isEqualToString:@"DZNEmptyDataSetView"];
+		}];
+		emptyDataSetView.alpha = 1.0 - executing.floatValue;
+	}];
+	
+	
+	//    [self.viewModel.requestRemoteDataCommand.executionSignals.switchToLatest subscribeNext:^(id _) {
+	//        @strongify(self);
+	//        /// 有网络
+	//        self.viewModel.disableNetwork = NO;
+	//    }];
+	//
+	//    [self.viewModel.requestRemoteDataCommand.errors subscribeNext:^(id _) {
+	//        @strongify(self);
+	//        /// 有无网络
+	//        self.viewModel.disableNetwork = !self.viewModel.services.client.reachabilityManager.reachable;
+	//    }];
+}
+
+#pragma mark - 设置子控件
+/// setup add `_su_` avoid sub class override it
+- (void)_su_setupSubViews{
+	// set up tableView
+	/// RQ FIXED: 纯代码布局,子类如果重新布局,建议用Masonry重新设置约束
+	// 创建布局对象
+    _flowLayout = [[UICollectionViewFlowLayout alloc] init];
+	// 设置最小行间距
+    _flowLayout.minimumLineSpacing = 8;
+	// 最小列间距
+    _flowLayout.minimumInteritemSpacing = 8;
+	/**
+	 *   设置item的大小 格局item的大小自动布局列间距
+	 *
+	 *  @param 50 宽
+	 *  @param 50 高
+	 *
+	 *  @return
+	 */
+    _flowLayout.itemSize = CGSizeMake(0, 0);
+	/**
+	 *  设置自动滚动的方向 垂直或者横向
+	 */
+    _flowLayout.scrollDirection = UICollectionViewScrollDirectionVertical;
+	/**
+	 *  设置集合视图内边距的大小
+	 *
+	 *  @param 20 上
+	 *  @param 20 左
+	 *  @param 20 下
+	 *  @param 20 右
+	 *
+	 *  @return  UIEdgeInsetsMake  与下面的方法相同  -(UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
+	 */
+    _flowLayout.sectionInset = UIEdgeInsetsMake(16, 16, 16, 16);
+	/**
+	 *  设置header区域大小
+	 *
+	 *  @param 414 414
+	 *  @param 70  无用
+	 *
+	 *  @return
+	 */
+    _flowLayout.headerReferenceSize = CGSizeMake(RQ_SCREEN_WIDTH, 40);
+	/**
+	 *  设置footer区域的大小
+	 *
+	 *  @param 414 无用
+	 *  @param 70  自己设置
+	 *
+	 *  @return  如果写了这里必须设置注册
+	 */
+    _flowLayout.footerReferenceSize = CGSizeMake(RQ_SCREEN_WIDTH, 10);
+	
+	/**
+	 创建UICollectionView前必须先创建布局对象UICollectionViewFlowLayout
+	 
+	 - returns: UICollectionViewFlowLayout(布局对象)
+	 */
+	RQCollectionView *collectionView = [[RQCollectionView alloc] initWithFrame:[UIScreen mainScreen].bounds collectionViewLayout:_flowLayout];
+	collectionView.backgroundColor = RQ_MAIN_BACKGROUNDCOLOR;
+	collectionView.showsVerticalScrollIndicator = NO;
+	collectionView.showsHorizontalScrollIndicator = NO;
+	collectionView.delegate = self;
+	collectionView.dataSource = self;
+	collectionView.bounces = YES;
+	collectionView.delaysContentTouches = false;
+	[self.view addSubview:collectionView];
+	
+	/// 占位符
+	//    tableView.emptyDataSetDelegate = self;
+	//    tableView.emptyDataSetSource = self;
+	//    [tableView mas_makeConstraints:^(MASConstraintMaker *make) {
+	//        make.edges.mas_equalTo(UIEdgeInsetsZero);
+	//    }];
+	
+	
+	self.collectionView = collectionView;
+	collectionView.contentInset = self.contentInset;
+	
+	// 注册cell
+	[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"UICollectionViewCell"];
+	
+	
+	/// RQ-MARK: 这里需要强制布局一下界面,解决由于设置了collectionView的contentInset,然而contentOffset始终是(0,0)的bug 但是这样会导致 collectionView 刷新一次,从而导致子类在 viewDidLoad 无法及时注册的cell,从而会有Crash的隐患
+	//        [self.collectionView layoutIfNeeded];
+	//        [self.collectionView setNeedsLayout];
+	//        [self.collectionView updateConstraintsIfNeeded];
+	//        [self.collectionView setNeedsUpdateConstraints];
+	//        [self.view layoutIfNeeded];
+	
+	/// 添加加载和刷新控件
+	if (self.viewModel.shouldPullDownToRefresh) {
+		/// 下拉刷新
+		@weakify(self);
+		[self.collectionView rq_addHeaderRefresh:^(MJRefreshNormalHeader *header) {
+			/// 加载下拉刷新的数据
+			@strongify(self);
+			[self tableViewDidTriggerHeaderRefresh];
+		}];
+		[self.collectionView.mj_header beginRefreshing];
+	}
+	
+	if (self.viewModel.shouldPullUpToLoadMore) {
+		/// 上拉加载
+		@weakify(self);
+		[self.collectionView rq_addFooterRefresh:^(MJRefreshAutoNormalFooter *footer) {
+			/// 加载上拉刷新的数据
+			@strongify(self);
+			[self tableViewDidTriggerFooterRefresh];
+		}];
+		
+		/// 隐藏footer or 无更多数据
+		RAC(self.collectionView.mj_footer, hidden) = [[RACObserve(self.viewModel, dataSource)
+												  deliverOnMainThread]
+												 map:^(NSArray *dataSource) {
+													 @strongify(self)
+													 NSUInteger count = dataSource.count;
+													 /// 无数据,默认隐藏mj_footer
+													 if (count == 0) return @1;
+													 
+													 if (self.viewModel.shouldEndRefreshingWithNoMoreData) return @(0);
+													 
+													 /// because of
+													 return (count % self.viewModel.perPage)?@1:@0;
+												 }];
+		
+	}
+	
+	
+	if (@available(iOS 11.0, *)) {
+		/// RQ: 适配 iPhone X + iOS 11,
+		RQAdjustsScrollViewInsets_Never(collectionView);
+	}
+}
+
+#pragma mark - 上下拉刷新事件
+/// 下拉事件
+- (void)tableViewDidTriggerHeaderRefresh{
+	@weakify(self)
+	[[[self.viewModel.requestRemoteDataCommand
+	   execute:@1]
+	  deliverOnMainThread]
+	 subscribeNext:^(id x) {
+		 @strongify(self)
+		 self.viewModel.page = 1;
+		 /// 重置没有更多的状态
+		 if (self.viewModel.shouldEndRefreshingWithNoMoreData) [self.collectionView.mj_footer resetNoMoreData];
+	 } error:^(NSError *error) {
+		 @strongify(self)
+		 /// 已经在bindViewModel中添加了对viewModel.dataSource的变化的监听来刷新数据,所以reload = NO即可
+		 [self.collectionView.mj_header endRefreshing];
+	 } completed:^{
+		 @strongify(self)
+		 /// 已经在bindViewModel中添加了对viewModel.dataSource的变化的监听来刷新数据,所以只要结束刷新即可
+		 [self.collectionView.mj_header endRefreshing];
+		 /// 请求完成
+		 [self _requestDataCompleted];
+	 }];
+}
+
+/// 上拉事件
+- (void)tableViewDidTriggerFooterRefresh{
+	@weakify(self);
+	[[[self.viewModel.requestRemoteDataCommand
+	   execute:@(self.viewModel.page + 1)]
+	  deliverOnMainThread]
+	 subscribeNext:^(id x) {
+		 @strongify(self)
+		 self.viewModel.page += 1;
+	 } error:^(NSError *error) {
+		 @strongify(self);
+		 [self.collectionView.mj_footer endRefreshing];
+	 } completed:^{
+		 @strongify(self)
+		 [self.collectionView.mj_footer endRefreshing];
+		 /// 请求完成
+		 [self _requestDataCompleted];
+	 }];
+}
+
+
+#pragma mark - sub class can override it
+- (UIEdgeInsets)contentInset{
+	return UIEdgeInsetsMake((RQ_APPLICATION_NAV_BAR_HEIGHT + RQ_APPLICATION_STATUS_BAR_HEIGHT), 0, 0, 0);
+}
+
+/// reload tableView data
+- (void)reloadData{
+	[self.collectionView reloadData];
+}
+
+/// duqueueReusavleCell
+- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath {
+	return [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
+}
+
+/// configure cell data
+- (void)configureCell:(UICollectionViewCell *)cell atIndexPath:(NSIndexPath *)indexPath withObject:(id)object {
+	
+}
+
+#pragma mark - 辅助方法
+- (void)_requestDataCompleted{
+	NSUInteger count = self.viewModel.dataSource.count;
+	/// RQ Fixed: 这里必须要等到,底部控件结束刷新后,再来设置无更多数据,否则被叠加无效
+	if (self.viewModel.shouldEndRefreshingWithNoMoreData && count%self.viewModel.perPage) [self.collectionView.mj_footer endRefreshingWithNoMoreData];
+}
+
+
+#pragma mark - UICollectionViewDatasource
+- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
+	if (self.viewModel.shouldMultiSections) return self.viewModel.dataSource ? self.viewModel.dataSource.count : 0;
+	return 1;
+}
+
+- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
+	if (self.viewModel.shouldMultiSections) return [(NSArray *)self.viewModel.dataSource[section] count];
+	return self.viewModel.dataSource.count;
+}
+
+- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
+	UICollectionViewCell *cell = [self collectionView:collectionView dequeueReusableCellWithIdentifier:@"UICollectionViewCell" forIndexPath:indexPath];
+	
+	// fetch object
+	id object = nil;
+	if (self.viewModel.shouldMultiSections) object = self.viewModel.dataSource[indexPath.section][indexPath.row];
+	if (!self.viewModel.shouldMultiSections) object = self.viewModel.dataSource[indexPath.row];
+	
+	/// bind model
+	[self configureCell:cell atIndexPath:indexPath withObject:(id)object];
+	return cell;
+}
+
+
+#pragma mark - UICollectionViewDelegate
+- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
+	[collectionView deselectItemAtIndexPath:indexPath animated:YES];
+	// execute commond
+	[self.viewModel.didSelectCommand execute:indexPath];
+}
+
+#pragma mark - collectionViewCell点击高亮
+- (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath{
+//	UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
+}
+- (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath{
+	
+//	UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
+//	cell.backgroundColor = [UIColor whiteColor];
+}
+- (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath{
+	
+	return YES;
+}
+
+
+@end

+ 37 - 0
jiaPei/Modules/BaseModule/BaseClass/ViewController/RQNavigationControllerStack.h

@@ -0,0 +1,37 @@
+//
+//  RQNavigationControllerStack.h
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/14.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "RQRouter.h"
+
+@protocol RQViewModelServices;
+
+@interface RQNavigationControllerStack : NSObject
+/// Initialization method. This is the preferred way to create a new navigation controller stack.
+///
+/// services - The service bus of the `Model` layer.
+///
+/// Returns a new navigation controller stack.
+- (instancetype)initWithServices:(id<RQViewModelServices>)services;
+
+/// Pushes the navigation controller.
+///
+/// navigationController - the navigation controller
+- (void)pushNavigationController:(UINavigationController *)navigationController;
+
+/// Pops the top navigation controller in the stack.
+///
+/// Returns the popped navigation controller.
+- (UINavigationController *)popNavigationController;
+
+/// Retrieves the top navigation controller in the stack.
+///
+/// Returns the top navigation controller in the stack.
+- (UINavigationController *)topNavigationController;
+@end
+

+ 116 - 0
jiaPei/Modules/BaseModule/BaseClass/ViewController/RQNavigationControllerStack.m

@@ -0,0 +1,116 @@
+//
+//  RQNavigationControllerStack.m
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/14.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import "RQNavigationControllerStack.h"
+
+@interface RQNavigationControllerStack ()
+@property (nonatomic, strong) id<RQViewModelServices> services;
+@property (nonatomic, strong) NSMutableArray *navigationControllers;
+@end
+
+
+@implementation RQNavigationControllerStack
+- (instancetype)initWithServices:(id<RQViewModelServices>)services {
+	self = [super init];
+	if (self) {
+		self.services = services;
+		self.navigationControllers = [[NSMutableArray alloc] init];
+		[self registerNavigationHooks];
+	}
+	return self;
+}
+
+- (void)pushNavigationController:(UINavigationController *)navigationController {
+	
+	if ([self.navigationControllers containsObject:navigationController]) return;
+	
+	[self.navigationControllers addObject:navigationController];
+}
+
+- (UINavigationController *)popNavigationController {
+	UINavigationController *navigationController = self.navigationControllers.lastObject;
+	[self.navigationControllers removeLastObject];
+	return navigationController;
+}
+
+- (UINavigationController *)topNavigationController {
+	return self.navigationControllers.lastObject;
+}
+
+- (void)registerNavigationHooks {
+	@weakify(self)
+	[[(NSObject *)self.services
+	  rac_signalForSelector:@selector(pushViewModel:animated:)]
+	 subscribeNext:^(RACTuple *tuple) {
+		 @strongify(self)
+		 RQBaseViewController *topViewController = (RQBaseViewController *)[self.navigationControllers.lastObject topViewController];
+		 if (topViewController.tabBarController) {
+			 topViewController.snapshot = [topViewController.tabBarController.view snapshotViewAfterScreenUpdates:NO];
+		 } else {
+			 topViewController.snapshot = [[self.navigationControllers.lastObject view] snapshotViewAfterScreenUpdates:NO];
+		 }
+		 UIViewController *viewController = (UIViewController *)[RQRouter.sharedInstance viewControllerForViewModel:tuple.first];
+		 [self.navigationControllers.lastObject pushViewController:viewController animated:[tuple.second boolValue]];
+	 }];
+	
+	[[(NSObject *)self.services
+	  rac_signalForSelector:@selector(popViewModelAnimated:)]
+	 subscribeNext:^(RACTuple *tuple) {
+		 @strongify(self)
+		 [self.navigationControllers.lastObject popViewControllerAnimated:[tuple.first boolValue]];
+	 }];
+	
+	[[(NSObject *)self.services
+	  rac_signalForSelector:@selector(popToRootViewModelAnimated:)]
+	 subscribeNext:^(RACTuple *tuple) {
+		 @strongify(self)
+		 [self.navigationControllers.lastObject popToRootViewControllerAnimated:[tuple.first boolValue]];
+	 }];
+	
+	[[(NSObject *)self.services
+	  rac_signalForSelector:@selector(presentViewModel:animated:completion:)]
+	 subscribeNext:^(RACTuple *tuple) {
+        @strongify(self)
+        UIViewController *viewController = (UIViewController *)[RQRouter.sharedInstance viewControllerForViewModel:tuple.first];
+        
+        UINavigationController *presentingViewController = self.navigationControllers.lastObject;
+        if (![viewController isKindOfClass:UINavigationController.class]) {
+            viewController = [[RQBaseNavigationController alloc] initWithRootViewController:viewController];
+        }
+        [self pushNavigationController:(UINavigationController *)viewController];
+        
+        viewController.modalPresentationStyle = UIModalPresentationFullScreen;
+        
+        [presentingViewController presentViewController:viewController animated:[tuple.second boolValue] completion:tuple.third];
+	 }];
+	
+	[[(NSObject *)self.services
+	  rac_signalForSelector:@selector(dismissViewModelAnimated:completion:)]
+	 subscribeNext:^(RACTuple *tuple) {
+		 @strongify(self)
+		 [self popNavigationController];
+		 [self.navigationControllers.lastObject dismissViewControllerAnimated:[tuple.first boolValue] completion:tuple.second];
+	 }];
+	
+	[[(NSObject *)self.services
+	  rac_signalForSelector:@selector(resetRootViewModel:)]
+	 subscribeNext:^(RACTuple *tuple) {
+		 @strongify(self)
+		 [self.navigationControllers removeAllObjects];
+		 /// VM映射VC
+		 UIViewController *viewController = (UIViewController *)[RQRouter.sharedInstance viewControllerForViewModel:tuple.first];
+		 if (![viewController isKindOfClass:[UINavigationController class]] &&
+			 ![viewController isKindOfClass:[RQTabBarController class]]) {
+			 viewController = [[RQBaseNavigationController alloc] initWithRootViewController:viewController];
+			 [self pushNavigationController:(UINavigationController *)viewController];
+		 }
+		 RQSharedAppDelegate.window.rootViewController = viewController;
+	 }];
+}
+
+@end

+ 18 - 0
jiaPei/Modules/BaseModule/BaseClass/ViewController/RQTabBarController.h

@@ -0,0 +1,18 @@
+//
+//  RQTabBarController.h
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/14.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import "RQBaseViewController.h"
+#import "RQTabBarViewModel.h"
+
+@interface RQTabBarController : RQBaseViewController <UITabBarControllerDelegate>
+
+/// The `tabBarController` instance
+@property (nonatomic, readonly, strong) QMUITabBarViewController *tabBarController;
+
+@end
+

+ 50 - 0
jiaPei/Modules/BaseModule/BaseClass/ViewController/RQTabBarController.m

@@ -0,0 +1,50 @@
+//
+//  RQTabBarController.m
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/14.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import "RQTabBarController.h"
+#import "RQTabBar.h"
+
+@interface RQTabBarController ()
+/// tabBarController
+@property (nonatomic, strong, readwrite) QMUITabBarViewController *tabBarController;
+@end
+
+@implementation RQTabBarController
+
+- (void)viewDidLoad {
+    [super viewDidLoad];
+	self.tabBarController = [[QMUITabBarViewController alloc] init];
+	/// 添加子控制器
+	[self.view addSubview:self.tabBarController.view];
+	[self addChildViewController:self.tabBarController];
+	[self.tabBarController didMoveToParentViewController:self];
+	
+	// kvc替换系统的tabBar
+	RQTabBar *tabbar = [[RQTabBar alloc] init];
+	//kvc实质是修改了系统的_tabBar
+	[self.tabBarController setValue:tabbar forKeyPath:@"tabBar"];
+}
+
+#pragma mark - Ovveride
+- (BOOL)shouldAutorotate {
+	return self.tabBarController.selectedViewController.shouldAutorotate;
+}
+
+- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
+	return self.tabBarController.selectedViewController.supportedInterfaceOrientations;
+}
+
+- (UIStatusBarStyle)preferredStatusBarStyle {
+	return self.tabBarController.selectedViewController.preferredStatusBarStyle;
+}
+
+- (BOOL)prefersStatusBarHidden{
+	return self.tabBarController.selectedViewController.prefersStatusBarHidden;
+}
+
+@end

+ 30 - 0
jiaPei/Modules/BaseModule/BaseClass/ViewController/RQTableViewController.h

@@ -0,0 +1,30 @@
+//
+//  RQTableViewController.h
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/23.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import "RQBaseViewController.h"
+#import "RQTableView.h"
+#import "RQTableViewModel.h"
+
+@interface RQTableViewController : RQBaseViewController <UITableViewDelegate , UITableViewDataSource>
+/// The table view for tableView controller.
+/// tableView
+@property (nonatomic, readonly, weak) RQTableView *tableView;
+
+/// `tableView` 的内容缩进,default is UIEdgeInsetsMake(64,0,0,0),you can override it
+@property (nonatomic, readonly, assign) UIEdgeInsets contentInset;
+
+/// reload tableView data , sub class can override
+- (void)reloadData;
+
+/// dequeueReusableCell
+- (UITableViewCell *)tableView:(UITableView *)tableView dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath;
+
+/// configure cell data
+- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath withObject:(id)object;
+@end
+

+ 304 - 0
jiaPei/Modules/BaseModule/BaseClass/ViewController/RQTableViewController.m

@@ -0,0 +1,304 @@
+//
+//  RQTableViewController.m
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/23.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import "RQTableViewController.h"
+
+
+@interface RQTableViewController ()
+/// tableView
+@property (nonatomic, readwrite, weak)   RQTableView *tableView;
+/// contentInset defaul is (64 , 0 , 0 , 0)
+@property (nonatomic, readwrite, assign) UIEdgeInsets contentInset;
+/// 视图模型
+@property (nonatomic, readonly, strong) RQTableViewModel *viewModel;
+@end
+
+@implementation RQTableViewController
+
+@dynamic viewModel;
+
+- (void)dealloc {
+	// set nil
+	_tableView.dataSource = nil;
+	_tableView.delegate = nil;
+}
+
+/// init
+- (instancetype)initWithViewModel:(RQTableViewModel *)viewModel {
+	self = [super initWithViewModel:viewModel];
+	if (self) {
+		if ([viewModel shouldRequestRemoteDataOnViewDidLoad]) {
+			@weakify(self)
+			[[self rac_signalForSelector:@selector(viewDidLoad)] subscribeNext:^(id x) {
+				@strongify(self)
+				/// 请求第一页的网络数据
+                [[self.viewModel.requestRemoteDataCommand execute:@1] subscribeCompleted:^{
+                    if (self.viewModel.dataSource.count >= self.viewModel.perPage) {
+                        self.viewModel.page = 2;
+                    }
+                }];
+			}];
+		}
+	}
+	return self;
+}
+
+
+- (void)viewDidLoad
+{
+	[super viewDidLoad];
+	// 设置子控件
+	[self _su_setupSubViews];
+	
+}
+
+/// override
+- (void)bindViewModel
+{
+	[super bindViewModel];
+	
+	/// observe viewModel's dataSource
+	@weakify(self)
+	[[RACObserve(self.viewModel, dataSource)
+	  deliverOnMainThread]
+	 subscribeNext:^(id x) {
+		 @strongify(self)
+		 // 刷新数据
+		 [self reloadData];
+	 }];
+	
+	/// 隐藏emptyView
+	[self.viewModel.requestRemoteDataCommand.executing subscribeNext:^(NSNumber *executing) {
+		@strongify(self)
+		UIView *emptyDataSetView = [self.tableView.subviews.rac_sequence objectPassingTest:^(UIView *view) {
+			return [NSStringFromClass(view.class) isEqualToString:@"DZNEmptyDataSetView"];
+		}];
+		emptyDataSetView.alpha = 1.0 - executing.floatValue;
+	}];
+	
+	
+	//    [self.viewModel.requestRemoteDataCommand.executionSignals.switchToLatest subscribeNext:^(id _) {
+	//        @strongify(self);
+	//        /// 有网络
+	//        self.viewModel.disableNetwork = NO;
+	//    }];
+	//
+	//    [self.viewModel.requestRemoteDataCommand.errors subscribeNext:^(id _) {
+	//        @strongify(self);
+	//        /// 有无网络
+	//        self.viewModel.disableNetwork = !self.viewModel.services.client.reachabilityManager.reachable;
+	//    }];
+}
+
+#pragma mark - 设置子控件
+/// setup add `_su_` avoid sub class override it
+- (void)_su_setupSubViews{
+	// set up tableView
+	/// RQ FIXED: 纯代码布局,子类如果重新布局,建议用Masonry重新设置约束
+	RQTableView *tableView = [[RQTableView alloc] initWithFrame:[UIScreen mainScreen].bounds style:self.viewModel.style];
+	tableView.backgroundColor = RQ_LIST_BACKGROUNDCOLOR;
+	tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
+	// set delegate and dataSource
+	tableView.delegate = self;
+	tableView.dataSource = self;
+	[self.view addSubview:tableView];
+	
+	/// 占位符
+	//    tableView.emptyDataSetDelegate = self;
+	//    tableView.emptyDataSetSource = self;
+	//    [tableView mas_makeConstraints:^(MASConstraintMaker *make) {
+	//        make.edges.mas_equalTo(UIEdgeInsetsZero);
+	//    }];
+	
+	
+	self.tableView = tableView;
+	tableView.contentInset  = self.contentInset;
+	// 注册cell
+	[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"UITableViewCell"];
+	
+	/// RQ FIXED: 这里需要强制布局一下界面,解决由于设置了tableView的contentInset,然而contentOffset始终是(0,0)的bug 但是这样会导致 tableView 刷新一次,从而导致子类在 viewDidLoad 无法及时注册的cell,从而会有Crash的隐患
+	//        [self.tableView layoutIfNeeded];
+	//        [self.tableView setNeedsLayout];
+	//        [self.tableView updateConstraintsIfNeeded];
+	//        [self.tableView setNeedsUpdateConstraints];
+	//        [self.view layoutIfNeeded];
+	
+	/// 添加加载和刷新控件
+	if (self.viewModel.shouldPullDownToRefresh) {
+		/// 下拉刷新
+		@weakify(self);
+		[self.tableView rq_addHeaderRefresh:^(MJRefreshNormalHeader *header) {
+			/// 加载下拉刷新的数据
+			@strongify(self);
+			[self tableViewDidTriggerHeaderRefresh];
+		}];
+//		[self.tableView.mj_header beginRefreshing];
+	}
+	
+	if (self.viewModel.shouldPullUpToLoadMore) {
+		/// 上拉加载
+		@weakify(self);
+		[self.tableView rq_addFooterRefresh:^(MJRefreshAutoNormalFooter *footer) {
+			/// 加载上拉刷新的数据
+			@strongify(self);
+			[self tableViewDidTriggerFooterRefresh];
+		}];
+        
+		/// 隐藏footer or 无更多数据
+		RAC(self.tableView.mj_footer, hidden) = [[RACObserve(self.viewModel, dataSource)
+												  deliverOnMainThread]
+												 map:^(NSArray *dataSource) {
+													 @strongify(self)
+													 NSUInteger count = dataSource.count;
+													 /// 无数据,默认隐藏mj_footer
+													 if (count == 0) return @1;
+            
+                                                     /// 无更多数据,隐藏mj_footer
+                                                     if (count == self.viewModel.dataTotalNum) return @1;
+													 
+													 if (self.viewModel.shouldEndRefreshingWithNoMoreData) return @(0);
+													 
+													 /// because of
+													 return (count % self.viewModel.perPage)?@1:@0;
+												 }];
+		
+	}
+	
+	
+	if (@available(iOS 11.0, *)) {
+		/// RQ: 适配 iPhone X + iOS 11,
+		RQAdjustsScrollViewInsets_Never(tableView);
+		/// iOS 11上发生tableView顶部有留白,原因是代码中只实现了heightForHeaderInSection方法,而没有实现viewForHeaderInSection方法。那样写是不规范的,只实现高度,而没有实现view,但代码这样写在iOS 11之前是没有问题的,iOS 11之后应该是由于开启了估算行高机制引起了bug。
+		tableView.estimatedRowHeight = 0;
+		tableView.estimatedSectionHeaderHeight = 0;
+		tableView.estimatedSectionFooterHeight = 0;
+	}
+}
+
+#pragma mark - 上下拉刷新事件
+/// 下拉事件
+- (void)tableViewDidTriggerHeaderRefresh{
+	@weakify(self)
+    self.viewModel.page = 1;
+	[[[self.viewModel.requestRemoteDataCommand
+	   execute:@1]
+	  deliverOnMainThread]
+	 subscribeNext:^(id x) {
+		 @strongify(self)
+        if (self.viewModel.dataSource.count >= self.viewModel.perPage) {
+            self.viewModel.page = 2;
+        }
+		 /// 重置没有更多的状态
+		 if (self.viewModel.shouldEndRefreshingWithNoMoreData) [self.tableView.mj_footer resetNoMoreData];
+	 } error:^(NSError *error) {
+		 @strongify(self)
+		 /// 已经在bindViewModel中添加了对viewModel.dataSource的变化的监听来刷新数据,所以reload = NO即可
+		 [self.tableView.mj_header endRefreshing];
+	 } completed:^{
+		 @strongify(self)
+		 /// 已经在bindViewModel中添加了对viewModel.dataSource的变化的监听来刷新数据,所以只要结束刷新即可
+		 [self.tableView.mj_header endRefreshing];
+		 /// 请求完成
+		 [self _requestDataCompleted];
+	 }];
+}
+
+/// 上拉事件
+- (void)tableViewDidTriggerFooterRefresh{
+	@weakify(self);
+    NSInteger currentPage = self.viewModel.page + 1;
+	[[[self.viewModel.requestRemoteDataCommand
+	   execute:@(currentPage)]
+	  deliverOnMainThread]
+	 subscribeNext:^(id x) {
+		 @strongify(self)
+		 self.viewModel.page += 1;
+	 } error:^(NSError *error) {
+		 @strongify(self);
+		 [self.tableView.mj_footer endRefreshing];
+	 } completed:^{
+		 @strongify(self)
+		 [self.tableView.mj_footer endRefreshing];
+		 /// 请求完成
+		 [self _requestDataCompleted];
+	 }];
+}
+
+
+#pragma mark - sub class can override it
+/// 配置tableView的区域
+- (UIEdgeInsets)contentInset {
+    return UIEdgeInsetsMake((RQ_APPLICATION_NAV_BAR_HEIGHT + RQ_APPLICATION_STATUS_BAR_HEIGHT), 0, 0, 0);
+}
+
+/// reload tableView data
+- (void)reloadData{
+	[self.tableView reloadData];
+}
+
+/// duqueueReusavleCell
+- (UITableViewCell *)tableView:(UITableView *)tableView dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath {
+	return [tableView dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
+}
+
+/// configure cell data
+- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath withObject:(id)object {}
+
+
+#pragma mark - 辅助方法
+- (void)_requestDataCompleted{
+	NSUInteger count = self.viewModel.dataSource.count;
+	/// RQ Fixed: 这里必须要等到,底部控件结束刷新后,再来设置无更多数据,否则被叠加无效
+	if (self.viewModel.shouldEndRefreshingWithNoMoreData && count%self.viewModel.perPage) [self.tableView.mj_footer endRefreshingWithNoMoreData];
+}
+
+
+#pragma mark - UITableViewDataSource
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
+	
+	if (self.viewModel.shouldMultiSections) return self.viewModel.dataSource ? self.viewModel.dataSource.count : 0;
+	return 1;
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
+	if (self.viewModel.shouldMultiSections) return [(NSArray *)self.viewModel.dataSource[section] count];
+	return self.viewModel.dataSource.count;
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
+	
+	UITableViewCell *cell = [self tableView:tableView dequeueReusableCellWithIdentifier:@"UITableViewCell" forIndexPath:indexPath];
+	
+	// fetch object
+	id object = nil;
+	if (self.viewModel.shouldMultiSections) object = self.viewModel.dataSource[indexPath.section][indexPath.row];
+	if (!self.viewModel.shouldMultiSections) object = self.viewModel.dataSource[indexPath.row];
+	
+	/// bind model
+	[self configureCell:cell atIndexPath:indexPath withObject:(id)object];
+	return cell;
+}
+
+
+#pragma mark - UITableViewDelegate
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
+	[tableView deselectRowAtIndexPath:indexPath animated:YES];
+	// execute commond
+	[self.viewModel.didSelectCommand execute:indexPath];
+}
+
+
+- (BOOL)shouldCustomizeNavigationBarTransitionIfHideable {
+    return YES;
+}
+
+- (void)qmui_themeDidChangeByManager:(QMUIThemeManager *)manager identifier:(__kindof NSObject<NSCopying> *)identifier theme:(__kindof NSObject *)theme {
+    [super qmui_themeDidChangeByManager:manager identifier:identifier theme:theme];
+    [self.tableView reloadData];
+}
+@end

+ 22 - 0
jiaPei/Modules/BaseModule/BaseClass/ViewController/RQWebViewViewController.h

@@ -0,0 +1,22 @@
+//
+//  RQWebViewViewController.h
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/27.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import "RQBaseViewController.h"
+#import "RQWebViewModel.h"
+#import <WebKit/WebKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface RQWebViewViewController : RQBaseViewController <WKNavigationDelegate, WKUIDelegate, WKScriptMessageHandler>
+/// webView
+@property (nonatomic, weak, readonly) WKWebView *webView;
+/// 内容缩进 (64,0,0,0)
+@property (nonatomic, readonly, assign) UIEdgeInsets contentInset;
+@end
+
+NS_ASSUME_NONNULL_END

+ 366 - 0
jiaPei/Modules/BaseModule/BaseClass/ViewController/RQWebViewViewController.m

@@ -0,0 +1,366 @@
+//
+//  RQWebViewViewController.m
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/27.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import "RQWebViewViewController.h"
+#import "WKWebViewJavascriptBridge.h"
+#import <AlipaySDK/AlipaySDK.h>
+
+@interface RQWebViewViewController ()
+/// webView
+@property (nonatomic, weak, readwrite) WKWebView *webView;
+/// 进度条
+@property (nonatomic, readwrite, strong) UIProgressView *progressView;
+/// 返回按钮
+@property (nonatomic, readwrite, strong) UIBarButtonItem *backItem;
+/// 关闭按钮 (点击关闭按钮  退出WebView)
+@property (nonatomic, readwrite, strong) UIBarButtonItem *closeItem;
+/// viewModel
+@property (nonatomic, strong, readonly) RQWebViewModel *viewModel;
+@end
+
+@implementation RQWebViewViewController
+@dynamic viewModel;
+
+- (void)dealloc{
+	RQDealloc;
+	/// remove observer ,otherwise will crash
+	[_webView stopLoading];
+}
+
+- (void)viewWillAppear:(BOOL)animated {
+	[super viewWillAppear:animated];
+    /// 清除缓存
+    NSSet *websiteDataTypes = [WKWebsiteDataStore allWebsiteDataTypes];
+    NSDate *dateFrom = [NSDate dateWithTimeIntervalSince1970:0];
+    [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:websiteDataTypes modifiedSince:dateFrom completionHandler:^{
+    }];
+
+    
+    
+	[self.navigationController.navigationBar addSubview:self.progressView];
+    if (self.viewModel.request) {
+        [self.webView loadRequest:self.viewModel.request];
+    } else if (RQStringIsNotEmpty(self.viewModel.requestUrl)) {
+        /// 加载请求数据
+        NSString *encodedString = (NSString*) CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,(CFStringRef)self.viewModel.requestUrl,(CFStringRef)@"!$&'()*+,-./:;=?@_~%#[]",NULL, kCFStringEncodingUTF8));
+        NSURL *httpUrl = [NSURL URLWithString:encodedString];
+        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:httpUrl];
+        [self.webView loadRequest:request];
+    }
+}
+
+- (void)viewWillDisappear:(BOOL)animated {
+	[super viewWillDisappear:animated];
+	[self.progressView removeFromSuperview];
+}
+
+- (void)viewDidLoad {
+	[super viewDidLoad];
+	
+	/// 添加断言,request错误 应用直接crash
+//	NSParameterAssert(self.viewModel.request);
+	
+	
+    [self.navigationItem setLeftBarButtonItems:@[self.backItem]];
+    [self.navigationItem setRightBarButtonItems:@[self.closeItem]];
+    
+
+	
+	///CoderMikeHe FIXED: 切记 lightempty_ios 是前端跟H5商量的结果,请勿修改。
+	NSString *userAgent = @"";
+	
+	if (!(RQIOSVersion>=9.0)) [[NSUserDefaults standardUserDefaults] registerDefaults:@{@"userAgent":userAgent}];
+	
+	/// 注册JS
+	WKUserContentController *userContentController = [[WKUserContentController alloc] init];
+	/// 这里可以注册JS的处理 涉及公司私有方法 这里笔者不作处理
+    if (self.viewModel.webViewType == RQWebViewType_Exam) {
+        [userContentController addScriptMessageHandler:self name:@"backView"];
+        [userContentController addScriptMessageHandler:self name:@"displayRow"];
+        [userContentController addScriptMessageHandler:self name:@"displayCol"];
+    }
+    WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
+    configuration.allowsInlineMediaPlayback = YES;
+    if (@available(iOS 10.0, *)) {
+        configuration.mediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypeNone;
+    }
+    NSMutableString *jsString = [NSMutableString string];
+    /// 自适应屏幕宽度
+    [jsString appendString:@"var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);"];
+    /// 禁止长按
+    [jsString appendString:@"document.documentElement.style.webkitTouchCallout='none';"];
+    /// 禁止选择
+    [jsString appendString:@"document.documentElement.style.webkitUserSelect='none';"];
+    /// 关闭H5的缩放手势
+    [jsString appendString:@"var script = document.createElement('meta');"
+     "script.name = 'viewport';"
+     "script.content=\"width=device-width, initial-scale=1.0,maximum-scale=1.0, minimum-scale=1.0, user-scalable=no\";"
+     "document.getElementsByTagName('head')[0].appendChild(script);"];
+    
+    WKUserScript *userScript = [[WKUserScript alloc] initWithSource:jsString injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
+	// 添加自适应屏幕宽度js调用的方法
+	[userContentController addUserScript:userScript];
+	/// 赋值userContentController
+	configuration.userContentController = userContentController;
+	
+	WKWebView *webView = [[WKWebView alloc] initWithFrame:RQ_SCREEN_BOUNDS configuration:configuration];
+	webView.navigationDelegate = self;
+	webView.UIDelegate = self;
+	
+	if (@available(iOS 9.0, *)) {
+		webView.customUserAgent = userAgent;
+	} else {
+		// Fallback on earlier versions
+	}
+	self.webView = webView;
+	[self.view addSubview:webView];
+	
+	/// oc调用js
+	[webView evaluateJavaScript:@"navigator.userAgent" completionHandler:^(id result, NSError *error) {
+		NSLog(@"navigator.userAgent.result is ++++ %@", result);
+	}];
+	
+	/// 监听数据
+	@weakify(self);
+	/// binding self.viewModel.avatarUrlString
+	[RACObserve(self.webView, title) subscribeNext:^(NSString *titleStr) {
+		@strongify(self);
+		NSLog(@"%@",titleStr);
+		/// CoderMikeHe FIXED: 这里只设置导航栏的title 以免self.title 设置了tabBarItem.title
+		if (!self.viewModel.shouldDisableWebViewTitle) self.titleView.title = self.webView.title;
+	}];
+	
+	[RACObserve(self.webView, loading) subscribeNext:^(NSNumber *currentLoadingState) {
+        @strongify(self)
+		BOOL isLoading = [currentLoadingState boolValue];
+		if (isLoading) {
+			NSLog(@"--- webView is loading ---");
+            if ((self.viewModel.webViewType == RQWebViewType_Exam)) {
+                [self enterLandscapeFullScreen:UIInterfaceOrientationLandscapeLeft animated:YES];
+                [self.webView setNeedsLayout];
+                [self.webView layoutIfNeeded];
+            }
+		}else {
+			NSLog(@"--- webView isn't loading ---");
+		}
+	}];
+	
+	[RACObserve(self.webView, estimatedProgress) subscribeNext:^(NSNumber *estimatedProgressValue) {
+		NSLog(@"%@",estimatedProgressValue);
+		@strongify(self)
+		[self.progressView setAlpha:1.0f];
+		[self.progressView setProgress:self.webView.estimatedProgress animated:YES];
+		if(self.webView.estimatedProgress >= 1.0f) {
+            @weakify(self);
+			[UIView animateWithDuration:0.3 delay:0.3 options:UIViewAnimationOptionCurveEaseOut animations:^{
+                @strongify(self)
+				[self.progressView setAlpha:0.0f];
+			} completion:^(BOOL finished) {
+                @strongify(self)
+				[self.progressView setProgress:0.0f animated:NO];
+			}];
+		}
+	}];
+	
+	/// 添加刷新控件
+	if(self.viewModel.shouldPullDownToRefresh){
+		[self.webView.scrollView rq_addHeaderRefresh:^(MJRefreshNormalHeader *header) {
+			@strongify(self)
+			[self.webView reload];
+		}];
+		[self.webView.scrollView.mj_header beginRefreshing];
+	}
+	self.webView.scrollView.contentInset = self.contentInset;
+	
+	/// CoderMikeHe: 适配 iPhone X + iOS 11,去掉安全区域
+	if (@available(iOS 11.0, *)) {
+		RQAdjustsScrollViewInsets_Never(webView.scrollView);
+	}
+}
+
+#pragma mark - 事件处理
+- (void)_backItemDidClicked{ /// 返回按钮事件处理
+	/// 可以返回到上一个网页,就返回到上一个网页
+	if (self.webView.canGoBack) {
+		[self.webView goBack];
+	}else{/// 不能返回上一个网页,就返回到上一个界面
+		/// 判断 是Push还是Present进来的,
+		if (self.presentingViewController) {
+            if (self.viewModel.webViewType == RQWebViewType_VIP) {
+                [self dismissViewControllerAnimated:YES completion:NULL];
+            } else {
+                [self.viewModel.services dismissViewModelAnimated:YES completion:NULL];
+            }
+		} else {
+			[self.viewModel.services popViewModelAnimated:YES];
+		}
+	}
+}
+
+- (void)_closeItemDidClicked{
+	/// 判断 是Push还是Present进来的
+	if (self.presentingViewController) {
+        if (self.viewModel.webViewType == RQWebViewType_VIP) {
+            [self dismissViewControllerAnimated:YES completion:NULL];
+        } else {
+            [self.viewModel.services dismissViewModelAnimated:YES completion:NULL];
+        }
+	} else {
+		[self.viewModel.services popViewModelAnimated:YES];
+	}
+}
+
+- (UIEdgeInsets)contentInset{
+    BOOL isExamUrl = (self.viewModel.webViewType == RQWebViewType_Exam);
+    return UIEdgeInsetsMake(isExamUrl? 0 : (RQ_APPLICATION_NAV_BAR_HEIGHT + RQ_APPLICATION_STATUS_BAR_HEIGHT), isExamUrl? 0 : 0, isExamUrl? 0 : RQ_APPLICATION_SAFEAREA_BOTTOM_HEIGHT, isExamUrl? 0 : 0);
+}
+
+
+- (void)enterLandscapeFullScreen:(UIInterfaceOrientation)orientation animated:(BOOL)animated {
+    CGFloat cellHeight = RQ_SCREEN_HEIGHT - RQ_APPLICATION_STATUS_BAR_HEIGHT - RQ_APPLICATION_SAFEAREA_BOTTOM_HEIGHT;
+    CGFloat rotation = 0;
+    CGFloat duration = [UIApplication sharedApplication].statusBarOrientationAnimationDuration;
+    if (orientation == UIInterfaceOrientationLandscapeLeft) {
+        rotation = M_PI_2;
+    }else if (orientation == UIInterfaceOrientationLandscapeRight) {
+        rotation = M_PI_2 * 3;
+    }
+    UIView *presentView = self.webView;
+    @weakify(presentView)
+    CGRect landRect = CGRectMake(RQ_APPLICATION_STATUS_BAR_HEIGHT, 0, cellHeight, RQ_SCREEN_WIDTH);
+    [UIView animateWithDuration:duration animations:^{
+        @strongify(presentView)
+        presentView.layer.affineTransform = CGAffineTransformMakeRotation(rotation);
+    } completion:^(BOOL finished) {
+        @strongify(presentView)
+        presentView.layer.bounds = landRect;
+    }];
+}
+
+#pragma mark - WKScriptMessageHandler
+- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
+	/// js call OC function
+//    [QMUIConsole log:[NSString stringWithFormat:@"js call OC function :%@",message.name]];
+    NSLog(@"js call OC function :%@",message.name);
+    if ([message.name isEqualToString:@"displayRow"]) {
+        [self enterLandscapeFullScreen:UIInterfaceOrientationLandscapeLeft animated:YES];
+        [self.webView setNeedsLayout];
+        [self.webView layoutIfNeeded];
+    } else if ([message.name isEqualToString:@"backView"]) {
+        [userContentController removeScriptMessageHandlerForName:@"backView"];
+        [userContentController removeScriptMessageHandlerForName:@"displayRow"];
+        [userContentController removeScriptMessageHandlerForName:@"displayCol"];
+//        [self _backItemDidClicked];
+        [self.viewModel.services popViewModelAnimated:YES];
+    }
+}
+
+#pragma mark - WKNavigationDelegate
+/// 内容开始返回时调用
+- (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation {
+	/// 不显示关闭按钮
+	if(self.viewModel.shouldDisableWebViewClose) return;
+	
+	UIBarButtonItem *backItem = self.navigationItem.leftBarButtonItems.firstObject;
+	if (backItem) {
+		if ([self.webView canGoBack]) {
+			[self.navigationItem setLeftBarButtonItems:@[backItem, self.closeItem]];
+		} else {
+			[self.navigationItem setLeftBarButtonItems:@[backItem]];
+		}
+	}
+}
+
+// 导航完成时,会回调(也就是页面载入完成了)
+- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation {
+    if (self.viewModel.shouldPullDownToRefresh) [webView.scrollView.mj_header endRefreshing];
+    [webView evaluateJavaScript:@"document.body.scrollHeight" completionHandler:^(id _Nullable result, NSError * _Nullable error) {
+        double height = [result doubleValue];
+        NSLog(@"%f",height);
+    }];
+}
+
+// 导航失败时会回调
+- (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error {
+    if (self.viewModel.shouldPullDownToRefresh) [webView.scrollView.mj_header endRefreshing];
+}
+
+// 在发送请求之前,决定是否跳转
+- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
+    NSLog(@"navigationAction.request.URL:   %@", navigationAction.request.URL);
+    decisionHandler(WKNavigationActionPolicyAllow);
+}
+
+- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {
+    decisionHandler(WKNavigationResponsePolicyAllow);
+}
+
+- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *__nullable credential))completionHandler {
+    NSURLCredential *cred = [[NSURLCredential alloc] initWithTrust:challenge.protectionSpace.serverTrust];
+    completionHandler(NSURLSessionAuthChallengeUseCredential, cred);
+}
+
+#pragma mark - WKUIDelegate
+- (nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures {
+	/// CoderMike Fixed : 解决点击网页的链接 不跳转的Bug。
+	WKFrameInfo *frameInfo = navigationAction.targetFrame;
+	if (![frameInfo isMainFrame]) {
+		[webView loadRequest:navigationAction.request];
+	}
+	return nil;
+}
+
+#pragma mark runJavaScript
+- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
+//	[NSObject rq_showAlertViewWithTitle:nil message:message confirmTitle:@"我知道了"];
+	completionHandler();
+}
+
+- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler {
+	completionHandler(YES);
+}
+
+- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * __nullable result))completionHandler {
+	completionHandler(defaultText);
+}
+
+
+
+#pragma mark - Getter & Setter
+- (UIProgressView *)progressView {
+	if (!_progressView) {
+		CGFloat progressViewW = RQ_SCREEN_WIDTH;
+		CGFloat progressViewH = 3;
+		CGFloat progressViewX = 0;
+		CGFloat progressViewY = CGRectGetHeight(self.navigationController.navigationBar.frame) - progressViewH + 1;
+		UIProgressView *progressView = [[UIProgressView alloc] initWithFrame:CGRectMake(progressViewX, progressViewY, progressViewW, progressViewH)];
+		progressView.progressTintColor = RQ_MAIN_TINTCOLOR;
+		progressView.trackTintColor = [UIColor clearColor];
+		[self.view addSubview:progressView];
+		self.progressView = progressView;
+	}
+	return _progressView;
+}
+
+
+- (UIBarButtonItem *)backItem
+{
+	if (_backItem == nil) {
+		_backItem = [UIBarButtonItem rq_backItemWithTitle:@"返回" imageName:@"backIcon" target:self action:@selector(_backItemDidClicked)];
+	}
+	return _backItem;
+}
+
+- (UIBarButtonItem *)closeItem {
+	if (!_closeItem) {
+		_closeItem = [UIBarButtonItem rq_systemItemWithTitle:@"关闭" titleColor:RQ_MAIN_COLOR imageName:nil target:self selector:@selector(_closeItemDidClicked) textType:YES];
+	}
+	return _closeItem;
+}
+
+@end

+ 94 - 0
jiaPei/Modules/BaseModule/BaseClass/ViewModel/RQBaseViewModel.h

@@ -0,0 +1,94 @@
+//
+//  RQBaseViewModel.h
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/13.
+//  Copyright © 2018 张嵘. All rights reserved.
+//	所有自定义的视图模型的基类
+
+#import <Foundation/Foundation.h>
+#import "RQMacros.h"
+
+/// MVVM View
+/// The base map of 'params'
+/// The `params` parameter in `-initWithParams:` method.
+/// Key-Values's key
+/// 传递唯一ID的key:例如:商品id 用户id...
+FOUNDATION_EXTERN NSString *const RQViewModelIDKey;
+/// 传递导航栏title的key:例如 导航栏的title...
+FOUNDATION_EXTERN NSString *const RQViewModelTitleKey;
+/// 传递数据模型的key:例如 商品模型的传递 用户模型的传递...
+FOUNDATION_EXTERN NSString *const RQViewModelUtilKey;
+/// 传递webView Request的key:例如 webView request...
+FOUNDATION_EXTERN NSString *const RQViewModelRequestKey;
+/// 传递数据的key
+FOUNDATION_EXTERN NSString *const RQViewCommonValueKey;
+@protocol RQViewModelServices;
+
+@class RACSubject;
+@interface RQBaseViewModel : NSObject
+/// Initialization method. This is the preferred way to create a new view model.
+///
+/// services - The service bus of the `Model` layer.
+/// params   - The parameters to be passed to view model.
+///
+/// Returns a new view model.
+- (instancetype)initWithServices:(id<RQViewModelServices>)services params:(NSDictionary *)params;
+
+/// The `services` parameter in `-initWithServices:params:` method.
+@property (nonatomic, readonly, strong) id<RQViewModelServices> services;
+
+/// The `params` parameter in `-initWithParams:` method.
+/// The `params` Key's `kBaseViewModelParamsKey`
+@property (nonatomic, readonly, copy) NSDictionary *params;
+
+/// navItem.title
+@property (nonatomic, readwrite, copy) NSString *title;
+/// 返回按钮的title,default is nil 。
+/// 如果设置了该值,那么当Push到一个新的控制器,则导航栏左侧返回按钮的title为backTitle
+@property (nonatomic, readwrite, copy) NSString *backTitle;
+
+/// The callback block. 当Push/Present时,通过block反向传值
+@property (nonatomic, readwrite, copy) VoidBlock_id callback;
+
+/// A RACSubject object, which representing all errors occurred in view model.
+@property (nonatomic, readonly, strong) RACSubject *errors;
+
+/** should fetch local data when viewModel init  . default is YES */
+@property (nonatomic, readwrite, assign) BOOL shouldFetchLocalDataOnViewModelInitialize;
+/** should request data when viewController videwDidLoad . default is YES*/
+/** 是否需要在控制器viewDidLoad */
+@property (nonatomic, readwrite, assign) BOOL shouldRequestRemoteDataOnViewDidLoad;
+/// will disappear signal
+@property (nonatomic, strong, readonly) RACSubject *willDisappearSignal;
+
+/// FDFullscreenPopGesture
+/// Whether the interactive pop gesture is disabled when contained in a navigation
+/// stack. (是否取消掉左滑pop到上一层的功能(栈底控制器无效),默认为NO,不取消)
+@property (nonatomic, readwrite, assign) BOOL interactivePopDisabled;
+/// Indicate this view controller prefers its navigation bar hidden or not,
+/// checked when view controller based navigation bar's appearance is enabled.
+/// Default to NO, bars are more likely to show.
+/// 是否隐藏该控制器的导航栏 默认是不隐藏 (NO)
+@property (nonatomic, readwrite, assign) BOOL prefersNavigationBarHidden;
+
+/// 是否隐藏该控制器的导航栏底部的分割线 默认不隐藏 (NO)
+@property (nonatomic, readwrite, assign) BOOL prefersNavigationBarBottomLineHidden;
+
+/// IQKeyboardManager
+/// 是否让IQKeyboardManager的管理键盘的事件 默认是YES(键盘管理)
+@property (nonatomic, readwrite, assign) BOOL keyboardEnable;
+/// 是否键盘弹起的时候,点击其他局域键盘弹起 默认是 YES
+@property (nonatomic, readwrite, assign) BOOL shouldResignOnTouchOutside;
+/// To set keyboard distance from textField. can't be less than zero. Default is 10.0.
+/// keyboardDistanceFromTextField
+@property (nonatomic, readwrite, assign) CGFloat keyboardDistanceFromTextField;
+
+/// An additional method, in which you can initialize data, RACCommand etc.
+///
+/// This method will be execute after the execution of `-initWithParams:` method. But
+/// the premise is that you need to inherit `BaseViewModel`.
+- (void)initialize;
+@end
+
+

+ 86 - 0
jiaPei/Modules/BaseModule/BaseClass/ViewModel/RQBaseViewModel.m

@@ -0,0 +1,86 @@
+//
+//  RQBaseViewModel.m
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/13.
+//  Copyright © 2018 张嵘. All rights reserved.
+//	所有自定义的视图模型的基类
+
+#import "RQBaseViewModel.h"
+
+/// MVVM View
+/// The base map of 'params'
+/// The `params` parameter in `-initWithParams:` method.
+/// Key-Values's key
+/// 传递唯一ID的key:例如:商品id 用户id...
+NSString *const RQViewModelIDKey = @"RQViewModelIDKey";
+/// 传递导航栏title的key:例如 导航栏的title...
+NSString *const RQViewModelTitleKey = @"RQViewModelTitleKey";
+/// 传递数据模型的key:例如 商品模型的传递 用户模型的传递...
+NSString *const RQViewModelUtilKey = @"RQViewModelUtilKey";
+/// 传递webView Request的key:例如 webView request...
+NSString *const RQViewModelRequestKey = @"RQViewModelRequestKey";
+/// 传递数据的key
+NSString *const RQViewCommonValueKey = @"RQViewCommonValueKey";
+
+@interface RQBaseViewModel ()
+/// 整个应用的服务层 The `services` parameter in `-initWithServices:params` method.
+@property (nonatomic, strong, readwrite) id<RQViewModelServices> services;
+/// The `params` parameter in `-initWithServices:params` method.
+@property (nonatomic, readwrite, copy) NSDictionary *params;
+/// A RACSubject object, which representing all errors occurred in view model.
+@property (nonatomic, readwrite, strong) RACSubject *errors;
+/// The `View` willDisappearSignal
+@property (nonatomic, readwrite, strong) RACSubject *willDisappearSignal;
+@end
+
+@implementation RQBaseViewModel
+/// when `BaseViewModel` created and call `initWithParams` method , so we can ` initialize `
++ (instancetype)allocWithZone:(struct _NSZone *)zone {
+	RQBaseViewModel *viewModel = [super allocWithZone:zone];
+	@weakify(viewModel)
+	[[viewModel
+	  rac_signalForSelector:@selector(initWithServices:params:)]
+	 subscribeNext:^(id x) {
+		 @strongify(viewModel)
+		 [viewModel initialize];
+	 }];
+	return viewModel;
+}
+
+/// create `viewModel` instance
+- (instancetype)initWithServices:(id<RQViewModelServices>)services params:(NSDictionary *)params {
+	self = [super init];
+	if (self) {
+		/// 默认在viewDidLoad里面加载本地和服务器的数据
+		self.shouldFetchLocalDataOnViewModelInitialize = YES;
+		self.shouldRequestRemoteDataOnViewDidLoad = YES;
+		/// 允许IQKeyboardMananger接管键盘弹出事件
+		self.keyboardEnable = YES;
+		self.shouldResignOnTouchOutside = YES;
+		self.keyboardDistanceFromTextField = 10.0f;
+		
+		self.title = params[RQViewModelTitleKey];
+		/// 赋值
+		self.services = services;
+		self.params   = params;
+	}
+	return self;
+}
+
+
+- (RACSubject *)errors {
+	if (!_errors) _errors = [RACSubject subject];
+	return _errors;
+}
+
+- (RACSubject *)willDisappearSignal {
+	if (!_willDisappearSignal) _willDisappearSignal = [RACSubject subject];
+	return _willDisappearSignal;
+}
+
+/// sub class can override
+- (void)initialize {
+	
+}
+@end

+ 57 - 0
jiaPei/Modules/BaseModule/BaseClass/ViewModel/RQCollectionViewModel.h

@@ -0,0 +1,57 @@
+//
+//  RQCollectionViewModel.h
+//  YueXueChe
+//
+//  Created by 张嵘 on 2018/12/18.
+//  Copyright © 2018 lee. All rights reserved.
+//
+
+#import "RQBaseViewModel.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface RQCollectionViewModel : RQBaseViewModel
+/// The data source of collection view. 这里不能用NSMutableArray,因为NSMutableArray不支持KVO,不能被RACObserve
+@property (nonatomic, readwrite, copy) NSArray *dataSource;
+/// 需要支持下来刷新 defalut is NO
+@property (nonatomic, readwrite, assign) BOOL shouldPullDownToRefresh;
+/// 需要支持上拉加载 defalut is NO
+@property (nonatomic, readwrite, assign) BOOL shouldPullUpToLoadMore;
+/// 是否数据是多段 (It's effect collectionView dataSource 'numberOfSectionsInCollectionView:') defalut is NO
+@property (nonatomic, readwrite, assign) BOOL shouldMultiSections;
+/// 是否在上拉加载后的数据,dataSource.count < pageSize 提示没有更多的数据.default is NO 默认做法是数据不够时,隐藏mj_footer
+@property (nonatomic, readwrite, assign) BOOL shouldEndRefreshingWithNoMoreData;
+
+/// 当前页 defalut is 1
+@property (nonatomic, readwrite, assign) NSUInteger page;
+/// 每一页的数据 defalut is 20
+@property (nonatomic, readwrite, assign) NSUInteger perPage;
+
+
+/// 选中命令 eg:  didSelectRowAtIndexPath:
+@property (nonatomic, readwrite, strong) RACCommand *didSelectCommand;
+/// 请求服务器数据的命令
+@property (nonatomic, readonly, strong) RACCommand *requestRemoteDataCommand;
+
+/// 占位empty类型
+//@property (nonatomic, readwrite, assign) SBDefaultEmptyBackgroundType emptyType;
+/// 网络不可用 default is NO
+@property (nonatomic, readwrite, assign) BOOL disableNetwork;
+
+/** fetch the local data */
+- (id)fetchLocalData;
+
+/// 请求错误信息过滤
+- (BOOL (^)(NSError *error))requestRemoteDataErrorsFilter;
+
+/// 当前页之前的所有数据
+- (NSUInteger)offsetForPage:(NSUInteger)page;
+
+/** request remote data or local data, sub class can override it
+ *  page - 请求第几页的数据
+ */
+- (RACSignal *)requestRemoteDataSignalWithPage:(NSUInteger)page;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 56 - 0
jiaPei/Modules/BaseModule/BaseClass/ViewModel/RQCollectionViewModel.m

@@ -0,0 +1,56 @@
+//
+//  RQCollectionViewModel.m
+//  YueXueChe
+//
+//  Created by 张嵘 on 2018/12/18.
+//  Copyright © 2018 lee. All rights reserved.
+//
+
+#import "RQCollectionViewModel.h"
+
+@interface RQCollectionViewModel ()
+/// request remote data cmd
+@property (nonatomic, readwrite, strong) RACCommand *requestRemoteDataCommand;
+@end
+
+@implementation RQCollectionViewModel
+- (void)initialize {
+	[super initialize];
+	
+	self.page = 1;
+	self.perPage = 20;
+	
+	/// request remote data
+	@weakify(self)
+	self.requestRemoteDataCommand = [[RACCommand alloc] initWithSignalBlock:^(NSNumber *page) {
+		@strongify(self)
+		return [[self requestRemoteDataSignalWithPage:page.unsignedIntegerValue] takeUntil:self.rac_willDeallocSignal];
+	}];
+	
+	/// 过滤错误信息
+	[[self.requestRemoteDataCommand.errors
+	  filter:[self requestRemoteDataErrorsFilter]]
+	 subscribe:self.errors];
+}
+
+/// sub class can ovrride it
+- (BOOL (^)(NSError *error))requestRemoteDataErrorsFilter {
+	return ^(NSError *error) {
+		return YES;
+	};
+}
+
+- (id)fetchLocalData {
+	return nil;
+}
+
+- (NSUInteger)offsetForPage:(NSUInteger)page {
+	return (page - 1) * self.perPage;
+}
+
+
+- (RACSignal *)requestRemoteDataSignalWithPage:(NSUInteger)page {
+	return [RACSignal empty];
+}
+
+@end

+ 53 - 0
jiaPei/Modules/BaseModule/BaseClass/ViewModel/RQNavigationProtocol.h

@@ -0,0 +1,53 @@
+//
+//  RQNavigationProtocol.h
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/16.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "RQConstant.h"
+
+@class RQBaseViewModel;
+@protocol RQNavigationProtocol <NSObject>
+/// Pushes the corresponding view controller.
+///
+/// Uses a horizontal slide transition.
+/// Has no effect if the corresponding view controller is already in the stack.
+///
+/// viewModel - the view model
+/// animated  - use animation or not
+- (void)pushViewModel:(RQBaseViewModel *)viewModel animated:(BOOL)animated;
+
+/// Pops the top view controller in the stack.
+///
+/// animated - use animation or not
+- (void)popViewModelAnimated:(BOOL)animated;
+
+/// Pops until there's only a single view controller left on the stack.
+///
+/// animated - use animation or not
+- (void)popToRootViewModelAnimated:(BOOL)animated;
+
+/// Present the corresponding view controller.
+///
+/// viewModel  - the view model
+/// animated   - use animation or not
+/// completion - the completion handler
+- (void)presentViewModel:(RQBaseViewModel *)viewModel animated:(BOOL)animated completion:(VoidBlock)completion;
+
+/// Dismiss the presented view controller.
+///
+/// animated   - use animation or not
+/// completion - the completion handler
+- (void)dismissViewModelAnimated:(BOOL)animated completion:(VoidBlock)completion;
+
+/// Reset the corresponding view controller as the root view controller of the application's window.
+///
+/// viewModel - the view model
+- (void)resetRootViewModel:(RQBaseViewModel *)viewModel;
+
+@end
+
+

+ 13 - 0
jiaPei/Modules/BaseModule/BaseClass/ViewModel/RQTabBarViewModel.h

@@ -0,0 +1,13 @@
+//
+//  RQTabBarViewModel.h
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/26.
+//  Copyright © 2018 张嵘. All rights reserved.
+//	对应于`RQTabBarController`的视图模型 ,只是一个用来过渡的视图模型
+
+#import "RQBaseViewModel.h"
+
+@interface RQTabBarViewModel : RQBaseViewModel
+
+@end

+ 13 - 0
jiaPei/Modules/BaseModule/BaseClass/ViewModel/RQTabBarViewModel.m

@@ -0,0 +1,13 @@
+//
+//  RQTabBarViewModel.m
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/26.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import "RQTabBarViewModel.h"
+
+@implementation RQTabBarViewModel
+
+@end

+ 60 - 0
jiaPei/Modules/BaseModule/BaseClass/ViewModel/RQTableViewModel.h

@@ -0,0 +1,60 @@
+//
+//  RQTableViewModel.h
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/23.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import "RQBaseViewModel.h"
+
+@interface RQTableViewModel : RQBaseViewModel
+/// The data source of table view. 这里不能用NSMutableArray,因为NSMutableArray不支持KVO,不能被RACObserve
+@property (nonatomic, readwrite, copy) NSArray *dataSource;
+
+/// tableView‘s style defalut is UITableViewStylePlain , 只适合 UITableView 有效
+@property (nonatomic, readwrite, assign) UITableViewStyle style;
+
+/// 需要支持下来刷新 defalut is NO
+@property (nonatomic, readwrite, assign) BOOL shouldPullDownToRefresh;
+/// 需要支持上拉加载 defalut is NO
+@property (nonatomic, readwrite, assign) BOOL shouldPullUpToLoadMore;
+/// 是否数据是多段 (It's effect tableView's dataSource 'numberOfSectionsInTableView:') defalut is NO
+@property (nonatomic, readwrite, assign) BOOL shouldMultiSections;
+/// 是否在上拉加载后的数据,dataSource.count < pageSize 提示没有更多的数据.default is NO 默认做法是数据不够时,隐藏mj_footer
+@property (nonatomic, readwrite, assign) BOOL shouldEndRefreshingWithNoMoreData;
+
+/// 当前页 defalut is 1
+@property (nonatomic, readwrite, assign) NSUInteger page;
+/// 每一页的数据 defalut is 20
+@property (nonatomic, readwrite, assign) NSUInteger perPage;
+/// 数据总数 defalut is 0
+@property (nonatomic, readwrite, assign) NSUInteger dataTotalNum;
+
+/// 选中命令 eg:  didSelectRowAtIndexPath:
+@property (nonatomic, readwrite, strong) RACCommand *didSelectCommand;
+/// 请求服务器数据的命令
+@property (nonatomic, readonly, strong) RACCommand *requestRemoteDataCommand;
+
+/// 占位empty类型
+//@property (nonatomic, readwrite, assign) SBDefaultEmptyBackgroundType emptyType;
+@property (nonatomic, readwrite, strong) UIImage *emptyImage;
+@property (nonatomic, readwrite, copy) NSString *emptyTex;
+
+/// 网络不可用 default is NO
+@property (nonatomic, readwrite, assign) BOOL disableNetwork;
+
+/** fetch the local data */
+- (id)fetchLocalData;
+
+/// 请求错误信息过滤
+- (BOOL (^)(NSError *error))requestRemoteDataErrorsFilter;
+
+/// 当前页之前的所有数据
+- (NSUInteger)offsetForPage:(NSUInteger)page;
+
+/** request remote data or local data, sub class can override it
+ *  page - 请求第几页的数据
+ */
+- (RACSignal *)requestRemoteDataSignalWithPage:(NSUInteger)page;
+@end

+ 57 - 0
jiaPei/Modules/BaseModule/BaseClass/ViewModel/RQTableViewModel.m

@@ -0,0 +1,57 @@
+//
+//  RQTableViewModel.m
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/23.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import "RQTableViewModel.h"
+
+@interface RQTableViewModel ()
+/// request remote data cmd
+@property (nonatomic, readwrite, strong) RACCommand *requestRemoteDataCommand;
+@end
+
+@implementation RQTableViewModel
+- (void)initialize {
+	[super initialize];
+	
+	self.page = 1;
+	self.perPage = 20;
+    self.dataTotalNum = 0;
+	
+	/// request remote data
+	@weakify(self)
+	self.requestRemoteDataCommand = [[RACCommand alloc] initWithSignalBlock:^(NSNumber *page) {
+		@strongify(self)
+		return [[self requestRemoteDataSignalWithPage:page.unsignedIntegerValue] takeUntil:self.rac_willDeallocSignal];
+	}];
+	
+	/// 过滤错误信息
+	[[self.requestRemoteDataCommand.errors
+	  filter:[self requestRemoteDataErrorsFilter]]
+	 subscribe:self.errors];
+}
+
+/// sub class can ovrride it
+- (BOOL (^)(NSError *error))requestRemoteDataErrorsFilter {
+	return ^(NSError *error) {
+		return YES;
+	};
+}
+
+- (id)fetchLocalData {
+	return nil;
+}
+
+- (NSUInteger)offsetForPage:(NSUInteger)page {
+	return (page - 1) * self.perPage;
+}
+
+
+- (RACSignal *)requestRemoteDataSignalWithPage:(NSUInteger)page {
+	return [RACSignal empty];
+}
+
+@end

+ 18 - 0
jiaPei/Modules/BaseModule/BaseClass/ViewModel/RQViewModelServices.h

@@ -0,0 +1,18 @@
+//
+//  RQViewModelServices.h
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/13.
+//  Copyright © 2018 张嵘. All rights reserved.
+//  视图模型服务层测协议 (导航栏操作的服务层 + 网络的服务层 )
+
+#import <Foundation/Foundation.h>
+#import "RQNavigationProtocol.h"
+#import "RQHTTPService.h"
+
+@protocol RQViewModelServices <NSObject, RQNavigationProtocol>
+/// A reference to MHHTTPService instance.
+/// 全局通过这个Client来请求数据,处理用户信息
+@property (nonatomic, readonly, strong) RQHTTPService *client;
+@end
+

+ 15 - 0
jiaPei/Modules/BaseModule/BaseClass/ViewModel/RQViewModelServicesImpl.h

@@ -0,0 +1,15 @@
+//
+//  RQViewModelServicesImpl.h
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/14.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "RQViewModelServices.h"
+
+@interface RQViewModelServicesImpl : NSObject <RQViewModelServices>
+
+@end
+

+ 35 - 0
jiaPei/Modules/BaseModule/BaseClass/ViewModel/RQViewModelServicesImpl.m

@@ -0,0 +1,35 @@
+//
+//  RQViewModelServicesImpl.m
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/14.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import "RQViewModelServicesImpl.h"
+
+@implementation RQViewModelServicesImpl
+@synthesize client = _client;
+- (instancetype)init
+{
+	self = [super init];
+	if (self) {
+		_client = [RQHTTPService sharedInstance];
+	}
+	return self;
+}
+
+
+#pragma mark - RQNavigationProtocol empty operation
+- (void)pushViewModel:(RQBaseViewModel *)viewModel animated:(BOOL)animated {}
+
+- (void)popViewModelAnimated:(BOOL)animated {}
+
+- (void)popToRootViewModelAnimated:(BOOL)animated {}
+
+- (void)presentViewModel:(RQBaseViewModel *)viewModel animated:(BOOL)animated completion:(VoidBlock)completion {}
+
+- (void)dismissViewModelAnimated:(BOOL)animated completion:(VoidBlock)completion {}
+
+- (void)resetRootViewModel:(RQBaseViewModel *)viewModel {}
+@end

+ 38 - 0
jiaPei/Modules/BaseModule/BaseClass/ViewModel/RQWebViewModel.h

@@ -0,0 +1,38 @@
+//
+//  RQWebViewModel.h
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/23.
+//  Copyright © 2018 张嵘. All rights reserved.
+//	所有含有`WKWebView`的视图的视图模型基类
+
+#import "RQBaseViewModel.h"
+
+/// 页面类型
+typedef NS_ENUM(NSUInteger, RQWebViewType) {
+    ///     通用
+    RQWebViewType_Common        = 1,
+    ///     真实模拟考试
+    RQWebViewType_Exam          = 2,
+    ///     VIP弹窗
+    RQWebViewType_VIP           = 3,
+};
+
+/// 传递webView Type的key:例如 RQWebViewType_Common...
+FOUNDATION_EXTERN NSString *const RQViewModelWebViewTypeKey;
+
+@interface RQWebViewModel : RQBaseViewModel
+/// web url quest
+@property (nonatomic, readwrite, copy) NSURLRequest *request;
+@property (nonatomic, readwrite, copy) NSString *requestUrl;
+@property (nonatomic, readonly, assign) RQWebViewType webViewType;
+/// 下拉刷新 defalut is NO
+@property (nonatomic, readwrite, assign) BOOL shouldPullDownToRefresh;
+
+/// 是否取消导航栏的title等于webView的title。默认是不取消,default is NO
+@property (nonatomic, readwrite, assign) BOOL shouldDisableWebViewTitle;
+
+/// 是否取消关闭按钮。默认是不取消,default is NO
+@property (nonatomic, readwrite, assign) BOOL shouldDisableWebViewClose;
+
+@end

+ 29 - 0
jiaPei/Modules/BaseModule/BaseClass/ViewModel/RQWebViewModel.m

@@ -0,0 +1,29 @@
+//
+//  RQWebViewModel.m
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/23.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import "RQWebViewModel.h"
+
+/// 传递webView Request的key:例如 webView request...
+NSString *const RQViewModelWebViewTypeKey = @"RQViewModelWebViewTypeKey";
+@interface RQWebViewModel ()
+@property (nonatomic, readwrite, assign) RQWebViewType webViewType;
+@end
+@implementation RQWebViewModel
+- (instancetype)initWithServices:(id<RQViewModelServices>)services params:(NSDictionary *)params {
+	self = [super initWithServices:services params:params];
+	if (self) {
+        if ([params[RQViewModelRequestKey] isKindOfClass:[NSString class]]) {
+            self.requestUrl = params[RQViewModelRequestKey];
+        } else if ([params[RQViewModelRequestKey] isKindOfClass:[NSURLRequest class]]) {
+            self.request = params[RQViewModelRequestKey];
+        }
+        self.webViewType = params[RQViewModelWebViewTypeKey]? [params[RQViewModelWebViewTypeKey] integerValue] : RQWebViewType_Common;
+	}
+	return self;
+}
+@end

+ 15 - 0
jiaPei/Modules/BaseModule/BaseModule.h

@@ -0,0 +1,15 @@
+//
+//  BaseModule.h
+//  XinShouJiaDao
+//
+//  Created by 张嵘 on 2021/7/8.
+//  Copyright © 2021 JCZ. All rights reserved.
+//
+
+#ifndef BaseModule_h
+#define BaseModule_h
+
+#import "RQBaseClass.h"
+#import "Common.h"
+
+#endif /* BaseModule_h */

+ 45 - 0
jiaPei/Modules/BaseModule/Common/Common.h

@@ -0,0 +1,45 @@
+//
+//  Common.h
+//  XinShouJiaDao
+//
+//  Created by 张嵘 on 2021/7/8.
+//  Copyright © 2021 JCZ. All rights reserved.
+//
+
+#ifndef Common_h
+#define Common_h
+
+#pragma mark - ViewController
+#import "RQCommonViewController.h"
+#import "RQCommonCollectionViewController.h"
+
+
+#pragma mark - ViewModel
+#import "RQCommonViewModel.h"
+#import "RQCommonItemViewModel.h"
+#import "RQCommonGroupViewModel.h"
+#import "RQCommonValueItemViewModel.h"
+#import "RQCommonArrowItemViewModel.h"
+#import "RQCommonLabelItemViewModel.h"
+#import "RQCommonAvatarItemViewModel.h"
+#import "RQCommonSwitchItemViewModel.h"
+#import "RQCommonQRCodeItemViewModel.h"
+#import "CommonProfileHeaderItemViewModel.h"
+#import "RQCollectionViewModel.h"
+#import "RQCommonCollectionItemViewModel.h"
+
+#pragma mark - View
+#import "RQCommonHeaderView.h"
+#import "RQCommonFooterView.h"
+#import "RQCommonCell.h"
+#import "RQDebugTouchView.h"
+#import "RQCommonCollectionViewCell.h"
+#import "RQCommonReusableView.h"
+
+#pragma mark - Model
+//#import "RQUserModel.h"
+
+#import "QDThemeManager.h"
+
+
+#endif /* Common_h */

+ 33 - 0
jiaPei/Modules/BaseModule/Common/Model/RQCommonGroupModel.h

@@ -0,0 +1,33 @@
+//
+//  RQCommonGroupModel.h
+//  jiaPei
+//
+//  Created by 张嵘 on 2022/6/22.
+//  Copyright © 2022 JCZ. All rights reserved.
+//
+
+#import "RQBaseModel.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface RQCommonGroupModel : RQBaseModel
+/// 组头
+@property (nonatomic, copy) NSString *header;
+/// headerHeight defalult is .001
+@property (nonatomic, readwrite, assign) CGFloat headerHeight;
+@property (nonatomic, readwrite, strong) UIFont *headerFont;
+@property (nonatomic, readwrite, strong) UIColor *headerLabelColor;
+@property (nonatomic, readwrite, strong) UIColor *headerBgColor;
+
+/// 组尾
+@property (nonatomic, copy) NSString *footer;
+/// footerHeight defalult is .001
+@property (nonatomic, readwrite, assign) CGFloat footerHeight;
+@property (nonatomic, readwrite, strong) UIFont *footerFont;
+@property (nonatomic, readwrite, strong) UIColor *footerLabelColor;
+@property (nonatomic, readwrite, strong) UIColor *footerBgColor;
+
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 13 - 0
jiaPei/Modules/BaseModule/Common/Model/RQCommonGroupModel.m

@@ -0,0 +1,13 @@
+//
+//  RQCommonGroupModel.m
+//  jiaPei
+//
+//  Created by 张嵘 on 2022/6/22.
+//  Copyright © 2022 JCZ. All rights reserved.
+//
+
+#import "RQCommonGroupModel.h"
+
+@implementation RQCommonGroupModel
+
+@end

+ 20 - 0
jiaPei/Modules/BaseModule/Common/View/RQCommonCell.h

@@ -0,0 +1,20 @@
+//
+//  RQCommonCell.h
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/27.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+#import "RQReactiveViewProtocol.h"
+
+@interface RQCommonCell : UITableViewCell <RQReactiveViewProtocol>
+
++ (instancetype)cellWithTableView:(UITableView *)tableView;
+
++ (instancetype)cellWithTableView:(UITableView *)tableView style:(UITableViewCellStyle)style;
+
+- (void)setIndexPath:(NSIndexPath *)indexPath rowsInSection:(NSInteger)rows;
+
+@end

+ 277 - 0
jiaPei/Modules/BaseModule/Common/View/RQCommonCell.m

@@ -0,0 +1,277 @@
+//
+//  RQCommonCell.m
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/27.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import "RQCommonCell.h"
+#import "RQCommonArrowItemViewModel.h"
+#import "RQCommonAvatarItemViewModel.h"
+#import "RQCommonQRCodeItemViewModel.h"
+#import "RQCommonLabelItemViewModel.h"
+#import "RQCommonSwitchItemViewModel.h"
+
+
+@interface RQCommonCell ()
+/// viewModel
+@property (nonatomic, readwrite, strong) RQCommonItemViewModel *viewModel;
+
+/// 箭头
+@property (nonatomic, readwrite, strong) UIImageView *rightArrow;
+/// 开光
+@property (nonatomic, readwrite, strong) UISwitch *rightSwitch;
+/// 标签
+@property (nonatomic, readwrite, strong) UILabel *rightLabel;
+/// avatar 头像
+@property (nonatomic, readwrite, weak) UIImageView *avatarView;
+/// QrCode
+@property (nonatomic, readwrite, weak) UIImageView *qrCodeView;
+
+/// 三条分割线
+@property (nonatomic, readwrite, weak) UIImageView *divider0;
+@property (nonatomic, readwrite, weak) UIImageView *divider1;
+@property (nonatomic, readwrite, weak) UIImageView *divider2;
+
+/// 中间偏左 view
+@property (nonatomic, readwrite, weak) UIImageView *centerLeftView;
+/// 中间偏右 view
+@property (nonatomic, readwrite, weak) UIImageView *centerRightView;
+
+@end
+
+@implementation RQCommonCell
+
+#pragma mark - 公共方法
++ (instancetype)cellWithTableView:(UITableView *)tableView{
+	return [self cellWithTableView:tableView style:UITableViewCellStyleValue1];
+}
+
++ (instancetype)cellWithTableView:(UITableView *)tableView style:(UITableViewCellStyle)style{
+	static NSString *ID = @"CommonCell";
+	RQCommonCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
+	if (!cell) {
+		cell = [[self alloc] initWithStyle:style reuseIdentifier:ID];
+	}
+	return cell;
+}
+
+- (void)setIndexPath:(NSIndexPath *)indexPath rowsInSection:(NSInteger)rows{
+	self.divider0.hidden = NO;
+	self.divider1.hidden = NO;
+	self.divider2.hidden = NO;
+	if (rows == 1) {                      /// 一段
+		self.divider1.hidden = YES;
+	}else if(indexPath.row == 0) {        /// 首行
+		self.divider2.hidden = YES;
+	}else if(indexPath.row == rows-1) {   /// 末行
+		self.divider1.hidden = YES;
+		self.divider0.hidden = YES;
+	}else{ /// 中间行
+		self.divider1.hidden = NO;
+		self.divider0.hidden = YES;
+		self.divider2.hidden = YES;
+	}
+}
+
+
+- (void)bindViewModel:(RQCommonItemViewModel *)viewModel{
+    @weakify(self)
+	self.viewModel = viewModel;
+	
+	self.avatarView.hidden = YES;
+	self.qrCodeView.hidden = YES;
+	
+	self.selectionStyle = viewModel.selectionStyle;
+	self.textLabel.text = viewModel.title;
+	self.imageView.image = (RQStringIsNotEmpty(viewModel.icon))?RQImageNamed(viewModel.icon):nil;
+	self.detailTextLabel.text = viewModel.subtitle;
+	/// 设置全新
+	if (RQStringIsNotEmpty(viewModel.centerLeftViewName)) {
+		self.centerLeftView.hidden = NO;
+		self.centerLeftView.image = RQImageNamed(viewModel.centerLeftViewName);
+		self.centerLeftView.rq_size = self.centerLeftView.image.size;
+	}else{
+		self.centerLeftView.hidden = YES;;
+	}
+	
+	/// 设置锁
+	if (RQStringIsNotEmpty(viewModel.centerRightViewName)) {
+		self.centerRightView.hidden = NO;
+		self.centerRightView.image = RQImageNamed(viewModel.centerRightViewName);
+		self.centerRightView.rq_size = self.centerRightView.image.size;
+	}else{
+		self.centerRightView.hidden = YES;;
+	}
+	
+	if ([viewModel isKindOfClass:[RQCommonArrowItemViewModel class]]) {  /// 纯带箭头
+		self.accessoryView = self.rightArrow;
+		if ([viewModel isKindOfClass:[RQCommonAvatarItemViewModel class]]) { // 头像
+			RQCommonAvatarItemViewModel *avatarViewModel = (RQCommonAvatarItemViewModel *)viewModel;
+			self.avatarView.hidden = NO;
+            [self.avatarView yy_setImageWithURL:[NSURL URLWithString:avatarViewModel.avatar] placeholder:RQWebAvatarImagePlaceholder() options:RQWebImageOptionAutomatic completion:^(UIImage * _Nullable image, NSURL * _Nonnull url, YYWebImageFromType from, YYWebImageStage stage, NSError * _Nullable error) {
+                @strongify(self)
+                self.avatarView.layer.cornerRadius = self.avatarView.width / 2.f;
+                self.avatarView.clipsToBounds = YES;
+            }];
+		}else if ([viewModel isKindOfClass:[RQCommonQRCodeItemViewModel class]]){ // 二维码
+			self.qrCodeView.hidden = NO;
+		}
+	}else if([viewModel isKindOfClass:[RQCommonSwitchItemViewModel class]]){ /// 开关
+		// 右边显示开关
+		RQCommonSwitchItemViewModel *switchViewModel = (RQCommonSwitchItemViewModel *)viewModel;
+		self.accessoryView = self.rightSwitch;
+		self.rightSwitch.on = !switchViewModel.off;
+	}else{
+		self.accessoryView = nil;
+	}
+}
+#pragma mark - 私有方法
+- (void)awakeFromNib {
+	[super awakeFromNib];
+	
+}
+- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
+	[super setSelected:selected animated:animated];
+}
+
+- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
+{
+	if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]){
+		// 初始化
+		[self _setup];
+		
+		// 创建自控制器
+		[self _setupSubViews];
+		
+		// 布局子控件
+		[self _makeSubViewsConstraints];
+	}
+	return self;
+}
+
+#pragma mark - 初始化
+- (void)_setup{
+    
+	self.contentView.backgroundColor = [UIColor whiteColor];
+    
+    self.textLabel.textColor = RQ_MAIN_TEXT_COLOR_1;
+    self.textLabel.font = RQRegularFont_17;
+    
+	self.detailTextLabel.textColor = RQ_MAIN_COLOR;
+	self.detailTextLabel.numberOfLines = 0;
+    self.detailTextLabel.font = RQRegularFont_15;
+
+}
+
+#pragma mark - 创建自控制器
+- (void)_setupSubViews{
+	
+	/// CoderMikeHe Fixed : 这里需要把divider添加到self,而不是self.contentView ,由于添加了 accessView,导致self.contentView的宽度<self的宽度
+	// 分割线
+	UIImageView *divider0 = [[UIImageView alloc] init];
+	self.divider0 = divider0;
+	[self addSubview:divider0];
+	UIImageView *divider1 = [[UIImageView alloc] init];
+	self.divider1 = divider1;
+	[self addSubview:divider1];
+	UIImageView *divider2 = [[UIImageView alloc] init];
+	self.divider2 = divider2;
+	[self addSubview:divider2];
+	divider0.backgroundColor = divider1.backgroundColor = divider2.backgroundColor = RQ_MAIN_LINE_COLOR_1;
+	
+	/// 添加用户头像
+	UIImageView *avatarView = [[UIImageView alloc] init];
+	self.avatarView = avatarView;
+	avatarView.hidden = YES;
+	[self.contentView addSubview:avatarView];
+	/// 设置圆角+线宽
+//	[avatarView zy_attachBorderWidth:1.0f color:RQColorFromHexString(@"#BFBFBF")];
+//	[avatarView zy_cornerRadiusAdvance:6.0f rectCornerType:UIRectCornerAllCorners];
+	
+	/// 二维码照片
+	UIImageView *qrCodeView = [[UIImageView alloc] initWithImage:RQImageNamed(@"setting_myQR_18x18")];
+	qrCodeView.hidden = YES;
+	self.qrCodeView = qrCodeView;
+	[self.contentView addSubview:qrCodeView];
+	
+	/// 中间偏左的图片
+	UIImageView *centerLeftView = [[UIImageView alloc] init];
+	centerLeftView.hidden = YES;
+	self.centerLeftView = centerLeftView;
+	[self.contentView addSubview:centerLeftView];
+	
+	/// 中间偏左的图片
+	UIImageView *centerRightView = [[UIImageView alloc] init];
+	centerRightView.hidden = YES;
+	self.centerRightView = centerRightView;
+	[self.contentView addSubview:centerRightView];
+}
+
+
+
+#pragma mark - 布局子控件
+- (void)_makeSubViewsConstraints{
+	
+	
+}
+
+#pragma mark - 布局
+- (void)layoutSubviews{
+	[super layoutSubviews];
+	/// 设置
+	if ((fabs(self.textLabel.rq_x - self.detailTextLabel.rq_x) <=.1f)) {
+		/// SubTitle
+		self.textLabel.rq_bottom = self.detailTextLabel.rq_top;
+	}else{
+		self.textLabel.rq_centerY = self.rq_height * .5f;
+	}
+	
+	
+	self.divider0.frame = CGRectMake(16, 0, self.rq_width -16 - 16, RQGlobalBottomLineHeight);
+	self.divider1.frame = CGRectMake(16, self.rq_height - RQGlobalBottomLineHeight, self.rq_width -16 - 16, RQGlobalBottomLineHeight);
+	self.divider2.frame = CGRectMake(16, self.rq_height - RQGlobalBottomLineHeight, self.rq_width-16 - 16, RQGlobalBottomLineHeight);
+	
+	/// 设置头像
+	self.avatarView.rq_size = CGSizeMake(34, 34);
+	self.avatarView.rq_right = self.accessoryView.rq_left - 16;
+	self.avatarView.rq_centerY = self.rq_height * .5f;
+	
+	/// 设置二维码
+	self.qrCodeView.rq_right = self.accessoryView.rq_left - 11;
+	self.qrCodeView.rq_centerY = self.rq_height * .5f;
+	
+	/// 配置Artboard
+	self.centerLeftView.rq_left = self.textLabel.rq_right + 14;
+	self.centerLeftView.rq_centerY = self.rq_height * .5f;
+	
+	/// 配置
+	self.centerRightView.rq_right = self.detailTextLabel.rq_left - 5;
+	self.centerRightView.rq_centerY = self.rq_height * .5f;
+}
+
+#pragma mark - 事件处理
+- (void)_switchValueDidiChanged:(UISwitch *)sender{
+	RQCommonSwitchItemViewModel *switchViewModel = (RQCommonSwitchItemViewModel *)self.viewModel;
+	switchViewModel.off = !sender.isOn;
+}
+
+
+
+#pragma mark - Setter Or Getter
+- (UIImageView *)rightArrow{
+	if (_rightArrow == nil) {
+		_rightArrow = [[UIImageView alloc] initWithImage:RQImageNamed(@"更多")];
+	}
+	return _rightArrow;
+}
+
+- (UISwitch *)rightSwitch{
+	if (_rightSwitch == nil) {
+		_rightSwitch = [[UISwitch alloc] init];
+		[_rightSwitch addTarget:self action:@selector(_switchValueDidiChanged:) forControlEvents:UIControlEventValueChanged];
+	}
+	return _rightSwitch;
+}
+@end

+ 17 - 0
jiaPei/Modules/BaseModule/Common/View/RQCommonCollectionViewCell.h

@@ -0,0 +1,17 @@
+//
+//  RQCommonCollectionViewCell.h
+//  YueXueChe
+//
+//  Created by 张嵘 on 2018/12/19.
+//  Copyright © 2018 lee. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+#import "RQReactiveViewProtocol.h"
+
+@interface RQCommonCollectionViewCell : UICollectionViewCell <RQReactiveViewProtocol>
+//@property (assign, readwrite, nonatomic) BOOL selected;
++ (instancetype)cellWithCollectionView:(UICollectionView *)collectionView forIndexPath:(NSIndexPath *)indexPath;
+- (void)setIndexPath:(NSIndexPath *)indexPath rowsInSection:(NSInteger)rows;
+
+@end

+ 142 - 0
jiaPei/Modules/BaseModule/Common/View/RQCommonCollectionViewCell.m

@@ -0,0 +1,142 @@
+//
+//  RQCommonCollectionViewCell.m
+//  YueXueChe
+//
+//  Created by 张嵘 on 2018/12/19.
+//  Copyright © 2018 lee. All rights reserved.
+//
+
+#import "RQCommonCollectionViewCell.h"
+#import "RQCommonCollectionItemViewModel.h"
+
+@interface RQCommonCollectionViewCell ()
+/// viewModel
+@property (nonatomic, readwrite, strong) RQCommonCollectionItemViewModel *viewModel;
+@property (strong, readwrite, nonatomic) UIImageView *iconImageView;
+@property (strong, readwrite, nonatomic) UILabel *titleLabel;
+@end
+
+@implementation RQCommonCollectionViewCell
+
++ (instancetype)cellWithCollectionView:(UICollectionView *)collectionView forIndexPath:(NSIndexPath *)indexPath {
+	NSString *ID = [NSString stringWithFormat:@"RQCommonCollectionViewCell%ld",(long)indexPath.section];
+	[collectionView registerClass:[RQCommonCollectionViewCell class] forCellWithReuseIdentifier:ID];
+	RQCommonCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:ID forIndexPath:indexPath];
+	cell.backgroundColor = RQ_MAIN_BACKGROUNDCOLOR;
+	if (!cell) {
+		cell = [[self alloc] init];
+        cell.iconImageView.hidden = YES;
+        cell.titleLabel.hidden = YES;
+	}
+	return cell;
+}
+
+- (void)bindViewModel:(RQCommonCollectionItemViewModel *)viewModel {
+	self.viewModel = viewModel;
+    BOOL isURL = [NSString rq_isValidURL:viewModel.icon];
+    if (isURL) {
+        [self.iconImageView yy_setImageWithURL:[NSURL URLWithString:viewModel.icon] placeholder:RQWebAvatarImagePlaceholder() options:RQWebImageOptionAutomatic completion:^(UIImage * _Nullable image, NSURL * _Nonnull url, YYWebImageFromType from, YYWebImageStage stage, NSError * _Nullable error) {
+            if(image) {
+                image = [image qmui_imageResizedInLimitedSize:CGSizeMake(RQ_FIT_HORIZONTAL(25.f), RQ_FIT_HORIZONTAL(25.f)) resizingMode:QMUIImageResizingModeScaleAspectFill];
+                self.iconImageView.image = image;
+             }
+        }];
+        
+//        [self.iconImageView sd_setImageWithURL:[NSURL URLWithString:viewModel.icon] placeholderImage:RQWebImagePlaceholder()];
+    } else {
+        self.iconImageView.image = [UIImage imageNamed:viewModel.icon];
+    }
+	
+	self.titleLabel.text = viewModel.title;
+    
+    self.iconImageView.hidden = RQStringIsEmpty(viewModel.icon);
+    self.titleLabel.hidden = RQStringIsEmpty(viewModel.title);
+}
+
+- (void)setIndexPath:(NSIndexPath *)indexPath rowsInSection:(NSInteger)rows {
+    
+}
+
+- (instancetype)init {
+	if (self = [super init]) {
+		// 初始化
+		[self _setup];
+		
+		// 创建自控制器
+		[self _setupSubViews];
+		
+		// 布局子控件
+		[self _makeSubViewsConstraints];
+	}
+	return self;
+}
+
+#pragma mark - 初始化
+- (void)_setup{
+	self.contentView.backgroundColor = RQ_MAIN_BACKGROUNDCOLOR;
+	
+}
+
+#pragma mark - 创建自控制器
+- (void)_setupSubViews {
+	[self.contentView addSubview:self.iconImageView];
+	[self.contentView addSubview:self.titleLabel];
+}
+
+#pragma mark - 布局子控件
+- (void)_makeSubViewsConstraints{
+	
+	
+}
+
+#pragma mark - 布局
+- (void)layoutSubviews{
+	[super layoutSubviews];
+    @weakify(self)
+	[self.iconImageView mas_makeConstraints:^(MASConstraintMaker *make) {
+        @strongify(self)
+		make.centerX.mas_equalTo(self.contentView);
+		make.centerY.mas_equalTo(self.contentView).mas_offset(-15.f);
+		make.size.mas_offset(CGSizeMake(RQ_FIT_HORIZONTAL(25.f), RQ_FIT_HORIZONTAL(25.f)));
+	}];
+	[self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
+        @strongify(self)
+		make.centerX.mas_equalTo(self.contentView);
+		make.top.mas_equalTo(self.iconImageView.mas_bottom).mas_offset(8.f);
+		make.height.mas_offset(18.f);
+	}];
+}
+
+#pragma mark - LazyLoad
+- (UIImageView *)iconImageView {
+	if (!_iconImageView) {
+		_iconImageView = [[UIImageView alloc] init];
+        _iconImageView.contentMode = UIViewContentModeScaleAspectFill;
+		[self.contentView addSubview:self.iconImageView];
+	}
+	return _iconImageView;
+}
+
+- (UILabel *)titleLabel {
+	if (!_titleLabel) {
+		_titleLabel = [[UILabel alloc] init];
+		_titleLabel.textColor = RQ_MAIN_TEXT_COLOR_1;
+		_titleLabel.font = RQRegularFont_15;
+		_titleLabel.textAlignment = NSTextAlignmentCenter;
+		[self.contentView addSubview:self.titleLabel];
+	}
+	return _titleLabel;
+}
+
+- (void)setSelected:(BOOL)selected {
+	[super setSelected:selected];
+	if (selected) {
+		self.contentView.layer.borderColor = RQ_MAIN_COLOR.CGColor;
+		self.titleLabel.textColor = RQ_MAIN_COLOR;
+	}else {
+		self.contentView.layer.borderColor = UIColor.lightGrayColor.CGColor;
+		self.titleLabel.textColor = RQ_MAIN_TEXT_COLOR_1;
+	}
+}
+
+@end

+ 15 - 0
jiaPei/Modules/BaseModule/Common/View/RQCommonFooterView.h

@@ -0,0 +1,15 @@
+//
+//  RQCommonFooterView.h
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/27.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+#import "RQReactiveViewProtocol.h"
+
+@interface RQCommonFooterView : UITableViewHeaderFooterView <RQReactiveViewProtocol>
+/// generate a footer
++ (instancetype)footerViewWithTableView:(UITableView *)tableView;
+@end

+ 111 - 0
jiaPei/Modules/BaseModule/Common/View/RQCommonFooterView.m

@@ -0,0 +1,111 @@
+//
+//  RQCommonFooterView.m
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/27.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import "RQCommonFooterView.h"
+#import "RQCommonGroupViewModel.h"
+
+@interface RQCommonFooterView ()
+/// viewModel
+@property (nonatomic, readwrite, strong) RQCommonGroupViewModel *viewModel;
+/// contentLabel
+@property (nonatomic, readwrite, weak) UILabel *contentLabel;
+@end
+
+@implementation RQCommonFooterView
+#pragma mark - 公共方法
++ (instancetype)footerViewWithTableView:(UITableView *)tableView{
+	static NSString *ID = @"CommonFooter";
+	RQCommonFooterView *footer = [tableView dequeueReusableHeaderFooterViewWithIdentifier:ID];
+	if (footer == nil) {
+		// 缓存池中没有, 自己创建
+		footer = [[self alloc] initWithReuseIdentifier:ID];
+	}
+	return footer;
+}
+
+- (void)bindViewModel:(RQCommonGroupViewModel *)viewModel{
+    self.viewModel = viewModel;
+    if (viewModel.groupModel) {
+        if (!RQObjectIsNil(self.viewModel.groupModel.footerBgColor)) {
+            self.contentView.backgroundColor = self.viewModel.groupModel.footerBgColor;
+            if (self.viewModel.groupModel.footerBgColor == UIColor.clearColor) {
+                self.hidden = YES;
+            } else {
+                self.hidden = NO;
+            }
+        }
+        
+        if (RQStringIsNotEmpty(self.viewModel.groupModel.footer)) {
+            self.hidden = NO;
+            self.contentLabel.text = self.viewModel.groupModel.footer;
+            if (!RQObjectIsNil(self.viewModel.groupModel.footerFont)) {
+                self.contentLabel.font = self.viewModel.groupModel.footerFont;
+            }
+            if (!RQObjectIsNil(self.viewModel.groupModel.footerLabelColor)) {
+                self.contentLabel.textColor = self.viewModel.groupModel.footerLabelColor;
+            }
+            [self.contentLabel mas_updateConstraints:^(MASConstraintMaker *make) {
+                make.size.mas_equalTo(CGSizeMake(RQ_SCREEN_WIDTH - 40, self.viewModel.groupModel.footerHeight));
+            }];
+        }
+       
+    } else {
+        self.contentLabel.text = viewModel.footer;
+    }
+}
+
+#pragma mark - 私有方法
+- (instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier
+{
+	if (self = [super initWithReuseIdentifier:reuseIdentifier]) {
+		// 初始化
+		[self _setup];
+		
+		// 创建自控制器
+		[self _setupSubViews];
+		
+		// 布局子控件
+		[self _makeSubViewsConstraints];
+	}
+	return self;
+}
+
+
+#pragma mark - 初始化
+- (void)_setup{
+	///
+	self.contentView.backgroundColor = RQ_LIST_BACKGROUNDCOLOR;
+	
+}
+
+#pragma mark - 创建自控制器
+- (void)_setupSubViews{
+	// label
+	UILabel *contentLabel = [[UILabel alloc] init];
+	contentLabel.textColor = RQColorFromHexString(@"#888888");
+	contentLabel.font = RQRegularFont_14;
+	contentLabel.numberOfLines = 0;
+	contentLabel.textAlignment = NSTextAlignmentLeft;
+	[self.contentView addSubview:contentLabel];
+	self.contentLabel = contentLabel;
+}
+
+
+
+#pragma mark - 布局子控件
+- (void)_makeSubViewsConstraints{
+    @weakify(self)
+    [self.contentLabel mas_makeConstraints:^(MASConstraintMaker *make) {
+        @strongify(self)
+        make.left.mas_equalTo(self.contentView).mas_offset(20);
+        make.centerY.mas_equalTo(self.contentView);
+        make.size.mas_equalTo(CGSizeMake(RQ_SCREEN_WIDTH - 40, self.contentView.height));
+    }];
+}
+
+@end

+ 16 - 0
jiaPei/Modules/BaseModule/Common/View/RQCommonHeaderView.h

@@ -0,0 +1,16 @@
+//
+//  RQCommonHeaderView.h
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/27.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+#import "RQReactiveViewProtocol.h"
+
+@interface RQCommonHeaderView : UITableViewHeaderFooterView <RQReactiveViewProtocol>
+/// generate a header
++ (instancetype)headerViewWithTableView:(UITableView *)tableView;
+@end
+

+ 108 - 0
jiaPei/Modules/BaseModule/Common/View/RQCommonHeaderView.m

@@ -0,0 +1,108 @@
+//
+//  RQCommonHeaderView.m
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/27.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import "RQCommonHeaderView.h"
+#import "RQCommonGroupViewModel.h"
+
+@interface RQCommonHeaderView ()
+/// viewModel
+@property (nonatomic, readwrite, strong) RQCommonGroupViewModel *viewModel;
+/// contentLabel
+@property (nonatomic, readwrite, weak) UILabel *contentLabel;
+@end
+
+@implementation RQCommonHeaderView
+#pragma mark - 公共方法
++ (instancetype)headerViewWithTableView:(UITableView *)tableView{
+	static NSString *ID = @"CommonHeader";
+	RQCommonHeaderView *header = [tableView dequeueReusableHeaderFooterViewWithIdentifier:ID];
+	if (header == nil) {
+		// 缓存池中没有, 自己创建
+		header = [[self alloc] initWithReuseIdentifier:ID];
+	}
+	return header;
+}
+
+- (void)bindViewModel:(RQCommonGroupViewModel *)viewModel {
+    self.viewModel = viewModel;
+    if (viewModel.groupModel) {
+        if (!RQObjectIsNil(self.viewModel.groupModel.headerBgColor)) {
+            self.contentView.backgroundColor = self.viewModel.groupModel.headerBgColor;
+            if (self.viewModel.groupModel.headerBgColor == UIColor.clearColor) {
+                self.hidden = YES;
+            } else {
+                self.hidden = NO;
+            }
+        }
+        if (RQStringIsNotEmpty(self.viewModel.groupModel.header)) {
+            self.hidden = NO;
+            self.contentLabel.text = self.viewModel.groupModel.header;
+            if (!RQObjectIsNil(self.viewModel.groupModel.headerFont)) {
+                self.contentLabel.font = self.viewModel.groupModel.headerFont;
+            }
+            if (!RQObjectIsNil(self.viewModel.groupModel.headerLabelColor)) {
+                self.contentLabel.textColor = self.viewModel.groupModel.headerLabelColor;
+            }
+            [self.contentLabel mas_updateConstraints:^(MASConstraintMaker *make) {
+                make.size.mas_equalTo(CGSizeMake(RQ_SCREEN_WIDTH - 40, self.viewModel.groupModel.headerHeight));
+            }];
+        }
+    } else {
+        self.contentLabel.text = viewModel.header;
+    }
+}
+
+#pragma mark - 私有方法
+- (instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier
+{
+	if (self = [super initWithReuseIdentifier:reuseIdentifier]) {
+		// 初始化
+		[self _setup];
+		
+		// 创建自控制器
+		[self _setupSubViews];
+		
+		// 布局子控件
+		[self _makeSubViewsConstraints];
+	}
+	return self;
+}
+
+
+#pragma mark - 初始化
+- (void)_setup{
+	self.contentView.backgroundColor = RQ_LIST_BACKGROUNDCOLOR;
+}
+
+#pragma mark - 创建自控制器
+- (void)_setupSubViews{
+	// label
+	UILabel *contentLabel = [[UILabel alloc] init];
+	contentLabel.textColor = RQColorFromHexString(@"#888888");
+	contentLabel.font = RQRegularFont_14;
+	contentLabel.numberOfLines = 0;
+	contentLabel.textAlignment = NSTextAlignmentLeft;
+	[self.contentView addSubview:contentLabel];
+	self.contentLabel = contentLabel;
+}
+
+
+
+#pragma mark - 布局子控件
+- (void)_makeSubViewsConstraints
+{
+    @weakify(self)
+	[self.contentLabel mas_makeConstraints:^(MASConstraintMaker *make) {
+        @strongify(self)
+        make.left.mas_equalTo(self.contentView).mas_offset(20);
+        make.centerY.mas_equalTo(self.contentView);
+        make.size.mas_equalTo(CGSizeMake(RQ_SCREEN_WIDTH - 40, self.contentView.height));
+	}];
+}
+
+@end

+ 22 - 0
jiaPei/Modules/BaseModule/Common/View/RQCommonReusableView.h

@@ -0,0 +1,22 @@
+//
+//  RQCommonReusableView.h
+//  YueXueChe
+//
+//  Created by 张嵘 on 2018/12/19.
+//  Copyright © 2018 lee. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+#import "RQReactiveViewProtocol.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface RQCommonReusableView : UICollectionReusableView <RQReactiveViewProtocol>
+/// contentLabel
+@property (nonatomic, readwrite, strong) UILabel *headerContentLabel;
+@property (nonatomic, readwrite, strong) UILabel *footerContentLabel;
+/// generate a header
++ (instancetype)reusableViewWithCollectionView:(UICollectionView *)collectionView OfKind:(NSString *)elementKind forIndexPath:(NSIndexPath *)indexPath;
+@end
+
+NS_ASSUME_NONNULL_END

+ 182 - 0
jiaPei/Modules/BaseModule/Common/View/RQCommonReusableView.m

@@ -0,0 +1,182 @@
+//
+//  RQCommonReusableView.m
+//  YueXueChe
+//
+//  Created by 张嵘 on 2018/12/19.
+//  Copyright © 2018 lee. All rights reserved.
+//
+
+#import "RQCommonReusableView.h"
+#import "RQCommonGroupViewModel.h"
+
+
+@interface RQCommonReusableView ()
+/// viewModel
+@property (nonatomic, readwrite, strong) RQCommonGroupViewModel *viewModel;
+@property (nonatomic, readwrite, strong) UIView *line;
+@property (nonatomic, readwrite, strong) NSString *kind;
+@end
+
+@implementation RQCommonReusableView
++ (instancetype)reusableViewWithCollectionView:(UICollectionView *)collectionView OfKind:(NSString *)elementKind forIndexPath:(NSIndexPath *)indexPath {
+	NSString *ID = [NSString stringWithFormat:@"%@%@", @"CommonReusableView",elementKind];
+	[collectionView registerClass:[RQCommonReusableView class] forSupplementaryViewOfKind:elementKind withReuseIdentifier:ID];
+	RQCommonReusableView *reusableView = [collectionView dequeueReusableSupplementaryViewOfKind:elementKind withReuseIdentifier:ID forIndexPath:indexPath];
+	if (elementKind == UICollectionElementKindSectionHeader) {
+		reusableView.backgroundColor = RQ_LIST_BACKGROUNDCOLOR;
+        reusableView.headerContentLabel.hidden = NO;
+        reusableView.footerContentLabel.hidden = YES;
+	}else {
+		reusableView.backgroundColor = RQ_LIST_BACKGROUNDCOLOR;
+        reusableView.headerContentLabel.hidden = YES;
+        reusableView.footerContentLabel.hidden = NO;
+	}
+	return reusableView;
+}
+
+- (void)bindViewModel:(RQCommonGroupViewModel *)viewModel {
+	self.viewModel = viewModel;
+	
+    if (viewModel.groupModel) {
+        if ([self.reuseIdentifier containsString:UICollectionElementKindSectionHeader]) {
+            if (!RQObjectIsNil(self.viewModel.groupModel.headerBgColor)) {
+                self.backgroundColor = self.viewModel.groupModel.headerLabelColor;
+            }
+            
+            if (RQStringIsNotEmpty(self.viewModel.groupModel.header)) {
+                [self initHeaderView];
+                self.headerContentLabel.hidden = NO;
+            }
+            
+        } else if ([self.reuseIdentifier containsString:UICollectionElementKindSectionFooter]) {
+            if (!RQObjectIsNil(self.viewModel.groupModel.footerBgColor)) {
+                self.backgroundColor = self.viewModel.groupModel.footerBgColor;
+            }
+            
+            if (RQStringIsNotEmpty(self.viewModel.groupModel.footer)) {
+                [self initFooterView];
+                self.footerContentLabel.hidden = NO;
+            }
+        }
+    } else {
+        if (viewModel.header && viewModel.footer) {
+            [self initHeaderView];
+            [self initFooterView];
+            self.headerContentLabel.hidden = NO;
+            self.footerContentLabel.hidden = NO;
+        } else if (viewModel.header || viewModel.footer) {
+            if (viewModel.header && !viewModel.footer) {
+                [self initHeaderView];
+                self.headerContentLabel.hidden = NO;
+                self.footerContentLabel.hidden = YES;
+            }else if (!viewModel.header && viewModel.footer) {
+                [self initFooterView];
+                self.headerContentLabel.hidden = YES;
+                self.footerContentLabel.hidden = NO;
+            }
+        } else {
+            self.headerContentLabel.hidden = YES;
+            self.footerContentLabel.hidden = YES;
+        }
+    }
+}
+
+- (void)initHeaderView {
+    @weakify(self)
+	[self addSubview:self.headerContentLabel];
+	[_headerContentLabel mas_makeConstraints:^(MASConstraintMaker *make) {
+        @strongify(self)
+		make.centerY.mas_equalTo(self);
+		make.left.mas_equalTo(self).mas_equalTo(11.f);
+		make.right.mas_equalTo(self).mas_equalTo(-11.f);
+	}];
+	[self addSubview:self.line];
+	[_line mas_makeConstraints:^(MASConstraintMaker *make) {
+        @strongify(self)
+		make.left.right.bottom.mas_equalTo(self);
+		make.height.mas_offset(1);
+	}];
+    if (RQObjectIsNil(self.viewModel.groupModel)) {
+        _headerContentLabel.text = self.viewModel.header;
+        _headerContentLabel.font = self.viewModel.headerFont;
+    } else {
+        if (RQStringIsNotEmpty(self.viewModel.groupModel.header)) {
+            _headerContentLabel.text = self.viewModel.groupModel.header;
+        }
+        
+        if (!RQObjectIsNil(self.viewModel.groupModel.headerFont)) {
+            _headerContentLabel.font = self.viewModel.groupModel.headerFont;
+        }
+        
+        if (!RQObjectIsNil(self.viewModel.groupModel.headerLabelColor)) {
+            _headerContentLabel.textColor = self.viewModel.groupModel.headerLabelColor;
+        }
+    }
+}
+
+- (void)initFooterView {
+    @weakify(self)
+	[self addSubview:self.footerContentLabel];
+	[_footerContentLabel mas_makeConstraints:^(MASConstraintMaker *make) {
+        @strongify(self)
+		make.centerY.mas_equalTo(self);
+		make.left.mas_equalTo(self).mas_equalTo(11.f);
+		make.right.mas_equalTo(self).mas_equalTo(-11.f);
+	}];
+	[self addSubview:self.line];
+	[_line mas_makeConstraints:^(MASConstraintMaker *make) {
+        @strongify(self)
+		make.left.right.bottom.mas_equalTo(self);
+		make.height.mas_offset(1);
+	}];
+    if (RQObjectIsNil(self.viewModel.groupModel)) {
+        _footerContentLabel.text = self.viewModel.footer;
+        _footerContentLabel.font = self.viewModel.footerFont;
+    } else {
+        if (RQStringIsNotEmpty(self.viewModel.groupModel.footer)) {
+            _footerContentLabel.text = self.viewModel.groupModel.footer;
+        }
+        
+        if (!RQObjectIsNil(self.viewModel.groupModel.footerFont)) {
+            _footerContentLabel.font = self.viewModel.groupModel.footerFont;
+        }
+        
+        if (!RQObjectIsNil(self.viewModel.groupModel.headerLabelColor)) {
+            _footerContentLabel.textColor = self.viewModel.groupModel.footerLabelColor;
+        }
+    }
+}
+
+
+
+- (UILabel *)headerContentLabel {
+	if (!_headerContentLabel) {
+		_headerContentLabel = [[UILabel alloc] init];
+		_headerContentLabel.textColor = RQ_MAIN_TEXT_COLOR_1;
+		_headerContentLabel.font = RQSemiboldFont(19);
+		_headerContentLabel.numberOfLines = 0;
+		_headerContentLabel.textAlignment = NSTextAlignmentLeft;
+	}
+	return _headerContentLabel;
+}
+
+- (UILabel *)footerContentLabel {
+	if (!_footerContentLabel) {
+		_footerContentLabel = [[UILabel alloc] init];
+		_footerContentLabel.textColor = RQ_MAIN_TEXT_COLOR_1;
+		_footerContentLabel.font = RQSemiboldFont(19);
+		_footerContentLabel.numberOfLines = 0;
+		_footerContentLabel.textAlignment = NSTextAlignmentLeft;
+	}
+	return _footerContentLabel;
+}
+
+- (UIView *)line {
+	if (!_line) {
+		_line = [[UIView alloc] init];
+		_line.backgroundColor = RQ_MAIN_BACKGROUNDCOLOR;
+	}
+	return _line;
+}
+
+@end

+ 17 - 0
jiaPei/Modules/BaseModule/Common/View/RQDebugTouchView.h

@@ -0,0 +1,17 @@
+//
+//  RQDebugTouchView.h
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/23.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@interface RQDebugTouchView : UIImageView
+///init
+@singleton(RQDebugTouchView);
+/// 设置显示or隐藏
+- (void)setHide:(BOOL)hide;
+- (BOOL)isHide;
+@end

+ 251 - 0
jiaPei/Modules/BaseModule/Common/View/RQDebugTouchView.m

@@ -0,0 +1,251 @@
+//
+//  RQDebugTouchView.m
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/23.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import "RQDebugTouchView.h"
+#import "RQConfigureManager.h"
+#import "RQControllerHelper.h"
+
+/// 显示状态的key
+static NSString * const RQDebugTouchViewStatusKey = @"RQDebugTouchViewStatusKey";
+
+
+@interface RQDebugTouchView ()
+/// closeBtn
+@property (nonatomic, readwrite, weak) UIButton *closeBtn;
+/// 开始点
+@property (nonatomic, readwrite, assign) CGPoint startPoint;
+
+/// 是否在动画
+@property (nonatomic, readwrite, assign , getter = isAnimated) BOOL animated;
+@end
+
+@implementation RQDebugTouchView
+@def_singleton(RQDebugTouchView);
+- (instancetype)initWithFrame:(CGRect)frame {
+	self = [super initWithFrame:frame];
+	if (self) {
+		self.image = RQImageNamed(@"assistivetouch");
+		self.userInteractionEnabled = YES;
+		
+		/// 添加tap手势
+		[self addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_tapAction:)]];
+		UIWindow *window = [UIApplication sharedApplication].keyWindow;
+		[window addSubview:self];
+		self.rq_x = [UIScreen mainScreen].bounds.size.width - self.rq_width - 20;
+		self.rq_y = 84;
+		
+		self.hidden = [self isHide];
+	}
+	return self;
+}
+
+
+
+- (void)setHide:(BOOL)hide {
+	
+	if (self.isAnimated) return;
+	
+	[[NSUserDefaults standardUserDefaults] setBool:hide forKey:RQDebugTouchViewStatusKey];
+	[[NSUserDefaults standardUserDefaults] synchronize];
+	
+	hide?[self close]:[self open];
+}
+
+
+- (BOOL)isHide{
+	BOOL temp = [[NSUserDefaults standardUserDefaults] boolForKey:RQDebugTouchViewStatusKey];
+	NSLog(@"++++ Touch View Is Hide %d ++++" , temp);
+	return temp;
+}
+
+
+- (void)open{
+	/// 动画开始,禁止交互
+	self.userInteractionEnabled = NO;
+	self.animated = YES;
+	
+	self.hidden = NO;
+	self.transform = CGAffineTransformIdentity;
+	self.transform = CGAffineTransformMakeScale(.2, .2);
+	[UIView animateWithDuration:.25f delay:0 usingSpringWithDamping:.3f initialSpringVelocity:0 options:UIViewAnimationOptionCurveLinear animations:^{
+		self.transform = CGAffineTransformMakeScale(1.0, 1.0);
+	} completion:^(BOOL finished) {
+		self.transform = CGAffineTransformIdentity;
+		/// 动画结束,允许交互
+		self.userInteractionEnabled = YES;
+		
+		self.animated = NO;
+	}];
+	
+	
+}
+
+- (void)close{
+	/// 动画开始,禁止交互
+	self.animated = YES;
+	self.userInteractionEnabled = NO;
+	[UIView animateWithDuration:.25f animations:^{
+		self.transform = CGAffineTransformMakeScale(.2, .2);
+	} completion:^(BOOL finished) {
+		self.transform = CGAffineTransformIdentity;
+		self.hidden = YES;
+		/// 动画结束,允许交互
+		self.userInteractionEnabled = YES;
+		self.animated = NO;
+	}];
+}
+
+
+#pragma mark - Action
+- (void)_tapAction:(UITapGestureRecognizer *)tapGr{
+	LCActionSheet *actionSheet = [LCActionSheet sheetWithTitle:nil cancelButtonTitle:@"取消" didDismiss:^(LCActionSheet * _Nonnull actionSheet, NSInteger buttonIndex) {
+		if (buttonIndex == 0) return ;
+		
+		if (buttonIndex == 1) {
+			/// 环境的切换
+			/// 主题
+			NSString *title = nil;
+			if ([RQConfigureManager applicationFormalSetting]) {
+//				title = [RQConfigureManager applicationAppStoreFormalSetting]? [NSString stringWithFormat:@"当前环境:%@", @"上线环境"]: [NSString stringWithFormat:@"当前环境:%@", @"预上线环境"];
+                title = [NSString stringWithFormat:@"当前环境:%@", @"上线环境"];
+			}else{
+				title = [NSString stringWithFormat:@"当前环境:%@", @"测试环境"];
+			}
+			
+			/// 预上线
+//			UIAlertAction *switchToPrePublish = [UIAlertAction actionWithTitle:@"预上线环境" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
+//				[RQConfigureManager setApplicationFormalSetting:YES];
+//				[RQConfigureManager setApplicationAppStoreFormalSetting:NO];
+//				[RQ_USER_MANAGER logoutUser];
+//				dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+//					exit(0);
+//				});
+//			}];
+//			switchToPrePublish.textColor = RQColorFromHexString(@"#0ECCCA");
+			/// 上线
+			UIAlertAction *switchToPublish = [UIAlertAction actionWithTitle:@"上线环境" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
+				[RQConfigureManager setApplicationFormalSetting:YES];
+//				[RQConfigureManager setApplicationAppStoreFormalSetting:YES];
+				[RQ_USER_MANAGER logoutUser];
+				dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+					exit(0);
+				});
+				
+			}];
+			switchToPublish.textColor = RQColorFromHexString(@"#0ECCCA");
+			/// 测试
+			UIAlertAction *switchToDevelop = [UIAlertAction actionWithTitle:@"测试环境" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
+				[RQConfigureManager setApplicationFormalSetting:NO];
+//				[RQConfigureManager setApplicationAppStoreFormalSetting:NO];
+				[RQ_USER_MANAGER logoutUser];
+				dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+					exit(0);
+				});
+			}];
+			switchToDevelop.textColor = RQColorFromHexString(@"#0ECCCA");
+			/// 取消
+			UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
+			}];
+			cancelAction.textColor = RQColorFromHexString(@"#8E929D");
+			/// alertController
+			UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:@"您确定要切换环境,程序将会闪退哦\n⚡️⚡️⚡️" preferredStyle:UIAlertControllerStyleAlert];
+			[alertController addAction:cancelAction];
+			alertController.titleColor = RQColorFromHexString(@"#3C3E44");
+			alertController.messageColor = RQColorFromHexString(@"#9A9A9C");
+			
+			if ([RQConfigureManager applicationFormalSetting]) {
+                [alertController addAction:switchToDevelop];
+//				if ([RQConfigureManager applicationAppStoreFormalSetting]) {
+//					/// 当前 ————> 上线环境
+//					[alertController addAction:switchToPrePublish];
+//					[alertController addAction:switchToDevelop];
+//				}else{
+//					/// 当前 ————> 预上线环境
+//					[alertController addAction:switchToPublish];
+//					[alertController addAction:switchToDevelop];
+//				}
+			}else{
+				/// 当前 ————> 测试环境
+//				[alertController addAction:switchToPrePublish];
+				[alertController addAction:switchToPublish];
+			}
+			[[RQControllerHelper currentViewController] presentViewController:alertController animated:YES completion:nil];
+		}else if(buttonIndex == 2){
+			/// Http/Https的切换
+			NSString *title = [NSString stringWithFormat:@"当前方式:%@", [RQConfigureManager applicationUseHttps]?@"https":@"http"];
+			
+			[NSObject rq_showAlertViewWithTitle:title message:@"您确定要切换环境,程序将会闪退哦\n⚡️⚡️⚡️" confirmTitle:[RQConfigureManager applicationUseHttps]?@"http":@"https" cancelTitle:@"取消" confirmAction:^{
+				[RQConfigureManager setApplicationUseHttps:![RQConfigureManager applicationUseHttps]];
+				dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+					exit(0);
+				});
+			} cancelAction:NULL];
+		}
+	} otherButtonTitleArray:@[@"切换正式/测试环境"/*,@"切换http/https"*/]];
+	
+	[actionSheet show];
+}
+
+
+#pragma mark - Override
+- (void)setFrame:(CGRect)frame{
+	frame.size.width = 65;
+	frame.size.height = 65;
+	[super setFrame:frame];
+}
+
+- (void)layoutSubviews{
+	[super layoutSubviews];
+	
+	CGFloat closeBtnWH = 36;
+	self.closeBtn.frame = CGRectMake(self.frame.size.width - closeBtnWH , 0, closeBtnWH, closeBtnWH);
+}
+
+
+
+#pragma mark - touch move
+- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
+	//保存触摸起始点位置
+	CGPoint point = [[touches anyObject] locationInView:self];
+	self.startPoint = point;
+	
+	//该view置于最前
+	[[self superview] bringSubviewToFront:self];
+}
+
+-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
+	//计算位移=当前位置-起始位置
+	CGPoint point = [[touches anyObject] locationInView:self];
+	float dx = point.x - self.startPoint.x;
+	float dy = point.y - self.startPoint.y;
+	
+	//计算移动后的view中心点
+	CGPoint newcenter = CGPointMake(self.center.x + dx, self.center.y + dy);
+	
+	/* 限制用户不可将视图托出屏幕 */
+	float halfx = CGRectGetMidX(self.bounds);
+	//x坐标左边界
+	newcenter.x = MAX(halfx, newcenter.x);
+	//x坐标右边界
+	newcenter.x = MIN(self.superview.bounds.size.width - halfx, newcenter.x);
+	
+	//y坐标同理
+	float halfy = CGRectGetMidY(self.bounds);
+	newcenter.y = MAX(halfy, newcenter.y);
+	newcenter.y = MIN(self.superview.bounds.size.height - halfy, newcenter.y);
+	
+	/// 动画过度
+	[UIView animateWithDuration:.25f delay:0 usingSpringWithDamping:.3f initialSpringVelocity:0 options:UIViewAnimationOptionCurveLinear animations:^{
+		//移动view
+		self.center = newcenter;
+	} completion:^(BOOL finished) {
+		
+	}];
+}
+
+@end

+ 14 - 0
jiaPei/Modules/BaseModule/Common/ViewController/RQCommonCollectionViewController.h

@@ -0,0 +1,14 @@
+//
+//  RQCommonCollectionViewController.h
+//  YueXueChe
+//
+//  Created by 张嵘 on 2018/12/19.
+//  Copyright © 2018 lee. All rights reserved.
+//
+
+#import "RQCollectionViewController.h"
+#import "RQCommonCollectionViewModel.h"
+
+@interface RQCommonCollectionViewController : RQCollectionViewController
+
+@end

+ 107 - 0
jiaPei/Modules/BaseModule/Common/ViewController/RQCommonCollectionViewController.m

@@ -0,0 +1,107 @@
+//
+//  RQCommonCollectionViewController.m
+//  YueXueChe
+//
+//  Created by 张嵘 on 2018/12/19.
+//  Copyright © 2018 lee. All rights reserved.
+//
+
+#import "RQCommonCollectionViewController.h"
+#import "RQCommonCollectionViewCell.h"
+#import "RQCommonReusableView.h"
+
+@interface RQCommonCollectionViewController ()
+/// viewModel
+@property (nonatomic, readwrite, strong) RQCommonCollectionViewModel *viewModel;
+@end
+
+@implementation RQCommonCollectionViewController
+@dynamic viewModel;
+
+- (void)viewDidLoad {
+	[super viewDidLoad];
+}
+
+#pragma mark - Override
+- (void)bindViewModel {
+	[super bindViewModel];
+}
+
+- (UIEdgeInsets)contentInset {
+	return UIEdgeInsetsMake((RQ_APPLICATION_NAV_BAR_HEIGHT + RQ_APPLICATION_STATUS_BAR_HEIGHT), 0, 0, 0);
+}
+
+- (void)configureCell:(RQCommonCollectionViewCell *)cell atIndexPath:(NSIndexPath *)indexPath withObject:(id)object {
+	[cell bindViewModel:object];
+}
+
+- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath {
+	return [RQCommonCollectionViewCell cellWithCollectionView:collectionView forIndexPath:indexPath];
+}
+
+#pragma mark - UICollectionViewDelegate & UICollectionViewDataSource
+- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
+	return self.viewModel.dataSource.count;
+}
+
+- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
+	RQCommonGroupViewModel *groupViewModel = self.viewModel.dataSource[section];
+	return groupViewModel.itemViewModels.count;
+}
+
+- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
+	/// fetch cell
+	RQCommonCollectionViewCell *cell = (RQCommonCollectionViewCell *)[self collectionView:collectionView dequeueReusableCellWithIdentifier:@"UICollectionViewCell" forIndexPath:indexPath];
+	RQCommonGroupViewModel *groupViewModel = self.viewModel.dataSource[indexPath.section];
+	id object = groupViewModel.itemViewModels[indexPath.row];
+	/// bind model
+	[self configureCell:cell atIndexPath:indexPath withObject:(id)object];
+	[cell setIndexPath:indexPath rowsInSection:groupViewModel.itemViewModels.count];
+	return cell;
+}
+
+- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
+	// 如果是头视图
+	if (kind == UICollectionElementKindSectionHeader) {
+		RQCommonReusableView *headerView = [RQCommonReusableView reusableViewWithCollectionView:collectionView OfKind:kind forIndexPath:indexPath];
+		RQCommonGroupViewModel *groupViewModel = self.viewModel.dataSource[indexPath.section];
+		[headerView bindViewModel:groupViewModel];
+		headerView.headerContentLabel.hidden = NO;
+		headerView.footerContentLabel.hidden = YES;
+		return headerView;
+	}else {
+		RQCommonReusableView *footerView = [RQCommonReusableView reusableViewWithCollectionView:collectionView OfKind:kind forIndexPath:indexPath];
+		RQCommonGroupViewModel *groupViewModel = self.viewModel.dataSource[indexPath.section];
+		[footerView bindViewModel:groupViewModel];
+		footerView.headerContentLabel.hidden = YES;
+		footerView.footerContentLabel.hidden = NO;
+		return footerView;
+	}
+}
+
+- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
+	RQCommonGroupViewModel *groupViewModel = self.viewModel.dataSource[indexPath.section];
+	RQCommonCollectionItemViewModel *itemViewModel = groupViewModel.itemViewModels[indexPath.row];
+	return itemViewModel.itemSize;
+}
+
+- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section {
+	RQCommonGroupViewModel *groupViewModel = self.viewModel.dataSource[section];
+    if (groupViewModel.groupModel) {
+        return CGSizeMake(RQ_SCREEN_WIDTH, groupViewModel.groupModel.headerHeight);
+    } else {
+        return CGSizeMake(RQ_SCREEN_WIDTH, groupViewModel.headerHeight);
+    }
+}
+
+- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section {
+	RQCommonGroupViewModel *groupViewModel = self.viewModel.dataSource[section];
+    if (groupViewModel.groupModel) {
+        return CGSizeMake(RQ_SCREEN_WIDTH, groupViewModel.groupModel.footerHeight);
+    } else {
+        return CGSizeMake(RQ_SCREEN_WIDTH, groupViewModel.footerHeight);
+    }
+}
+
+
+@end

+ 14 - 0
jiaPei/Modules/BaseModule/Common/ViewController/RQCommonViewController.h

@@ -0,0 +1,14 @@
+//
+//  RQCommonViewController.h
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/27.
+//  Copyright © 2018 张嵘. All rights reserved.
+//	用于快速搭建 类似 设置界面的样式
+
+#import "RQTableViewController.h"
+#import "RQCommonViewModel.h"
+
+@interface RQCommonViewController : RQTableViewController
+
+@end

+ 101 - 0
jiaPei/Modules/BaseModule/Common/ViewController/RQCommonViewController.m

@@ -0,0 +1,101 @@
+//
+//  RQCommonViewController.m
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/27.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import "RQCommonViewController.h"
+#import "RQCommonHeaderView.h"
+#import "RQCommonFooterView.h"
+#import "RQCommonCell.h"
+
+
+@interface RQCommonViewController ()
+/// viewModel
+@property (nonatomic, readwrite, strong) RQCommonViewModel *viewModel;
+@end
+
+@implementation RQCommonViewController
+@dynamic viewModel;
+- (void)viewDidLoad {
+	[super viewDidLoad];
+}
+
+#pragma mark - Override
+- (void)bindViewModel{
+	[super bindViewModel];
+}
+
+- (UIEdgeInsets)contentInset{
+	return UIEdgeInsetsMake((RQ_APPLICATION_NAV_BAR_HEIGHT + RQ_APPLICATION_STATUS_BAR_HEIGHT) , 0, 0, 0);
+}
+
+- (void)configureCell:(RQCommonCell *)cell atIndexPath:(NSIndexPath *)indexPath withObject:(id)object{
+	[cell bindViewModel:object];
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath{
+	return [RQCommonCell cellWithTableView:tableView];
+}
+
+#pragma mark - UITableViewDelegate & UITableViewDataSource
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
+	return self.viewModel.dataSource.count;
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
+	RQCommonGroupViewModel *groupViewModel = self.viewModel.dataSource[section];
+	return groupViewModel.itemViewModels.count;
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
+	/// fetch cell
+	RQCommonCell *cell = (RQCommonCell *)[self tableView:tableView dequeueReusableCellWithIdentifier:@"UITableViewCell" forIndexPath:indexPath];
+	RQCommonGroupViewModel *groupViewModel = self.viewModel.dataSource[indexPath.section];
+	id object = groupViewModel.itemViewModels[indexPath.row];
+	/// bind model
+	[self configureCell:cell atIndexPath:indexPath withObject:(id)object];
+	[cell setIndexPath:indexPath rowsInSection:groupViewModel.itemViewModels.count];
+	return cell;
+}
+
+- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section{
+	RQCommonFooterView *footerView = [RQCommonFooterView footerViewWithTableView:tableView];
+	RQCommonGroupViewModel *groupViewModel = self.viewModel.dataSource[section];
+	[footerView bindViewModel:groupViewModel];
+	return footerView;
+}
+
+- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{
+	RQCommonHeaderView *headerView = [RQCommonHeaderView headerViewWithTableView:tableView];
+	RQCommonGroupViewModel *groupViewModel = self.viewModel.dataSource[section];
+	[headerView bindViewModel:groupViewModel];
+	return headerView;
+}
+
+- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
+	RQCommonGroupViewModel *groupViewModel = self.viewModel.dataSource[indexPath.section];
+	RQCommonItemViewModel *itemViewModel = groupViewModel.itemViewModels[indexPath.row];
+	return itemViewModel.rowHeight;
+}
+
+- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{
+    RQCommonGroupViewModel *groupViewModel = self.viewModel.dataSource[section];
+    if (groupViewModel.groupModel) {
+        return groupViewModel.groupModel.headerHeight;
+    } else {
+        return groupViewModel.headerHeight;
+    }
+}
+
+- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section{
+    RQCommonGroupViewModel *groupViewModel = self.viewModel.dataSource[section];
+    if (groupViewModel.groupModel) {
+        return groupViewModel.groupModel.footerHeight;
+    } else {
+        return groupViewModel.footerHeight;
+    }
+}
+@end

+ 18 - 0
jiaPei/Modules/BaseModule/Common/ViewModel/CommonProfileHeaderItemViewModel.h

@@ -0,0 +1,18 @@
+//
+//  CommonProfileHeaderItemViewModel.h
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/27.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import "RQCommonItemViewModel.h"
+
+@interface CommonProfileHeaderItemViewModel : RQCommonItemViewModel
+/// 用户头像
+@property (nonatomic, readonly, strong) RQUserModel *user;
+
+- (instancetype)initViewModelWithUser:(RQUserModel *)user;
+
+
+@end

+ 24 - 0
jiaPei/Modules/BaseModule/Common/ViewModel/CommonProfileHeaderItemViewModel.m

@@ -0,0 +1,24 @@
+//
+//  CommonProfileHeaderItemViewModel.m
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/27.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import "CommonProfileHeaderItemViewModel.h"
+
+@interface CommonProfileHeaderItemViewModel ()
+/// 用户头像
+@property (nonatomic, readwrite, strong) RQUserModel *user;
+@end
+
+@implementation CommonProfileHeaderItemViewModel
+- (instancetype)initViewModelWithUser:(RQUserModel *)user {
+	if (self = [super init]) {
+		self.user = user;
+	}
+	return self;
+}
+
+@end

+ 13 - 0
jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonArrowItemViewModel.h

@@ -0,0 +1,13 @@
+//
+//  RQCommonArrowItemViewModel.h
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/27.
+//  Copyright © 2018 张嵘. All rights reserved.
+//	带箭头的  (icon + title + subTitle + arrow)
+
+#import "RQCommonItemViewModel.h"
+
+@interface RQCommonArrowItemViewModel : RQCommonItemViewModel
+
+@end

+ 13 - 0
jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonArrowItemViewModel.m

@@ -0,0 +1,13 @@
+//
+//  RQCommonArrowItemViewModel.m
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/27.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import "RQCommonArrowItemViewModel.h"
+
+@implementation RQCommonArrowItemViewModel
+
+@end

+ 14 - 0
jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonAvatarItemViewModel.h

@@ -0,0 +1,14 @@
+//
+//  RQCommonAvatarItemViewModel.h
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/27.
+//  Copyright © 2018 张嵘. All rights reserved.
+//	头像
+
+#import "RQCommonArrowItemViewModel.h"
+
+@interface RQCommonAvatarItemViewModel : RQCommonArrowItemViewModel
+/// 用户头像
+@property (nonatomic, readwrite, copy) NSString *avatar;
+@end

+ 13 - 0
jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonAvatarItemViewModel.m

@@ -0,0 +1,13 @@
+//
+//  RQCommonAvatarItemViewModel.m
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/27.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import "RQCommonAvatarItemViewModel.h"
+
+@implementation RQCommonAvatarItemViewModel
+
+@end

+ 42 - 0
jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonCollectionItemViewModel.h

@@ -0,0 +1,42 @@
+//
+//  RQCommonCollectionItemViewModel.h
+//  YueXueChe
+//
+//  Created by 张嵘 on 2018/12/19.
+//  Copyright © 2018 lee. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@protocol RQCommonCollecttionItemDelegate <NSObject>
+
+/**
+ item的View类名
+ @return item的View类名
+ */
+- (NSString *)itemClassName;
+
+@end
+
+@interface RQCommonCollectionItemViewModel : NSObject <RQCommonCollecttionItemDelegate>
+/// 图标
+@property (nonatomic, readwrite, copy) NSString *icon;
+/// 标题
+@property (nonatomic, readwrite, copy) NSString *title;
+/// 子标题
+@property (nonatomic, readwrite, copy) NSString *subtitle;
+
+/// itemSize , default is CGSizeMake(44.f, 44.f)
+@property (nonatomic, readwrite, assign) CGSize itemSize;
+/// 右上角显示的数字标记
+@property (nonatomic, readwrite, copy) NSString *badgeValue;
+/// 点击这行cell,需要调转到哪个控制器的视图模型 destViewModelClass:必须是SBViewModel的子类
+@property (nonatomic, readwrite, assign) Class destViewModelClass;
+/// 封装点击这行cell想做的事情
+@property (nonatomic, readwrite, copy) void (^operation)(void);
+@property (nonatomic, readwrite, weak) id<RQCommonCollecttionItemDelegate> delegate;
+
+/// init title or icon
++ (instancetype)itemViewModelWithTitle:(NSString *)title icon:(NSString *)icon;
+@end
+

+ 31 - 0
jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonCollectionItemViewModel.m

@@ -0,0 +1,31 @@
+//
+//  RQCommonCollectionItemViewModel.m
+//  YueXueChe
+//
+//  Created by 张嵘 on 2018/12/19.
+//  Copyright © 2018 lee. All rights reserved.
+//
+
+#import "RQCommonCollectionItemViewModel.h"
+
+@implementation RQCommonCollectionItemViewModel
++ (instancetype)itemViewModelWithTitle:(NSString *)title icon:(NSString *)icon {
+	RQCommonCollectionItemViewModel *item = [[self alloc] init];
+	item.title = title;
+	item.icon = icon;
+	return item;
+}
+
+- (instancetype)init {
+	self = [super init];
+	if (self) {
+		_itemSize = CGSizeMake(RQ_SCREEN_WIDTH / 4.f,  (RQ_SCREEN_WIDTH / 4.f) * (88.f / 93.75f));
+        _delegate = self;
+	}
+	return self;
+}
+
+- (NSString *)itemClassName {
+    return @"RQCommonCollectionViewCell";
+}
+@end

+ 15 - 0
jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonCollectionViewModel.h

@@ -0,0 +1,15 @@
+//
+//  RQCommonCollectionViewModel.h
+//  YueXueChe
+//
+//  Created by 张嵘 on 2018/12/19.
+//  Copyright © 2018 lee. All rights reserved.
+//
+
+#import "RQCollectionViewModel.h"
+#import "RQCommonGroupViewModel.h"
+#import "RQCommonCollectionItemViewModel.h"
+
+@interface RQCommonCollectionViewModel : RQCollectionViewModel
+
+@end

+ 35 - 0
jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonCollectionViewModel.m

@@ -0,0 +1,35 @@
+//
+//  RQCommonCollectionViewModel.m
+//  YueXueChe
+//
+//  Created by 张嵘 on 2018/12/19.
+//  Copyright © 2018 lee. All rights reserved.
+//
+
+#import "RQCommonCollectionViewModel.h"
+
+@implementation RQCommonCollectionViewModel
+- (void)initialize {
+	[super initialize];
+	
+	@weakify(self);
+	/// 选中cell的命令
+	/// UI Test
+	self.didSelectCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(NSIndexPath *indexPath) {
+		@strongify(self);
+		RQCommonGroupViewModel *groupViewModel = self.dataSource[indexPath.section] ;
+		RQCommonCollectionItemViewModel *itemViewModel = groupViewModel.itemViewModels[indexPath.row];
+		
+		if (itemViewModel.operation) {
+			/// 有操作执行操作
+			itemViewModel.operation();
+		}else if(itemViewModel.destViewModelClass){
+			/// 没有操作就跳转VC
+			Class viewModelClass = itemViewModel.destViewModelClass;
+			RQBaseViewModel *viewModel = [[viewModelClass alloc] initWithServices:self.services params:@{RQViewModelTitleKey:itemViewModel.title}];
+			[self.services pushViewModel:viewModel animated:YES];
+		}
+		return [RACSignal empty];
+	}];
+}
+@end

+ 28 - 0
jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonCollecttionItemViewModel.h

@@ -0,0 +1,28 @@
+//
+//  RQCommonCollecttionItemViewModel.h
+//  YueXueChe
+//
+//  Created by 张嵘 on 2018/12/19.
+//  Copyright © 2018 lee. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface RQCommonCollecttionItemViewModel : NSObject
+/// 图标
+@property (nonatomic, readwrite, copy) NSString *icon;
+/// 标题
+@property (nonatomic, readwrite, copy) NSString *title;
+/// itemSize , default is CGSizeMake(44.f, 44.f)
+@property (nonatomic, readwrite, assign) CGSize itemSize;
+/// 右上角显示的数字标记
+@property (nonatomic, readwrite, copy) NSString *badgeValue;
+/// 点击这行cell,需要调转到哪个控制器的视图模型 destViewModelClass:必须是SBViewModel的子类
+@property (nonatomic, readwrite, assign) Class destViewModelClass;
+/// 封装点击这行cell想做的事情
+@property (nonatomic, readwrite, copy) void (^operation)(void);
+
+/// init title or icon
++ (instancetype)itemViewModelWithTitle:(NSString *)title icon:(NSString *)icon;
+@end
+

+ 27 - 0
jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonCollecttionItemViewModel.m

@@ -0,0 +1,27 @@
+//
+//  RQCommonCollecttionItemViewModel.m
+//  YueXueChe
+//
+//  Created by 张嵘 on 2018/12/19.
+//  Copyright © 2018 lee. All rights reserved.
+//
+
+#import "RQCommonCollecttionItemViewModel.h"
+
+@implementation RQCommonCollecttionItemViewModel
++ (instancetype)itemViewModelWithTitle:(NSString *)title icon:(NSString *)icon {
+	RQCommonCollecttionItemViewModel *item = [[self alloc] init];
+	item.title = title;
+	item.icon = icon;
+	return item;
+}
+
+- (instancetype)init
+{
+	self = [super init];
+	if (self) {
+		_itemSize = CGSizeMake(RQ_SCREEN_WIDTH / 4.f, RQ_SCREEN_WIDTH / 4.f);
+	}
+	return self;
+}
+@end

+ 15 - 0
jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonCollecttionViewModel.h

@@ -0,0 +1,15 @@
+//
+//  RQCommonCollecttionViewModel.h
+//  YueXueChe
+//
+//  Created by 张嵘 on 2018/12/19.
+//  Copyright © 2018 lee. All rights reserved.
+//
+
+#import "RQCollectionViewModel.h"
+#import "RQCommonGroupViewModel.h"
+#import "RQCommonCollecttionItemViewModel.h"
+
+@interface RQCommonCollecttionViewModel : RQCollectionViewModel
+
+@end

+ 35 - 0
jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonCollecttionViewModel.m

@@ -0,0 +1,35 @@
+//
+//  RQCommonCollecttionViewModel.m
+//  YueXueChe
+//
+//  Created by 张嵘 on 2018/12/19.
+//  Copyright © 2018 lee. All rights reserved.
+//
+
+#import "RQCommonCollecttionViewModel.h"
+
+@implementation RQCommonCollecttionViewModel
+- (void)initialize {
+	[super initialize];
+	
+	@weakify(self);
+	/// 选中cell的命令
+	/// UI Test
+	self.didSelectCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(NSIndexPath *indexPath) {
+		@strongify(self);
+		RQCommonGroupViewModel *groupViewModel = self.dataSource[indexPath.section] ;
+		RQCommonCollecttionItemViewModel *itemViewModel = groupViewModel.itemViewModels[indexPath.row];
+		
+		if (itemViewModel.operation) {
+			/// 有操作执行操作
+			itemViewModel.operation();
+		}else if(itemViewModel.destViewModelClass){
+			/// 没有操作就跳转VC
+			Class viewModelClass = itemViewModel.destViewModelClass;
+			RQBaseViewModel *viewModel = [[viewModelClass alloc] initWithServices:self.services params:@{RQViewModelTitleKey:itemViewModel.title}];
+			[self.services pushViewModel:viewModel animated:YES];
+		}
+		return [RACSignal empty];
+	}];
+}
+@end

+ 31 - 0
jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonGroupViewModel.h

@@ -0,0 +1,31 @@
+//
+//  RQCommonGroupViewModel.h
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/27.
+//  Copyright © 2018 张嵘. All rights reserved.
+//	组视图模型
+
+#import <Foundation/Foundation.h>
+#import "RQCommonGroupModel.h"
+
+@interface RQCommonGroupViewModel : NSObject
+/// 组头
+@property (nonatomic, copy) NSString *header;
+/// headerHeight defalult is .001
+@property (nonatomic, readwrite, assign) CGFloat headerHeight;
+@property (nonatomic, readwrite, strong) UIFont *headerFont;
+
+/// 组尾
+@property (nonatomic, copy) NSString *footer;
+/// footerHeight defalult is .001
+@property (nonatomic, readwrite, assign) CGFloat footerHeight;
+@property (nonatomic, readwrite, strong) UIFont *footerFont;
+
+/// 里面装着都是 MHCommonItemViewModel 以及其子类
+@property (nonatomic, readwrite, strong) NSArray *itemViewModels;
+@property (nonatomic, readonly, strong) RQCommonGroupModel *groupModel;
+
++ (instancetype)groupViewModel;
+- (void)setCustomStyleWithRQCommonGroupModel:(void(^)(RQCommonGroupModel *groupModel))handler;
+@end

+ 40 - 0
jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonGroupViewModel.m

@@ -0,0 +1,40 @@
+//
+//  RQCommonGroupViewModel.m
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/27.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import "RQCommonGroupViewModel.h"
+
+@interface RQCommonGroupViewModel ()
+@property (nonatomic, readwrite, strong) RQCommonGroupModel *groupModel;
+
+@end
+
+@implementation RQCommonGroupViewModel
++ (instancetype)groupViewModel {
+	return [[self alloc] init];
+}
+
+- (instancetype)init {
+	self = [super init];
+	if (self) {
+		_footerHeight = CGFLOAT_MIN;
+		_headerHeight = CGFLOAT_MIN;
+        _headerFont = RQRegularFont_17;
+        _footerFont = RQRegularFont_17;
+	}
+	return self;
+}
+
+- (void)setCustomStyleWithRQCommonGroupModel:(void(^)(RQCommonGroupModel *groupModel))handler {
+    RQCommonGroupModel *groupModel = [[RQCommonGroupModel alloc] init];
+    if (handler) {
+        handler(groupModel);
+    }
+    
+    self.groupModel = groupModel;
+}
+@end

+ 52 - 0
jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonItemViewModel.h

@@ -0,0 +1,52 @@
+//
+//  RQCommonItemViewModel.h
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/27.
+//  Copyright © 2018 张嵘. All rights reserved.
+//	基类 (icon + title + subTitle)
+
+#import <Foundation/Foundation.h>
+
+@protocol RQCommonItemDelegate <NSObject>
+
+/**
+ item的View类名
+ @return item的View类名
+ */
+- (NSString *)itemClassName;
+
+@end
+
+@interface RQCommonItemViewModel : NSObject <RQCommonItemDelegate>
+/// 图标
+@property (nonatomic, readwrite, copy) NSString *icon;
+/// 标题
+@property (nonatomic, readwrite, copy) NSString *title;
+/// 子标题
+@property (nonatomic, readwrite, copy) NSString *subtitle;
+
+
+/// rowHeight , default is 44.0f
+@property (nonatomic, readwrite, assign) CGFloat rowHeight;
+// default is UITableViewCellSelectionStyleGray.
+@property (nonatomic, readwrite, assign) UITableViewCellSelectionStyle selectionStyle;
+
+/// 右边显示的数字标记
+@property (nonatomic, readwrite, copy) NSString *badgeValue;
+
+/// 中间偏左icon的图片名字
+@property (nonatomic, readwrite, copy) NSString *centerLeftViewName;
+/// 中间偏右icon的图片名字
+@property (nonatomic, readwrite, copy) NSString *centerRightViewName;
+/// 点击这行cell,需要调转到哪个控制器的视图模型 destViewModelClass:必须是SBViewModel的子类
+@property (nonatomic, readwrite, assign) Class destViewModelClass;
+/// 封装点击这行cell想做的事情
+@property (nonatomic, readwrite, copy) void (^operation)(void);
+@property (nonatomic, readwrite, weak) id<RQCommonItemDelegate> delegate;
+/// init title or icon
++ (instancetype)itemViewModelWithTitle:(NSString *)title icon:(NSString *)icon;
+/// init title
++ (instancetype)itemViewModelWithTitle:(NSString *)title;
+
+@end

+ 37 - 0
jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonItemViewModel.m

@@ -0,0 +1,37 @@
+//
+//  RQCommonItemViewModel.m
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/27.
+//  Copyright © 2018 张嵘. All rights reserved.
+//	
+
+#import "RQCommonItemViewModel.h"
+
+@implementation RQCommonItemViewModel
++ (instancetype)itemViewModelWithTitle:(NSString *)title icon:(NSString *)icon{
+	RQCommonItemViewModel *item = [[self alloc] init];
+	item.title = title;
+	item.icon = icon;
+	return item;
+}
+
++ (instancetype)itemViewModelWithTitle:(NSString *)title{
+	return [self itemViewModelWithTitle:title icon:nil];
+}
+
+- (instancetype)init
+{
+	self = [super init];
+	if (self) {
+		_selectionStyle = UITableViewCellSelectionStyleGray;
+		_rowHeight = RQ_FIT_HORIZONTAL(60.0f);
+        _delegate = self;
+	}
+	return self;
+}
+
+- (NSString *)itemClassName {
+    return @"RQCommonCell";
+}
+@end

+ 15 - 0
jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonLabelItemViewModel.h

@@ -0,0 +1,15 @@
+//
+//  RQCommonLabelItemViewModel.h
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/27.
+//  Copyright © 2018 张嵘. All rights reserved.
+//	右边显示的内容
+
+#import "RQCommonItemViewModel.h"
+
+@interface RQCommonLabelItemViewModel : RQCommonItemViewModel
+/** 右边label显示的内容 */
+@property (nonatomic, readwrite, copy) NSString *text;
+
+@end

+ 13 - 0
jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonLabelItemViewModel.m

@@ -0,0 +1,13 @@
+//
+//  RQCommonLabelItemViewModel.m
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/27.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import "RQCommonLabelItemViewModel.h"
+
+@implementation RQCommonLabelItemViewModel
+
+@end

+ 13 - 0
jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonQRCodeItemViewModel.h

@@ -0,0 +1,13 @@
+//
+//  RQCommonQRCodeItemViewModel.h
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/27.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import "RQCommonArrowItemViewModel.h"
+
+@interface RQCommonQRCodeItemViewModel : RQCommonArrowItemViewModel
+
+@end

+ 13 - 0
jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonQRCodeItemViewModel.m

@@ -0,0 +1,13 @@
+//
+//  RQCommonQRCodeItemViewModel.m
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/27.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import "RQCommonQRCodeItemViewModel.h"
+
+@implementation RQCommonQRCodeItemViewModel
+
+@end

+ 15 - 0
jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonSwitchItemViewModel.h

@@ -0,0 +1,15 @@
+//
+//  RQCommonSwitchItemViewModel.h
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/27.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import "RQCommonValueItemViewModel.h"
+
+@interface RQCommonSwitchItemViewModel : RQCommonValueItemViewModel
+/// 开关状态
+@property (nonatomic, readwrite, assign) BOOL off;
+
+@end

+ 21 - 0
jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonSwitchItemViewModel.m

@@ -0,0 +1,21 @@
+//
+//  RQCommonSwitchItemViewModel.m
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/27.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import "RQCommonSwitchItemViewModel.h"
+
+@implementation RQCommonSwitchItemViewModel
+- (void)setOff:(BOOL)off {
+	_off = off;
+	[RQPreferenceSettingHelper setBool:off forKey:self.key];
+}
+
+- (void)setKey:(NSString *)key {
+	[super setKey:key];
+	_off = [RQPreferenceSettingHelper boolForKey:key];
+}
+@end

+ 14 - 0
jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonValueItemViewModel.h

@@ -0,0 +1,14 @@
+//
+//  RQCommonValueItemViewModel.h
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/27.
+//  Copyright © 2018 张嵘. All rights reserved.
+//	需要存储数据的item都继承自它
+
+#import "RQCommonItemViewModel.h"
+
+@interface RQCommonValueItemViewModel : RQCommonItemViewModel
+/// 存储数据用的key
+@property (nonatomic, readwrite, copy) NSString *key;
+@end

+ 12 - 0
jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonValueItemViewModel.m

@@ -0,0 +1,12 @@
+//
+//  RQCommonValueItemViewModel.m
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/27.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import "RQCommonValueItemViewModel.h"
+
+@implementation RQCommonValueItemViewModel
+@end

+ 15 - 0
jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonViewModel.h

@@ -0,0 +1,15 @@
+//
+//  RQCommonViewModel.h
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/27.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import "RQTableViewModel.h"
+#import "RQCommonItemViewModel.h"
+#import "RQCommonGroupViewModel.h"
+
+@interface RQCommonViewModel : RQTableViewModel
+
+@end

+ 37 - 0
jiaPei/Modules/BaseModule/Common/ViewModel/RQCommonViewModel.m

@@ -0,0 +1,37 @@
+//
+//  RQCommonViewModel.m
+//  RQCommon
+//
+//  Created by 张嵘 on 2018/11/27.
+//  Copyright © 2018 张嵘. All rights reserved.
+//
+
+#import "RQCommonViewModel.h"
+
+
+@implementation RQCommonViewModel
+- (void)initialize{
+	[super initialize];
+	
+	@weakify(self);
+	/// 选中cell的命令
+	/// UI Test
+	self.didSelectCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(NSIndexPath *indexPath) {
+		@strongify(self);
+		RQCommonGroupViewModel *groupViewModel = self.dataSource[indexPath.section] ;
+		RQCommonItemViewModel *itemViewModel = groupViewModel.itemViewModels[indexPath.row];
+		
+		if (itemViewModel.operation) {
+			/// 有操作执行操作
+			itemViewModel.operation();
+		}else if(itemViewModel.destViewModelClass){
+			/// 没有操作就跳转VC
+			Class viewModelClass = itemViewModel.destViewModelClass;
+			RQBaseViewModel *viewModel = [[viewModelClass alloc] initWithServices:self.services params:@{RQViewModelTitleKey:itemViewModel.title}];
+			[self.services pushViewModel:viewModel animated:YES];
+		}
+		return [RACSignal empty];
+	}];
+}
+
+@end

+ 21 - 0
jiaPei/Modules/ChangeSchool/Controller/StudentChangeSchoolVC.h

@@ -0,0 +1,21 @@
+//
+//  StudentChangeSchoolVC.h
+//  jiaPei
+//
+//  Created by 张嵘 on 2020/12/1.
+//  Copyright © 2020 JCZ. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface StudentChangeSchoolVC : UIViewController
+@property (weak, nonatomic) IBOutlet UITableView *tableView;
+@property (weak, nonatomic) IBOutlet UIView *footerView;
+@property (weak, nonatomic) IBOutlet UIButton *confirmBtn;
+@property (weak, nonatomic) IBOutlet NSLayoutConstraint *bottomConst;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 201 - 0
jiaPei/Modules/ChangeSchool/Controller/StudentChangeSchoolVC.m

@@ -0,0 +1,201 @@
+//
+//  StudentChangeSchoolVC.m
+//  jiaPei
+//
+//  Created by 张嵘 on 2020/12/1.
+//  Copyright © 2020 JCZ. All rights reserved.
+//
+
+#import "StudentChangeSchoolVC.h"
+#import "StudentChangeSchoolModel.h"
+
+@interface StudentChangeSchoolVC () <UITableViewDelegate, UITableViewDataSource>
+@property (nonatomic, readwrite, strong) StudentChangeSchoolModel * studentChangeSchoolModel;
+@property (nonatomic , assign) ScsInSchoolAudit       SCS_IN_SCHOOL_AUDIT;
+@end
+
+@implementation StudentChangeSchoolVC
+
+- (void)viewDidLoad {
+    [super viewDidLoad];
+	[self initUI];
+}
+
+#pragma mark - Private Functions
+- (void)initUI {
+	self.title = @"转校记录";
+	self.SCS_IN_SCHOOL_AUDIT = ScsInSchoolAudit_Default;
+	[self configNavigationBar];
+	_tableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
+		[self getData];
+	}];
+	[_tableView.mj_header beginRefreshing];
+	[RACObserve(self, SCS_IN_SCHOOL_AUDIT) subscribeNext:^(id  _Nullable x) {
+		_footerView.hidden = _SCS_IN_SCHOOL_AUDIT != ScsInSchoolAudit_Wait;
+	}];
+}
+/// 请求数据
+- (void)getData {
+	NSMutableArray *arr = [NSMutableArray array];
+	[arr addPro:@"dqbh"     	Value:defUser.userDict[@"city"]];
+	[arr addPro:@"stuOutId" 	    Value:defUser.userDict[@"outId"]];
+	
+	NSString *method = @"getStudentChangeSchool";
+	
+	[jiaPeiManager requestAnythingWithURL:method array:arr data:nil completion:^(NSDictionary *root) {
+		[_tableView.mj_header endRefreshing];
+		if (!root) {
+			ShowMsgFailed();
+			return;
+		}
+		if ([root[@"code"] isEqualToString:@"0"]) {
+			_studentChangeSchoolModel = [StudentChangeSchoolModel modelWithDictionary:root[@"body"]];
+			self.SCS_IN_SCHOOL_AUDIT = _studentChangeSchoolModel.SCS_IN_SCHOOL_AUDIT;
+			[_tableView reloadData];
+			
+		}else {
+			if ([root[@"body"] isKindOfClass:[NSString class]]) {
+				ShowMsg(root[@"body"]);
+			} else {
+				ShowMsg(root[@"msg"]);
+			}
+			return;
+		}
+	}];
+}
+
+- (IBAction)confirmAction:(id)sender {
+	[RQ_SHARE_FUNCTION showAlertWithTitle:@"温馨提示" message:@"是否同意转校?" alertControllerStyle:UIAlertControllerStyleActionSheet cancelButtonTitle:@"取消" otherButtonTitles:@[@"同意",@"拒绝"] otherButtonStyles:nil completion:^(NSUInteger selectedOtherButtonIndex) {
+		switch (selectedOtherButtonIndex) {
+			case 0:
+				///同意
+				[self updateStudentChangeSchoolWithAudit:@"3"];
+				break;
+			case 1:
+				///拒绝
+				[self updateStudentChangeSchoolWithAudit:@"4"];
+				break;
+				
+			default:
+				break;
+		}
+	}];
+}
+
+- (void)updateStudentChangeSchoolWithAudit:(NSString *)audit {
+	///		* URL=http://192.168.1.6:8082/student/updateStudentChangeSchool?ts={timestamp}&sign={sign_str}&user={cert_sn}
+	///		* HTTP方法:POST
+	///		* 报文格式:{'stuOutId':'3502004042','audit':'3','dqbh':'3502'} audit :3同意 4拒绝
+	///		* return结果: {"body":"","code":"0"}
+	
+	NSMutableArray *arr = [NSMutableArray array];
+	[arr addObject:[NSDictionary dictionaryWithObjectsAndKeys:defUser.userDict[@"outId"],@"stuOutId", nil]];
+	[arr addObject:[NSDictionary dictionaryWithObjectsAndKeys:audit,@"audit", nil]];
+	[arr addObject:[NSDictionary dictionaryWithObjectsAndKeys:defUser.userDict[@"city"],@"dqbh", nil]];
+	
+	NSString *method1 = @"updateStudentChangeSchool";
+	
+	[jiaPeiManager requestAnythingWithURL:method1 array:arr data:nil completion:^(NSDictionary *dict) {
+		RemoveHUD();
+		if ([dict[@"code"] isEqualToString:@"0"]) {
+			[self getData];
+		} else {
+			ShowMsg(dict[@"msg"]);
+		}
+	}];
+}
+#pragma mark - UITableViewDataSource
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
+	return 6;
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
+	UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];
+	if (!cell) {
+		cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:@"Cell"];
+	}
+	switch (indexPath.row) {
+		case 0: {
+			cell.textLabel.text = @"姓名";
+			cell.detailTextLabel.text = self.studentChangeSchoolModel.TSO_NAME? : @"返回姓名为空";
+			break;
+		}
+			
+		case 1: {
+			cell.textLabel.text = @"身份证号码";
+			cell.detailTextLabel.text = self.studentChangeSchoolModel.TSO_IDCARD? : @"返回身份证号码为空";
+			break;
+		}
+			
+		case 2: {
+			cell.textLabel.text = @"转出驾校";
+			cell.detailTextLabel.text = self.studentChangeSchoolModel.OUT_SCHOOL? : @"返回转出驾校为空";
+			break;
+		}
+			
+		case 3: {
+			cell.textLabel.text = @"转入驾校";
+			cell.detailTextLabel.text = self.studentChangeSchoolModel.IN_SCHOOL? : @"返回转入驾校为空";
+			break;
+		}
+			
+		case 4: {
+			cell.textLabel.text = @"管理部门审核情况";
+			cell.detailTextLabel.text = (self.studentChangeSchoolModel.SCS_AUDIT_VIEW == ScsAuditViewType_Wait)? @"待审核" : ((self.studentChangeSchoolModel.SCS_AUDIT_VIEW == ScsAuditViewType_Pass)? @"审核通过" : ((self.studentChangeSchoolModel.SCS_AUDIT_VIEW == ScsAuditViewType_Return)? @"审核未通过" : @"暂无审核状态")) ;
+			break;
+		}
+			
+		case 5: {
+			cell.textLabel.text = @"转校进度";
+			switch (self.studentChangeSchoolModel.SCS_IN_SCHOOL_AUDIT) {
+					/// 学员待确认
+				case ScsInSchoolAudit_Wait:
+					cell.detailTextLabel.text = @"学员待确认";
+					break;
+					/// 驾校同意
+				case ScsInSchoolAudit_SchAllow:
+					cell.detailTextLabel.text = @"驾校同意";
+					break;
+					/// 驾校不接收
+				case ScsInSchoolAudit_SchRefuse:
+					cell.detailTextLabel.text = @"驾校不接收";
+					break;
+					/// 学员同意
+				case ScsInSchoolAudit_StuAllow:
+					cell.detailTextLabel.text = @"学员同意";
+					break;
+					/// 学员拒绝
+				case ScsInSchoolAudit_StuRefuse:
+					cell.detailTextLabel.text = @"学员拒绝";
+					break;
+					
+				default:
+					cell.detailTextLabel.text = @"未知进度";
+					break;
+			}
+			
+			break;
+		}
+			
+		default:
+			break;
+	}
+	return cell;
+}
+
+#pragma mark - LazyLoad
+- (StudentChangeSchoolModel *)studentChangeSchoolModel {
+	if (!_studentChangeSchoolModel) {
+		_studentChangeSchoolModel = [[StudentChangeSchoolModel alloc] init];
+		_studentChangeSchoolModel.TSO_NAME = @"本地数据-张三";
+		_studentChangeSchoolModel.TSO_IDCARD = @"本地数据-3501**********1234";
+		_studentChangeSchoolModel.OUT_SCHOOL = @"本地数据-驾校A";
+		_studentChangeSchoolModel.IN_SCHOOL = @"本地数据-驾校B";
+		_studentChangeSchoolModel.SCS_IN_SCHOOL_AUDIT = ScsInSchoolAudit_Default;
+		_studentChangeSchoolModel.SCS_AUDIT_VIEW = ScsAuditViewType_Default;
+		self.SCS_IN_SCHOOL_AUDIT = _studentChangeSchoolModel.SCS_IN_SCHOOL_AUDIT;
+	}
+	return _studentChangeSchoolModel;
+}
+
+@end

+ 77 - 0
jiaPei/Modules/ChangeSchool/Controller/StudentChangeSchoolVC.xib

@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17506" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
+    <device id="retina6_1" orientation="portrait" appearance="light"/>
+    <dependencies>
+        <deployment version="2304" identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17505"/>
+        <capability name="System colors in document resources" minToolsVersion="11.0"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <objects>
+        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="StudentChangeSchoolVC">
+            <connections>
+                <outlet property="bottomConst" destination="uyn-FK-jYZ" id="7ht-7i-0sM"/>
+                <outlet property="confirmBtn" destination="fzf-0Q-M5C" id="fyc-8e-zhK"/>
+                <outlet property="footerView" destination="2TT-zD-Yi2" id="yug-cU-hD2"/>
+                <outlet property="tableView" destination="INJ-Sr-Vjo" id="a2G-IA-ONP"/>
+                <outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
+            </connections>
+        </placeholder>
+        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
+        <view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="i5M-Pr-FkT">
+            <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
+            <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+            <subviews>
+                <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" style="grouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="-1" estimatedSectionHeaderHeight="-1" sectionFooterHeight="-1" estimatedSectionFooterHeight="-1" contentViewInsetsToSafeArea="NO" translatesAutoresizingMaskIntoConstraints="NO" id="INJ-Sr-Vjo">
+                    <rect key="frame" x="0.0" y="0.0" width="414" height="846"/>
+                    <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                    <connections>
+                        <outlet property="dataSource" destination="-1" id="IKP-jq-o4n"/>
+                        <outlet property="delegate" destination="-1" id="xkC-74-7di"/>
+                    </connections>
+                </tableView>
+                <view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="2TT-zD-Yi2">
+                    <rect key="frame" x="0.0" y="846" width="414" height="50"/>
+                    <subviews>
+                        <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="fzf-0Q-M5C">
+                            <rect key="frame" x="0.0" y="0.0" width="414" height="50"/>
+                            <state key="normal" title="同意/拒绝">
+                                <color key="titleColor" systemColor="labelColor"/>
+                            </state>
+                            <connections>
+                                <action selector="confirmAction:" destination="-1" eventType="touchUpInside" id="x8Q-Jl-jhb"/>
+                            </connections>
+                        </button>
+                    </subviews>
+                    <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                    <constraints>
+                        <constraint firstAttribute="height" constant="50" id="BpP-Tp-ryK"/>
+                        <constraint firstItem="fzf-0Q-M5C" firstAttribute="centerY" secondItem="2TT-zD-Yi2" secondAttribute="centerY" id="Fhg-ji-PEy"/>
+                        <constraint firstItem="fzf-0Q-M5C" firstAttribute="centerX" secondItem="2TT-zD-Yi2" secondAttribute="centerX" id="HaX-ut-RyW"/>
+                        <constraint firstItem="fzf-0Q-M5C" firstAttribute="width" secondItem="2TT-zD-Yi2" secondAttribute="width" id="QCC-Ay-hCj"/>
+                        <constraint firstItem="fzf-0Q-M5C" firstAttribute="height" secondItem="2TT-zD-Yi2" secondAttribute="height" id="amo-da-YIb"/>
+                    </constraints>
+                </view>
+            </subviews>
+            <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+            <constraints>
+                <constraint firstItem="INJ-Sr-Vjo" firstAttribute="leading" secondItem="i5M-Pr-FkT" secondAttribute="leading" id="95b-Yd-ee5"/>
+                <constraint firstAttribute="trailing" secondItem="2TT-zD-Yi2" secondAttribute="trailing" id="EfQ-Wg-Vol"/>
+                <constraint firstItem="INJ-Sr-Vjo" firstAttribute="top" secondItem="i5M-Pr-FkT" secondAttribute="top" id="GDA-Ot-zRC"/>
+                <constraint firstItem="2TT-zD-Yi2" firstAttribute="leading" secondItem="i5M-Pr-FkT" secondAttribute="leading" id="KpY-Kp-UvP"/>
+                <constraint firstAttribute="bottom" secondItem="2TT-zD-Yi2" secondAttribute="bottom" id="Vyq-pb-ZZH"/>
+                <constraint firstAttribute="trailing" secondItem="INJ-Sr-Vjo" secondAttribute="trailing" id="Wqb-Ah-SFb"/>
+                <constraint firstAttribute="bottom" secondItem="INJ-Sr-Vjo" secondAttribute="bottom" constant="50" id="uyn-FK-jYZ"/>
+            </constraints>
+            <point key="canvasLocation" x="137.68115942028987" y="79.6875"/>
+        </view>
+    </objects>
+    <resources>
+        <systemColor name="labelColor">
+            <color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+        </systemColor>
+        <systemColor name="systemBackgroundColor">
+            <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+        </systemColor>
+    </resources>
+</document>

+ 51 - 0
jiaPei/Modules/ChangeSchool/Model/StudentChangeSchoolModel.h

@@ -0,0 +1,51 @@
+//
+//  StudentChangeSchoolModel.h
+//  jiaPei
+//
+//  Created by 张嵘 on 2019/7/1.
+//  Copyright © 2019 JCZ. All rights reserved.
+//
+
+#import "RQBaseModel.h"
+
+typedef NS_ENUM(NSInteger, ScsAuditViewType) {
+	/// -1 默认
+	ScsAuditViewType_Default					= -1,
+	/// 0 待审核
+	ScsAuditViewType_Wait						= 0,
+	/// 1通过
+	ScsAuditViewType_Pass						= 1,
+	/// 2退回
+	ScsAuditViewType_Return						= 2,
+};
+
+typedef NS_ENUM(NSInteger, ScsInSchoolAudit) {
+	/// -1 默认
+	ScsInSchoolAudit_Default					= -1,
+	/// 0 学员待确认(待接收)
+	ScsInSchoolAudit_Wait						= 0,
+	/// 1驾校同意
+	ScsInSchoolAudit_SchAllow					= 1,
+	/// 2驾校不接收
+	ScsInSchoolAudit_SchRefuse					= 2,
+	/// 3学员同意
+	ScsInSchoolAudit_StuAllow					= 3,
+	/// 4学员拒绝
+	ScsInSchoolAudit_StuRefuse					= 4,
+};
+
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface StudentChangeSchoolModel : RQBaseModel
+@property (nonatomic , copy) NSString          	    * TSO_IDCARD;
+@property (nonatomic , assign) ScsAuditViewType       SCS_AUDIT_VIEW;
+@property (nonatomic , assign) ScsInSchoolAudit       SCS_IN_SCHOOL_AUDIT;
+@property (nonatomic , copy) NSString              	* TSO_NAME;
+@property (nonatomic , copy) NSString              	* IN_SCHOOL;
+@property (nonatomic , copy) NSString              	* OUT_SCHOOL;
+
+@end
+
+
+NS_ASSUME_NONNULL_END

+ 15 - 0
jiaPei/Modules/ChangeSchool/Model/StudentChangeSchoolModel.m

@@ -0,0 +1,15 @@
+//
+//  StudentChangeSchoolModel.m
+//  jiaPei
+//
+//  Created by 张嵘 on 2019/7/1.
+//  Copyright © 2019 JCZ. All rights reserved.
+//
+
+#import "StudentChangeSchoolModel.h"
+
+@implementation StudentChangeSchoolModel
++  (BOOL)propertyIsOptional:(NSString *)propertyName{
+	return  YES;
+}
+@end

+ 17 - 0
jiaPei/Modules/DiscoverModule/Controller/RQDiscoverViewController.h

@@ -0,0 +1,17 @@
+//
+//  RQDiscoverViewController.h
+//  jiaPei
+//
+//  Created by 张嵘 on 2021/11/19.
+//  Copyright © 2021 JCZ. All rights reserved.
+//
+
+#import "RQCommonViewController.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface RQDiscoverViewController : RQCommonViewController
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 32 - 0
jiaPei/Modules/DiscoverModule/Controller/RQDiscoverViewController.m

@@ -0,0 +1,32 @@
+//
+//  RQDiscoverViewController.m
+//  jiaPei
+//
+//  Created by 张嵘 on 2021/11/19.
+//  Copyright © 2021 JCZ. All rights reserved.
+//
+
+#import "RQDiscoverViewController.h"
+
+@interface RQDiscoverViewController ()
+
+@end
+
+@implementation RQDiscoverViewController
+
+- (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

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor