Browse Source

本地录制

Steven 4 years ago
parent
commit
9c08f94d80
32 changed files with 982 additions and 49 deletions
  1. 32 20
      MusicGradeExam/MusicGradeExam.xcodeproj/project.pbxproj
  2. 22 0
      MusicGradeExam/MusicGradeExam/Assets.xcassets/RecordExam/cancle_button.imageset/Contents.json
  3. BIN
      MusicGradeExam/MusicGradeExam/Assets.xcassets/RecordExam/cancle_button.imageset/cancle_button@2x.png
  4. BIN
      MusicGradeExam/MusicGradeExam/Assets.xcassets/RecordExam/cancle_button.imageset/cancle_button@3x.png
  5. 22 0
      MusicGradeExam/MusicGradeExam/Assets.xcassets/RecordExam/record_add.imageset/Contents.json
  6. BIN
      MusicGradeExam/MusicGradeExam/Assets.xcassets/RecordExam/record_add.imageset/record_add@2x.png
  7. BIN
      MusicGradeExam/MusicGradeExam/Assets.xcassets/RecordExam/record_add.imageset/record_add@3x.png
  8. 22 0
      MusicGradeExam/MusicGradeExam/Assets.xcassets/RecordExam/record_bg.imageset/Contents.json
  9. 1 1
      MusicGradeExam/MusicGradeExam/KSRequestManager.m
  10. 2 0
      MusicGradeExam/MusicGradeExam/Tools/Custom/KSMediaManager.h
  11. 3 2
      MusicGradeExam/MusicGradeExam/Tools/Custom/KSMediaManager.m
  12. 9 5
      MusicGradeExam/MusicGradeExam/UI/Exam/Controller/WaitExamViewController.m
  13. 2 1
      MusicGradeExam/MusicGradeExam/UI/Exam/Model/TicketDetailModel.h
  14. 8 1
      MusicGradeExam/MusicGradeExam/UI/Exam/Model/TicketDetailModel.m
  15. 3 2
      MusicGradeExam/MusicGradeExam/UI/Exam/View/WaitExamBodyView.m
  16. 2 0
      MusicGradeExam/MusicGradeExam/UI/RecordExam/Controller/RecordExamViewController.h
  17. 225 4
      MusicGradeExam/MusicGradeExam/UI/RecordExam/Controller/RecordExamViewController.m
  18. 28 0
      MusicGradeExam/MusicGradeExam/UI/RecordExam/Model/RecordExamModel.h
  19. 166 0
      MusicGradeExam/MusicGradeExam/UI/RecordExam/Model/RecordExamModel.m
  20. 25 0
      MusicGradeExam/MusicGradeExam/UI/RecordExam/Model/SongJson.h
  21. 130 0
      MusicGradeExam/MusicGradeExam/UI/RecordExam/Model/SongJson.m
  22. 2 0
      MusicGradeExam/MusicGradeExam/UI/RecordExam/View/RecordBodyView.h
  23. 72 1
      MusicGradeExam/MusicGradeExam/UI/RecordExam/View/RecordBodyView.m
  24. 3 0
      MusicGradeExam/MusicGradeExam/UI/RecordExam/View/RecordBodyView.xib
  25. 2 0
      MusicGradeExam/MusicGradeExam/UI/RecordExam/View/RecordBottomView.h
  26. 3 0
      MusicGradeExam/MusicGradeExam/UI/RecordExam/View/RecordBottomView.xib
  27. 12 0
      MusicGradeExam/MusicGradeExam/UI/RecordExam/View/RecordListCell.h
  28. 54 0
      MusicGradeExam/MusicGradeExam/UI/RecordExam/View/RecordListCell.m
  29. 105 11
      MusicGradeExam/MusicGradeExam/UI/RecordExam/View/RecordListCell.xib
  30. 2 0
      MusicGradeExam/MusicGradeExam/UI/RecordExam/View/RecordTipsView.h
  31. 20 0
      MusicGradeExam/MusicGradeExam/UI/RecordExam/View/RecordTipsView.m
  32. 5 1
      MusicGradeExam/MusicGradeExam/UI/RecordExam/View/RecordTipsView.xib

+ 32 - 20
MusicGradeExam/MusicGradeExam.xcodeproj/project.pbxproj

@@ -27,6 +27,8 @@
 		2729F7F924C8425E00E1F3C4 /* RecordTipsView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2729F7F824C8425E00E1F3C4 /* RecordTipsView.m */; };
 		2729F7FB24C8427200E1F3C4 /* RecordTipsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2729F7FA24C8427100E1F3C4 /* RecordTipsView.xib */; };
 		2729F7FE24C851FA00E1F3C4 /* BaseExamModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2729F7FC24C851F900E1F3C4 /* BaseExamModel.m */; };
+		2729F80624C9771B00E1F3C4 /* RecordExamModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2729F80224C9771300E1F3C4 /* RecordExamModel.m */; };
+		2729F80724C9771B00E1F3C4 /* SongJson.m in Sources */ = {isa = PBXBuildFile; fileRef = 2729F80524C9771400E1F3C4 /* SongJson.m */; };
 		27476F4824BBFB5900181362 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 27476F4724BBFB5900181362 /* AppDelegate.m */; };
 		27476F4E24BBFB5900181362 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 27476F4D24BBFB5900181362 /* ViewController.m */; };
 		27476F5124BBFB5900181362 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 27476F4F24BBFB5900181362 /* Main.storyboard */; };
