diff --git a/QuickLocation.xcodeproj/project.pbxproj b/QuickLocation.xcodeproj/project.pbxproj index d778cfc..6ce3f11 100644 --- a/QuickLocation.xcodeproj/project.pbxproj +++ b/QuickLocation.xcodeproj/project.pbxproj @@ -186,6 +186,10 @@ 3062E8BC2FCEAC7100CEF511 /* CreateGroupVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3062E8BB2FCEAC7100CEF511 /* CreateGroupVC.swift */; }; 3062E8BE2FCEBD0E00CEF511 /* GroupIconListVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3062E8BD2FCEBD0E00CEF511 /* GroupIconListVC.swift */; }; 3062E8C02FCED7BB00CEF511 /* GroupIconListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3062E8BF2FCED7BB00CEF511 /* GroupIconListView.swift */; }; + 3062E8C22FCFB86800CEF511 /* CreateGroupViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3062E8C12FCFB86800CEF511 /* CreateGroupViewModel.swift */; }; + 3062E8C42FCFC90F00CEF511 /* CreateGroupVipPopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3062E8C32FCFC90F00CEF511 /* CreateGroupVipPopView.swift */; }; + 3062E8C72FCFD02F00CEF511 /* VipRechargeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3062E8C62FCFD02F00CEF511 /* VipRechargeView.swift */; }; + 3062E8C92FCFD03B00CEF511 /* VipRechargeVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3062E8C82FCFD03B00CEF511 /* VipRechargeVC.swift */; }; 30A7A9112FCAEE3D00105780 /* GroupListPopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A7A9102FCAEE3D00105780 /* GroupListPopView.swift */; }; 30BAB84D2FCD2FDE00C33B5C /* InviteJoinView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BAB84C2FCD2FDE00C33B5C /* InviteJoinView.swift */; }; 30BAB84F2FCD2FED00C33B5C /* InviteJoinVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BAB84E2FCD2FED00C33B5C /* InviteJoinVC.swift */; }; @@ -195,6 +199,8 @@ 30BAB8652FCD718A00C33B5C /* JoinGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BAB8642FCD718A00C33B5C /* JoinGroupView.swift */; }; 30BAB8682FCD750E00C33B5C /* Mask_group@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 30BAB8672FCD750E00C33B5C /* Mask_group@3x.png */; }; 30BAB8692FCD750E00C33B5C /* Mask_group@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 30BAB8662FCD750E00C33B5C /* Mask_group@2x.png */; }; + 30DC18522FD009CD0041DCD1 /* VipExpenseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DC18512FD009CD0041DCD1 /* VipExpenseModel.swift */; }; + 30DC18542FD00C4A0041DCD1 /* VipRechargeVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DC18532FD00C4A0041DCD1 /* VipRechargeVM.swift */; }; C49B37352A45A02C28FF41BA /* Pods_QuickLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D1C77B42994F352054070537 /* Pods_QuickLocation.framework */; }; /* End PBXBuildFile section */ @@ -386,6 +392,10 @@ 3062E8BB2FCEAC7100CEF511 /* CreateGroupVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateGroupVC.swift; sourceTree = ""; }; 3062E8BD2FCEBD0E00CEF511 /* GroupIconListVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupIconListVC.swift; sourceTree = ""; }; 3062E8BF2FCED7BB00CEF511 /* GroupIconListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupIconListView.swift; sourceTree = ""; }; + 3062E8C12FCFB86800CEF511 /* CreateGroupViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateGroupViewModel.swift; sourceTree = ""; }; + 3062E8C32FCFC90F00CEF511 /* CreateGroupVipPopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateGroupVipPopView.swift; sourceTree = ""; }; + 3062E8C62FCFD02F00CEF511 /* VipRechargeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VipRechargeView.swift; sourceTree = ""; }; + 3062E8C82FCFD03B00CEF511 /* VipRechargeVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VipRechargeVC.swift; sourceTree = ""; }; 30A7A9102FCAEE3D00105780 /* GroupListPopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupListPopView.swift; sourceTree = ""; }; 30BAB84C2FCD2FDE00C33B5C /* InviteJoinView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteJoinView.swift; sourceTree = ""; }; 30BAB84E2FCD2FED00C33B5C /* InviteJoinVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteJoinVC.swift; sourceTree = ""; }; @@ -395,6 +405,8 @@ 30BAB8642FCD718A00C33B5C /* JoinGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinGroupView.swift; sourceTree = ""; }; 30BAB8662FCD750E00C33B5C /* Mask_group@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Mask_group@2x.png"; sourceTree = ""; }; 30BAB8672FCD750E00C33B5C /* Mask_group@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Mask_group@3x.png"; sourceTree = ""; }; + 30DC18512FD009CD0041DCD1 /* VipExpenseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VipExpenseModel.swift; sourceTree = ""; }; + 30DC18532FD00C4A0041DCD1 /* VipRechargeVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VipRechargeVM.swift; sourceTree = ""; }; 3E4359082FC48D26003470A5 /* QuickLocation.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = QuickLocation.app; sourceTree = BUILT_PRODUCTS_DIR; }; 93647DF3683AA5E71EC2FB1A /* Pods-QuickLocation.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-QuickLocation.release.xcconfig"; path = "Target Support Files/Pods-QuickLocation/Pods-QuickLocation.release.xcconfig"; sourceTree = ""; }; D1C77B42994F352054070537 /* Pods_QuickLocation.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_QuickLocation.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -851,6 +863,7 @@ 305A76352FCA8C7000227D26 /* Map */, 305A76392FCA8C7000227D26 /* Mine */, 305A798E2FCAC5F600227D26 /* InviteMember */, + 3062E8C52FCFD01000CEF511 /* VipRecharge */, 3062E8B32FCE6BA400CEF511 /* Scan */, ); path = Section; @@ -1032,12 +1045,25 @@ children = ( 3062E8BB2FCEAC7100CEF511 /* CreateGroupVC.swift */, 3062E8B92FCEAC6500CEF511 /* CreateGroupView.swift */, + 3062E8C12FCFB86800CEF511 /* CreateGroupViewModel.swift */, 3062E8BD2FCEBD0E00CEF511 /* GroupIconListVC.swift */, 3062E8BF2FCED7BB00CEF511 /* GroupIconListView.swift */, + 3062E8C32FCFC90F00CEF511 /* CreateGroupVipPopView.swift */, ); path = CreateGroup; sourceTree = ""; }; + 3062E8C52FCFD01000CEF511 /* VipRecharge */ = { + isa = PBXGroup; + children = ( + 3062E8C82FCFD03B00CEF511 /* VipRechargeVC.swift */, + 3062E8C62FCFD02F00CEF511 /* VipRechargeView.swift */, + 30DC18532FD00C4A0041DCD1 /* VipRechargeVM.swift */, + 30DC18512FD009CD0041DCD1 /* VipExpenseModel.swift */, + ); + path = VipRecharge; + sourceTree = ""; + }; 30BAB84B2FCD2FA400C33B5C /* InviteJoin */ = { isa = PBXGroup; children = ( @@ -1257,6 +1283,7 @@ 305A768A2FCA8C7000227D26 /* Single+Response.swift in Sources */, 305A768B2FCA8C7000227D26 /* API.swift in Sources */, 305A768C2FCA8C7000227D26 /* APIProvider.swift in Sources */, + 3062E8C92FCFD03B00CEF511 /* VipRechargeVC.swift in Sources */, 305A768D2FCA8C7000227D26 /* AppNetworkConfig.swift in Sources */, 305A768E2FCA8C7000227D26 /* SignPlugin.swift in Sources */, 305A768F2FCA8C7000227D26 /* SystemAPI.swift in Sources */, @@ -1272,6 +1299,7 @@ 305A76992FCA8C7000227D26 /* ImagePickerPopup.swift in Sources */, 305A769A2FCA8C7000227D26 /* PopupAnimator.swift in Sources */, 3062E8BE2FCEBD0E00CEF511 /* GroupIconListVC.swift in Sources */, + 3062E8C22FCFB86800CEF511 /* CreateGroupViewModel.swift in Sources */, 305A769B2FCA8C7000227D26 /* PopupAnimators.swift in Sources */, 305A769C2FCA8C7000227D26 /* PopupViewController.swift in Sources */, 305A769D2FCA8C7000227D26 /* PopupViewController+Extension.swift in Sources */, @@ -1287,6 +1315,7 @@ 305A76A62FCA8C7000227D26 /* Int+Extension.swift in Sources */, 30A7A9112FCAEE3D00105780 /* GroupListPopView.swift in Sources */, 305A76A72FCA8C7000227D26 /* NSAttributedString+Extension.swift in Sources */, + 30DC18542FD00C4A0041DCD1 /* VipRechargeVM.swift in Sources */, 305A76A82FCA8C7000227D26 /* ObjectMapper+Extension.swift in Sources */, 305A76A92FCA8C7000227D26 /* Optional+Extension.swift in Sources */, 305A76AA2FCA8C7000227D26 /* Response+ObjectMapper.swift in Sources */, @@ -1327,6 +1356,7 @@ 305A76CC2FCA8C7000227D26 /* FileTools.swift in Sources */, 305A76CD2FCA8C7000227D26 /* Permission.swift in Sources */, 305A76CE2FCA8C7000227D26 /* RouterManager.swift in Sources */, + 3062E8C72FCFD02F00CEF511 /* VipRechargeView.swift in Sources */, 305A76CF2FCA8C7000227D26 /* CountDownService.swift in Sources */, 305A76D02FCA8C7000227D26 /* MoneyFormatter.swift in Sources */, 305A76D12FCA8C7000227D26 /* TimeSpecificNotificationManager.swift in Sources */, @@ -1371,6 +1401,7 @@ 305A76EF2FCA8C7000227D26 /* MineViewController.swift in Sources */, 305A76F02FCA8C7000227D26 /* MineViewModel.swift in Sources */, 305A76F12FCA8C7000227D26 /* SystemService.swift in Sources */, + 30DC18522FD009CD0041DCD1 /* VipExpenseModel.swift in Sources */, 305A76F22FCA8C7000227D26 /* UserService.swift in Sources */, 305A76F32FCA8C7000227D26 /* AutoLayout+NSLayoutConstraint.swift in Sources */, 305A76F42FCA8C7000227D26 /* AutoLayout+UIView.swift in Sources */, @@ -1395,6 +1426,7 @@ 305A77062FCA8C7000227D26 /* MXScrollViewController.m in Sources */, 305A77072FCA8C7000227D26 /* Helper.swift in Sources */, 305A77082FCA8C7000227D26 /* PageCollectionViewFlowLayout.swift in Sources */, + 3062E8C42FCFC90F00CEF511 /* CreateGroupVipPopView.swift in Sources */, 305A77092FCA8C7000227D26 /* PageContentView.swift in Sources */, 305A770A2FCA8C7000227D26 /* PageStyle.swift in Sources */, 305A770B2FCA8C7000227D26 /* PageTitleView.swift in Sources */, diff --git a/QuickLocation.xcworkspace/xcuserdata/yanghong.xcuserdatad/UserInterfaceState.xcuserstate b/QuickLocation.xcworkspace/xcuserdata/yanghong.xcuserdatad/UserInterfaceState.xcuserstate index b6ab28d..975aca8 100644 Binary files a/QuickLocation.xcworkspace/xcuserdata/yanghong.xcuserdatad/UserInterfaceState.xcuserstate and b/QuickLocation.xcworkspace/xcuserdata/yanghong.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/QuickLocation/API/APIProvider.swift b/QuickLocation/API/APIProvider.swift index d7c38f1..7047f3f 100644 --- a/QuickLocation/API/APIProvider.swift +++ b/QuickLocation/API/APIProvider.swift @@ -125,6 +125,9 @@ enum GatewayStatusCode: Int { case noAuthority = 500 // 审核中 case review = 201 + /** ============== 业务 ============== */ + // 您创建的圈子个数已达上限,请升级会员等级 + case groupLimit = 20009 /** ============== 未知错误 ============== */ case unknownError = -9999 diff --git a/QuickLocation/API/SystemAPI.swift b/QuickLocation/API/SystemAPI.swift index 462b7f3..753fd95 100644 --- a/QuickLocation/API/SystemAPI.swift +++ b/QuickLocation/API/SystemAPI.swift @@ -18,7 +18,10 @@ enum SystemAPI { /// - phone: 手机号 case sendCode(phone: String) - + /// 充值内容 + /// - Parameters: + /// - type: 类型 member + case rechargeInfo(type: String) } extension SystemAPI: MultiTargetProtocol { @@ -29,12 +32,14 @@ extension SystemAPI: MultiTargetProtocol { return "api/user/config" case .sendCode: return "api/user/sms/code" + case .rechargeInfo: + return "api/order/goods" } } var method: Moya.Method { switch self { - case .userConfig: + case .userConfig, .rechargeInfo: return .get case .sendCode: return .post @@ -50,6 +55,11 @@ extension SystemAPI: MultiTargetProtocol { var params = Parameters() params["phone"] = phone return .requestParameters(parameters: params, encoding: JSONEncoding()) + + case let .rechargeInfo(type): + var params = Parameters() + params["type"] = type + return .requestParameters(parameters: params, encoding: URLEncoding()) } } } diff --git a/QuickLocation/Assets.xcassets/Group/close.imageset/Contents.json b/QuickLocation/Assets.xcassets/Group/close.imageset/Contents.json new file mode 100644 index 0000000..da6de61 --- /dev/null +++ b/QuickLocation/Assets.xcassets/Group/close.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Group_1545@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Group_1545@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/Group/close.imageset/Group_1545@2x.png b/QuickLocation/Assets.xcassets/Group/close.imageset/Group_1545@2x.png new file mode 100644 index 0000000..6fd01b6 Binary files /dev/null and b/QuickLocation/Assets.xcassets/Group/close.imageset/Group_1545@2x.png differ diff --git a/QuickLocation/Assets.xcassets/Group/close.imageset/Group_1545@3x.png b/QuickLocation/Assets.xcassets/Group/close.imageset/Group_1545@3x.png new file mode 100644 index 0000000..2d74d4b Binary files /dev/null and b/QuickLocation/Assets.xcassets/Group/close.imageset/Group_1545@3x.png differ diff --git a/QuickLocation/Assets.xcassets/Group/upgrade_bg.imageset/Contents.json b/QuickLocation/Assets.xcassets/Group/upgrade_bg.imageset/Contents.json new file mode 100644 index 0000000..72ff416 --- /dev/null +++ b/QuickLocation/Assets.xcassets/Group/upgrade_bg.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "upgrade_bg@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "upgrade_bg@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/Group/upgrade_bg.imageset/upgrade_bg@2x.png b/QuickLocation/Assets.xcassets/Group/upgrade_bg.imageset/upgrade_bg@2x.png new file mode 100644 index 0000000..a7351ff Binary files /dev/null and b/QuickLocation/Assets.xcassets/Group/upgrade_bg.imageset/upgrade_bg@2x.png differ diff --git a/QuickLocation/Assets.xcassets/Group/upgrade_bg.imageset/upgrade_bg@3x.png b/QuickLocation/Assets.xcassets/Group/upgrade_bg.imageset/upgrade_bg@3x.png new file mode 100644 index 0000000..66799b0 Binary files /dev/null and b/QuickLocation/Assets.xcassets/Group/upgrade_bg.imageset/upgrade_bg@3x.png differ diff --git a/QuickLocation/Assets.xcassets/Group/vip_pop.imageset/Contents.json b/QuickLocation/Assets.xcassets/Group/vip_pop.imageset/Contents.json new file mode 100644 index 0000000..9236155 --- /dev/null +++ b/QuickLocation/Assets.xcassets/Group/vip_pop.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Group_2396@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Group_2396@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/Group/vip_pop.imageset/Group_2396@2x.png b/QuickLocation/Assets.xcassets/Group/vip_pop.imageset/Group_2396@2x.png new file mode 100644 index 0000000..5cde4ed Binary files /dev/null and b/QuickLocation/Assets.xcassets/Group/vip_pop.imageset/Group_2396@2x.png differ diff --git a/QuickLocation/Assets.xcassets/Group/vip_pop.imageset/Group_2396@3x.png b/QuickLocation/Assets.xcassets/Group/vip_pop.imageset/Group_2396@3x.png new file mode 100644 index 0000000..0747968 Binary files /dev/null and b/QuickLocation/Assets.xcassets/Group/vip_pop.imageset/Group_2396@3x.png differ diff --git a/QuickLocation/Assets.xcassets/VipRecharge/Contents.json b/QuickLocation/Assets.xcassets/VipRecharge/Contents.json new file mode 100644 index 0000000..6e96565 --- /dev/null +++ b/QuickLocation/Assets.xcassets/VipRecharge/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/QuickLocation/Assets.xcassets/VipRecharge/alipay.imageset/Contents.json b/QuickLocation/Assets.xcassets/VipRecharge/alipay.imageset/Contents.json new file mode 100644 index 0000000..cab8b8c --- /dev/null +++ b/QuickLocation/Assets.xcassets/VipRecharge/alipay.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "alipay@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "alipay@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/VipRecharge/alipay.imageset/alipay@2x.png b/QuickLocation/Assets.xcassets/VipRecharge/alipay.imageset/alipay@2x.png new file mode 100644 index 0000000..5a74935 Binary files /dev/null and b/QuickLocation/Assets.xcassets/VipRecharge/alipay.imageset/alipay@2x.png differ diff --git a/QuickLocation/Assets.xcassets/VipRecharge/alipay.imageset/alipay@3x.png b/QuickLocation/Assets.xcassets/VipRecharge/alipay.imageset/alipay@3x.png new file mode 100644 index 0000000..9545c41 Binary files /dev/null and b/QuickLocation/Assets.xcassets/VipRecharge/alipay.imageset/alipay@3x.png differ diff --git a/QuickLocation/Assets.xcassets/VipRecharge/checkbox.imageset/Contents.json b/QuickLocation/Assets.xcassets/VipRecharge/checkbox.imageset/Contents.json new file mode 100644 index 0000000..be2b233 --- /dev/null +++ b/QuickLocation/Assets.xcassets/VipRecharge/checkbox.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "tick-circle@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "tick-circle@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/VipRecharge/checkbox.imageset/tick-circle@2x.png b/QuickLocation/Assets.xcassets/VipRecharge/checkbox.imageset/tick-circle@2x.png new file mode 100644 index 0000000..bbcc296 Binary files /dev/null and b/QuickLocation/Assets.xcassets/VipRecharge/checkbox.imageset/tick-circle@2x.png differ diff --git a/QuickLocation/Assets.xcassets/VipRecharge/checkbox.imageset/tick-circle@3x.png b/QuickLocation/Assets.xcassets/VipRecharge/checkbox.imageset/tick-circle@3x.png new file mode 100644 index 0000000..7eb154e Binary files /dev/null and b/QuickLocation/Assets.xcassets/VipRecharge/checkbox.imageset/tick-circle@3x.png differ diff --git a/QuickLocation/Assets.xcassets/VipRecharge/checkbox_on.imageset/Contents.json b/QuickLocation/Assets.xcassets/VipRecharge/checkbox_on.imageset/Contents.json new file mode 100644 index 0000000..7df9bd8 --- /dev/null +++ b/QuickLocation/Assets.xcassets/VipRecharge/checkbox_on.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "icon_yindao_change@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "icon_yindao_change@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/VipRecharge/checkbox_on.imageset/icon_yindao_change@2x.png b/QuickLocation/Assets.xcassets/VipRecharge/checkbox_on.imageset/icon_yindao_change@2x.png new file mode 100644 index 0000000..3c4f691 Binary files /dev/null and b/QuickLocation/Assets.xcassets/VipRecharge/checkbox_on.imageset/icon_yindao_change@2x.png differ diff --git a/QuickLocation/Assets.xcassets/VipRecharge/checkbox_on.imageset/icon_yindao_change@3x.png b/QuickLocation/Assets.xcassets/VipRecharge/checkbox_on.imageset/icon_yindao_change@3x.png new file mode 100644 index 0000000..3bb2699 Binary files /dev/null and b/QuickLocation/Assets.xcassets/VipRecharge/checkbox_on.imageset/icon_yindao_change@3x.png differ diff --git a/QuickLocation/Assets.xcassets/VipRecharge/count_bg.imageset/Contents.json b/QuickLocation/Assets.xcassets/VipRecharge/count_bg.imageset/Contents.json new file mode 100644 index 0000000..46ca737 --- /dev/null +++ b/QuickLocation/Assets.xcassets/VipRecharge/count_bg.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Rectangle 42116@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Rectangle 42116@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/VipRecharge/count_bg.imageset/Rectangle 42116@2x.png b/QuickLocation/Assets.xcassets/VipRecharge/count_bg.imageset/Rectangle 42116@2x.png new file mode 100644 index 0000000..1cf7693 Binary files /dev/null and b/QuickLocation/Assets.xcassets/VipRecharge/count_bg.imageset/Rectangle 42116@2x.png differ diff --git a/QuickLocation/Assets.xcassets/VipRecharge/count_bg.imageset/Rectangle 42116@3x.png b/QuickLocation/Assets.xcassets/VipRecharge/count_bg.imageset/Rectangle 42116@3x.png new file mode 100644 index 0000000..61b5a72 Binary files /dev/null and b/QuickLocation/Assets.xcassets/VipRecharge/count_bg.imageset/Rectangle 42116@3x.png differ diff --git a/QuickLocation/Assets.xcassets/VipRecharge/create_count.imageset/Contents.json b/QuickLocation/Assets.xcassets/VipRecharge/create_count.imageset/Contents.json new file mode 100644 index 0000000..7b61b41 --- /dev/null +++ b/QuickLocation/Assets.xcassets/VipRecharge/create_count.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "create_count@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "create_count@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/VipRecharge/create_count.imageset/create_count@2x.png b/QuickLocation/Assets.xcassets/VipRecharge/create_count.imageset/create_count@2x.png new file mode 100644 index 0000000..59e4162 Binary files /dev/null and b/QuickLocation/Assets.xcassets/VipRecharge/create_count.imageset/create_count@2x.png differ diff --git a/QuickLocation/Assets.xcassets/VipRecharge/create_count.imageset/create_count@3x.png b/QuickLocation/Assets.xcassets/VipRecharge/create_count.imageset/create_count@3x.png new file mode 100644 index 0000000..a83d6bc Binary files /dev/null and b/QuickLocation/Assets.xcassets/VipRecharge/create_count.imageset/create_count@3x.png differ diff --git a/QuickLocation/Assets.xcassets/VipRecharge/expense.imageset/Contents.json b/QuickLocation/Assets.xcassets/VipRecharge/expense.imageset/Contents.json new file mode 100644 index 0000000..336fa3d --- /dev/null +++ b/QuickLocation/Assets.xcassets/VipRecharge/expense.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "expense@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "expense@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/VipRecharge/expense.imageset/expense@2x.png b/QuickLocation/Assets.xcassets/VipRecharge/expense.imageset/expense@2x.png new file mode 100644 index 0000000..9f84867 Binary files /dev/null and b/QuickLocation/Assets.xcassets/VipRecharge/expense.imageset/expense@2x.png differ diff --git a/QuickLocation/Assets.xcassets/VipRecharge/expense.imageset/expense@3x.png b/QuickLocation/Assets.xcassets/VipRecharge/expense.imageset/expense@3x.png new file mode 100644 index 0000000..535ead7 Binary files /dev/null and b/QuickLocation/Assets.xcassets/VipRecharge/expense.imageset/expense@3x.png differ diff --git a/QuickLocation/Assets.xcassets/VipRecharge/expense_on.imageset/Contents.json b/QuickLocation/Assets.xcassets/VipRecharge/expense_on.imageset/Contents.json new file mode 100644 index 0000000..d3f0b1b --- /dev/null +++ b/QuickLocation/Assets.xcassets/VipRecharge/expense_on.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "expense_on@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "expense_on@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/VipRecharge/expense_on.imageset/expense_on@2x.png b/QuickLocation/Assets.xcassets/VipRecharge/expense_on.imageset/expense_on@2x.png new file mode 100644 index 0000000..185829d Binary files /dev/null and b/QuickLocation/Assets.xcassets/VipRecharge/expense_on.imageset/expense_on@2x.png differ diff --git a/QuickLocation/Assets.xcassets/VipRecharge/expense_on.imageset/expense_on@3x.png b/QuickLocation/Assets.xcassets/VipRecharge/expense_on.imageset/expense_on@3x.png new file mode 100644 index 0000000..f533f1f Binary files /dev/null and b/QuickLocation/Assets.xcassets/VipRecharge/expense_on.imageset/expense_on@3x.png differ diff --git a/QuickLocation/Assets.xcassets/VipRecharge/expense_tips.imageset/Contents.json b/QuickLocation/Assets.xcassets/VipRecharge/expense_tips.imageset/Contents.json new file mode 100644 index 0000000..c6aa04a --- /dev/null +++ b/QuickLocation/Assets.xcassets/VipRecharge/expense_tips.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Rectangle 868@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Rectangle 868@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/VipRecharge/expense_tips.imageset/Rectangle 868@2x.png b/QuickLocation/Assets.xcassets/VipRecharge/expense_tips.imageset/Rectangle 868@2x.png new file mode 100644 index 0000000..b9b0720 Binary files /dev/null and b/QuickLocation/Assets.xcassets/VipRecharge/expense_tips.imageset/Rectangle 868@2x.png differ diff --git a/QuickLocation/Assets.xcassets/VipRecharge/expense_tips.imageset/Rectangle 868@3x.png b/QuickLocation/Assets.xcassets/VipRecharge/expense_tips.imageset/Rectangle 868@3x.png new file mode 100644 index 0000000..5460b4b Binary files /dev/null and b/QuickLocation/Assets.xcassets/VipRecharge/expense_tips.imageset/Rectangle 868@3x.png differ diff --git a/QuickLocation/Assets.xcassets/VipRecharge/header_bg.imageset/Contents.json b/QuickLocation/Assets.xcassets/VipRecharge/header_bg.imageset/Contents.json new file mode 100644 index 0000000..3c4bace --- /dev/null +++ b/QuickLocation/Assets.xcassets/VipRecharge/header_bg.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "header_bg@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "header_bg@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/VipRecharge/header_bg.imageset/header_bg@2x.png b/QuickLocation/Assets.xcassets/VipRecharge/header_bg.imageset/header_bg@2x.png new file mode 100644 index 0000000..9523a4a Binary files /dev/null and b/QuickLocation/Assets.xcassets/VipRecharge/header_bg.imageset/header_bg@2x.png differ diff --git a/QuickLocation/Assets.xcassets/VipRecharge/header_bg.imageset/header_bg@3x.png b/QuickLocation/Assets.xcassets/VipRecharge/header_bg.imageset/header_bg@3x.png new file mode 100644 index 0000000..5f585f5 Binary files /dev/null and b/QuickLocation/Assets.xcassets/VipRecharge/header_bg.imageset/header_bg@3x.png differ diff --git a/QuickLocation/Assets.xcassets/VipRecharge/join_count.imageset/Contents.json b/QuickLocation/Assets.xcassets/VipRecharge/join_count.imageset/Contents.json new file mode 100644 index 0000000..199fbab --- /dev/null +++ b/QuickLocation/Assets.xcassets/VipRecharge/join_count.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "join_count@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "join_count@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/VipRecharge/join_count.imageset/join_count@2x.png b/QuickLocation/Assets.xcassets/VipRecharge/join_count.imageset/join_count@2x.png new file mode 100644 index 0000000..c9578dd Binary files /dev/null and b/QuickLocation/Assets.xcassets/VipRecharge/join_count.imageset/join_count@2x.png differ diff --git a/QuickLocation/Assets.xcassets/VipRecharge/join_count.imageset/join_count@3x.png b/QuickLocation/Assets.xcassets/VipRecharge/join_count.imageset/join_count@3x.png new file mode 100644 index 0000000..53fcfea Binary files /dev/null and b/QuickLocation/Assets.xcassets/VipRecharge/join_count.imageset/join_count@3x.png differ diff --git a/QuickLocation/Assets.xcassets/VipRecharge/member_count.imageset/Contents.json b/QuickLocation/Assets.xcassets/VipRecharge/member_count.imageset/Contents.json new file mode 100644 index 0000000..04df864 --- /dev/null +++ b/QuickLocation/Assets.xcassets/VipRecharge/member_count.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "member_count@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "member_count@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/VipRecharge/member_count.imageset/member_count@2x.png b/QuickLocation/Assets.xcassets/VipRecharge/member_count.imageset/member_count@2x.png new file mode 100644 index 0000000..ae8d18b Binary files /dev/null and b/QuickLocation/Assets.xcassets/VipRecharge/member_count.imageset/member_count@2x.png differ diff --git a/QuickLocation/Assets.xcassets/VipRecharge/member_count.imageset/member_count@3x.png b/QuickLocation/Assets.xcassets/VipRecharge/member_count.imageset/member_count@3x.png new file mode 100644 index 0000000..45c07a3 Binary files /dev/null and b/QuickLocation/Assets.xcassets/VipRecharge/member_count.imageset/member_count@3x.png differ diff --git a/QuickLocation/Assets.xcassets/VipRecharge/pay_bg.imageset/Contents.json b/QuickLocation/Assets.xcassets/VipRecharge/pay_bg.imageset/Contents.json new file mode 100644 index 0000000..66ed810 --- /dev/null +++ b/QuickLocation/Assets.xcassets/VipRecharge/pay_bg.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "组 47671@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "组 47671@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/VipRecharge/pay_bg.imageset/组 47671@2x.png b/QuickLocation/Assets.xcassets/VipRecharge/pay_bg.imageset/组 47671@2x.png new file mode 100644 index 0000000..014c373 Binary files /dev/null and b/QuickLocation/Assets.xcassets/VipRecharge/pay_bg.imageset/组 47671@2x.png differ diff --git a/QuickLocation/Assets.xcassets/VipRecharge/pay_bg.imageset/组 47671@3x.png b/QuickLocation/Assets.xcassets/VipRecharge/pay_bg.imageset/组 47671@3x.png new file mode 100644 index 0000000..2ad4bdd Binary files /dev/null and b/QuickLocation/Assets.xcassets/VipRecharge/pay_bg.imageset/组 47671@3x.png differ diff --git a/QuickLocation/Assets.xcassets/VipRecharge/separator.imageset/Contents.json b/QuickLocation/Assets.xcassets/VipRecharge/separator.imageset/Contents.json new file mode 100644 index 0000000..39a9a68 --- /dev/null +++ b/QuickLocation/Assets.xcassets/VipRecharge/separator.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Rectangle 42118@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Rectangle 42118@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/VipRecharge/separator.imageset/Rectangle 42118@2x.png b/QuickLocation/Assets.xcassets/VipRecharge/separator.imageset/Rectangle 42118@2x.png new file mode 100644 index 0000000..0f4b30f Binary files /dev/null and b/QuickLocation/Assets.xcassets/VipRecharge/separator.imageset/Rectangle 42118@2x.png differ diff --git a/QuickLocation/Assets.xcassets/VipRecharge/separator.imageset/Rectangle 42118@3x.png b/QuickLocation/Assets.xcassets/VipRecharge/separator.imageset/Rectangle 42118@3x.png new file mode 100644 index 0000000..4e93411 Binary files /dev/null and b/QuickLocation/Assets.xcassets/VipRecharge/separator.imageset/Rectangle 42118@3x.png differ diff --git a/QuickLocation/Assets.xcassets/VipRecharge/wechat.imageset/Contents.json b/QuickLocation/Assets.xcassets/VipRecharge/wechat.imageset/Contents.json new file mode 100644 index 0000000..cb4bb44 --- /dev/null +++ b/QuickLocation/Assets.xcassets/VipRecharge/wechat.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "wechat@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "wechat@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/VipRecharge/wechat.imageset/wechat@2x.png b/QuickLocation/Assets.xcassets/VipRecharge/wechat.imageset/wechat@2x.png new file mode 100644 index 0000000..1717ccb Binary files /dev/null and b/QuickLocation/Assets.xcassets/VipRecharge/wechat.imageset/wechat@2x.png differ diff --git a/QuickLocation/Assets.xcassets/VipRecharge/wechat.imageset/wechat@3x.png b/QuickLocation/Assets.xcassets/VipRecharge/wechat.imageset/wechat@3x.png new file mode 100644 index 0000000..965b5db Binary files /dev/null and b/QuickLocation/Assets.xcassets/VipRecharge/wechat.imageset/wechat@3x.png differ diff --git a/QuickLocation/Manager/App/ApiManager.swift b/QuickLocation/Manager/App/ApiManager.swift index 25e32c5..c601a63 100644 --- a/QuickLocation/Manager/App/ApiManager.swift +++ b/QuickLocation/Manager/App/ApiManager.swift @@ -101,10 +101,10 @@ extension ApiManager { handlePopView(message) default: /// 统一提示错误 -// let code = GatewayStatusCode(rawValue: code ?? -9999) ?? .unknownError -// if code == .unknownError, handle { + let code = GatewayStatusCode(rawValue: code ?? -9999) ?? .unknownError + if code == .unknownError, handle { DLToast.show(text: combineMessage) -// } + } } return .failure(handleError(with: code, domain: "Data Error", message: combineMessage, data: response)) case let .failure(error): diff --git a/QuickLocation/Manager/App/RouterManager.swift b/QuickLocation/Manager/App/RouterManager.swift index 565a6e6..22d445e 100644 --- a/QuickLocation/Manager/App/RouterManager.swift +++ b/QuickLocation/Manager/App/RouterManager.swift @@ -23,6 +23,8 @@ enum Route: String { case scan = "scan" /// 圈子图标 case groupIconList = "groupIconList" + /// 会员充值 + case vipRecharge = "vipRecharge" } extension Route: RouterTarget { @@ -127,11 +129,16 @@ extension AppRouter: AppRouterProtocol { ScanVC() } - // MARK: - 扫一扫 + // MARK: - 圈子图标列表 AppRouter.register(Route.groupIconList) { url, parameters in let vc = GroupIconListVC(iconIndex: parameters["iconIndex"].safeString) return vc } + + // MARK: - 充值会员 + AppRouter.register(Route.vipRecharge) { url, parameters in + VipRechargeVC() + } } } diff --git a/QuickLocation/Section/Group/CreateGroup/CreateGroupVC.swift b/QuickLocation/Section/Group/CreateGroup/CreateGroupVC.swift index 0fdf944..915c3f1 100644 --- a/QuickLocation/Section/Group/CreateGroup/CreateGroupVC.swift +++ b/QuickLocation/Section/Group/CreateGroup/CreateGroupVC.swift @@ -8,6 +8,7 @@ import UIKit import RxSwift import RxCocoa +import RxDataSources class CreateGroupVC: BaseViewController { @@ -17,16 +18,57 @@ class CreateGroupVC: BaseViewController { rootView = CreateGroupView(frame: UIScreen.main.bounds) view = rootView } + + private var viewModel = CreateGroupViewModel() override func viewDidLoad() { super.viewDidLoad() + bindViewModel() + reactiveAction() + + viewModel.loadData() + } + + private func reactiveAction() { rootView.groupIconInputView.rx.tapGesture.subscribe { _ in let vc = GroupIconListVC(iconIndex: "1") vc.onSelectIcon = { index in - + self.viewModel.iconIndex = index + self.rootView.groupIconImgView.image = UIImage(named: "GroupIcon/\(index)") } self.navigationController?.pushViewController(vc, animated: true) }.disposed(by: disposeBag) + + rootView.submitBtn.rx.tap.subscribe(onNext: { _ in + self.viewModel.requestCreateGroup() + }).disposed(by: disposeBag) } + + private func bindViewModel() { + rootView.groupNameTF.rx.text.orEmpty + .bind(to: viewModel.groupName) + .disposed(by: disposeBag) + + rootView.groupContentTV.rx.text.orEmpty + .bind(to: viewModel.groupDesc) + .disposed(by: disposeBag) + + viewModel.output.sectionedItems + .bind(to: rootView.tagView.rx.items(dataSource: dataSource)) + .disposed(by: disposeBag) + + rootView.tagView.rx.modelSelected(String.self) + .subscribe(viewModel.cellAction.inputs) + .disposed(by: disposeBag) + } + + // MARK: - dataSource + private lazy var dataSource: RxCollectionViewSectionedReloadDataSource = { + RxCollectionViewSectionedReloadDataSource { datasource, collectionView, indexPath, item in + let cell: TagCell = collectionView.dequeueReusableCell(for: indexPath) + cell.configure(item, isSelected: self.viewModel.isSelected(tag: item)) + return cell + } + }() } diff --git a/QuickLocation/Section/Group/CreateGroup/CreateGroupView.swift b/QuickLocation/Section/Group/CreateGroup/CreateGroupView.swift index 2733efa..9329716 100644 --- a/QuickLocation/Section/Group/CreateGroup/CreateGroupView.swift +++ b/QuickLocation/Section/Group/CreateGroup/CreateGroupView.swift @@ -14,8 +14,6 @@ class CreateGroupView: UIView { var disposeBag = DisposeBag() private let limitCount = 50 - private let tagList = ["私密", "游戏", "运动", "美食", - "自驾", "聚会", "旅行", "学习"] private func setupRx() { groupNameTF.rx.text.orEmpty @@ -385,9 +383,7 @@ class CreateGroupView: UIView { let cv = UICollectionView(frame: .zero, collectionViewLayout: layout) cv.backgroundColor = .clear cv.isScrollEnabled = false - cv.register(TagCell.self, forCellWithReuseIdentifier: TagCell.reuseId) - cv.delegate = self - cv.dataSource = self + cv.register(TagCell.self) return cv }() @@ -415,7 +411,6 @@ class CreateGroupView: UIView { backgroundColor = .white setupUI() setupRx() - tagView.reloadData() } required init?(coder aDecoder: NSCoder) { @@ -423,26 +418,6 @@ class CreateGroupView: UIView { } } -// MARK: - UICollectionViewDelegate, UICollectionViewDataSource -extension CreateGroupView: UICollectionViewDelegate, UICollectionViewDataSource { - - func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return tagList.count - } - - func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TagCell.reuseId, for: indexPath) as! TagCell - cell.configure(tagList[indexPath.item], isSelected: false) - return cell - } - - func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - if let cell = collectionView.cellForItem(at: indexPath) as? TagCell { - cell.toggleSelection() - } - } -} - // MARK: - TagCell final class TagCell: UICollectionViewCell { static let reuseId = "TagCell" diff --git a/QuickLocation/Section/Group/CreateGroup/CreateGroupViewModel.swift b/QuickLocation/Section/Group/CreateGroup/CreateGroupViewModel.swift new file mode 100644 index 0000000..2236c3b --- /dev/null +++ b/QuickLocation/Section/Group/CreateGroup/CreateGroupViewModel.swift @@ -0,0 +1,105 @@ +// +// CreateGroupViewModel.swift +// QuickLocation +// +// Created by 八条 on 2026/6/3. +// + +import RxSwift +import RxCocoa +import RxDataSources +import SwiftyUserDefaults + +typealias GroupTagListSectionModel = SectionModel + +class CreateGroupViewModel { + struct Input { + + } + struct Output { + var sectionedItems: Observable<[GroupTagListSectionModel]> + } + + let input: Input + let output: Output + var disposeBag = DisposeBag() + + private let sectionedItems = PublishSubject<[GroupTagListSectionModel]>() + + private let tagList = ["私密", "游戏", "运动", "美食", + "自驾", "聚会", "旅行", "学习"] + + var selectedTagList: [String] = [] { + didSet { + loadData() + } + } + + var iconIndex = 1 + let groupName = BehaviorRelay(value: "") + let groupDesc = BehaviorRelay(value: "") + + // MARK: - Cell点击 + lazy var cellAction: Action = { this in + return Action { tag in + if let idx = this.selectedTagList.firstIndex(of: tag) { + this.selectedTagList.remove(at: idx) + } else { + this.selectedTagList.append(tag) + } + return .empty() + } + }(self) + + /// 当前tag是否选中 + func isSelected(tag: String) -> Bool { + return selectedTagList.first(where: { tag == $0 }) != nil + } + + // MARK: - 加载数据 + func loadData() { + sectionedItems.onNext(tagList.mapSection()) + } + + // MARK: - Request + func requestCreateGroup() { + let name = groupName.value.trimmingCharacters(in: .whitespacesAndNewlines) + guard !name.isEmpty else { + DLToast.show(text: "请输入圈子名称") + return + } + + let desc = groupDesc.value.trimmingCharacters(in: .whitespacesAndNewlines) + guard !desc.isEmpty else { + DLToast.show(text: "请输入圈子描述") + return + } + + DLToast.showLoading() + GroupService.operate(opType: "create", + requestData: ["group_name": name, + "icon_index": iconIndex, + "description": desc, + "labels": selectedTagList]).subscribe(onNext: { response in + DLToast.showSuccess(text: "创建成功") { + AppRouter.shared.popOrDismiss() + } + }, onError: { (error) in + guard let code = error.underlyingError?.code else { return } + if code == 20009 { // "您创建的圈子个数已达上限,请升级会员等级" + CreateGroupVipPopView.show() + } + else { + DLToast.show(text: error.localizedDescription) + } + }).disposed(by: disposeBag) + } + + // MARK: - init + init() { + input = Input() + output = Output( + sectionedItems: sectionedItems.asObservable() + ) + } +} diff --git a/QuickLocation/Section/Group/CreateGroup/CreateGroupVipPopView.swift b/QuickLocation/Section/Group/CreateGroup/CreateGroupVipPopView.swift new file mode 100644 index 0000000..6533321 --- /dev/null +++ b/QuickLocation/Section/Group/CreateGroup/CreateGroupVipPopView.swift @@ -0,0 +1,119 @@ +// +// CreateGroupVipPopView.swift +// QuickLocation +// +// Created by 八条 on 2026/6/3. +// + +import UIKit +import RxSwift +import RxCocoa + +class CreateGroupVipPopView: UIView { + + private static let shared = CreateGroupVipPopView(frame: CGRect(origin: .zero, size: kScreenSize)) + + var disposeBag = DisposeBag() + + static func show() { + guard let superView = kKeyWindow else { + return + } + + if CreateGroupVipPopView.shared.superview != nil { + CreateGroupVipPopView.shared.removeFromSuperview() + CreateGroupVipPopView.shared.bgView.frame = .zero + } + CreateGroupVipPopView.shared.bgView.alpha = 1 + CreateGroupVipPopView.shared.bgView.frame = CGRect(x: 0, y: 0, width: kScreenWidth, height: kScreenHeight) + superView.addSubview(CreateGroupVipPopView.shared) + superView.bringSubviewToFront(CreateGroupVipPopView.shared) + + UIView.animate(withDuration: 0.25) { + CreateGroupVipPopView.shared.bgView.alpha = 1 + } + } + + /// 关闭 + static func dismiss() { + guard CreateGroupVipPopView.shared.superview != nil else { return } + + UIView.animate(withDuration: 0.25, delay: 0, options: [.curveEaseIn]) { + CreateGroupVipPopView.shared.bgView.alpha = 0 + } completion: { _ in + CreateGroupVipPopView.shared.removeFromSuperview() + } + } + + private func setupRx() { + upgradedBtn.rx.tap.subscribe(onNext: { _ in + CreateGroupVipPopView.dismiss() + AppRouter.push(Route.vipRecharge) + }).disposed(by: disposeBag) + + closeBtn.rx.tap.subscribe(onNext: { _ in + CreateGroupVipPopView.dismiss() + }).disposed(by: disposeBag) + } + + private lazy var bgView: UIView = { + let view = UIView() + view.backgroundColor = .black.withAlphaComponent(0.5) + return view + }() + + lazy var vipImgView: UIImageView = { + let view = UIImageView() + view.image = UIImage(named: "Group/vip_pop") + return view + }() + + lazy var upgradedBtn: UIButton = { + let btn = UIButton(type: .custom) + btn.backgroundColor = .clear + btn.setBackgroundImage(UIImage(named: "Group/upgrade_bg"), for: .normal) + return btn + }() + + lazy var closeBtn: UIButton = { + let btn = UIButton(type: .custom) + btn.backgroundColor = .clear + btn.setBackgroundImage(UIImage(named: "Group/close"), for: .normal) + btn.extendEdgeInsets = UIEdgeInsets(top: 10, left: 100, bottom: 100, right: 100) + return btn + }() + + // MARK: - Init + override init(frame: CGRect) { + super.init(frame: frame) + backgroundColor = .clear + addSubview(bgView) + bgView.addSubview(vipImgView) + bgView.addSubview(upgradedBtn) + bgView.addSubview(closeBtn) + + vipImgView.layoutChain + .centerY() + .edgesHorzontal(25) + .heightToWidth(814/648) + + upgradedBtn.layoutChain + .topToBottomOfView(vipImgView, offset: -20) + .centerX() + .width(240) + .height(60) + + closeBtn.layoutChain + .topToBottomOfView(upgradedBtn, offset: 15) + .centerX() + .width(22) + .height(22) + + setupRx() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/QuickLocation/Section/Group/CreateGroup/GroupIconListVC.swift b/QuickLocation/Section/Group/CreateGroup/GroupIconListVC.swift index 3b63fc0..cee25ae 100644 --- a/QuickLocation/Section/Group/CreateGroup/GroupIconListVC.swift +++ b/QuickLocation/Section/Group/CreateGroup/GroupIconListVC.swift @@ -6,6 +6,8 @@ // import UIKit +import RxSwift +import RxCocoa class GroupIconListVC: BaseViewController { @@ -23,6 +25,13 @@ class GroupIconListVC: BaseViewController { super.viewDidLoad() rootView.selectedIndex = iconIndex.integer rootView.iconCollectionView.delegate = self + + rootView.doneBtn.rx.tap.subscribe(onNext: { _ in + if let onSelectIcon = self.onSelectIcon { + onSelectIcon(self.rootView.selectedIndex) + AppRouter.shared.popOrDismiss() + } + }).disposed(by: disposeBag) } // MARK: - Init diff --git a/QuickLocation/Section/Group/CreateGroup/GroupIconListView.swift b/QuickLocation/Section/Group/CreateGroup/GroupIconListView.swift index e511783..760c5fd 100644 --- a/QuickLocation/Section/Group/CreateGroup/GroupIconListView.swift +++ b/QuickLocation/Section/Group/CreateGroup/GroupIconListView.swift @@ -34,6 +34,7 @@ class GroupIconListView: UIView { addSubview(navBarView) navBarView.addSubview(navTitleLabel) navBarView.addSubview(backBtn) + navBarView.addSubview(doneBtn) addSubview(selectedIconView) addSubview(titleLab) addSubview(iconCollectionView) @@ -57,6 +58,10 @@ class GroupIconListView: UIView { .width(24) .height(24) + doneBtn.layoutChain + .centerY(navTitleLabel) + .right(15) + selectedIconView.layoutChain .topToBottomOfView(navBarView, offset: 30) .centerX() @@ -71,6 +76,8 @@ class GroupIconListView: UIView { .topToBottomOfView(titleLab, offset: 13) .edgesHorzontal(40) .bottom(kSafeBottomMargin + 10) + + doneBtn.sizeToFit() } lazy var navBgView: UIImageView = { @@ -102,6 +109,15 @@ class GroupIconListView: UIView { return btn }() + lazy var doneBtn: UIButton = { + let btn = UIButton(type: .custom) + btn.setTitle("完成", for: .normal) + btn.setTitleColor(ThemeManager.shared.color.titleAuxColor, for: .normal) + btn.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium) + btn.extendEdgeInsets = UIEdgeInsets(top: 54, left: 100, bottom: 100, right: 15) + return btn + }() + lazy var selectedIconView: UIImageView = { let view = UIImageView() view.cornerRadius = 40 diff --git a/QuickLocation/Section/VipRecharge/VipExpenseModel.swift b/QuickLocation/Section/VipRecharge/VipExpenseModel.swift new file mode 100644 index 0000000..6c82e41 --- /dev/null +++ b/QuickLocation/Section/VipRecharge/VipExpenseModel.swift @@ -0,0 +1,88 @@ +// +// VipExpenseModel.swift +// QuickLocation +// +// Created by 八条 on 2026/6/3. +// + +import ObjectMapper +import RxDataSources + +struct VipExpenseResponse: BaseModelProtocol { + // 状态码 + var code: String? + // 消息 + var message: String? + // + var list: [VipExpenseModel] = [] + + init?(map: Map) {} + + mutating func mapping(map: Map) { + code <- map["code"] + message <- map["msg"] + list <- map["data"] + } +} + +struct VipExpenseModel: Mappable, Equatable { + /// + var goods_id: String = "" + /// 资费名字 + var goods_name: String = "" + /// 资费 + var price: String = "" + /// 资费原价 + var origin_price: String = "" + /// 资费标签 + var tips: String = "" + /// 资费说明 + var tips2: String = "" + /// 单位 月1m 年 12m 得换算 + var value: String = "" + var unit: String { + guard value.hasSuffix("m") else { return "" } + let monthStr = value.replacingOccurrences(of: "m", with: "") + guard !monthStr.isEmpty, let month = Int(monthStr) else { return "" } + + if month >= 12 { + let num = month / 12 + return num > 1 ? "/\(num)年" : "/年" + } else if month % 3 == 0 { + let num = month / 3 + return num > 1 ? "/\(num)季" : "/季" + } else { + return month > 1 ? "/\(month)月" : "/月" + } + } + + /// 支付方式 + var pay_type: String = "" + + /// 选中状态 + var checked: Bool = false + + init?(map: Map) { + + } + + mutating func mapping(map: Map) { + goods_id <- map["goods_id"] + goods_name <- map["goods_name"] + price <- map["price"] + origin_price <- map["origin_price"] + tips <- map["tips"] + tips2 <- map["tips2"] + value <- map["value"] + checked <- map["checked"] + pay_type <- map["pay_type"] + } +} + +extension VipExpenseModel: IdentifiableType { + public typealias Identity = String + + public var identity: String { + return goods_id + } +} diff --git a/QuickLocation/Section/VipRecharge/VipRechargeVC.swift b/QuickLocation/Section/VipRecharge/VipRechargeVC.swift new file mode 100644 index 0000000..1d9881a --- /dev/null +++ b/QuickLocation/Section/VipRecharge/VipRechargeVC.swift @@ -0,0 +1,71 @@ +// +// VipRechargeVC.swift +// QuickLocation +// +// Created by 八条 on 2026/6/3. +// + +import UIKit +import RxSwift +import RxCocoa +import RxDataSources + +class VipRechargeVC: BaseViewController { + + fileprivate var rootView: VipRechargeView! + + override func loadView() { + rootView = VipRechargeView(frame: UIScreen.main.bounds) + view = rootView + } + + private var viewModel = VipRechargeVM() + + override func viewDidLoad() { + super.viewDidLoad() + bindViewModel() + reactiveAction() + requestRechargeInfo() + } + + private func bindViewModel() { + viewModel.output.sectionedItems + .bind(to: rootView.expenseCollectionView.rx.items(dataSource: dataSource)) + .disposed(by: disposeBag) + } + + private func reactiveAction() { + Observable.zip( + rootView.expenseCollectionView.rx.itemSelected, + rootView.expenseCollectionView.rx.modelSelected(VipExpenseModel.self) + ).subscribe(onNext: { indexPath, model in + self.viewModel.selectedIndex = indexPath.row + self.rootView.setupPayTypes(model.pay_type) + self.rootView.animatePrice(to: model.price) + self.rootView.discountLab.text = self.viewModel.discountPriceString + self.viewModel.refreshData() + }) + .disposed(by: disposeBag) + } + + // MARK: - dataSource + private lazy var dataSource: RxCollectionViewSectionedReloadDataSource = { + RxCollectionViewSectionedReloadDataSource { datasource, collectionView, indexPath, model in + let cell: ExpenseCell = collectionView.dequeueReusableCell(for: indexPath) + cell.configure(model: model, isSelected: self.viewModel.selectedIndex == indexPath.row) + return cell + } + }() + + // MARK: - API + private func requestRechargeInfo() { + DLToast.showLoading() + SystemService.rechargeInfo(type: "member").subscribe(onNext: { [weak self] response in + guard let self = self else { return } + self.viewModel.loadData(list: response.list) + self.rootView.setupPayTypes(self.viewModel.payType) + self.rootView.animatePrice(to: self.viewModel.price) + self.rootView.discountLab.text = self.viewModel.discountPriceString + }).disposed(by: disposeBag) + } +} diff --git a/QuickLocation/Section/VipRecharge/VipRechargeVM.swift b/QuickLocation/Section/VipRecharge/VipRechargeVM.swift new file mode 100644 index 0000000..7581e60 --- /dev/null +++ b/QuickLocation/Section/VipRecharge/VipRechargeVM.swift @@ -0,0 +1,72 @@ +// +// VipRechargeVM.swift +// QuickLocation +// +// Created by 八条 on 2026/6/3. +// + +import RxSwift +import RxCocoa +import RxDataSources +import SwiftyUserDefaults + +typealias ExpenseListSectionModel = SectionModel + +class VipRechargeVM { + struct Input { + + } + struct Output { + var sectionedItems: Observable<[ExpenseListSectionModel]> + } + + let input: Input + let output: Output + var disposeBag = DisposeBag() + + private let sectionedItems = PublishSubject<[ExpenseListSectionModel]>() + + var selectedIndex: Int = -1 + var list: [VipExpenseModel] = [] + var payType: String { + guard list.count > 0 else { return "" } + return list[selectedIndex].pay_type + } + + var price: String { + guard list.count > 0 else { return "" } + return list[selectedIndex].price + } + + var discountPriceString: String { + guard list.count > 0 else { return "已优惠0元" } + + let price = list[selectedIndex].price + let oPrice = list[selectedIndex].origin_price + + guard oPrice.int >= price.int else { return "已优惠0元" } + + let diff = oPrice.double - price.double + let diffStr = diff.truncatingRemainder(dividingBy: 1) == 0 ? String(format: "%.0f", diff) : String(diff) + return "已优惠\(diffStr)元" + } + + // MARK: - 加载数据 + func loadData(list: [VipExpenseModel]) { + self.list = list + selectedIndex = list.firstIndex(where: { $0.checked == true }) ?? 0 + sectionedItems.onNext(list.mapSection()) + } + + func refreshData() { + sectionedItems.onNext(list.mapSection()) + } + + // MARK: - init + init() { + input = Input() + output = Output( + sectionedItems: sectionedItems.asObservable() + ) + } +} diff --git a/QuickLocation/Section/VipRecharge/VipRechargeView.swift b/QuickLocation/Section/VipRecharge/VipRechargeView.swift new file mode 100644 index 0000000..46e8ee0 --- /dev/null +++ b/QuickLocation/Section/VipRecharge/VipRechargeView.swift @@ -0,0 +1,684 @@ +// +// VipRechargeView.swift +// QuickLocation +// +// Created by 八条 on 2026/6/3. +// + +import UIKit +import RxSwift +import RxCocoa + +class VipRechargeView: UIView { + + var disposeBag = DisposeBag() + + private func setupRx() { + backBtn.rx.tap.subscribe(onNext: { _ in + AppRouter.shared.popOrDismiss() + }).disposed(by: disposeBag) + + agreementLab.rx.tapGesture.subscribe { _ in + // TODO: 打开会员服务协议 + }.disposed(by: disposeBag) + } + + private func setupUI() { + addSubview(scrollView) + scrollView.addSubview(scrollContentView) + scrollContentView.addSubview(headerBgImgView) + scrollContentView.addSubview(cornerView) + scrollContentView.addSubview(expenseCollectionView) + scrollContentView.addSubview(vipRightsView) + scrollContentView.addSubview(agreementLab) + scrollContentView.addSubview(tipsLab) + + vipRightsView.addSubview(vipRightsTitleView) + + addSubview(bottomView) + bottomView.addSubview(payTypeStackView) + bottomView.addSubview(payBtnView) + bottomView.addSubview(payPriceView) + + addSubview(navBarView) + navBarView.addSubview(navTitleLabel) + addSubview(backBtn) + + navBarView.layoutChain + .edges(excludingEdge: .bottom) + .height(kNaviHeight) + + navTitleLabel.layoutChain + .top(kStatusBarHeight + 12) + .centerY(backBtn) + .centerX() + + backBtn.layoutChain + .top(kStatusBarHeight + 12) + .left(15) + .width(24) + .height(24) + + bottomView.layoutChain + .edgesHorzontal() + .heightToWidth(144/375) + .bottom() + + payTypeStackView.layoutChain + .top(25) + .edgesHorzontal(16) + .centerX() + + payBtnView.layoutChain + .edgesHorzontal(16) + .heightToWidth(50/343) + .centerY() + + payPriceView.layoutChain + .left(32) + .centerY(payBtnView, offset: -7) + .height(30) + + scrollView.layoutChain + .edges(excludingEdge: .bottom) + .bottomToTopOfView(bottomView) + + scrollContentView.layoutChain + .edges() + .widthToView(scrollView) + + headerBgImgView.layoutChain + .top(-kStatusBarHeight) + .edgesHorzontal() + // .edges(excludingEdge: .bottom) + .heightToWidth(267/375) + + cornerView.layoutChain + .topToBottomOfView(headerBgImgView, offset: -20) + .edges(excludingEdge: .top) + + let expenseCollectionViewHeight = (kScreenWidth - 32 - 32) / 3 * (144/110) + 10 + expenseCollectionView.layoutChain + .topToView(cornerView, offset: -43) + .edgesHorzontal() + .height(expenseCollectionViewHeight * 1.1) + + vipRightsView.layoutChain + .topToBottomOfView(expenseCollectionView, offset: 18) + .edgesHorzontal(16) + .height(302) + + vipRightsTitleView.layoutChain + .top(14) + .centerX() + + agreementLab.layoutChain + .topToBottomOfView(vipRightsView, offset: 6) + .leftToView(vipRightsView) + .rightToView(vipRightsView) + + tipsLab.layoutChain + .topToBottomOfView(agreementLab, offset: 4) + .leftToView(vipRightsView) + .rightToView(vipRightsView) + .bottom(10) + } + + lazy var navBarView: UIView = { + let view = UIView() + view.backgroundColor = .white + view.alpha = 0 + return view + }() + + lazy var navTitleLabel: UILabel = { + let label = UILabel() + label.text = "升级会员" + label.font = .systemFont(ofSize: 18, weight: .medium) + label.textColor = ThemeManager.shared.color.titleAuxColor + label.textAlignment = .center + return label + }() + + lazy var backBtn: UIButton = { + let btn = UIButton(type: .custom) + btn.setImage(UIImage(named: "Common/back"), for: .normal) + btn.extendEdgeInsets = UIEdgeInsets(top: 54, left: 15, bottom: 100, right: 100) + return btn + }() + + lazy var scrollView: UIScrollView = { + let view = UIScrollView() + view.backgroundColor = UIColor(hexStr: "#F5FBFB") + view.showsVerticalScrollIndicator = false + view.delegate = self + view.bounces = false + return view + }() + + lazy var scrollContentView: UIView = { + let view = UIView() + view.backgroundColor = .clear + return view + }() + + lazy var headerBgImgView: UIImageView = { + let view = UIImageView() + view.image = UIImage(named: "VipRecharge/header_bg") + return view + }() + + lazy var cornerView: UIView = { + let view = UIView() + view.backgroundColor = UIColor(hexStr: "#F5FBFB") + view.clipsToBounds = false + return view + }() + + /// 资费 + lazy var expenseCollectionView: UICollectionView = { + let layout = UICollectionViewFlowLayout() + let spacing: CGFloat = 16 + let cvWidth = kScreenWidth - 32 + let itemW = (cvWidth - spacing * 2) / 3 + layout.itemSize = CGSize(width: itemW, height: itemW * (144/110) + 10) + layout.sectionInset = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16) + layout.minimumLineSpacing = spacing + layout.scrollDirection = .horizontal + + let cv = UICollectionView(frame: .zero, collectionViewLayout: layout) + cv.backgroundColor = .clear + cv.showsHorizontalScrollIndicator = false + cv.register(ExpenseCell.self) + return cv + }() + + /// 会员权益 + lazy var vipRightsView: UIView = { + let view = UIView() + view.backgroundColor = .white + view.cornerRadius = 10 + return view + }() + + lazy var vipRightsTitleView: UIView = { + let view = UIView() + view.backgroundColor = .clear + + let titleLab = UILabel() + titleLab.text = "会员权益" + titleLab.font = .systemFont(ofSize: 14, weight: .bold) + titleLab.textColor = UIColor(hexStr: "#3D3D3D") + titleLab.textAlignment = .center + view.addSubview(titleLab) + titleLab.layoutChain + .edgesVertical() + .centerX() + + let leftLine1 = UIView() + leftLine1.backgroundColor = UIColor(hexStr: "#B7F34E") + leftLine1.cornerRadius = 1 + view.addSubview(leftLine1) + leftLine1.layoutChain + .left() + .width(2) + .height(10) + .centerY() + + let leftLine2 = UIView() + leftLine2.backgroundColor = UIColor(hexStr: "#B7F34E") + leftLine2.cornerRadius = 1 + view.addSubview(leftLine2) + leftLine2.layoutChain + .leftToRightOfView(leftLine1, offset: 4) + .width(2) + .edgesVertical() + .rightToLeftOfView(titleLab, offset: -10) + + let leftLine3 = UIView() + leftLine3.backgroundColor = UIColor(hexStr: "#B7F34E") + leftLine3.cornerRadius = 1 + view.addSubview(leftLine3) + leftLine3.layoutChain + .leftToRightOfView(titleLab, offset: 10) + .width(2) + .edgesVertical() + + let leftLine4 = UIView() + leftLine4.backgroundColor = UIColor(hexStr: "#B7F34E") + leftLine4.cornerRadius = 1 + view.addSubview(leftLine4) + leftLine4.layoutChain + .leftToRightOfView(leftLine3, offset: 4) + .width(2) + .height(10) + .centerY() + + return view + }() + + lazy var groupCountView: UIView = { + let view = UIView() + view.backgroundColor = .clear + + // 创建圈子 + let createCountView = UIView() + createCountView.backgroundColor = .clear + + let createIcon = UIImageView(image: UIImage(named: "VipRecharge/create_count")) + + let createBgImg = UIImageView(image: UIImage(named: "VipRecharge/count_bg")) + + let createUnitLab = UILabel() + createUnitLab.text = "个" + createUnitLab.font = .systemFont(ofSize: 10, weight: .medium) + createUnitLab.textColor = UIColor(hexStr: "#1A1A1A") + + let createTitleLab = UILabel() + createTitleLab.text = "创建圈子" + createTitleLab.font = .systemFont(ofSize: 12, weight: .medium) + createTitleLab.textColor = UIColor(hexStr: "#1A1A1A") + + createCountView.addSubview(createIcon) + createCountView.addSubview(createBgImg) + createCountView.addSubview(createUnitLab) + createCountView.addSubview(createTitleLab) + createCountView.addSubview(createCountLab) + + createIcon.layoutChain + .top() + .centerX() + .width(34).height(34) + + createBgImg.layoutChain + .top(8) + + createCountLab.layoutChain + .topToBottomOfView(createIcon) + + // 分隔线 + let separator1 = UIImageView(image: UIImage(named: "VipRecharge/separator")) + + // 圈子人数 + + let separator2 = UIImageView(image: UIImage(named: "VipRecharge/separator")) + + return view + }() + + lazy var createCountLab: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 24, weight: .bold) + label.textColor = UIColor(hexStr: "#FF4F44") + return label + }() + + lazy var agreementLab: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 10, weight: .medium) + label.textColor = UIColor(hexStr: "#767676") + label.isUserInteractionEnabled = true + + let text = "*开通前请阅读《会员服务协议》" + let attr = NSMutableAttributedString(string: text) + let range = (text as NSString).range(of: "《会员服务协议》") + attr.addAttribute(.foregroundColor, value: UIColor(hexStr: "#2DBBFF"), range: range) + label.attributedText = attr + return label + }() + + lazy var tipsLab: UILabel = { + let label = UILabel() + label.text = "*根据相关隐私保护的规定,本产品功能需双方下载并授权同意后再使用,请在自己的设备上使用,不得在未经过对方同意和授权的情况下使用,仅限家庭/亲人/朋友/情侣等熟人间使用。" + label.font = .systemFont(ofSize: 10, weight: .medium) + label.textColor = UIColor(hexStr: "#767676") + label.numberOfLines = 0 + return label + }() + + lazy var payTypeStackView: UIStackView = { + let stack = UIStackView() + stack.axis = .horizontal + stack.spacing = 30 +// stack.distribution = .fillEqually + stack.alignment = .center + return stack + }() + + var selectedPayTypeTag: Int = 0 + + /// 根据 pay_type 字符串动态构建支付方式 (格式: "alipay,weixin") + func setupPayTypes(_ payTypeStr: String) { + payTypeStackView.arrangedSubviews.forEach { $0.removeFromSuperview() } + let types = payTypeStr.components(separatedBy: ",").map { $0.trimmingCharacters(in: .whitespaces) } + let payTypeMap: [(String, String)] = types.compactMap { + if $0 == "weixin" { return ("wechat", "微信支付") } + if $0 == "alipay" { return ("alipay", "支付宝支付") } + return nil + } + guard !payTypeMap.isEmpty else { return } + + selectedPayTypeTag = 0 + for (idx, (icon, name)) in payTypeMap.enumerated() { + let isSelected = idx == 0 + let view = makePayTypeView(tag: idx, icon: icon, name: name, isSelected: isSelected) + payTypeStackView.addArrangedSubview(view) + } + } + + private func makePayTypeView(tag: Int, icon: String, name: String, isSelected: Bool) -> UIView { + let view = UIView() + view.tag = tag + view.isUserInteractionEnabled = true + + let checkbox = UIImageView(image: UIImage(named: isSelected ? "VipRecharge/checkbox_on" : "VipRecharge/checkbox")) + checkbox.contentMode = .scaleAspectFit + checkbox.tag = 100 + view.addSubview(checkbox) + checkbox.layoutChain.left().centerY().width(18).height(18) + + let payIcon = UIImageView(image: UIImage(named: "VipRecharge/\(icon)")) + payIcon.contentMode = .scaleAspectFit + view.addSubview(payIcon) + payIcon.layoutChain.leftToRightOfView(checkbox, offset: 8).centerY().width(22).height(22) + + let nameLab = UILabel() + nameLab.text = name + nameLab.font = .systemFont(ofSize: 14, weight: .medium) + nameLab.textColor = UIColor(hexStr: "#1A1A1A") + view.addSubview(nameLab) + nameLab.layoutChain.leftToRightOfView(payIcon, offset: 6).centerY() + + let tap = UITapGestureRecognizer(target: self, action: #selector(onPayTypeTap(_:))) + view.addGestureRecognizer(tap) + return view + } + + @objc private func onPayTypeTap(_ gesture: UITapGestureRecognizer) { + guard let tag = gesture.view?.tag, tag != selectedPayTypeTag else { return } + selectedPayTypeTag = tag + for view in payTypeStackView.arrangedSubviews { + let isSelected = view.tag == tag + if let checkbox = view.viewWithTag(100) as? UIImageView { + checkbox.image = UIImage(named: isSelected ? "VipRecharge/checkbox_on" : "VipRecharge/checkbox") + } + } + } + + lazy var bottomView: UIView = { + let view = UIView() + view.backgroundColor = .white + view.layer.shadowColor = UIColor.black.cgColor + view.layer.shadowOffset = CGSize(width: 0, height: -3) + view.layer.shadowOpacity = 0.06 + return view + }() + + lazy var payBtnView: UIView = { + let view = UIView() + view.backgroundColor = .clear + + let bgImg = UIImageView() + bgImg.image = UIImage(named: "VipRecharge/pay_bg") + view.addSubview(bgImg) + bgImg.layoutChain.edges() + + let unlockLab = UILabel() + unlockLab.text = "解锁会员" + unlockLab.font = .systemFont(ofSize: 16, weight: .heavy) + unlockLab.textColor = .white + view.addSubview(unlockLab) + unlockLab.layoutChain.right(21).centerY() + + return view + }() + + lazy var payPriceView: UIView = { + let view = UIView() + view.backgroundColor = .clear + + let titleLab = UILabel() + titleLab.text = "合计" + titleLab.font = .systemFont(ofSize: 14, weight: .bold) + titleLab.textColor = UIColor(hexStr: "#1A1A1A") + view.addSubview(titleLab) + titleLab.layoutChain + .left() + .bottom() + + let symbolLab = UILabel() + symbolLab.text = "¥" + symbolLab.font = .systemFont(ofSize: 12, weight: .heavy) + symbolLab.textColor = UIColor(hexStr: "#FF3B05") + view.addSubview(symbolLab) + symbolLab.layoutChain + .leftToRightOfView(titleLab) + .bottomToView(titleLab) + + view.addSubview(priceLab) + priceLab.layoutChain + .leftToRightOfView(symbolLab) + .bottomToView(titleLab, offset: 3) + + view.addSubview(discountLab) + discountLab.layoutChain + .leftToRightOfView(priceLab, offset: 5) + .bottomToView(titleLab) + .right() + + return view + }() + + lazy var priceLab: UILabel = { + let label = UILabel() + label.text = "0" + label.font = .systemFont(ofSize: 24, weight: .heavy) + label.textColor = UIColor(hexStr: "#FF3B05") + return label + }() + + /// 数字翻滚动画 — 每位数字独立向上或向下滚动 + func animatePrice(to newValue: String) { + guard let target = Double(newValue) else { + priceLab.text = newValue + return + } + let current = Double(priceLab.text ?? "0") ?? 0 + let diff = target - current + let duration = 0.6 + let steps = 30 + var step = 0 + + Timer.scheduledTimer(withTimeInterval: duration / Double(steps), repeats: true) { [weak self] t in + step += 1 + guard let self = self else { t.invalidate(); return } + if step >= steps { + t.invalidate() + self.priceLab.text = newValue + } else { + let progress = Double(step) / Double(steps) + // ease out + overshoot on increase + let eased = diff > 0 + ? 1 - pow(1 - progress, 3) + : pow(progress, 0.5) + let value = current + diff * eased + self.priceLab.text = String(format: "%.0f", value) + } + } + } + + lazy var discountLab: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 14, weight: .bold) + label.textColor = UIColor(hexStr: "#1A1A1A") + return label + }() + + override func layoutSubviews() { + super.layoutSubviews() + cornerView.setNeedsLayout() + cornerView.layoutIfNeeded() + cornerView.setCornerRadius(corners: [.topLeft, .topRight], withCornerRadii: CGSize(width: 10, height: 10)) + } + + override init(frame: CGRect) { + super.init(frame: .zero) + backgroundColor = .white + setupUI() + setupRx() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +// MARK: - ExpenseCell +final class ExpenseCell: UICollectionViewCell { + + private let tagBadgeView: UIView = { + let iv = UIView() +// iv.image = UIImage(named: "VipRecharge/expense_tips") +// iv.contentMode = .scaleAspectFill + iv.backgroundColor = UIColor(hexStr: "#FF6643") + iv.isHidden = true + return iv + }() + + private let tagLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 10, weight: .bold) + label.textColor = .white + label.textAlignment = .center + return label + }() + + private let bgImgView: UIImageView = { + let iv = UIImageView() + iv.image = UIImage(named: "VipRecharge/expense") + iv.contentMode = .scaleAspectFill + return iv + }() + + private let titleLab: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 14, weight: .bold) + label.textColor = UIColor(hexStr: "#1A1A1A") + label.textAlignment = .center + return label + }() + + lazy var priceLab: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 12, weight: .bold) + label.textColor = UIColor(hexStr: "#1A1A1A") + label.textAlignment = .center + return label + }() + + lazy var originPriceLab: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 12, weight: .bold) + label.textColor = UIColor(hexStr: "#767676") + label.textAlignment = .center + return label + }() + + lazy var tipsLab: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 10, weight: .bold) + label.textColor = UIColor(hexStr: "#1A1A1A") + label.textAlignment = .center + return label + }() + + override init(frame: CGRect) { + super.init(frame: frame) + contentView.addSubview(bgImgView) + contentView.addSubview(tagBadgeView) + tagBadgeView.addSubview(tagLabel) + contentView.addSubview(titleLab) + contentView.addSubview(priceLab) + contentView.addSubview(originPriceLab) + contentView.addSubview(tipsLab) + + tagBadgeView.layoutChain + .top().left() + + tagLabel.layoutChain + .edgesHorzontal(10) + .edgesVertical(3) + + bgImgView.layoutChain + .top(10) + .left().right().bottom() + + titleLab.layoutChain + .centerX() + .top(30) + .edgesHorzontal(2) + + priceLab.layoutChain + .centerY() + .edgesHorzontal(2) + + originPriceLab.layoutChain + .topToBottomOfView(priceLab, offset: 5) + .edgesHorzontal(2) + + tipsLab.layoutChain + .edgesHorzontal(2) + .bottom(7) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func configure(model: VipExpenseModel, isSelected: Bool) { + titleLab.text = model.goods_name + tagBadgeView.isHidden = model.tips.isEmpty + tagLabel.text = model.tips + tipsLab.text = model.tips2 + let priceAttr = NSMutableAttributedString(string: "¥", attributes: [.font: UIFont.systemFont(ofSize: 12, weight: .medium)]) + priceAttr.append(NSAttributedString(string: model.price, attributes: [.font: UIFont.systemFont(ofSize: 30, weight: .medium)])) + priceAttr.append(NSAttributedString(string: model.unit, attributes: [.font: UIFont.systemFont(ofSize: 12, weight: .medium)])) + priceLab.attributedText = priceAttr + originPriceLab.text = "¥" + model.origin_price + originPriceLab.setupStrikethroughStyle() + setSelected(isSelected, animated: false) + } + + func setSelected(_ selected: Bool, animated: Bool) { + let scale: CGFloat = selected ? 1.1 : 1.0 + let imageName = selected ? "VipRecharge/expense_on" : "VipRecharge/expense" + bgImgView.image = UIImage(named: imageName) + titleLab.textColor = selected ? UIColor(hexStr: "#16B3FF") : UIColor(hexStr: "#1A1A1A") + priceLab.textColor = selected ? UIColor(hexStr: "#FF4F44") : UIColor(hexStr: "#1A1A1A") + + let animations = { + self.transform = CGAffineTransform(scaleX: scale, y: scale) + } + if animated { + UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseOut, animations: animations) + } else { + animations() + } + } + + override func layoutSubviews() { + super.layoutSubviews() + tagBadgeView.setNeedsLayout() + tagBadgeView.layoutIfNeeded() + tagBadgeView.setCornerRadius(corners: [.topLeft, .bottomRight], withCornerRadii: CGSize(width: 10, height: 10)) + } +} + +extension VipRechargeView: UIScrollViewDelegate { + func scrollViewDidScroll(_ scrollView: UIScrollView) { + let maxY = scrollView.contentOffset.y + let alpha = maxY / kNaviHeight + navBarView.alpha = alpha < 0.0 ? 0.0 : alpha + } +} diff --git a/QuickLocation/Section/VipRecharge/VipRechargeViewModel.swift b/QuickLocation/Section/VipRecharge/VipRechargeViewModel.swift new file mode 100644 index 0000000..c7f44ab --- /dev/null +++ b/QuickLocation/Section/VipRecharge/VipRechargeViewModel.swift @@ -0,0 +1,47 @@ +// +// VipRechargeViewModel.swift +// QuickLocation +// +// Created by 八条 on 2026/6/3. +// + +import RxSwift +import RxCocoa +import RxDataSources + +class VipRechargeViewModel { + + struct Output { + var sectionedItems: Observable<[ExpenseSectionModel]> + } + + let output: Output + private let sectionedItems = PublishSubject<[ExpenseSectionModel]>() + + private let expenseList: [ExpenseItem] = [ + ExpenseItem(title: "白银会员", tag: "推荐"), + ExpenseItem(title: "黄金会员", tag: "热门"), + ExpenseItem(title: "钻石会员", tag: "爆款"), + ExpenseItem(title: "永久会员", tag: "超值") + ] + + var selectedIndex: Int = 0 { + didSet { + loadData() + } + } + + func loadData() { + sectionedItems.onNext([SectionModel(model: "expense", items: expenseList)]) + } + + func isSelected(at index: Int) -> Bool { + return index == selectedIndex + } + + init() { + output = Output( + sectionedItems: sectionedItems.asObservable() + ) + } +} diff --git a/QuickLocation/Service/SystemService.swift b/QuickLocation/Service/SystemService.swift index 28327a5..1506a9e 100644 --- a/QuickLocation/Service/SystemService.swift +++ b/QuickLocation/Service/SystemService.swift @@ -25,4 +25,14 @@ struct SystemService { .map(SmsCodeResponse.self) .asObservable() } + + /// 充值内容 + /// - Parameters: + /// - type: 类型 member + static func rechargeInfo(type: String) -> Observable { + let api = SystemAPI.rechargeInfo(type: type).multiTarget + return APIProvider.request(token: api) + .map(VipExpenseResponse.self) + .asObservable() + } }