@@ -418,6 +420,10 @@
 		2729F7FA24C8427100E1F3C4 /* RecordTipsView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = RecordTipsView.xib; sourceTree = "<group>"; };
 		2729F7FC24C851F900E1F3C4 /* BaseExamModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BaseExamModel.m; sourceTree = "<group>"; };
 		2729F7FD24C851FA00E1F3C4 /* BaseExamModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BaseExamModel.h; sourceTree = "<group>"; };
+		2729F80224C9771300E1F3C4 /* RecordExamModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RecordExamModel.m; sourceTree = "<group>"; };
+		2729F80324C9771300E1F3C4 /* RecordExamModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RecordExamModel.h; sourceTree = "<group>"; };
+		2729F80424C9771400E1F3C4 /* SongJson.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SongJson.h; sourceTree = "<group>"; };
+		2729F80524C9771400E1F3C4 /* SongJson.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SongJson.m; sourceTree = "<group>"; };
 		27476F4324BBFB5900181362 /* 酷乐秀.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "酷乐秀.app"; sourceTree = BUILT_PRODUCTS_DIR; };
 		27476F4624BBFB5900181362 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
 		27476F4724BBFB5900181362 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
@@ -1124,6 +1130,10 @@
 		2729F7D624C81CA300E1F3C4 /* Model */ = {
 			isa = PBXGroup;
 			children = (
+				2729F80324C9771300E1F3C4 /* RecordExamModel.h */,
+				2729F80224C9771300E1F3C4 /* RecordExamModel.m */,
+				2729F80424C9771400E1F3C4 /* SongJson.h */,
+				2729F80524C9771400E1F3C4 /* SongJson.m */,
 			);
 			path = Model;
 			sourceTree = "<group>";
@@ -2024,36 +2034,36 @@
 		274770F524BC0C0400181362 /* Custom */ = {
 			isa = PBXGroup;
 			children = (
+				2747716E24BC0C0400181362 /* GRCreateManager.h */,
 				274770F624BC0C0400181362 /* GRCreateManager.m */,
 				274770F724BC0C0400181362 /* GRScanManager.h */,
+				2747716D24BC0C0400181362 /* GRScanManager.m */,
+				2747710C24BC0C0400181362 /* JXView */,
+				2747717324BC0C0400181362 /* KSChoosePicker.h */,
 				274770F824BC0C0400181362 /* KSChoosePicker.m */,
-				274770F924BC0C0400181362 /* PressRecord */,
-				2747710624BC0C0400181362 /* KSImageButton.h */,
+				2747717124BC0C0400181362 /* KSFullDatePicker.h */,
 				2747710724BC0C0400181362 /* KSFullDatePicker.m */,
-				2747710824BC0C0400181362 /* NSString+phone.h */,
-				2747710924BC0C0400181362 /* KSMediaManager.m */,
-				2747710A24BC0C0400181362 /* KSMessageInputView.h */,
-				2747710B24BC0C0400181362 /* NSString+MD5.h */,
-				2747710C24BC0C0400181362 /* JXView */,
-				2747715F24BC0C0400181362 /* MBProgressHUD+NJ.m */,
+				2747710624BC0C0400181362 /* KSImageButton.h */,
+				2747717224BC0C0400181362 /* KSImageButton.m */,
 				2747716024BC0C0400181362 /* KSInputView */,
-				2747716A24BC0C0400181362 /* NSDate+KSBaseDatePicker.h */,
-				2747716B24BC0C0400181362 /* StateView.h */,
 				2747716C24BC0C0400181362 /* KSInputView.h */,
-				2747716D24BC0C0400181362 /* GRScanManager.m */,
-				2747716E24BC0C0400181362 /* GRCreateManager.h */,
+				2747717724BC0C0400181362 /* KSInputView.m */,
 				2747716F24BC0C0400181362 /* KSMediaManager.h */,
-				2747717024BC0C0400181362 /* NSString+phone.m */,
-				2747717124BC0C0400181362 /* KSFullDatePicker.h */,
-				2747717224BC0C0400181362 /* KSImageButton.m */,
-				2747717324BC0C0400181362 /* KSChoosePicker.h */,
+				2747710924BC0C0400181362 /* KSMediaManager.m */,
+				2747710A24BC0C0400181362 /* KSMessageInputView.h */,
+				2747717624BC0C0400181362 /* KSMessageInputView.m */,
+				2747717A24BC0C0400181362 /* LLPhotoBrowser */,
 				2747717424BC0C0400181362 /* MBProgressHUD+NJ.h */,
+				2747715F24BC0C0400181362 /* MBProgressHUD+NJ.m */,
+				2747716A24BC0C0400181362 /* NSDate+KSBaseDatePicker.h */,
+				2747717924BC0C0400181362 /* NSDate+KSBaseDatePicker.m */,
+				2747710B24BC0C0400181362 /* NSString+MD5.h */,
 				2747717524BC0C0400181362 /* NSString+MD5.m */,
-				2747717624BC0C0400181362 /* KSMessageInputView.m */,
-				2747717724BC0C0400181362 /* KSInputView.m */,
+				2747710824BC0C0400181362 /* NSString+phone.h */,
+				2747717024BC0C0400181362 /* NSString+phone.m */,
+				274770F924BC0C0400181362 /* PressRecord */,
+				2747716B24BC0C0400181362 /* StateView.h */,
 				2747717824BC0C0400181362 /* StateView.m */,
-				2747717924BC0C0400181362 /* NSDate+KSBaseDatePicker.m */,
-				2747717A24BC0C0400181362 /* LLPhotoBrowser */,
 			);
 			path = Custom;
 			sourceTree = "<group>";
@@ -3018,6 +3028,7 @@
 				27A008EE24BDA7100002452B /* AboutBodyView.m in Sources */,
 				2729F7F424C8395300E1F3C4 /* RecordBottomView.m in Sources */,
 				27476F4E24BBFB5900181362 /* ViewController.m in Sources */,
+				2729F80724C9771B00E1F3C4 /* SongJson.m in Sources */,
 				274771DC24BC0C0500181362 /* TurnPageMessage.m in Sources */,
 				2747721124BC0C0500181362 /* NSArray+ks_SafeAccess.m in Sources */,
 				2747722924BC0C0500181362 /* KSChoosePicker.m in Sources */,
@@ -3040,6 +3051,7 @@
 				2729F7E724C8326900E1F3C4 /* RecordExamViewController.m in Sources */,
 				2747722024BC0C0500181362 /* UIImage+Property.m in Sources */,
 				274771C624BC0C0500181362 /* KSTabBarController.m in Sources */,
+				2729F80624C9771B00E1F3C4 /* RecordExamModel.m in Sources */,
 				27EF3F2A24C02DE9002068A2 /* EmptyView.m in Sources */,
 				2747724524BC0C0500181362 /* JXCategoryTitleCellModel.m in Sources */,
 				2747720B24BC0C0500181362 /* UIView+Dealloc.m in Sources */,

+ 22 - 0
MusicGradeExam/MusicGradeExam/Assets.xcassets/RecordExam/cancle_button.imageset/Contents.json

@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "cancle_button@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "cancle_button@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
MusicGradeExam/MusicGradeExam/Assets.xcassets/RecordExam/cancle_button.imageset/cancle_button@2x.png


BIN
MusicGradeExam/MusicGradeExam/Assets.xcassets/RecordExam/cancle_button.imageset/cancle_button@3x.png


+ 22 - 0
MusicGradeExam/MusicGradeExam/Assets.xcassets/RecordExam/record_add.imageset/Contents.json

@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "record_add@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "record_add@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
MusicGradeExam/MusicGradeExam/Assets.xcassets/RecordExam/record_add.imageset/record_add@2x.png


BIN
MusicGradeExam/MusicGradeExam/Assets.xcassets/RecordExam/record_add.imageset/record_add@3x.png


+ 22 - 0
MusicGradeExam/MusicGradeExam/Assets.xcassets/RecordExam/record_bg.imageset/Contents.json

@@ -7,11 +7,33 @@
     {
       "filename" : "record_bg@2x.png",
       "idiom" : "universal",
+      "resizing" : {
+        "cap-insets" : {
+          "left" : 198,
+          "right" : 24
+        },
+        "center" : {
+          "mode" : "tile",
+          "width" : 9
+        },
+        "mode" : "3-part-horizontal"
+      },
       "scale" : "2x"
     },
     {
       "filename" : "record_bg@3x.png",
       "idiom" : "universal",
+      "resizing" : {
+        "cap-insets" : {
+          "left" : 297,
+          "right" : 36
+        },
+        "center" : {
+          "mode" : "tile",
+          "width" : 1
+        },
+        "mode" : "3-part-horizontal"
+      },
       "scale" : "3x"
     }
   ],

+ 1 - 1
MusicGradeExam/MusicGradeExam/KSRequestManager.m

@@ -757,7 +757,7 @@
     [parm setValue:level forKey:@"level"];
     [parm setValue:page forKey:@"page"];
     [parm setValue:rows forKey:@"rows"];
-    [self request:get url:url parm:nil success:success faliure:faliure];
+    [self request:get url:url parm:parm success:success faliure:faliure];
 }
 
 #pragma mark ----- 录播考试

+ 2 - 0
MusicGradeExam/MusicGradeExam/Tools/Custom/KSMediaManager.h

@@ -31,6 +31,8 @@ typedef void(^MediaCallback)(NSString * _Nullable videoUrl, NSMutableArray * _Nu
 
 @property (nonatomic, assign) NSInteger maxPhotoNumber;
 
+@property (nonatomic, assign) NSInteger maxDuration;  // 设置最大录制时常
+
 - (void)showAlertCallbackWithBlock:(MediaCallback)callback;
 
 - (void)noAlertCallback:(MediaCallback)callback;

+ 3 - 2
MusicGradeExam/MusicGradeExam/Tools/Custom/KSMediaManager.m

@@ -39,6 +39,7 @@
 
 - (void)configDefaultConfig {
     self.mediaType = MEDIATYPE_PHOTO;
+    self.maxDuration = 480;
     self.maxPhotoNumber = 9;
 }
 
@@ -129,7 +130,7 @@
         imagePickerVc.allowPickingVideo = YES;
         imagePickerVc.allowPickingImage = NO;
     }
-    imagePickerVc.videoMaximumDuration = 480;
+    imagePickerVc.videoMaximumDuration = self.maxDuration;
     // 设置视频拍摄质量
     [imagePickerVc setUiImagePickerControllerSettingBlock:^(UIImagePickerController *imagePickerController) {
         imagePickerController.videoQuality = UIImagePickerControllerQualityType640x480;
@@ -313,7 +314,7 @@
         }];
     }
     else if ([type isEqualToString:@"public.movie"]) {
-        tzImagePickerVc.videoMaximumDuration = 480;
+        tzImagePickerVc.videoMaximumDuration = self.maxDuration;
         // 设置视频拍摄质量
         [tzImagePickerVc setUiImagePickerControllerSettingBlock:^(UIImagePickerController *imagePickerController) {
             imagePickerController.videoQuality = UIImagePickerControllerQualityType640x480;

+ 9 - 5
MusicGradeExam/MusicGradeExam/UI/Exam/Controller/WaitExamViewController.m

@@ -40,6 +40,7 @@
 // 去录播
 - (void)toRecordExam {
     RecordExamViewController *ctrl = [[RecordExamViewController alloc] init];
+    ctrl.examRegistrationId = self.examRegistrationId;
     [self.navigationController pushViewController:ctrl animated:YES];
 }
 
@@ -120,13 +121,14 @@
             break;
         case JOINROOMACTION_JOIN:  // 加入房间
         {
-//            if (self.sourceModel.openFlag == 1) { // 去录播
-//                RecordExamViewController *ctrl = [[RecordExamViewController alloc] init];
-//                [self.navigationController pushViewController:ctrl animated:YES];
+//            if (self.sourceModel.recordFlag == 1) { // 去录播
+                RecordExamViewController *ctrl = [[RecordExamViewController alloc] init];
+                ctrl.examRegistrationId = self.examRegistrationId;
+                [self.navigationController pushViewController:ctrl animated:YES];
 //            }
 //            else {
-                NSString *roomId = [NSString stringWithFormat:@"%.0f", source.examRegistrationId];
-                [self joinRoomAction:roomId];
+//                NSString *roomId = [NSString stringWithFormat:@"%.0f", source.examRegistrationId];
+//                [self joinRoomAction:roomId];
 //            }
         }
             break;
@@ -142,8 +144,10 @@
     [KSRequestManager signInRequest:KS_POST examRegistrationId:self.examRegistrationId success:^(NSDictionary * _Nonnull dic) {
         [self removehub];
         if ([dic integerValueForKey:@"code"] == 200 && [dic boolValueForKey:@"status"]) {
+            [self MBPShow:@"签到成功"];
             self.bodyView.isSign = YES;
             // 判断是否能进入教室
+            [self requestDataWithHub:NO];
         }
         else {
             [self MBPShow:MESSAGEKEY];

+ 2 - 1
MusicGradeExam/MusicGradeExam/UI/Exam/Model/TicketDetailModel.h

@@ -1,7 +1,7 @@
 //
 //  TicketDetailModel.h
 //
-//  Created by   on 2020/7/16
+//  Created by   on 2020/7/23
 //  Copyright (c) 2020 __MyCompanyName__. All rights reserved.
 //
 
@@ -17,6 +17,7 @@
 @property (nonatomic, assign) double finishedExam;
 @property (nonatomic, strong) NSString *desc;
 @property (nonatomic, assign) double openFlag;
+@property (nonatomic, assign) double recordFlag;
 @property (nonatomic, assign) double classroomSwitch;
 @property (nonatomic, strong) NSString *signInTime;
 @property (nonatomic, assign) double waitNum;

+ 8 - 1
MusicGradeExam/MusicGradeExam/UI/Exam/Model/TicketDetailModel.m

@@ -1,7 +1,7 @@
 //
 //  TicketDetailModel.m
 //
-//  Created by   on 2020/7/16
+//  Created by   on 2020/7/23
 //  Copyright (c) 2020 __MyCompanyName__. All rights reserved.
 //
 
@@ -14,6 +14,7 @@ NSString *const kTicketDetailModelExamStartTime = @"examStartTime";
 NSString *const kTicketDetailModelFinishedExam = @"finishedExam";
 NSString *const kTicketDetailModelDesc = @"desc";
 NSString *const kTicketDetailModelOpenFlag = @"openFlag";
+NSString *const kTicketDetailModelRecordFlag = @"recordFlag";
 NSString *const kTicketDetailModelClassroomSwitch = @"classroomSwitch";
 NSString *const kTicketDetailModelSignInTime = @"signInTime";
 NSString *const kTicketDetailModelWaitNum = @"waitNum";
@@ -36,6 +37,7 @@ NSString *const kTicketDetailModelExamRoomStudentRelationId = @"examRoomStudentR
 @synthesize finishedExam = _finishedExam;
 @synthesize desc = _desc;
 @synthesize openFlag = _openFlag;
+@synthesize recordFlag = _recordFlag;
 @synthesize classroomSwitch = _classroomSwitch;
 @synthesize signInTime = _signInTime;
 @synthesize waitNum = _waitNum;
@@ -62,6 +64,7 @@ NSString *const kTicketDetailModelExamRoomStudentRelationId = @"examRoomStudentR
             self.finishedExam = [[self objectOrNilForKey:kTicketDetailModelFinishedExam fromDictionary:dict] doubleValue];
             self.desc = [self objectOrNilForKey:kTicketDetailModelDesc fromDictionary:dict];
             self.openFlag = [[self objectOrNilForKey:kTicketDetailModelOpenFlag fromDictionary:dict] doubleValue];
+            self.recordFlag = [[self objectOrNilForKey:kTicketDetailModelRecordFlag fromDictionary:dict] doubleValue];
             self.classroomSwitch = [[self objectOrNilForKey:kTicketDetailModelClassroomSwitch fromDictionary:dict] doubleValue];
             self.signInTime = [self objectOrNilForKey:kTicketDetailModelSignInTime fromDictionary:dict];
             self.waitNum = [[self objectOrNilForKey:kTicketDetailModelWaitNum fromDictionary:dict] doubleValue];
@@ -84,6 +87,7 @@ NSString *const kTicketDetailModelExamRoomStudentRelationId = @"examRoomStudentR
     [mutableDict setValue:[NSNumber numberWithDouble:self.finishedExam] forKey:kTicketDetailModelFinishedExam];
     [mutableDict setValue:self.desc forKey:kTicketDetailModelDesc];
     [mutableDict setValue:[NSNumber numberWithDouble:self.openFlag] forKey:kTicketDetailModelOpenFlag];
+    [mutableDict setValue:[NSNumber numberWithDouble:self.recordFlag] forKey:kTicketDetailModelRecordFlag];
     [mutableDict setValue:[NSNumber numberWithDouble:self.classroomSwitch] forKey:kTicketDetailModelClassroomSwitch];
     [mutableDict setValue:self.signInTime forKey:kTicketDetailModelSignInTime];
     [mutableDict setValue:[NSNumber numberWithDouble:self.waitNum] forKey:kTicketDetailModelWaitNum];
@@ -119,6 +123,7 @@ NSString *const kTicketDetailModelExamRoomStudentRelationId = @"examRoomStudentR
     self.finishedExam = [aDecoder decodeDoubleForKey:kTicketDetailModelFinishedExam];
     self.desc = [aDecoder decodeObjectForKey:kTicketDetailModelDesc];
     self.openFlag = [aDecoder decodeDoubleForKey:kTicketDetailModelOpenFlag];
+    self.recordFlag = [aDecoder decodeDoubleForKey:kTicketDetailModelRecordFlag];
     self.classroomSwitch = [aDecoder decodeDoubleForKey:kTicketDetailModelClassroomSwitch];
     self.signInTime = [aDecoder decodeObjectForKey:kTicketDetailModelSignInTime];
     self.waitNum = [aDecoder decodeDoubleForKey:kTicketDetailModelWaitNum];
@@ -137,6 +142,7 @@ NSString *const kTicketDetailModelExamRoomStudentRelationId = @"examRoomStudentR
     [aCoder encodeDouble:_finishedExam forKey:kTicketDetailModelFinishedExam];
     [aCoder encodeObject:_desc forKey:kTicketDetailModelDesc];
     [aCoder encodeDouble:_openFlag forKey:kTicketDetailModelOpenFlag];
+    [aCoder encodeDouble:_recordFlag forKey:kTicketDetailModelRecordFlag];
     [aCoder encodeDouble:_classroomSwitch forKey:kTicketDetailModelClassroomSwitch];
     [aCoder encodeObject:_signInTime forKey:kTicketDetailModelSignInTime];
     [aCoder encodeDouble:_waitNum forKey:kTicketDetailModelWaitNum];
@@ -157,6 +163,7 @@ NSString *const kTicketDetailModelExamRoomStudentRelationId = @"examRoomStudentR
         copy.finishedExam = self.finishedExam;
         copy.desc = [self.desc copyWithZone:zone];
         copy.openFlag = self.openFlag;
+        copy.recordFlag = self.recordFlag;
         copy.classroomSwitch = self.classroomSwitch;
         copy.signInTime = [self.signInTime copyWithZone:zone];
         copy.waitNum = self.waitNum;

+ 3 - 2
MusicGradeExam/MusicGradeExam/UI/Exam/View/WaitExamBodyView.m

@@ -62,14 +62,15 @@
         self.signDescLabel.text = @"您已签到,请点击下方按钮进入教室";
     }
     
-    if (self.sourceModel.openFlag == 1) {
+    if (self.sourceModel.recordFlag == 1) {
         [self.joinButton setTitle:@"录播考试" forState:UIControlStateNormal];
     }
     else {
         [self.joinButton setTitle:@"进入教室" forState:UIControlStateNormal];
     }
     
-    self.canJoinRoom = self.sourceModel.classroomSwitch;
+//    self.canJoinRoom = self.sourceModel.classroomSwitch;
+    self.canJoinRoom = YES;
     
     self.classDate.text = [[self.sourceModel.examStartTime componentsSeparatedByString:@" "] firstObject];
     self.subjectLabel.text = [NSString returnNoNullStringWithString:self.sourceModel.baseExamName];

+ 2 - 0
MusicGradeExam/MusicGradeExam/UI/RecordExam/Controller/RecordExamViewController.h

@@ -12,6 +12,8 @@ NS_ASSUME_NONNULL_BEGIN
 
 @interface RecordExamViewController : KSBaseViewController
 
+@property (nonatomic, strong) NSString *examRegistrationId;
+
 @end
 
 NS_ASSUME_NONNULL_END

+ 225 - 4
MusicGradeExam/MusicGradeExam/UI/RecordExam/Controller/RecordExamViewController.m

@@ -11,8 +11,18 @@
 #import "RecordListCell.h"
 #import "RecordBottomView.h"
 #import "RecordTipsView.h"
+#import "RecordExamModel.h"
+#import "KSMediaManager.h"
+#import "WMPlayer.h"
 
-@interface RecordExamViewController ()<UITableViewDelegate, UITableViewDataSource>
+@interface RecordExamViewController ()<UITableViewDelegate, UITableViewDataSource,WMPlayerDelegate>
+{
+    WMPlayer *_wmPlayer;
+    CGRect _playerFrame;
+}
+@property (nonatomic, assign) BOOL isRatation;
+
+@property (nonatomic, strong) UIView *bgView;
 
 @property (nonatomic, strong) RecordBodyView *topView;
 
@@ -22,6 +32,16 @@
 
 @property (nonatomic, strong) RecordBottomView *bottomView;
 
+@property (nonatomic, strong) RecordExamModel *sourceModel;
+
+@property (nonatomic, strong) NSMutableArray *songArray;
+
+@property (nonatomic, strong) KSMediaManager *mediaManager;
+
+@property (strong, nonatomic) MBProgressHUD *HUD;
+
+@property (nonatomic, strong) NSMutableArray *fileUrlArray;
+
 @end
 
 @implementation RecordExamViewController
@@ -31,6 +51,7 @@
     // Do any additional setup after loading the view.
     self.ks_prefersNavigationBarHidden = YES;
     [self configUI];
+    [self requestData];
 }
 
 - (void)viewWillAppear:(BOOL)animated {
@@ -46,6 +67,29 @@
     }
 }
 
+- (void)requestData {
+    [self showhud];
+    [KSRequestManager stuRecordDetailRequest:KS_GET examRegistrationId:self.examRegistrationId success:^(NSDictionary * _Nonnull dic) {
+        [self removehub];
+        if ([dic integerValueForKey:@"code"] == 200 && [dic boolValueForKey:@"status"]) {
+            self.sourceModel = [[RecordExamModel alloc] initWithDictionary:[dic dictionaryValueForKey:@"data"]];
+            [self evaluateSource];
+        }
+        else {
+            [self MBPShow:MESSAGEKEY];
+        }
+    } faliure:^(NSError * _Nonnull error) {
+        [self removehub];
+    }];
+}
+
+- (void)evaluateSource {
+    [self.topView configTime:[NSString stringWithFormat:@"%.0f", self.sourceModel.subTime]];
+    [self.tipsView configWithEndTime:self.sourceModel.actualExamEndTime];
+    self.songArray = [NSMutableArray arrayWithArray:self.sourceModel.songJson];
+    [self.tableView reloadData];
+}
+
 - (void)configUI {
     
     [self.view addSubview:self.topView];
@@ -71,15 +115,65 @@
 
 #pragma mark ----- table data source
 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
-//    return self.dataArray.count;
+//    return self.songArray.count;
     return 3;
 }
 
 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
+    SongJson *model = self.songArray[indexPath.row];
     RecordListCell *cell = [tableView dequeueReusableCellWithIdentifier:@"RecordListCell"];
+    MJWeakSelf;
+    [cell configCellWithSource:model examRegistrationId:self.examRegistrationId operationCallback:^(RECORDTYPE type, NSString *fileKey) {
+        [weakSelf opreationCellType:type fileKey:fileKey];
+    }];
     return cell;
 }
 
+- (void)opreationCellType:(RECORDTYPE)type fileKey:(NSString *)fileKey {
+    if (type == RECORDTYPE_RECORD) { // 录像
+        self.mediaManager = [[KSMediaManager alloc] init];
+        self.mediaManager.mediaType = MEDIATYPE_VIDEO;
+        self.mediaManager.maxPhotoNumber = 1;
+        self.mediaManager.baseCtrl = self;
+        self.mediaManager.maxDuration = self.sourceModel.singleSongRecordMinutes * 60;
+        MJWeakSelf;
+        [self.mediaManager noAlertCallback:^(NSString * _Nullable videoUrl, NSMutableArray * _Nullable imageArray, NSMutableArray * _Nullable imageAsset) {
+            NSLog(@"%@", videoUrl);
+            // 上传视频
+            [weakSelf uploadVideoWithUrl:videoUrl fileKey:fileKey];
+        }];
+        [self.mediaManager takePhoto];
+    }
+    else { // 删除
+        [self deleteFileWithKey:fileKey];
+    }
+}
+
+- (void)deleteFileWithKey:(NSString *)fileKey {
+    NSDictionary *parm = UserDefault(fileKey);
+    NSString *localUrl = [parm stringValueForKey:@"localFileUrl"];
+    [self removeVideoWithPath:localUrl];
+    UserDefaultRemoveObjectForKey(fileKey);
+    [self.tableView reloadData];
+}
+
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
+    RecordListCell *cell = [tableView cellForRowAtIndexPath:indexPath];
+    if (cell.fileMessage) {
+        // 播放
+        NSString *localUrl = [cell.fileMessage stringValueForKey:@"localFileUrl"];
+        if ([[NSFileManager defaultManager] fileExistsAtPath:localUrl]) {
+            [self playVideoWithUrl:[NSURL fileURLWithPath:localUrl]];
+        }
+        else {
+            NSString *remoteUrl = [cell.fileMessage stringValueForKey:@"remoteUrl"];
+            [self playVideoWithUrl:[NSURL URLWithString:remoteUrl]];
+        }
+    }
+}
+
+
+
 #pragma mark --- lazying
 - (RecordBodyView *)topView {
     if (!_topView) {
@@ -99,7 +193,9 @@
         [self.navigationController popViewControllerAnimated:YES];
     }
     else { // 时间结束
-        
+        self.bottomView.finishButton.userInteractionEnabled = NO;
+        [self.bottomView.finishButton setTitle:@"考试已结束" forState:UIControlStateNormal];
+        [self.bottomView.finishButton setBackgroundColor:HexRGB(0xcccccc)];
     }
 }
 
@@ -112,7 +208,6 @@
 
 - (UITableView *)tableView {
     if (!_tableView) {
-//        CGFloat yPosition = kScreenWidth / 207 * 79 + 11;
         _tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
         _tableView.delegate = self;
         _tableView.dataSource = self;
@@ -143,8 +238,134 @@
 
 - (void)submitAction {
     // 提交 完成考试
+    NSString *videoUrl = @"";
+    [KSRequestManager stuEndRecordFinishRequest:KS_POST examRegistrationId:self.examRegistrationId videoUrl:videoUrl success:^(NSDictionary * _Nonnull dic) {
+        
+    } faliure:^(NSError * _Nonnull error) {
+        
+    }];
 }
 
+#pragma mark ------ 上传视频文件
+
+- (void)uploadVideoWithUrl:(NSString *)videoUrl fileKey:(NSString *)fileKey {
+    [self hudTipWillShow:YES];
+    
+    NSData *fileData = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:videoUrl]];
+    [KSRequestManager videoFileUpload:KS_POST fileData:fileData progress:^(int64_t bytesWritten, int64_t totalBytes) {
+        dispatch_main_async_safe(^{
+            // 显示进度
+            if (self.HUD) {
+                self.HUD.progress = bytesWritten / totalBytes;// progress是回调进度
+            }
+        });
+    } success:^(NSDictionary * _Nonnull dic) {
+        [self hudTipWillShow:NO];
+        if ([dic integerValueForKey:@"code"] == 200 && [dic boolValueForKey:@"status"]) {
+            NSString *fileUrl = [[dic dictionaryValueForKey:@"data"] stringValueForKey:@"url"];
+
+            // 保存文件路径
+            NSDictionary *parm = @{@"localFileUrl":videoUrl,@"remoteUrl":fileUrl};
+            UserDefaultSet(parm, fileKey);
+            [self.tableView reloadData];
+            
+            [self.fileUrlArray addObject:fileUrl];
+        }
+        else {
+            [self MBPShow:MESSAGEKEY];
+            
+        }
+    } faliure:^(NSError * _Nonnull error) {
+        [self hudTipWillShow:NO];
+    }];
+    
+}
+
+- (void)removeVideoWithPath:(NSString *)videoUrl {
+    NSFileManager *fileMamager = [NSFileManager defaultManager];
+    if ([fileMamager fileExistsAtPath:videoUrl]) {
+        [fileMamager removeItemAtPath:videoUrl error:nil];
+    }
+}
+
+- (void)hudTipWillShow:(BOOL)willShow{
+    if (willShow) {
+        [self resignFirstResponder];
+        UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
+        if (!_HUD) {
+            _HUD = [MBProgressHUD showHUDAddedTo:keyWindow animated:YES];
+            _HUD.mode = MBProgressHUDModeDeterminateHorizontalBar;
+            _HUD.label.text = @"正在转码视频...";
+            _HUD.removeFromSuperViewOnHide = YES;
+        }else{
+            _HUD.progress = 0;
+            [keyWindow addSubview:_HUD];
+            [_HUD showAnimated:YES];
+        }
+    }else{
+        [_HUD hideAnimated:YES];
+    }
+}
+
+
+- (NSMutableArray *)fileUrlArray {
+    if (!_fileUrlArray) {
+        _fileUrlArray = [NSMutableArray arrayWithCapacity:self.songArray.count];
+    }
+    return _fileUrlArray;
+}
+
+#pragma mark ------- 播放文件
+- (void)playVideoWithUrl:(NSURL *)fileUrl {
+    _playerFrame = CGRectMake(0, iPhoneXSafeTopMargin, kScreenWidth, kScreenHeight - iPhoneXSafeTopMargin - iPhoneXSafeBottomMargin);
+    _wmPlayer = [[WMPlayer alloc] initWithFrame:_playerFrame];
+    WMPlayerModel *playModel = [[WMPlayerModel alloc] init];
+    playModel.videoURL = fileUrl;
+    _wmPlayer.playerModel = playModel;
+    _wmPlayer.delegate = self;
+    _bgView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, kScreenWidth, kScreenHeight)];
+    _bgView.backgroundColor = [UIColor blackColor];
+    [[UIApplication sharedApplication].keyWindow addSubview:_bgView];
+    [[UIApplication sharedApplication].keyWindow addSubview:_wmPlayer];
+    [[UIApplication sharedApplication].keyWindow bringSubviewToFront:_wmPlayer];
+    
+    [_wmPlayer play];
+}
+
+- (void)wmplayer:(WMPlayer *)wmplayer clickedCloseButton:(UIButton *)backBtn {
+    [wmplayer removePlayer];
+    [_bgView removeFromSuperview];
+    [self setNeedsStatusBarAppearanceUpdate];
+}
+
+- (void)wmplayer:(WMPlayer *)wmplayer clickedFullScreenButton:(UIButton *)fullScreenBtn {
+    self.isRatation = !self.isRatation;
+    
+    if (self.isRatation) {
+        [wmplayer removeFromSuperview];
+        [UIView animateWithDuration:1.0f animations:^{
+            wmplayer.transform = CGAffineTransformMakeRotation(M_PI_2);
+            
+        } completion:^(BOOL finished) {
+            wmplayer.frame = CGRectMake(0, iPhoneXSafeTopMargin, kScreenWidth, kScreenHeight - iPhoneXSafeTopMargin - iPhoneXSafeBottomMargin);
+            [[UIApplication sharedApplication].keyWindow addSubview:wmplayer];
+            [[UIApplication sharedApplication].keyWindow bringSubviewToFront:wmplayer];
+        }];
+    }
+    else {
+        [wmplayer removeFromSuperview];
+        
+        [UIView animateWithDuration:1.0f animations:^{
+            //        复原
+            wmplayer.transform = CGAffineTransformIdentity;
+            
+        } completion:^(BOOL finished) {
+            wmplayer.frame = CGRectMake(0, iPhoneXSafeTopMargin, kScreenWidth, kScreenHeight - iPhoneXSafeTopMargin - iPhoneXSafeBottomMargin);
+            [[UIApplication sharedApplication].keyWindow addSubview:wmplayer];
+            [[UIApplication sharedApplication].keyWindow bringSubviewToFront:wmplayer];
+        }];
+    }
+}
 /*
 #pragma mark - Navigation
 

+ 28 - 0
MusicGradeExam/MusicGradeExam/UI/RecordExam/Model/RecordExamModel.h

@@ -0,0 +1,28 @@
+//
+//  RecordExamModel.h
+//
+//  Created by   on 2020/7/23
+//  Copyright (c) 2020 __MyCompanyName__. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "SongJson.h"
+
+
+
+@interface RecordExamModel : NSObject <NSCoding, NSCopying>
+
+@property (nonatomic, assign) double singleSongRecordMinutes;
+@property (nonatomic, strong) NSString *actualExamEndTime;
+@property (nonatomic, assign) double examRegistrationId;
+@property (nonatomic, strong) NSString *recordStartTime;
+@property (nonatomic, strong) NSArray *songJson;
+@property (nonatomic, assign) double recordTime;
+@property (nonatomic, assign) double subTime;
+@property (nonatomic, strong) NSString *examEndTime;
+
++ (instancetype)modelObjectWithDictionary:(NSDictionary *)dict;
+- (instancetype)initWithDictionary:(NSDictionary *)dict;
+- (NSDictionary *)dictionaryRepresentation;
+
+@end

+ 166 - 0
MusicGradeExam/MusicGradeExam/UI/RecordExam/Model/RecordExamModel.m

@@ -0,0 +1,166 @@
+//
+//  RecordExamModel.m
+//
+//  Created by   on 2020/7/23
+//  Copyright (c) 2020 __MyCompanyName__. All rights reserved.
+//
+
+#import "RecordExamModel.h"
+
+
+NSString *const kRecordExamModelSingleSongRecordMinutes = @"singleSongRecordMinutes";
+NSString *const kRecordExamModelActualExamEndTime = @"actualExamEndTime";
+NSString *const kRecordExamModelExamRegistrationId = @"examRegistrationId";
+NSString *const kRecordExamModelRecordStartTime = @"recordStartTime";
+NSString *const kRecordExamModelSongJson = @"songJson";
+NSString *const kRecordExamModelRecordTime = @"recordTime";
+NSString *const kRecordExamModelSubTime = @"subTime";
+NSString *const kRecordExamModelExamEndTime = @"examEndTime";
+
+
+@interface RecordExamModel ()
+
+- (id)objectOrNilForKey:(id)aKey fromDictionary:(NSDictionary *)dict;
+
+@end
+
+@implementation RecordExamModel
+
+@synthesize singleSongRecordMinutes = _singleSongRecordMinutes;
+@synthesize actualExamEndTime = _actualExamEndTime;
+@synthesize examRegistrationId = _examRegistrationId;
+@synthesize recordStartTime = _recordStartTime;
+@synthesize songJson = _songJson;
+@synthesize recordTime = _recordTime;
+@synthesize subTime = _subTime;
+@synthesize examEndTime = _examEndTime;
+
+
++ (instancetype)modelObjectWithDictionary:(NSDictionary *)dict
+{
+    return [[self alloc] initWithDictionary:dict];
+}
+
+- (instancetype)initWithDictionary:(NSDictionary *)dict
+{
+    self = [super init];
+    
+    // This check serves to make sure that a non-NSDictionary object
+    // passed into the model class doesn't break the parsing.
+    if(self && [dict isKindOfClass:[NSDictionary class]]) {
+            self.singleSongRecordMinutes = [[self objectOrNilForKey:kRecordExamModelSingleSongRecordMinutes fromDictionary:dict] doubleValue];
+            self.actualExamEndTime = [self objectOrNilForKey:kRecordExamModelActualExamEndTime fromDictionary:dict];
+            self.examRegistrationId = [[self objectOrNilForKey:kRecordExamModelExamRegistrationId fromDictionary:dict] doubleValue];
+            self.recordStartTime = [self objectOrNilForKey:kRecordExamModelRecordStartTime fromDictionary:dict];
+    NSObject *receivedSongJson = [dict objectForKey:kRecordExamModelSongJson];
+    NSMutableArray *parsedSongJson = [NSMutableArray array];
+    if ([receivedSongJson isKindOfClass:[NSArray class]]) {
+        for (NSDictionary *item in (NSArray *)receivedSongJson) {
+            if ([item isKindOfClass:[NSDictionary class]]) {
+                [parsedSongJson addObject:[SongJson modelObjectWithDictionary:item]];
+            }
+       }
+    } else if ([receivedSongJson isKindOfClass:[NSDictionary class]]) {
+       [parsedSongJson addObject:[SongJson modelObjectWithDictionary:(NSDictionary *)receivedSongJson]];
+    }
+
+    self.songJson = [NSArray arrayWithArray:parsedSongJson];
+            self.recordTime = [[self objectOrNilForKey:kRecordExamModelRecordTime fromDictionary:dict] doubleValue];
+            self.subTime = [[self objectOrNilForKey:kRecordExamModelSubTime fromDictionary:dict] doubleValue];
+            self.examEndTime = [self objectOrNilForKey:kRecordExamModelExamEndTime fromDictionary:dict];
+
+    }
+    
+    return self;
+    
+}
+
+- (NSDictionary *)dictionaryRepresentation
+{
+    NSMutableDictionary *mutableDict = [NSMutableDictionary dictionary];
+    [mutableDict setValue:[NSNumber numberWithDouble:self.singleSongRecordMinutes] forKey:kRecordExamModelSingleSongRecordMinutes];
+    [mutableDict setValue:self.actualExamEndTime forKey:kRecordExamModelActualExamEndTime];
+    [mutableDict setValue:[NSNumber numberWithDouble:self.examRegistrationId] forKey:kRecordExamModelExamRegistrationId];
+    [mutableDict setValue:self.recordStartTime forKey:kRecordExamModelRecordStartTime];
+    NSMutableArray *tempArrayForSongJson = [NSMutableArray array];
+    for (NSObject *subArrayObject in self.songJson) {
+        if([subArrayObject respondsToSelector:@selector(dictionaryRepresentation)]) {
+            // This class is a model object
+            [tempArrayForSongJson addObject:[subArrayObject performSelector:@selector(dictionaryRepresentation)]];
+        } else {
+            // Generic object
+            [tempArrayForSongJson addObject:subArrayObject];
+        }
+    }
+    [mutableDict setValue:[NSArray arrayWithArray:tempArrayForSongJson] forKey:kRecordExamModelSongJson];
+    [mutableDict setValue:[NSNumber numberWithDouble:self.recordTime] forKey:kRecordExamModelRecordTime];
+    [mutableDict setValue:[NSNumber numberWithDouble:self.subTime] forKey:kRecordExamModelSubTime];
+    [mutableDict setValue:self.examEndTime forKey:kRecordExamModelExamEndTime];
+
+    return [NSDictionary dictionaryWithDictionary:mutableDict];
+}
+
+- (NSString *)description 
+{
+    return [NSString stringWithFormat:@"%@", [self dictionaryRepresentation]];
+}
+
+#pragma mark - Helper Method
+- (id)objectOrNilForKey:(id)aKey fromDictionary:(NSDictionary *)dict
+{
+    id object = [dict objectForKey:aKey];
+    return [object isEqual:[NSNull null]] ? nil : object;
+}
+
+
+#pragma mark - NSCoding Methods
+
+- (id)initWithCoder:(NSCoder *)aDecoder
+{
+    self = [super init];
+
+    self.singleSongRecordMinutes = [aDecoder decodeDoubleForKey:kRecordExamModelSingleSongRecordMinutes];
+    self.actualExamEndTime = [aDecoder decodeObjectForKey:kRecordExamModelActualExamEndTime];
+    self.examRegistrationId = [aDecoder decodeDoubleForKey:kRecordExamModelExamRegistrationId];
+    self.recordStartTime = [aDecoder decodeObjectForKey:kRecordExamModelRecordStartTime];
+    self.songJson = [aDecoder decodeObjectForKey:kRecordExamModelSongJson];
+    self.recordTime = [aDecoder decodeDoubleForKey:kRecordExamModelRecordTime];
+    self.subTime = [aDecoder decodeDoubleForKey:kRecordExamModelSubTime];
+    self.examEndTime = [aDecoder decodeObjectForKey:kRecordExamModelExamEndTime];
+    return self;
+}
+
+- (void)encodeWithCoder:(NSCoder *)aCoder
+{
+
+    [aCoder encodeDouble:_singleSongRecordMinutes forKey:kRecordExamModelSingleSongRecordMinutes];
+    [aCoder encodeObject:_actualExamEndTime forKey:kRecordExamModelActualExamEndTime];
+    [aCoder encodeDouble:_examRegistrationId forKey:kRecordExamModelExamRegistrationId];
+    [aCoder encodeObject:_recordStartTime forKey:kRecordExamModelRecordStartTime];
+    [aCoder encodeObject:_songJson forKey:kRecordExamModelSongJson];
+    [aCoder encodeDouble:_recordTime forKey:kRecordExamModelRecordTime];
+    [aCoder encodeDouble:_subTime forKey:kRecordExamModelSubTime];
+    [aCoder encodeObject:_examEndTime forKey:kRecordExamModelExamEndTime];
+}
+
+- (id)copyWithZone:(NSZone *)zone
+{
+    RecordExamModel *copy = [[RecordExamModel alloc] init];
+    
+    if (copy) {
+
+        copy.singleSongRecordMinutes = self.singleSongRecordMinutes;
+        copy.actualExamEndTime = [self.actualExamEndTime copyWithZone:zone];
+        copy.examRegistrationId = self.examRegistrationId;
+        copy.recordStartTime = [self.recordStartTime copyWithZone:zone];
+        copy.songJson = [self.songJson copyWithZone:zone];
+        copy.recordTime = self.recordTime;
+        copy.subTime = self.subTime;
+        copy.examEndTime = [self.examEndTime copyWithZone:zone];
+    }
+    
+    return copy;
+}
+
+
+@end

+ 25 - 0
MusicGradeExam/MusicGradeExam/UI/RecordExam/Model/SongJson.h

@@ -0,0 +1,25 @@
+//
+//  SongJson.h
+//
+//  Created by   on 2020/7/23
+//  Copyright (c) 2020 __MyCompanyName__. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+
+
+@interface SongJson : NSObject <NSCoding, NSCopying>
+
+@property (nonatomic, assign) double songJsonIdentifier;
+@property (nonatomic, strong) NSString *uploadUrl;
+@property (nonatomic, strong) NSString *songName;
+@property (nonatomic, strong) NSString *type;
+@property (nonatomic, strong) NSString *songAuthor;
+@property (nonatomic, assign) double index;
+
++ (instancetype)modelObjectWithDictionary:(NSDictionary *)dict;
+- (instancetype)initWithDictionary:(NSDictionary *)dict;
+- (NSDictionary *)dictionaryRepresentation;
+
+@end

+ 130 - 0
MusicGradeExam/MusicGradeExam/UI/RecordExam/Model/SongJson.m

@@ -0,0 +1,130 @@
+//
+//  SongJson.m
+//
+//  Created by   on 2020/7/23
+//  Copyright (c) 2020 __MyCompanyName__. All rights reserved.
+//
+
+#import "SongJson.h"
+
+
+NSString *const kSongJsonId = @"id";
+NSString *const kSongJsonUploadUrl = @"uploadUrl";
+NSString *const kSongJsonSongName = @"songName";
+NSString *const kSongJsonType = @"type";
+NSString *const kSongJsonSongAuthor = @"songAuthor";
+NSString *const kSongJsonIndex = @"index";
+
+
+@interface SongJson ()
+
+- (id)objectOrNilForKey:(id)aKey fromDictionary:(NSDictionary *)dict;
+
+@end
+
+@implementation SongJson
+
+@synthesize songJsonIdentifier = _songJsonIdentifier;
+@synthesize uploadUrl = _uploadUrl;
+@synthesize songName = _songName;
+@synthesize type = _type;
+@synthesize songAuthor = _songAuthor;
+@synthesize index = _index;
+
+
++ (instancetype)modelObjectWithDictionary:(NSDictionary *)dict
+{
+    return [[self alloc] initWithDictionary:dict];
+}
+
+- (instancetype)initWithDictionary:(NSDictionary *)dict
+{
+    self = [super init];
+    
+    // This check serves to make sure that a non-NSDictionary object
+    // passed into the model class doesn't break the parsing.
+    if(self && [dict isKindOfClass:[NSDictionary class]]) {
+            self.songJsonIdentifier = [[self objectOrNilForKey:kSongJsonId fromDictionary:dict] doubleValue];
+            self.uploadUrl = [self objectOrNilForKey:kSongJsonUploadUrl fromDictionary:dict];
+            self.songName = [self objectOrNilForKey:kSongJsonSongName fromDictionary:dict];
+            self.type = [self objectOrNilForKey:kSongJsonType fromDictionary:dict];
+            self.songAuthor = [self objectOrNilForKey:kSongJsonSongAuthor fromDictionary:dict];
+            self.index = [[self objectOrNilForKey:kSongJsonIndex fromDictionary:dict] doubleValue];
+
+    }
+    
+    return self;
+    
+}
+
+- (NSDictionary *)dictionaryRepresentation
+{
+    NSMutableDictionary *mutableDict = [NSMutableDictionary dictionary];
+    [mutableDict setValue:[NSNumber numberWithDouble:self.songJsonIdentifier] forKey:kSongJsonId];
+    [mutableDict setValue:self.uploadUrl forKey:kSongJsonUploadUrl];
+    [mutableDict setValue:self.songName forKey:kSongJsonSongName];
+    [mutableDict setValue:self.type forKey:kSongJsonType];
+    [mutableDict setValue:self.songAuthor forKey:kSongJsonSongAuthor];
+    [mutableDict setValue:[NSNumber numberWithDouble:self.index] forKey:kSongJsonIndex];
+
+    return [NSDictionary dictionaryWithDictionary:mutableDict];
+}
+
+- (NSString *)description 
+{
+    return [NSString stringWithFormat:@"%@", [self dictionaryRepresentation]];
+}
+
+#pragma mark - Helper Method
+- (id)objectOrNilForKey:(id)aKey fromDictionary:(NSDictionary *)dict
+{
+    id object = [dict objectForKey:aKey];
+    return [object isEqual:[NSNull null]] ? nil : object;
+}
+
+
+#pragma mark - NSCoding Methods
+
+- (id)initWithCoder:(NSCoder *)aDecoder
+{
+    self = [super init];
+
+    self.songJsonIdentifier = [aDecoder decodeDoubleForKey:kSongJsonId];
+    self.uploadUrl = [aDecoder decodeObjectForKey:kSongJsonUploadUrl];
+    self.songName = [aDecoder decodeObjectForKey:kSongJsonSongName];
+    self.type = [aDecoder decodeObjectForKey:kSongJsonType];
+    self.songAuthor = [aDecoder decodeObjectForKey:kSongJsonSongAuthor];
+    self.index = [aDecoder decodeDoubleForKey:kSongJsonIndex];
+    return self;
+}
+
+- (void)encodeWithCoder:(NSCoder *)aCoder
+{
+
+    [aCoder encodeDouble:_songJsonIdentifier forKey:kSongJsonId];
+    [aCoder encodeObject:_uploadUrl forKey:kSongJsonUploadUrl];
+    [aCoder encodeObject:_songName forKey:kSongJsonSongName];
+    [aCoder encodeObject:_type forKey:kSongJsonType];
+    [aCoder encodeObject:_songAuthor forKey:kSongJsonSongAuthor];
+    [aCoder encodeDouble:_index forKey:kSongJsonIndex];
+}
+
+- (id)copyWithZone:(NSZone *)zone
+{
+    SongJson *copy = [[SongJson alloc] init];
+    
+    if (copy) {
+
+        copy.songJsonIdentifier = self.songJsonIdentifier;
+        copy.uploadUrl = [self.uploadUrl copyWithZone:zone];
+        copy.songName = [self.songName copyWithZone:zone];
+        copy.type = [self.type copyWithZone:zone];
+        copy.songAuthor = [self.songAuthor copyWithZone:zone];
+        copy.index = self.index;
+    }
+    
+    return copy;
+}
+
+
+@end

+ 2 - 0
MusicGradeExam/MusicGradeExam/UI/RecordExam/View/RecordBodyView.h

@@ -21,6 +21,8 @@ NS_ASSUME_NONNULL_BEGIN
 
 + (instancetype)shareInstance;
 
+- (void)configTime:(NSString *)subTime;
+
 - (void)topviewAction:(RecordTopBlock)block;
 
 @end

+ 72 - 1
MusicGradeExam/MusicGradeExam/UI/RecordExam/View/RecordBodyView.m

@@ -12,6 +12,11 @@
 
 @property (nonatomic, copy) RecordTopBlock block;
 
+@property (weak, nonatomic) IBOutlet UILabel *countLabel;
+
+@property (nonatomic, assign) NSInteger duration;
+
+@property (nonatomic, strong) NSTimer *timeTimer;
 @end
 
 @implementation RecordBodyView
@@ -21,6 +26,60 @@
     return view;
 }
 
+- (void)configTime:(NSString *)subTime {
+    if ([NSString isEmptyString:subTime]) {
+        self.duration = 0;
+    }
+    else {
+        self.duration = [subTime integerValue];
+        if (self.duration < 0 ) {
+            self.duration = 0;
+        }
+    }
+    [self formatTime];
+    [self.timeTimer setFireDate:[NSDate distantPast]];
+}
+
+
+- (void)setDuration:(NSInteger)duration {
+    _duration = duration;
+    if (_duration < 300) {
+        self.countLabel.textColor = HexRGB(0xff3535);
+    }
+    if (_duration < 0) {
+        [self stopDurationTimer];
+        if (self.block) {
+            self.block(RECORDTOPACTION_OVER);
+        }
+    }
+}
+
+- (NSString *)formatTime {
+    
+    if (self.duration < 0) {
+        return @"00分00秒";
+    }
+    NSInteger durationInteger = self.duration--;
+    NSInteger durationS = durationInteger % 60;
+    NSInteger durationM = (durationInteger - durationS) / 60;
+    NSString *minuteStr = [NSString stringWithFormat:@"%02ld", durationM];
+    NSString *secondStr = [NSString stringWithFormat:@"%02ld", durationS];
+    
+    return [NSString stringWithFormat:@"%@分%@秒", minuteStr, secondStr];
+}
+
+- (void)timeFunction:(NSTimer *)timer {
+    self.countLabel.text = [self formatTime];
+}
+
+- (void)stopDurationTimer {
+    [self.timeTimer setFireDate:[NSDate distantFuture]];
+    if (self.timeTimer.valid) {
+        [self.timeTimer invalidate];
+        self.timeTimer = nil;
+    }
+}
+
 - (void)topviewAction:(RecordTopBlock)block {
     if (block) {
         self.block = block;
@@ -33,7 +92,19 @@
     }
 }
 
-
+- (NSTimer *)timeTimer {
+    if (_timeTimer == nil) {
+        
+        _timeTimer = [NSTimer scheduledTimerWithTimeInterval:1
+                                                      target:self
+                                                    selector:@selector(timeFunction:)
+                                                    userInfo:nil
+                                                     repeats:YES];
+        [[NSRunLoop currentRunLoop] addTimer:_timeTimer forMode:NSRunLoopCommonModes];
+    }
+    
+    return _timeTimer;
+}
 /*
 // Only override drawRect: if you perform custom drawing.
 // An empty implementation adversely affects performance during animation.

+ 3 - 0
MusicGradeExam/MusicGradeExam/UI/RecordExam/View/RecordBodyView.xib

@@ -108,6 +108,9 @@
                 <constraint firstItem="68b-I8-Bmr" firstAttribute="bottom" secondItem="u8d-f2-rBu" secondAttribute="bottom" constant="11" id="sHM-ZD-pDL"/>
             </constraints>
             <viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
+            <connections>
+                <outlet property="countLabel" destination="Q8k-CG-YKq" id="962-eh-ilI"/>
+            </connections>
             <point key="canvasLocation" x="163.768115942029" y="71.651785714285708"/>
         </view>
     </objects>

+ 2 - 0
MusicGradeExam/MusicGradeExam/UI/RecordExam/View/RecordBottomView.h

@@ -14,6 +14,8 @@ NS_ASSUME_NONNULL_BEGIN
 
 @interface RecordBottomView : UIView
 
+@property (weak, nonatomic) IBOutlet UIButton *finishButton;
+
 + (instancetype)shareInstance;
 
 - (void)submitMediaAction:(SureCallback)callback;

+ 3 - 0
MusicGradeExam/MusicGradeExam/UI/RecordExam/View/RecordBottomView.xib

@@ -39,6 +39,9 @@
             <nil key="simulatedTopBarMetrics"/>
             <nil key="simulatedBottomBarMetrics"/>
             <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
+            <connections>
+                <outlet property="finishButton" destination="OVk-6l-fwy" id="Lzm-An-UPQ"/>
+            </connections>
             <point key="canvasLocation" x="252" y="148"/>
         </view>
     </objects>

+ 12 - 0
MusicGradeExam/MusicGradeExam/UI/RecordExam/View/RecordListCell.h

@@ -7,11 +7,23 @@
 //
 
 #import <UIKit/UIKit.h>
+#import "SongJson.h"
+
+typedef NS_ENUM(NSInteger, RECORDTYPE) {
+    RECORDTYPE_RECORD = 1,
+    RECORDTYPE_DELETE = 2,
+};
+
+typedef void(^RecordAction)(RECORDTYPE type, NSString *fileKey);
 
 NS_ASSUME_NONNULL_BEGIN
 
 @interface RecordListCell : UITableViewCell
 
+@property (nonatomic, strong) NSDictionary  * _Nullable fileMessage;
+
+- (void)configCellWithSource:(SongJson *)songModel examRegistrationId:(NSString *)examRegistrationId operationCallback:(RecordAction)action;
+
 @end
 
 NS_ASSUME_NONNULL_END

+ 54 - 0
MusicGradeExam/MusicGradeExam/UI/RecordExam/View/RecordListCell.m

@@ -8,6 +8,22 @@
 
 #import "RecordListCell.h"
 
+@interface RecordListCell ()
+
+@property (weak, nonatomic) IBOutlet UILabel *songName;
+
+@property (weak, nonatomic) IBOutlet UILabel *fileName;
+
+@property (weak, nonatomic) IBOutlet UIView *uploadView;
+
+@property (weak, nonatomic) IBOutlet UIButton *deleteButton;
+
+@property (nonatomic, strong) RecordAction callback;
+
+@property (nonatomic, strong) NSString *fileKey;
+
+@end
+
 @implementation RecordListCell
 
 - (void)awakeFromNib {
@@ -16,6 +32,44 @@
     self.selectionStyle = UITableViewCellSelectionStyleNone;
 }
 
+- (void)configCellWithSource:(SongJson *)songModel examRegistrationId:(NSString *)examRegistrationId operationCallback:(RecordAction)action {
+    if (action) {
+        self.callback = action;
+    }
+    self.songName.text = [NSString returnNoNullStringWithString:songModel.songName];
+    // 判断是否选择了曲目
+    NSString *fileKey = [NSString stringWithFormat:@"%@%@", examRegistrationId, songModel.songName];
+    self.fileKey = fileKey;
+    NSDictionary *fileMessage = UserDefault(fileKey);
+    if (fileMessage) {
+        self.fileMessage = fileMessage;
+        self.deleteButton.hidden = NO;
+        self.uploadView.hidden = YES;
+        self.fileName.hidden = NO;
+        self.fileName.text = [NSString returnNoNullStringWithString:[fileMessage stringValueForKey:@"fileName"]];
+    }
+    else {
+        self.fileMessage = nil;
+        self.deleteButton.hidden = YES;
+        self.uploadView.hidden = NO;
+        self.fileName.hidden = YES;
+    }
+}
+
+- (IBAction)uploadAction:(id)sender {
+    if (self.callback) {
+        self.callback(RECORDTYPE_RECORD,self.fileKey);
+    }
+}
+
+- (IBAction)deleteAction:(id)sender {
+    if (self.callback) {
+        self.callback(RECORDTYPE_DELETE,self.fileKey);
+    }
+}
+
+
+
 - (void)setSelected:(BOOL)selected animated:(BOOL)animated {
     [super setSelected:selected animated:animated];
 

+ 105 - 11
MusicGradeExam/MusicGradeExam/UI/RecordExam/View/RecordListCell.xib

@@ -10,31 +10,125 @@
     <objects>
         <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
         <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
-        <tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" id="KGk-i7-Jjw" customClass="RecordListCell">
-            <rect key="frame" x="0.0" y="0.0" width="375" height="130"/>
+        <tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" rowHeight="107" id="KGk-i7-Jjw" customClass="RecordListCell">
+            <rect key="frame" x="0.0" y="0.0" width="414" height="130"/>
             <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
             <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
-                <rect key="frame" x="0.0" y="0.0" width="375" height="130"/>
+                <rect key="frame" x="0.0" y="0.0" width="414" height="130"/>
                 <autoresizingMask key="autoresizingMask"/>
                 <subviews>
-                    <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="record_bg" translatesAutoresizingMaskIntoConstraints="NO" id="zrO-5f-aoY">
-                        <rect key="frame" x="0.0" y="0.0" width="375" height="130"/>
+                    <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="record_bg" translatesAutoresizingMaskIntoConstraints="NO" id="zrO-5f-aoY">
+                        <rect key="frame" x="5" y="5" width="404" height="120"/>
                     </imageView>
+                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="练习曲1" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="uFY-ec-x9t">
+                        <rect key="frame" x="120" y="41" width="195" height="22"/>
+                        <constraints>
+                            <constraint firstAttribute="height" constant="22" id="eff-H1-Mo7"/>
+                        </constraints>
+                        <fontDescription key="fontDescription" type="system" weight="medium" pointSize="16"/>
+                        <color key="textColor" red="0.20000000000000001" green="0.20000000000000001" blue="0.20000000000000001" alpha="1" colorSpace="calibratedRGB"/>
+                        <nil key="highlightedColor"/>
+                    </label>
+                    <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Wqj-xK-Tvy">
+                        <rect key="frame" x="325" y="50" width="68" height="30"/>
+                        <color key="backgroundColor" red="0.87058823529411766" green="0.97254901960784312" blue="0.95294117647058818" alpha="1" colorSpace="calibratedRGB"/>
+                        <constraints>
+                            <constraint firstAttribute="width" constant="68" id="cTy-ak-vPW"/>
+                            <constraint firstAttribute="height" constant="30" id="dvX-e8-Avr"/>
+                        </constraints>
+                        <state key="normal" title="删除">
+                            <color key="titleColor" red="0.1764705882" green="0.78039215689999997" blue="0.66666666669999997" alpha="1" colorSpace="calibratedRGB"/>
+                        </state>
+                        <userDefinedRuntimeAttributes>
+                            <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
+                                <real key="value" value="15"/>
+                            </userDefinedRuntimeAttribute>
+                        </userDefinedRuntimeAttributes>
+                        <connections>
+                            <action selector="deleteAction:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="Smd-In-2MQ"/>
+                        </connections>
+                    </button>
+                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="999999" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Wdp-bZ-PAU">
+                        <rect key="frame" x="120" y="65" width="195" height="20"/>
+                        <constraints>
+                            <constraint firstAttribute="height" constant="20" id="zHv-mg-kFE"/>
+                        </constraints>
+                        <fontDescription key="fontDescription" type="system" pointSize="14"/>
+                        <color key="textColor" red="0.59999999999999998" green="0.59999999999999998" blue="0.59999999999999998" alpha="1" colorSpace="calibratedRGB"/>
+                        <nil key="highlightedColor"/>
+                    </label>
+                    <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="LOP-Uk-tjH">
+                        <rect key="frame" x="120" y="65" width="140" height="30"/>
+                        <subviews>
+                            <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="record_add" translatesAutoresizingMaskIntoConstraints="NO" id="CyQ-1e-v7C">
+                                <rect key="frame" x="8" y="10" width="10" height="10"/>
+                            </imageView>
+                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="HJs-nz-sdY">
+                                <rect key="frame" x="0.0" y="0.0" width="140" height="30"/>
+                                <fontDescription key="fontDescription" type="system" pointSize="12"/>
+                                <state key="normal" title="上传曲目">
+                                    <color key="titleColor" red="0.10196078431372549" green="0.10196078431372549" blue="0.10196078431372549" alpha="1" colorSpace="calibratedRGB"/>
+                                </state>
+                                <connections>
+                                    <action selector="uploadAction:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="NrP-7l-9Ir"/>
+                                </connections>
+                            </button>
+                        </subviews>
+                        <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
+                        <constraints>
+                            <constraint firstItem="CyQ-1e-v7C" firstAttribute="centerY" secondItem="LOP-Uk-tjH" secondAttribute="centerY" id="3G9-g5-9X9"/>
+                            <constraint firstItem="HJs-nz-sdY" firstAttribute="leading" secondItem="LOP-Uk-tjH" secondAttribute="leading" id="Bh0-pi-3nA"/>
+                            <constraint firstItem="CyQ-1e-v7C" firstAttribute="leading" secondItem="LOP-Uk-tjH" secondAttribute="leading" constant="8" id="Ki7-2z-rKT"/>
+                            <constraint firstAttribute="width" constant="140" id="N39-y4-piS"/>
+                            <constraint firstAttribute="trailing" secondItem="HJs-nz-sdY" secondAttribute="trailing" id="YYg-bV-esR"/>
+                            <constraint firstItem="HJs-nz-sdY" firstAttribute="top" secondItem="LOP-Uk-tjH" secondAttribute="top" id="dbO-dw-mQP"/>
+                            <constraint firstAttribute="height" constant="30" id="eDs-6G-vlj"/>
+                            <constraint firstAttribute="bottom" secondItem="HJs-nz-sdY" secondAttribute="bottom" id="riJ-za-4Bw"/>
+                        </constraints>
+                        <userDefinedRuntimeAttributes>
+                            <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
+                                <real key="value" value="15"/>
+                            </userDefinedRuntimeAttribute>
+                            <userDefinedRuntimeAttribute type="number" keyPath="borderWidth">
+                                <real key="value" value="1"/>
+                            </userDefinedRuntimeAttribute>
+                            <userDefinedRuntimeAttribute type="color" keyPath="borderColor">
+                                <color key="value" red="0.89803921568627454" green="0.89803921568627454" blue="0.89803921568627454" alpha="1" colorSpace="calibratedRGB"/>
+                            </userDefinedRuntimeAttribute>
+                        </userDefinedRuntimeAttributes>
+                    </view>
                 </subviews>
                 <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                 <constraints>
-                    <constraint firstItem="zrO-5f-aoY" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" id="ZF4-0d-pe2"/>
-                    <constraint firstItem="zrO-5f-aoY" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" id="k0V-d3-rh5"/>
-                    <constraint firstAttribute="bottom" secondItem="zrO-5f-aoY" secondAttribute="bottom" id="sdX-xS-xan"/>
-                    <constraint firstAttribute="trailing" secondItem="zrO-5f-aoY" secondAttribute="trailing" id="tcQ-Vf-BkK"/>
+                    <constraint firstItem="Wdp-bZ-PAU" firstAttribute="leading" secondItem="uFY-ec-x9t" secondAttribute="leading" id="2F0-h7-bcf"/>
+                    <constraint firstItem="LOP-Uk-tjH" firstAttribute="leading" secondItem="uFY-ec-x9t" secondAttribute="leading" id="Uhv-K7-3Wd"/>
+                    <constraint firstItem="uFY-ec-x9t" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" constant="41" id="YVI-7f-TOe"/>
+                    <constraint firstItem="zrO-5f-aoY" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" constant="5" id="ZF4-0d-pe2"/>
+                    <constraint firstItem="uFY-ec-x9t" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" constant="120" id="aC8-hT-wgL"/>
+                    <constraint firstItem="Wqj-xK-Tvy" firstAttribute="leading" secondItem="uFY-ec-x9t" secondAttribute="trailing" constant="10" id="crF-rk-hQX"/>
+                    <constraint firstItem="Wqj-xK-Tvy" firstAttribute="centerY" secondItem="H2p-sc-9uM" secondAttribute="centerY" id="jnb-Ia-649"/>
+                    <constraint firstItem="zrO-5f-aoY" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" constant="5" id="k0V-d3-rh5"/>
+                    <constraint firstItem="Wdp-bZ-PAU" firstAttribute="top" secondItem="uFY-ec-x9t" secondAttribute="bottom" constant="2" id="lVG-fW-mCM"/>
+                    <constraint firstAttribute="trailing" secondItem="Wqj-xK-Tvy" secondAttribute="trailing" constant="21" id="rvT-Zp-ESr"/>
+                    <constraint firstAttribute="bottom" secondItem="zrO-5f-aoY" secondAttribute="bottom" constant="5" id="sdX-xS-xan"/>
+                    <constraint firstAttribute="trailing" secondItem="zrO-5f-aoY" secondAttribute="trailing" constant="5" id="tcQ-Vf-BkK"/>
+                    <constraint firstItem="Wqj-xK-Tvy" firstAttribute="leading" secondItem="Wdp-bZ-PAU" secondAttribute="trailing" constant="10" id="xNU-dI-dUo"/>
+                    <constraint firstItem="LOP-Uk-tjH" firstAttribute="top" secondItem="uFY-ec-x9t" secondAttribute="bottom" constant="2" id="yAN-YT-ro5"/>
                 </constraints>
             </tableViewCellContentView>
             <color key="backgroundColor" red="0.96078431372549022" green="0.96078431372549022" blue="0.96078431372549022" alpha="1" colorSpace="calibratedRGB"/>
             <viewLayoutGuide key="safeArea" id="aW0-zy-SZf"/>
-            <point key="canvasLocation" x="132" y="119"/>
+            <connections>
+                <outlet property="deleteButton" destination="Wqj-xK-Tvy" id="dvS-yA-aNi"/>
+                <outlet property="fileName" destination="Wdp-bZ-PAU" id="bby-zg-Uie"/>
+                <outlet property="songName" destination="uFY-ec-x9t" id="X8g-n8-uBo"/>
+                <outlet property="uploadView" destination="LOP-Uk-tjH" id="BaM-Ur-q8s"/>
+            </connections>
+            <point key="canvasLocation" x="53.623188405797109" y="99.776785714285708"/>
         </tableViewCell>
     </objects>
     <resources>
-        <image name="record_bg" width="351" height="120"/>
+        <image name="record_add" width="10" height="10"/>
+        <image name="record_bg" width="115.5" height="120"/>
     </resources>
 </document>

+ 2 - 0
MusicGradeExam/MusicGradeExam/UI/RecordExam/View/RecordTipsView.h

@@ -14,6 +14,8 @@ NS_ASSUME_NONNULL_BEGIN
 
 + (instancetype)shareInstance;
 
+- (void)configWithEndTime:(NSString *)endTime;
+
 @end
 
 NS_ASSUME_NONNULL_END

+ 20 - 0
MusicGradeExam/MusicGradeExam/UI/RecordExam/View/RecordTipsView.m

@@ -8,6 +8,12 @@
 
 #import "RecordTipsView.h"
 
+@interface RecordTipsView ()
+
+@property (weak, nonatomic) IBOutlet UILabel *tipsLabel;
+
+@end
+
 @implementation RecordTipsView
 
 
@@ -17,6 +23,20 @@
 }
 
 
+- (void)configWithEndTime:(NSString *)endTime {
+    if (![NSString isEmptyString:endTime]) {
+        NSString *messageStr = [NSString stringWithFormat:@"请于%@前依次完成考级曲目的提交,规定时间后,未提交完成,则视为缺考。", endTime];
+        NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
+        [paragraphStyle setLineSpacing:4];//调整行间距
+        NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithString:messageStr attributes:@{NSParagraphStyleAttributeName:paragraphStyle,NSFontAttributeName:[UIFont systemFontOfSize:14.0f weight:UIFontWeightMedium],NSForegroundColorAttributeName:HexRGB(0x808080)}];
+        [attrStr addAttributes:@{NSForegroundColorAttributeName:HexRGB(0xff5c00)} range:NSMakeRange(2, endTime.length)];
+        self.tipsLabel.attributedText = attrStr;
+    }
+    else {
+        self.tipsLabel.text = @"";
+    }
+}
+
 /*
 // Only override drawRect: if you perform custom drawing.
 // An empty implementation adversely affects performance during animation.

+ 5 - 1
MusicGradeExam/MusicGradeExam/UI/RecordExam/View/RecordTipsView.xib

@@ -23,7 +23,7 @@
                     <nil key="highlightedColor"/>
                 </label>
                 <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="请于2020-05-20 9:30:59前依次完成考级曲目的提交,规定时间后,未提交完成,则视为缺考。" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0KC-iu-RW4">
-                    <rect key="frame" x="20" y="49" width="374" height="33.5"/>
+                    <rect key="frame" x="20" y="49" width="374" height="60"/>
                     <fontDescription key="fontDescription" type="system" pointSize="14"/>
                     <color key="textColor" red="0.50196078431372548" green="0.50196078431372548" blue="0.50196078431372548" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                     <nil key="highlightedColor"/>
@@ -33,6 +33,7 @@
             <constraints>
                 <constraint firstItem="0KC-iu-RW4" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" id="M2a-Nq-7rx"/>
                 <constraint firstItem="Hf0-qG-qs5" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" id="SNd-PR-wqq"/>
+                <constraint firstAttribute="bottom" secondItem="0KC-iu-RW4" secondAttribute="bottom" constant="10" id="cS7-ht-sD8"/>
                 <constraint firstItem="Hf0-qG-qs5" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" constant="17" id="cxg-AT-Izz"/>
                 <constraint firstItem="0KC-iu-RW4" firstAttribute="top" secondItem="Hf0-qG-qs5" secondAttribute="bottom" constant="10" id="ihL-nu-17A"/>
                 <constraint firstAttribute="trailing" secondItem="0KC-iu-RW4" secondAttribute="trailing" constant="20" id="jEa-L6-JCm"/>
@@ -40,6 +41,9 @@
             <nil key="simulatedTopBarMetrics"/>
             <nil key="simulatedBottomBarMetrics"/>
             <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
+            <connections>
+                <outlet property="tipsLabel" destination="0KC-iu-RW4" id="MdO-eC-8vf"/>
+            </connections>
             <point key="canvasLocation" x="131.8840579710145" y="-71.986607142857139"/>
         </view>
     </objects>