diff --git a/QuickLocation.xcodeproj/project.pbxproj b/QuickLocation.xcodeproj/project.pbxproj index 76d5412..e41c882 100644 --- a/QuickLocation.xcodeproj/project.pbxproj +++ b/QuickLocation.xcodeproj/project.pbxproj @@ -188,11 +188,17 @@ 30C4C01D2FDBF557009215C1 /* RemoveMemberViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30C4C01C2FDBF557009215C1 /* RemoveMemberViewModel.swift */; }; 30C4C0202FDC0EC5009215C1 /* GroupInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30C4C01F2FDC0EC5009215C1 /* GroupInfoView.swift */; }; 30C4C0222FDC0ED3009215C1 /* GroupInfoVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30C4C0212FDC0ED3009215C1 /* GroupInfoVC.swift */; }; + 30CCDE4E2FE26CEA00F5214A /* PayResultPopVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30CCDE4D2FE26CEA00F5214A /* PayResultPopVC.swift */; }; + 30CCDE512FE2785D00F5214A /* SignInVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30CCDE502FE2785D00F5214A /* SignInVC.swift */; }; + 30CCDE532FE2786600F5214A /* SignInView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30CCDE522FE2786600F5214A /* SignInView.swift */; }; + 30CCDE552FE2903100F5214A /* SignInModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30CCDE542FE2903100F5214A /* SignInModel.swift */; }; 30D87CDB2FDFA9EE00E958FD /* MQTTService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D87CDA2FDFA9EE00E958FD /* MQTTService.swift */; }; 30D87CDD2FDFF07500E958FD /* InteractionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D87CDC2FDFF07500E958FD /* InteractionView.swift */; }; 30D87CDF2FDFF1A100E958FD /* QuickMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D87CDE2FDFF1A100E958FD /* QuickMessageView.swift */; }; 30D87D042FE1336300E958FD /* NavigationVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D87D012FE1336300E958FD /* NavigationVC.swift */; }; 30D87D052FE1336300E958FD /* NavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D87D022FE1336300E958FD /* NavigationView.swift */; }; + 30D891F52FE22E0600E958FD /* OrderAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D891F42FE22E0600E958FD /* OrderAPI.swift */; }; + 30D891F72FE22E6E00E958FD /* OrderService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D891F62FE22E6E00E958FD /* OrderService.swift */; }; 30DC18522FD009CD0041DCD1 /* VipExpenseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DC18512FD009CD0041DCD1 /* VipExpenseModel.swift */; }; 30DC18542FD00C4A0041DCD1 /* VipRechargeVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DC18532FD00C4A0041DCD1 /* VipRechargeVM.swift */; }; 30DC185A2FD11E7A0041DCD1 /* WebOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DC18562FD11E7A0041DCD1 /* WebOperations.swift */; }; @@ -425,11 +431,17 @@ 30C4C01C2FDBF557009215C1 /* RemoveMemberViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveMemberViewModel.swift; sourceTree = ""; }; 30C4C01F2FDC0EC5009215C1 /* GroupInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupInfoView.swift; sourceTree = ""; }; 30C4C0212FDC0ED3009215C1 /* GroupInfoVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupInfoVC.swift; sourceTree = ""; }; + 30CCDE4D2FE26CEA00F5214A /* PayResultPopVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayResultPopVC.swift; sourceTree = ""; }; + 30CCDE502FE2785D00F5214A /* SignInVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInVC.swift; sourceTree = ""; }; + 30CCDE522FE2786600F5214A /* SignInView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInView.swift; sourceTree = ""; }; + 30CCDE542FE2903100F5214A /* SignInModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInModel.swift; sourceTree = ""; }; 30D87CDA2FDFA9EE00E958FD /* MQTTService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MQTTService.swift; sourceTree = ""; }; 30D87CDC2FDFF07500E958FD /* InteractionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractionView.swift; sourceTree = ""; }; 30D87CDE2FDFF1A100E958FD /* QuickMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickMessageView.swift; sourceTree = ""; }; 30D87D012FE1336300E958FD /* NavigationVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationVC.swift; sourceTree = ""; }; 30D87D022FE1336300E958FD /* NavigationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationView.swift; sourceTree = ""; }; + 30D891F42FE22E0600E958FD /* OrderAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderAPI.swift; sourceTree = ""; }; + 30D891F62FE22E6E00E958FD /* OrderService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderService.swift; 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 = ""; }; 30DC18552FD11E7A0041DCD1 /* NavigationTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationTitleView.swift; sourceTree = ""; }; @@ -479,15 +491,11 @@ /* Begin PBXFileSystemSynchronizedRootGroup section */ 3070777D2FD2A214004C37CC /* lotties */ = { isa = PBXFileSystemSynchronizedRootGroup; - exceptions = ( - ); path = lotties; sourceTree = ""; }; 30D87CEF2FDFF52100E958FD /* TTGTagCollectionView */ = { isa = PBXFileSystemSynchronizedRootGroup; - exceptions = ( - ); path = TTGTagCollectionView; sourceTree = ""; }; @@ -526,6 +534,7 @@ 305A74CE2FCA8C7000227D26 /* SystemAPI.swift */, 305A74CF2FCA8C7000227D26 /* UserAPI.swift */, 30BAB8502FCD331C00C33B5C /* GroupAPI.swift */, + 30D891F42FE22E0600E958FD /* OrderAPI.swift */, ); path = API; sourceTree = ""; @@ -866,6 +875,7 @@ 30A7A9102FCAEE3D00105780 /* GroupListPopView.swift */, 30D87CDE2FDFF1A100E958FD /* QuickMessageView.swift */, 30D87CDC2FDFF07500E958FD /* InteractionView.swift */, + 30CCDE4F2FE2782700F5214A /* SignIn */, ); path = Home; sourceTree = ""; @@ -892,10 +902,10 @@ 305A76352FCA8C7000227D26 /* Map */ = { isa = PBXGroup; children = ( - 30D87D032FE1336300E958FD /* Navigation */, 305A76322FCA8C7000227D26 /* CircleMember.swift */, 305A76332FCA8C7000227D26 /* MemberAnnotation.swift */, 305A76342FCA8C7000227D26 /* MemberAnnotationView.swift */, + 30D87D032FE1336300E958FD /* Navigation */, ); path = Map; sourceTree = ""; @@ -939,6 +949,7 @@ 305A763B2FCA8C7000227D26 /* SystemService.swift */, 305A763C2FCA8C7000227D26 /* UserService.swift */, 30BAB8522FCD337C00C33B5C /* GroupService.swift */, + 30D891F62FE22E6E00E958FD /* OrderService.swift */, ); path = Service; sourceTree = ""; @@ -1034,6 +1045,7 @@ 305A76692FCA8C7000227D26 /* Pop */ = { isa = PBXGroup; children = ( + 30CCDE4D2FE26CEA00F5214A /* PayResultPopVC.swift */, 30EFF3BA2FD90D7600EB35D4 /* ConfirmPopVC.swift */, 305A76642FCA8C7000227D26 /* DLAlertPopVC.swift */, 305A76652FCA8C7000227D26 /* DLCustomPopVC.swift */, @@ -1190,6 +1202,16 @@ path = GroupInfo; sourceTree = ""; }; + 30CCDE4F2FE2782700F5214A /* SignIn */ = { + isa = PBXGroup; + children = ( + 30CCDE502FE2785D00F5214A /* SignInVC.swift */, + 30CCDE522FE2786600F5214A /* SignInView.swift */, + 30CCDE542FE2903100F5214A /* SignInModel.swift */, + ); + path = SignIn; + sourceTree = ""; + }; 30D87CD52FDF9F1900E958FD /* MQTT */ = { isa = PBXGroup; children = ( @@ -1445,10 +1467,14 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-QuickLocation/Pods-QuickLocation-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); + inputPaths = ( + ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-QuickLocation/Pods-QuickLocation-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); + outputPaths = ( + ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-QuickLocation/Pods-QuickLocation-frameworks.sh\"\n"; @@ -1462,10 +1488,14 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-QuickLocation/Pods-QuickLocation-resources-${CONFIGURATION}-input-files.xcfilelist", ); + inputPaths = ( + ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-QuickLocation/Pods-QuickLocation-resources-${CONFIGURATION}-output-files.xcfilelist", ); + outputPaths = ( + ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-QuickLocation/Pods-QuickLocation-resources.sh\"\n"; @@ -1579,6 +1609,7 @@ 305A76C92FCA8C7000227D26 /* ApiManager.swift in Sources */, 305A76CA2FCA8C7000227D26 /* AppSettings.swift in Sources */, 305A76CB2FCA8C7000227D26 /* Authorize.swift in Sources */, + 30CCDE552FE2903100F5214A /* SignInModel.swift in Sources */, 305A76CC2FCA8C7000227D26 /* FileTools.swift in Sources */, 305A76CD2FCA8C7000227D26 /* Permission.swift in Sources */, 305A76CE2FCA8C7000227D26 /* RouterManager.swift in Sources */, @@ -1614,6 +1645,7 @@ 30EFF3CF2FDA669800EB35D4 /* MyProfileVC.swift in Sources */, 30EFF3E52FDAA93400EB35D4 /* PrivacyPolicyVC.swift in Sources */, 305A76E22FCA8C7000227D26 /* GroupViewModel.swift in Sources */, + 30CCDE4E2FE26CEA00F5214A /* PayResultPopVC.swift in Sources */, 305A76E32FCA8C7000227D26 /* GroupMemberView.swift in Sources */, 30BAB84F2FCD2FED00C33B5C /* InviteJoinVC.swift in Sources */, 305A76E42FCA8C7000227D26 /* HomeView.swift in Sources */, @@ -1674,20 +1706,24 @@ 305A77072FCA8C7000227D26 /* Helper.swift in Sources */, 305A77082FCA8C7000227D26 /* PageCollectionViewFlowLayout.swift in Sources */, 3062E8C42FCFC90F00CEF511 /* CreateGroupVipPopView.swift in Sources */, + 30CCDE512FE2785D00F5214A /* SignInVC.swift in Sources */, 305A77092FCA8C7000227D26 /* PageContentView.swift in Sources */, 305A770A2FCA8C7000227D26 /* PageStyle.swift in Sources */, 305A770B2FCA8C7000227D26 /* PageTitleView.swift in Sources */, 305A770C2FCA8C7000227D26 /* PageView.swift in Sources */, 305A770D2FCA8C7000227D26 /* PageViewManager.swift in Sources */, + 30D891F52FE22E0600E958FD /* OrderAPI.swift in Sources */, 307073EA2FD2715A004C37CC /* GroupChatViewModel.swift in Sources */, 305A770E2FCA8C7000227D26 /* DLAlertPopVC.swift in Sources */, 305A770F2FCA8C7000227D26 /* DLCustomPopVC.swift in Sources */, 30EFF29B2FD668C900EB35D4 /* VoiceRecordView.swift in Sources */, + 30CCDE532FE2786600F5214A /* SignInView.swift in Sources */, 305A77102FCA8C7000227D26 /* DLSheetPopVC.swift in Sources */, 30EFF3D32FDA69F400EB35D4 /* AvatarIconListView.swift in Sources */, 30EFF3B72FD8F86200EB35D4 /* ReviewMemberListVM.swift in Sources */, 305A77112FCA8C7000227D26 /* DLViewTransition.m in Sources */, 3062E8BA2FCEAC6500CEF511 /* CreateGroupView.swift in Sources */, + 30D891F72FE22E6E00E958FD /* OrderService.swift in Sources */, 305A77192FCA8C7000227D26 /* CollectionHFlowLayout.swift in Sources */, 305A771A2FCA8C7000227D26 /* JJPageControl.swift in Sources */, 305A771B2FCA8C7000227D26 /* ReusableView.swift in Sources */, @@ -1730,7 +1766,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 7LP48T8ZJE; + DEVELOPMENT_TEAM = XSJJKSY9FB; ENABLE_USER_SCRIPT_SANDBOXING = "$(inherited)"; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = QuickLocation/Info.plist; @@ -1779,7 +1815,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 7LP48T8ZJE; + DEVELOPMENT_TEAM = XSJJKSY9FB; ENABLE_USER_SCRIPT_SANDBOXING = "$(inherited)"; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = QuickLocation/Info.plist; diff --git a/QuickLocation.xcworkspace/xcuserdata/yanghong.xcuserdatad/UserInterfaceState.xcuserstate b/QuickLocation.xcworkspace/xcuserdata/yanghong.xcuserdatad/UserInterfaceState.xcuserstate index 7fdc390..4ff717a 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.xcworkspace/xcuserdata/yanghong.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/QuickLocation.xcworkspace/xcuserdata/yanghong.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index a5519e7..445796d 100644 --- a/QuickLocation.xcworkspace/xcuserdata/yanghong.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/QuickLocation.xcworkspace/xcuserdata/yanghong.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -3,4 +3,22 @@ uuid = "AE547311-A826-480F-B1ED-BD6B815729FA" type = "0" version = "2.0"> + + + + + + diff --git a/QuickLocation/API/OrderAPI.swift b/QuickLocation/API/OrderAPI.swift new file mode 100644 index 0000000..e413dcd --- /dev/null +++ b/QuickLocation/API/OrderAPI.swift @@ -0,0 +1,65 @@ +// +// OrderAPI.swift +// QuickLocation +// +// Created by 八条 on 2026/6/17. +// + +import Moya +import SwiftyUserDefaults +internal import Alamofire + +/// 订单API +enum OrderAPI { + /// 充值内容 + /// - Parameters: + /// - type: 类型 member + case rechargeInfo(type: String) + + /// 支付参数 + /// - Parameters: + /// - goodsId: 商品ID + /// - payType: alipay、weixin + /// - source: center + /// - extra: 额外参数 + case orderPayParams(goodsId: String, payType: String, source: String, extra: [String:Any]=[:]) +} + +extension OrderAPI: MultiTargetProtocol { + + var path: String { + switch self { + case .rechargeInfo: + return "api/order/goods" + case .orderPayParams: + return "api/order" + } + } + + var method: Moya.Method { + switch self { + case .rechargeInfo: + return .get + default: + return .post + } + } + + var task: Moya.Task { + switch self { + case let .rechargeInfo(type): + var params = Parameters() + params["type"] = type + return .requestParameters(parameters: params, encoding: URLEncoding()) + + case let .orderPayParams(goodsId, payType, source, extra): + var params = Parameters() + params["goods_id"] = goodsId + params["pay_type"] = payType + params["source"] = source + params["pay_source"] = "app" + params["extra"] = extra + return .requestParameters(parameters: params, encoding: JSONEncoding()) + } + } +} diff --git a/QuickLocation/API/SystemAPI.swift b/QuickLocation/API/SystemAPI.swift index 173d98e..d543173 100644 --- a/QuickLocation/API/SystemAPI.swift +++ b/QuickLocation/API/SystemAPI.swift @@ -18,11 +18,6 @@ enum SystemAPI { /// - phone: 手机号 case sendCode(phone: String) - /// 充值内容 - /// - Parameters: - /// - type: 类型 member - case rechargeInfo(type: String) - /// 微信客服 case wechatService } @@ -35,8 +30,6 @@ extension SystemAPI: MultiTargetProtocol { return "api/user/config" case .sendCode: return "api/user/sms/code" - case .rechargeInfo: - return "api/order/goods" case .wechatService: return "api/weixin/service" } @@ -44,7 +37,7 @@ extension SystemAPI: MultiTargetProtocol { var method: Moya.Method { switch self { - case .userConfig, .rechargeInfo, .wechatService: + case .userConfig, .wechatService: return .get case .sendCode: return .post @@ -61,11 +54,6 @@ extension SystemAPI: MultiTargetProtocol { 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()) - case .wechatService: return .requestParameters(parameters: parameters, encoding: URLEncoding()) } diff --git a/QuickLocation/API/UserAPI.swift b/QuickLocation/API/UserAPI.swift index 12ff2c5..f447772 100644 --- a/QuickLocation/API/UserAPI.swift +++ b/QuickLocation/API/UserAPI.swift @@ -25,6 +25,9 @@ enum UserAPI { /// 用户IM Token case imToken + /// 签到信息 + case signInInfo + /// 更换手机号 case changePhone(timestamp: String, phone: String, code: String) @@ -62,6 +65,8 @@ extension UserAPI: MultiTargetProtocol { return "api/user" case .imToken: return "mapi/openim/user/token/get" + case .signInInfo: + return "mapi/user/signin" case .changePhone: return "api/user" case .setHeadPic: @@ -81,7 +86,7 @@ extension UserAPI: MultiTargetProtocol { var method: Moya.Method { switch self { - case .userInfo: + case .userInfo, .signInInfo: return .get case .changePhone, .setGender: return .put @@ -110,6 +115,9 @@ extension UserAPI: MultiTargetProtocol { params["force"] = true return .requestParameters(parameters: params, encoding: JSONEncoding()) + case .signInInfo: + return .requestParameters(parameters: Parameters(), encoding: URLEncoding()) + case let .changePhone(timestamp, phone, code): var params = Parameters() params["phone_timestamp"] = timestamp diff --git a/QuickLocation/AppDelegate.swift b/QuickLocation/AppDelegate.swift index 662d0cd..8b991e8 100644 --- a/QuickLocation/AppDelegate.swift +++ b/QuickLocation/AppDelegate.swift @@ -67,7 +67,23 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { - return WXApi.handleOpen(url, delegate: self) + print("📱 application open url: \(url.absoluteString) host: \(url.host ?? "")") + // 处理支付宝支付结果(不限制 host,适应新版 SDK) + if url.scheme?.hasPrefix("Alipay") == true || url.host == "safepay" { + AlipaySDK.defaultService().processOrder(withPaymentResult: url) { resultDic in + print("📱 Alipay callback: \(resultDic ?? [:])") + guard let result = resultDic, + let resultStatus = result["resultStatus"] as? String, + resultStatus != "6001" else { return } + + } + return true + } + else if url.host == "pay" { + return WXApi.handleOpen(url, delegate: self) + } + + return true } func setupLocation() { diff --git a/QuickLocation/Assets.xcassets/Map/current.imageset/Contents.json b/QuickLocation/Assets.xcassets/Map/current.imageset/Contents.json new file mode 100644 index 0000000..7ebe171 --- /dev/null +++ b/QuickLocation/Assets.xcassets/Map/current.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/Map/current.imageset/Group 1545@2x.png b/QuickLocation/Assets.xcassets/Map/current.imageset/Group 1545@2x.png new file mode 100644 index 0000000..e261059 Binary files /dev/null and b/QuickLocation/Assets.xcassets/Map/current.imageset/Group 1545@2x.png differ diff --git a/QuickLocation/Assets.xcassets/Map/current.imageset/Group 1545@3x.png b/QuickLocation/Assets.xcassets/Map/current.imageset/Group 1545@3x.png new file mode 100644 index 0000000..6c85cd3 Binary files /dev/null and b/QuickLocation/Assets.xcassets/Map/current.imageset/Group 1545@3x.png differ diff --git a/QuickLocation/Assets.xcassets/Map/end.imageset/Contents.json b/QuickLocation/Assets.xcassets/Map/end.imageset/Contents.json new file mode 100644 index 0000000..b13aefa --- /dev/null +++ b/QuickLocation/Assets.xcassets/Map/end.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Group_2075@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Group_2075@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/Map/end.imageset/Group_2075@2x.png b/QuickLocation/Assets.xcassets/Map/end.imageset/Group_2075@2x.png new file mode 100644 index 0000000..c3d09ac Binary files /dev/null and b/QuickLocation/Assets.xcassets/Map/end.imageset/Group_2075@2x.png differ diff --git a/QuickLocation/Assets.xcassets/Map/end.imageset/Group_2075@3x.png b/QuickLocation/Assets.xcassets/Map/end.imageset/Group_2075@3x.png new file mode 100644 index 0000000..dc650bc Binary files /dev/null and b/QuickLocation/Assets.xcassets/Map/end.imageset/Group_2075@3x.png differ diff --git a/QuickLocation/Assets.xcassets/Popup/failure.imageset/Contents.json b/QuickLocation/Assets.xcassets/Popup/failure.imageset/Contents.json new file mode 100644 index 0000000..995c11a --- /dev/null +++ b/QuickLocation/Assets.xcassets/Popup/failure.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "failure@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "failure@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/Popup/failure.imageset/failure@2x.png b/QuickLocation/Assets.xcassets/Popup/failure.imageset/failure@2x.png new file mode 100644 index 0000000..6a6695d Binary files /dev/null and b/QuickLocation/Assets.xcassets/Popup/failure.imageset/failure@2x.png differ diff --git a/QuickLocation/Assets.xcassets/Popup/failure.imageset/failure@3x.png b/QuickLocation/Assets.xcassets/Popup/failure.imageset/failure@3x.png new file mode 100644 index 0000000..a7f94eb Binary files /dev/null and b/QuickLocation/Assets.xcassets/Popup/failure.imageset/failure@3x.png differ diff --git a/QuickLocation/Assets.xcassets/Popup/success.imageset/Contents.json b/QuickLocation/Assets.xcassets/Popup/success.imageset/Contents.json new file mode 100644 index 0000000..01a29e9 --- /dev/null +++ b/QuickLocation/Assets.xcassets/Popup/success.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "success@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "success@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/Popup/success.imageset/success@2x.png b/QuickLocation/Assets.xcassets/Popup/success.imageset/success@2x.png new file mode 100644 index 0000000..c6e375f Binary files /dev/null and b/QuickLocation/Assets.xcassets/Popup/success.imageset/success@2x.png differ diff --git a/QuickLocation/Assets.xcassets/Popup/success.imageset/success@3x.png b/QuickLocation/Assets.xcassets/Popup/success.imageset/success@3x.png new file mode 100644 index 0000000..8b93d78 Binary files /dev/null and b/QuickLocation/Assets.xcassets/Popup/success.imageset/success@3x.png differ diff --git a/QuickLocation/Assets.xcassets/SignIn/Contents.json b/QuickLocation/Assets.xcassets/SignIn/Contents.json new file mode 100644 index 0000000..6e96565 --- /dev/null +++ b/QuickLocation/Assets.xcassets/SignIn/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/QuickLocation/Assets.xcassets/SignIn/signIn_text.imageset/Contents.json b/QuickLocation/Assets.xcassets/SignIn/signIn_text.imageset/Contents.json new file mode 100644 index 0000000..a0fc580 --- /dev/null +++ b/QuickLocation/Assets.xcassets/SignIn/signIn_text.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Group 2404@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Group 2404@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/SignIn/signIn_text.imageset/Group 2404@2x.png b/QuickLocation/Assets.xcassets/SignIn/signIn_text.imageset/Group 2404@2x.png new file mode 100644 index 0000000..77c262b Binary files /dev/null and b/QuickLocation/Assets.xcassets/SignIn/signIn_text.imageset/Group 2404@2x.png differ diff --git a/QuickLocation/Assets.xcassets/SignIn/signIn_text.imageset/Group 2404@3x.png b/QuickLocation/Assets.xcassets/SignIn/signIn_text.imageset/Group 2404@3x.png new file mode 100644 index 0000000..36f14df Binary files /dev/null and b/QuickLocation/Assets.xcassets/SignIn/signIn_text.imageset/Group 2404@3x.png differ diff --git a/QuickLocation/Assets.xcassets/SignIn/tips.imageset/Contents.json b/QuickLocation/Assets.xcassets/SignIn/tips.imageset/Contents.json new file mode 100644 index 0000000..9dc064c --- /dev/null +++ b/QuickLocation/Assets.xcassets/SignIn/tips.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Group_2400@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Group_2400@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/SignIn/tips.imageset/Group_2400@2x.png b/QuickLocation/Assets.xcassets/SignIn/tips.imageset/Group_2400@2x.png new file mode 100644 index 0000000..0eba319 Binary files /dev/null and b/QuickLocation/Assets.xcassets/SignIn/tips.imageset/Group_2400@2x.png differ diff --git a/QuickLocation/Assets.xcassets/SignIn/tips.imageset/Group_2400@3x.png b/QuickLocation/Assets.xcassets/SignIn/tips.imageset/Group_2400@3x.png new file mode 100644 index 0000000..88f131b Binary files /dev/null and b/QuickLocation/Assets.xcassets/SignIn/tips.imageset/Group_2400@3x.png differ diff --git a/QuickLocation/Assets.xcassets/SignIn/today_text.imageset/Contents.json b/QuickLocation/Assets.xcassets/SignIn/today_text.imageset/Contents.json new file mode 100644 index 0000000..306ca65 --- /dev/null +++ b/QuickLocation/Assets.xcassets/SignIn/today_text.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "today_signin@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "today_signin@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/SignIn/today_text.imageset/today_signin@2x.png b/QuickLocation/Assets.xcassets/SignIn/today_text.imageset/today_signin@2x.png new file mode 100644 index 0000000..9d2ec90 Binary files /dev/null and b/QuickLocation/Assets.xcassets/SignIn/today_text.imageset/today_signin@2x.png differ diff --git a/QuickLocation/Assets.xcassets/SignIn/today_text.imageset/today_signin@3x.png b/QuickLocation/Assets.xcassets/SignIn/today_text.imageset/today_signin@3x.png new file mode 100644 index 0000000..9816c66 Binary files /dev/null and b/QuickLocation/Assets.xcassets/SignIn/today_text.imageset/today_signin@3x.png differ diff --git a/QuickLocation/Assets.xcassets/map_avatar_1.imageset/Contents.json b/QuickLocation/Assets.xcassets/map_avatar_1.imageset/Contents.json deleted file mode 100644 index 098ace0..0000000 --- a/QuickLocation/Assets.xcassets/map_avatar_1.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images": [ - { - "idiom": "universal", - "scale": "1x" - }, - { - "filename": "map_avatar_1@2x.png", - "idiom": "universal", - "scale": "2x" - }, - { - "filename": "map_avatar_1@3x.png", - "idiom": "universal", - "scale": "3x" - } - ], - "info": { - "author": "xcode", - "version": 1 - } -} \ No newline at end of file diff --git a/QuickLocation/Assets.xcassets/map_avatar_1.imageset/map_avatar_1@2x.png b/QuickLocation/Assets.xcassets/map_avatar_1.imageset/map_avatar_1@2x.png deleted file mode 100644 index d025f83..0000000 Binary files a/QuickLocation/Assets.xcassets/map_avatar_1.imageset/map_avatar_1@2x.png and /dev/null differ diff --git a/QuickLocation/Assets.xcassets/map_avatar_1.imageset/map_avatar_1@3x.png b/QuickLocation/Assets.xcassets/map_avatar_1.imageset/map_avatar_1@3x.png deleted file mode 100644 index 653d6b6..0000000 Binary files a/QuickLocation/Assets.xcassets/map_avatar_1.imageset/map_avatar_1@3x.png and /dev/null differ diff --git a/QuickLocation/Assets.xcassets/map_avatar_2.imageset/Contents.json b/QuickLocation/Assets.xcassets/map_avatar_2.imageset/Contents.json deleted file mode 100644 index 7c92721..0000000 --- a/QuickLocation/Assets.xcassets/map_avatar_2.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images": [ - { - "idiom": "universal", - "scale": "1x" - }, - { - "filename": "map_avatar_2@2x.png", - "idiom": "universal", - "scale": "2x" - }, - { - "filename": "map_avatar_2@3x.png", - "idiom": "universal", - "scale": "3x" - } - ], - "info": { - "author": "xcode", - "version": 1 - } -} \ No newline at end of file diff --git a/QuickLocation/Assets.xcassets/map_avatar_2.imageset/map_avatar_2@2x.png b/QuickLocation/Assets.xcassets/map_avatar_2.imageset/map_avatar_2@2x.png deleted file mode 100644 index ca9e0a6..0000000 Binary files a/QuickLocation/Assets.xcassets/map_avatar_2.imageset/map_avatar_2@2x.png and /dev/null differ diff --git a/QuickLocation/Assets.xcassets/map_avatar_2.imageset/map_avatar_2@3x.png b/QuickLocation/Assets.xcassets/map_avatar_2.imageset/map_avatar_2@3x.png deleted file mode 100644 index 9bafe23..0000000 Binary files a/QuickLocation/Assets.xcassets/map_avatar_2.imageset/map_avatar_2@3x.png and /dev/null differ diff --git a/QuickLocation/Assets.xcassets/map_avatar_3.imageset/Contents.json b/QuickLocation/Assets.xcassets/map_avatar_3.imageset/Contents.json deleted file mode 100644 index d559721..0000000 --- a/QuickLocation/Assets.xcassets/map_avatar_3.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images": [ - { - "idiom": "universal", - "scale": "1x" - }, - { - "filename": "map_avatar_3@2x.png", - "idiom": "universal", - "scale": "2x" - }, - { - "filename": "map_avatar_3@3x.png", - "idiom": "universal", - "scale": "3x" - } - ], - "info": { - "author": "xcode", - "version": 1 - } -} \ No newline at end of file diff --git a/QuickLocation/Assets.xcassets/map_avatar_3.imageset/map_avatar_3@2x.png b/QuickLocation/Assets.xcassets/map_avatar_3.imageset/map_avatar_3@2x.png deleted file mode 100644 index 3033db0..0000000 Binary files a/QuickLocation/Assets.xcassets/map_avatar_3.imageset/map_avatar_3@2x.png and /dev/null differ diff --git a/QuickLocation/Assets.xcassets/map_avatar_3.imageset/map_avatar_3@3x.png b/QuickLocation/Assets.xcassets/map_avatar_3.imageset/map_avatar_3@3x.png deleted file mode 100644 index ce5f723..0000000 Binary files a/QuickLocation/Assets.xcassets/map_avatar_3.imageset/map_avatar_3@3x.png and /dev/null differ diff --git a/QuickLocation/Assets.xcassets/map_avatar_4.imageset/Contents.json b/QuickLocation/Assets.xcassets/map_avatar_4.imageset/Contents.json deleted file mode 100644 index dcb0be9..0000000 --- a/QuickLocation/Assets.xcassets/map_avatar_4.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images": [ - { - "idiom": "universal", - "scale": "1x" - }, - { - "filename": "map_avatar_4@2x.png", - "idiom": "universal", - "scale": "2x" - }, - { - "filename": "map_avatar_4@3x.png", - "idiom": "universal", - "scale": "3x" - } - ], - "info": { - "author": "xcode", - "version": 1 - } -} \ No newline at end of file diff --git a/QuickLocation/Assets.xcassets/map_avatar_4.imageset/map_avatar_4@2x.png b/QuickLocation/Assets.xcassets/map_avatar_4.imageset/map_avatar_4@2x.png deleted file mode 100644 index 44fbaf9..0000000 Binary files a/QuickLocation/Assets.xcassets/map_avatar_4.imageset/map_avatar_4@2x.png and /dev/null differ diff --git a/QuickLocation/Assets.xcassets/map_avatar_4.imageset/map_avatar_4@3x.png b/QuickLocation/Assets.xcassets/map_avatar_4.imageset/map_avatar_4@3x.png deleted file mode 100644 index 0d472ff..0000000 Binary files a/QuickLocation/Assets.xcassets/map_avatar_4.imageset/map_avatar_4@3x.png and /dev/null differ diff --git a/QuickLocation/Assets.xcassets/map_avatar_5.imageset/Contents.json b/QuickLocation/Assets.xcassets/map_avatar_5.imageset/Contents.json deleted file mode 100644 index 9622809..0000000 --- a/QuickLocation/Assets.xcassets/map_avatar_5.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images": [ - { - "idiom": "universal", - "scale": "1x" - }, - { - "filename": "map_avatar_5@2x.png", - "idiom": "universal", - "scale": "2x" - }, - { - "filename": "map_avatar_5@3x.png", - "idiom": "universal", - "scale": "3x" - } - ], - "info": { - "author": "xcode", - "version": 1 - } -} \ No newline at end of file diff --git a/QuickLocation/Assets.xcassets/map_avatar_5.imageset/map_avatar_5@2x.png b/QuickLocation/Assets.xcassets/map_avatar_5.imageset/map_avatar_5@2x.png deleted file mode 100644 index 148f6e8..0000000 Binary files a/QuickLocation/Assets.xcassets/map_avatar_5.imageset/map_avatar_5@2x.png and /dev/null differ diff --git a/QuickLocation/Assets.xcassets/map_avatar_5.imageset/map_avatar_5@3x.png b/QuickLocation/Assets.xcassets/map_avatar_5.imageset/map_avatar_5@3x.png deleted file mode 100644 index c707c51..0000000 Binary files a/QuickLocation/Assets.xcassets/map_avatar_5.imageset/map_avatar_5@3x.png and /dev/null differ diff --git a/QuickLocation/Common/Constant.swift b/QuickLocation/Common/Constant.swift index 7c9a74d..12a6139 100644 --- a/QuickLocation/Common/Constant.swift +++ b/QuickLocation/Common/Constant.swift @@ -28,6 +28,8 @@ extension Notification.Name { static let RefreshUserConfigNotification = Notification.Name("RefreshUserConfigNotification") /// 刷新用户圈子数据 static let RefreshGroupInfoNotification = Notification.Name("RefreshGroupInfoNotification") + /// 支付宝/微信支付结果回调 + static let RequestOrderPayStatusNotification = Notification.Name("RequestOrderPayStatusNotification") } diff --git a/QuickLocation/Info.plist b/QuickLocation/Info.plist index 5febac2..2aebdac 100644 --- a/QuickLocation/Info.plist +++ b/QuickLocation/Info.plist @@ -4,6 +4,16 @@ CFBundleURLTypes + + CFBundleTypeRole + Editor + CFBundleURLName + alipay + CFBundleURLSchemes + + Alipay2021005185689681 + + CFBundleTypeRole Editor @@ -17,13 +27,13 @@ LSApplicationQueriesSchemes - alipays - baidumap - qqmap - iosamap - weixin - weixinULAPI - weixinURLParamsAPI + alipays + baidumap + qqmap + iosamap + weixin + weixinULAPI + weixinURLParamsAPI NSAppTransportSecurity diff --git a/QuickLocation/Manager/MQTT/MQTTService.swift b/QuickLocation/Manager/MQTT/MQTTService.swift index 72bd7f0..83c9138 100644 --- a/QuickLocation/Manager/MQTT/MQTTService.swift +++ b/QuickLocation/Manager/MQTT/MQTTService.swift @@ -45,6 +45,14 @@ struct MqttLocation: Codable { let points: [Points] } +/// 签到上报数据 +struct MqttSignIn: Codable { + let topic: String? + let latitude: Double + let longitude: Double + let address: String +} + /// MQTT 收到的位置消息(接收解析用) struct MqttIncomingMessage: Decodable { let type: String? @@ -55,6 +63,8 @@ struct MqttIncomingMessage: Decodable { struct MqttIncomingData: Decodable { let points: [Points]? let battery: String? + let index: Int? + let group_key: String? } // MARK: - MQTTService @@ -98,11 +108,24 @@ final class MQTTService: NSObject { let mqtt = CocoaMQTT5(clientID: clientID, host: host, port: port) mqtt.username = userName mqtt.password = password - mqtt.keepAlive = 60 + mqtt.cleanSession = true + mqtt.keepAlive = 30 mqtt.autoReconnect = true mqtt.autoReconnectTimeInterval = 5 mqtt.delegate = self mqtt.logLevel = .warning + // MQTT 5 连接属性(对应 Android 端配置) + let connProps = MqttConnectProperties() + connProps.sessionExpiryInterval = 30 + connProps.receiveMaximum = 10 + connProps.maximumPacketSize = 10240 + connProps.topicAliasMaximum = 0 + mqtt.connectProperties = connProps + // Will 消息 + let will = CocoaMQTT5Message(topic: "willtopic", payload: []) + will.qos = .qos1 + will.retained = false + mqtt.willMessage = will self.mqtt = mqtt _ = mqtt.connect() @@ -129,6 +152,9 @@ final class MQTTService: NSObject { /// - callback: 可选,该 topic 的专用回调,收到消息时优先于此回调 func subscribe(topic: String, qos: CocoaMQTTQoS = .qos1, callback: ((CocoaMQTT5Message) -> Void)? = nil) { let subscription = MqttSubscription(topic: topic, qos: qos) + subscription.noLocal = true + subscription.retainAsPublished = true + subscription.retainHandling = .none mqtt?.subscribe([subscription]) if let cb = callback { topicCallbacks[topic] = cb @@ -199,6 +225,22 @@ final class MQTTService: NSObject { publish(topic: "\(topic)\(AppContextManager.shared.userId)", message: payload.toJsonString()) } + + // MARK: - 签到 + func reportSignIn(lat: Double, lon: Double, addr: String) { + let signIn = MqttSignIn(topic: nil, latitude: lat, longitude: lon, address: addr) + guard let jsonData = try? JSONEncoder().encode(signIn), + let dataDict = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any] + else { return } + + let payload: [String: Any] = [ + "type": MqttType.signIn.rawValue, + "data": dataDict, + "extra": "", + "sort": "" + ] + publish(topic: "\(topic)\(AppContextManager.shared.userId)", message: payload.toJsonString()) + } } // MARK: - CocoaMQTT5Delegate @@ -207,6 +249,8 @@ extension MQTTService: CocoaMQTT5Delegate { func mqtt5(_ mqtt5: CocoaMQTT5, didConnectAck ack: CocoaMQTTCONNACKReasonCode, connAckData: MqttDecodeConnAck?) { isConnected = true print("MQTT5 connected: \(ack)") + // 订阅基础 topic,接收 signIn/join/leave 等非位置消息 + subscribe(topic: topic) onConnected?() } diff --git a/QuickLocation/QuickLocation-Bridging-Header.h b/QuickLocation/QuickLocation-Bridging-Header.h index c28fd20..00c6d36 100644 --- a/QuickLocation/QuickLocation-Bridging-Header.h +++ b/QuickLocation/QuickLocation-Bridging-Header.h @@ -25,4 +25,7 @@ // 微信 #import +// 支付宝 +#import + #endif /* QuickLocation_Bridging_Header_h */ diff --git a/QuickLocation/QuickLocation.entitlements b/QuickLocation/QuickLocation.entitlements index 0c67376..9e7e98c 100644 --- a/QuickLocation/QuickLocation.entitlements +++ b/QuickLocation/QuickLocation.entitlements @@ -1,5 +1,10 @@ - + + com.apple.developer.associated-domains + + applinks:smartdrive.zuom8.cn + + diff --git a/QuickLocation/Section/Home/GroupMemberView.swift b/QuickLocation/Section/Home/GroupMemberView.swift index 11decca..832db91 100644 --- a/QuickLocation/Section/Home/GroupMemberView.swift +++ b/QuickLocation/Section/Home/GroupMemberView.swift @@ -258,6 +258,7 @@ class GroupMemberCell: UITableViewCell { .centerY(locationLab) .width(10) .height(10) + .right(80, relation: .greaterThanOrEqual) tagView.layoutChain .leftToRightOfView(nameLab, offset: 4) diff --git a/QuickLocation/Section/Home/HomeView.swift b/QuickLocation/Section/Home/HomeView.swift index cb96f51..3123e00 100644 --- a/QuickLocation/Section/Home/HomeView.swift +++ b/QuickLocation/Section/Home/HomeView.swift @@ -112,6 +112,7 @@ class HomeView: UIView { groupView.addSubview(groupNameLab) groupView.addSubview(groupArrowIconView) addSubview(messageView) + addSubview(messageStackView) addSubview(toolsView) addSubview(searchLottieView) addSubview(locationView) @@ -167,6 +168,10 @@ class HomeView: UIView { .width(36) .height(36) + messageStackView.layoutChain + .topToBottomOfView(messageView, offset: 23) + .edgesHorzontal(15) + toolsView.layoutChain .left(23) .centerY() @@ -440,6 +445,50 @@ class HomeView: UIView { view.cornerRadius = 5 return view }() + + lazy var messageStackView: UIStackView = { + let view = UIStackView(arrangedSubviews: [noticeView, messageBubbleView]) + view.axis = .vertical + view.alignment = .leading + view.spacing = 15 + view.backgroundColor = .clear + return view + }() + + // MARK: - 公告 + lazy var noticeView: UIView = { + let view = UIView() + view.isHidden = true + + view.layoutChain + .height(22) + + return view + }() + + // MARK: - 消息气泡 + lazy var messageBubbleView: UIView = { + let view = UIView() + view.backgroundColor = .white.withAlphaComponent(0.8) + view.cornerRadius = 10 + view.isHidden = true + + view.addSubview(messageLab) + messageLab.layoutChain + .edges(all: 12) + + return view + }() + + lazy var messageLab: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 12, weight: .medium) + label.textColor = .clear + return label + }() + + // MARK: - 表情展示 + // MARK: - 侧边工具栏 lazy var toolsView: UIStackView = { diff --git a/QuickLocation/Section/Home/HomeViewController.swift b/QuickLocation/Section/Home/HomeViewController.swift index 62b774f..5edccfe 100644 --- a/QuickLocation/Section/Home/HomeViewController.swift +++ b/QuickLocation/Section/Home/HomeViewController.swift @@ -140,6 +140,14 @@ class HomeViewController: BaseViewController { // MARK: - Actions private func reactiveAction() { + // 签到 + rootView.signInView.rx.tapGesture.subscribe { _ in + let vc = SignInVC(currentUserCoord: self.lastLocation?.coordinate) + vc.isNeedLogin = true + AppRouter.push(vc) + }.disposed(by: disposeBag) + + // 顶部圈子 rootView.groupView.rx.tapGesture.subscribe { _ in guard let groupModel = self.viewModel.groupModel else { return } let groupViewFrame = self.view.convert(self.rootView.groupView.frame, from: self.rootView) @@ -151,14 +159,17 @@ class HomeViewController: BaseViewController { } }.disposed(by: disposeBag) + // 圈子成员列表 刷新列表 rootView.groupMemberView.refreshBtn.rx.tap.subscribe(onNext: { _ in self.requestGroupInfo() }).disposed(by: disposeBag) + // 圈子成员列表 邀请加入 rootView.groupMemberView.inviteJoinBtn.rx.tap.subscribe(onNext: { _ in AppRouter.push(Route.inviteJoin, userInfo: ["groupInfo": self.viewModel.groupInfo]) }).disposed(by: disposeBag) + // 地图回到自己 rootView.locationView.rx.tapGesture.subscribe { _ in if let ann = self.currentUserAnnotation { self.rootView.mapView.setCenter(ann.coordinate, animated: true) @@ -193,15 +204,6 @@ class HomeViewController: BaseViewController { let vc = NavigationVC(member: member, currentUserCoord: userCoord, groupName: groupName, groupIcon: "\(iconIndex)") self.navigationController?.pushViewController(vc, animated: true) } - - // MQTT 接收 - MQTTService.shared.subscribe(topic: "smartdrive/") { message in - guard let msgStr = message.string, - let data = msgStr.data(using: .utf8), - let msg = try? JSONDecoder().decode(MqttIncomingMessage.self, from: data) - else { return } - print("📩 收到消息 -> 主题:\(message.topic),内容:\(msg)") - } } private func bindViewModel() { @@ -438,26 +440,30 @@ extension HomeViewController { subscribedMemberIds = newIds } - /// 处理成员位置更新(从 topic 提取 userId,更新地图标注) + /// 处理 MQTT 消息(按 type 分发) private func handleMemberLocation(topic: String, payload: String?) { + print("📩 收到消息 -> 主题:\(topic),内容:\(payload)") guard let payload = payload, let data = payload.data(using: .utf8), - let msg = try? JSONDecoder().decode(MqttIncomingMessage.self, from: data), - let firstPoint = msg.data?.points?.first + let msg = try? JSONDecoder().decode(MqttIncomingMessage.self, from: data) else { return } - print("📩 收到消息 -> 主题:\(topic),内容:\(msg)") + let userId = topic.replacingOccurrences(of: "smartdrive/", with: "") - let coord = CLLocationCoordinate2D(latitude: firstPoint.lat, longitude: firstPoint.lon) - guard CLLocationCoordinate2DIsValid(coord) else { return } + + switch msg.type { + case "track": + guard let firstPoint = msg.data?.points?.first else { return } + let coord = CLLocationCoordinate2D(latitude: firstPoint.lat, longitude: firstPoint.lon) + guard CLLocationCoordinate2DIsValid(coord) else { return } + + // 根据 time 字段判断在线状态 + let nowMs = Date().timeIntervalSince1970 * 1000 + let msgTimeMs = Double(firstPoint.time) + let diffSec = (nowMs - msgTimeMs) / 1000 + let isOnline = diffSec < 60 - // 根据 time 字段判断在线状态(毫秒时间戳) - let nowMs = Date().timeIntervalSince1970 * 1000 - let msgTimeMs = Double(firstPoint.time) - let diffSec = (nowMs - msgTimeMs) / 1000 - let isOnline = diffSec < 60 - - let battery = msg.data?.battery ?? "" - DispatchQueue.main.async { [weak self] in + let battery = msg.data?.battery ?? "" + DispatchQueue.main.async { [weak self] in guard let self = self else { return } // 记录更新时间 self.lastUpdateTimes[userId] = Date() @@ -475,6 +481,40 @@ extension HomeViewController { self.removeAnnotation(userId: userId) } } + case "disconnect": + self.removeAnnotation(userId: userId) + viewModel.setMemberOffline(userId: userId) + updateOnlineCount() + + case "emote": + guard let index = msg.data?.index else { break } + if index >= 30 { // 文字 + guard let gk = msg.data?.group_key, gk.components(separatedBy: "/").count >= 2 else { break } + let parts = gk.components(separatedBy: "/") + let emoteUserId = parts[1] + let textIdx = index % 10 + let texts = QuickMessageView.messageList + guard textIdx < texts.count else { break } + let nickName = self.viewModel.getUserNickName(id: emoteUserId) + let tip = nickName + let fullText = tip + "对你说:" + texts[textIdx] + let attr = NSMutableAttributedString(string: fullText) + attr.addAttribute(.font, value: UIFont.systemFont(ofSize: 12, weight: .medium), range: NSRange(location: 0, length: fullText.count)) + attr.addAttribute(.foregroundColor, value: UIColor(hexStr: "#16B3FF"), range: NSRange(location: 0, length: tip.count)) + attr.addAttribute(.foregroundColor, value: UIColor(hexStr: "#333333"), range: NSRange(location: tip.count, length: texts[textIdx].count)) + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + self.rootView.messageLab.attributedText = attr + self.rootView.messageBubbleView.isHidden = false + DispatchQueue.main.asyncAfter(deadline: .now() + 5) { + self.rootView.messageBubbleView.isHidden = true + } + } + } + + default: + print("📩 未处理 type=\(msg.type ?? "")") + } } /// 更新成员在线计数显示 @@ -520,7 +560,6 @@ extension HomeViewController { } let newAnn = MemberAnnotation(member: oldMember) mapView.addAnnotation(newAnn) - print("📌 updateAnnotation userId=\(userId) coord=\(coordinate.latitude),\(coordinate.longitude)") } #endif } diff --git a/QuickLocation/Section/Home/HomeViewModel.swift b/QuickLocation/Section/Home/HomeViewModel.swift index 219c468..55e9435 100644 --- a/QuickLocation/Section/Home/HomeViewModel.swift +++ b/QuickLocation/Section/Home/HomeViewModel.swift @@ -80,6 +80,10 @@ class HomeViewModel { return model.default_group_key.contains(id) } + func getUserNickName(id: String) -> String { + memberList.first { $0.user_id == id }?.nick_name ?? "" + } + var memberList: [GroupMemberModel] = [] { didSet { var tempList = memberList diff --git a/QuickLocation/Section/Home/QuickMessageView.swift b/QuickLocation/Section/Home/QuickMessageView.swift index 5a23c5b..abf867a 100644 --- a/QuickLocation/Section/Home/QuickMessageView.swift +++ b/QuickLocation/Section/Home/QuickMessageView.swift @@ -10,7 +10,7 @@ import TagListView class QuickMessageView: UIView { - private let list: [String] = [ + static let messageList: [String] = [ "爱你哦!", "预计到达时间?", "发生什么情况?", "注意安全", "在路上", "给我打电话", ] @@ -86,7 +86,7 @@ class QuickMessageView: UIView { backgroundColor = .clear setupUI() setupRx() - setupTagData(list: list) + setupTagData(list: QuickMessageView.messageList) } required init?(coder aDecoder: NSCoder) { diff --git a/QuickLocation/Section/Home/SignIn/SignInModel.swift b/QuickLocation/Section/Home/SignIn/SignInModel.swift new file mode 100644 index 0000000..ac51485 --- /dev/null +++ b/QuickLocation/Section/Home/SignIn/SignInModel.swift @@ -0,0 +1,52 @@ +// +// SignInModel.swift +// QuickLocation +// +// Created by 八条 on 2026/6/17. +// + +import ObjectMapper +import RxDataSources + +struct SignInInfoResponse: BaseModelProtocol { + // 状态码 + var code: String? + // 消息 + var message: String? + // + var model: SignInModel? + + init?(map: Map) {} + + mutating func mapping(map: Map) { + code <- map["code"] + message <- map["msg"] + model <- map["data"] + } +} + +struct SignInModel: Mappable, Equatable { + var uuid: String = UUID().uuidString + /// + var missCount: Int = 0 + var alertCount: Int = 0 + var lastTime: Int = 0 + /// 状态 0未签到 1已签到 2已有2天未签到 + var signInStatus: Int = 0 + var signCount: Int = 0 + var email: String = "" + + + init?(map: Map) { + + } + + mutating func mapping(map: Map) { + missCount <- map["missCount"] + alertCount <- map["alertCount"] + lastTime <- map["lastTime"] + signInStatus <- map["signInStatus"] + signCount <- map["signCount"] + email <- map["email"] + } +} diff --git a/QuickLocation/Section/Home/SignIn/SignInVC.swift b/QuickLocation/Section/Home/SignIn/SignInVC.swift new file mode 100644 index 0000000..b67a2eb --- /dev/null +++ b/QuickLocation/Section/Home/SignIn/SignInVC.swift @@ -0,0 +1,55 @@ +// +// SignInVC.swift +// QuickLocation +// +// Created by 八条 on 2026/6/17. +// + +import UIKit +import RxSwift +import RxCocoa +import CoreLocation + +class SignInVC: BaseViewController { + + fileprivate var rootView: SignInView! + /// 当前用户坐标(由外部传入) + var currentCoordinate: CLLocationCoordinate2D? + + override func loadView() { + rootView = SignInView(frame: UIScreen.main.bounds) + view = rootView + } + + override func viewDidLoad() { + super.viewDidLoad() + + requestSignInInfo() + + rootView.signInLottieView.rx.tapGesture.subscribe(onNext: { [weak self] _ in + guard let c = self?.currentCoordinate else { return } + MQTTService.shared.reportSignIn(lat: c.latitude, lon: c.longitude, addr: "") + DLToast.show(text: "签到成功") { + self?.requestSignInInfo() + } + }).disposed(by: disposeBag) + } + + // MARK: - API + private func requestSignInInfo() { + DLToast.showLoading() + UserService.signInInfo().subscribe(onNext: { response in + guard let model = response.model else { return } + self.rootView.setupData(model) + }, onError: { _ in }).disposed(by: disposeBag) + } + + init(currentUserCoord: CLLocationCoordinate2D?) { + self.currentCoordinate = currentUserCoord + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/QuickLocation/Section/Home/SignIn/SignInView.swift b/QuickLocation/Section/Home/SignIn/SignInView.swift new file mode 100644 index 0000000..748f8a4 --- /dev/null +++ b/QuickLocation/Section/Home/SignIn/SignInView.swift @@ -0,0 +1,279 @@ +// +// SignInView.swift +// QuickLocation +// +// Created by 八条 on 2026/6/17. +// + +import UIKit +import RxSwift +import RxCocoa +import Lottie +import CoreLocation + +class SignInView: UIView { + + var disposeBag = DisposeBag() + + func setupData(_ model: SignInModel) { + + signInTextImg.image = UIImage(named: model.signInStatus == 1 ? "SignIn/today_text" : "SignIn/signIn_text") + + let text = "已连续签到\(model.signCount)天" + let attr = NSMutableAttributedString(string: text) + let range = (text as NSString).range(of: model.signCount.string) + attr.addAttribute(.foregroundColor, value: UIColor(hexStr: "#FF8B39"), range: range) + signInDaysLab.attributedText = attr + } + + /// 签到按钮回调 + var onSignInTap: ((CLLocationCoordinate2D?) -> Void)? + /// 当前坐标(由 VC 传入) + var currentCoordinate: CLLocationCoordinate2D? + + private func setupRx() { + backBtn.rx.tap.subscribe(onNext: { _ in + AppRouter.shared.popOrDismiss() + }).disposed(by: disposeBag) + + + } + + private func setupUI() { + addSubview(navBgView) + addSubview(navBarView) + navBarView.addSubview(navTitleLabel) + navBarView.addSubview(backBtn) + + addSubview(emailView) + addSubview(signInLottieView) + addSubview(signInTextImg) + addSubview(signInDaysLab) + addSubview(tipsView) + addSubview(agreementTV) + + navBgView.layoutChain + .edges(excludingEdge: .bottom) + .heightToWidth(160/375) + + navBarView.layoutChain + .edges(excludingEdge: .bottom) + .height(kNaviHeight) + + navTitleLabel.layoutChain + .top(kStatusBarHeight + 12) + .centerY(backBtn) + .centerX() + + backBtn.layoutChain + .centerY(navTitleLabel) + .left(15) + .width(24) + .height(24) + + emailView.layoutChain + .topToBottomOfView(navBarView, offset: 20) + .centerX() + + signInLottieView.layoutChain + .topToBottomOfView(emailView, offset: 30) + .edgesHorzontal(16) + .heightToWidth(1.0) + + signInTextImg.layoutChain + .centerX(signInLottieView) + .centerY(signInLottieView) + + signInDaysLab.layoutChain + .topToBottomOfView(signInLottieView, offset: 16) + .centerX() + + agreementTV.layoutChain + .centerX() + .bottom(kSafeBottomMargin + 30) + + tipsView.layoutChain + .bottomToTopOfView(agreementTV, offset: -20) + .edgesHorzontal(40) + .centerX() + } + + lazy var navBgView: UIImageView = { + let iv = UIImageView() + iv.image = UIImage(named: "Common/navBar_bg_2") + iv.contentMode = .scaleAspectFill + return iv + }() + + lazy var navBarView: UIView = { + let view = UIView() + view.backgroundColor = .clear + 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 emailView: UIView = { + let view = UIView() + view.backgroundColor = .clear + + let title = UILabel() + title.text = "紧急人邮箱:" + title.textColor = UIColor(hexStr: "#999999") + title.font = .systemFont(ofSize: 14, weight: .regular) + view.addSubview(title) + title.layoutChain + .left() + .centerY() + .width(84) + + view.addSubview(emailLab) + emailLab.layoutChain + .leftToRightOfView(title) + .edgesVertical(2) + + let icon = UIImageView() + icon.image = UIImage(named: "Group/edit") + view.addSubview(icon) + icon.layoutChain + .leftToRightOfView(emailLab, offset: 10) + .right(15) + .width(20) + .height(20) + .centerY() + + return view + }() + + lazy var emailLab: UILabel = { + let label = UILabel() + label.text = "暂未添加" + label.textColor = UIColor(hexStr: "#333333") + label.font = .systemFont(ofSize: 14, weight: .medium) + return label + }() + + lazy var signInLottieView: LottieAnimationView = { + let view = LottieAnimationView(name: "sign_in_continuous_data") + view.loopMode = .loop + return view + }() + + lazy var signInTextImg: UIImageView = { + let view = UIImageView(image: UIImage(named: "SignIn/signIn_text")) + + return view + }() + + lazy var signInDaysLab: UILabel = { + let label = UILabel() + label.textColor = UIColor(hexStr: "#333333") + label.font = .systemFont(ofSize: 16, weight: .medium) + return label + }() + + lazy var tipsView: UIView = { + let view = UIView() + view.backgroundColor = UIColor(hexStr: "#F5FBFF") + view.cornerRadius = 5 + + let icon = UIImageView(image: UIImage(named: "SignIn/tips")) + view.addSubview(icon) + icon.layoutChain + .top(14) + .left(12) + .width(14) + .height(14) + + view.addSubview(tipsLab) + tipsLab.layoutChain + .leftToRightOfView(icon, offset: 3) + .edgesVertical(13) + .right(14) + + return view + }() + + lazy var tipsLab: UILabel = { + let label = UILabel() + label.text = "提示:2日未签到,系统将以您的名义, 在次日邮件通知您的紧急联系人。" + label.numberOfLines = 0 + label.textColor = UIColor(hexStr: "#666666") + label.font = .systemFont(ofSize: 14, weight: .regular) + return label + }() + + lazy var agreementTV: UITextView = { + let textView = UITextView() + textView.font = .systemFont(ofSize: 12, weight: .regular) + textView.backgroundColor = .clear + textView.isEditable = false + textView.isScrollEnabled = false + textView.isSelectable = false + textView.linkTextAttributes = [:] + textView.delegate = self + + let text = "签到即同意 用户协议 和 隐私政策" + let attributedString = NSMutableAttributedString(string: text) + attributedString.addAttributes([.foregroundColor: UIColor(hexStr: "#666666")], + range: NSRange(location: 0, length: text.length)) + attributedString.addAttributes([.foregroundColor: UIColor(hexStr: "#16B3FF"), + .link: "UserAgreement"], + range: (text as NSString).range(of: "用户协议")) + attributedString.addAttributes([.foregroundColor: UIColor(hexStr: "#16B3FF"), + .link: "PrivacyPolicy"], + range: (text as NSString).range(of: "隐私政策")) + + textView.attributedText = attributedString + return textView + }() + + override init(frame: CGRect) { + super.init(frame: .zero) + backgroundColor = .white + setupUI() + setupRx() + + signInLottieView.play() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +extension SignInView: UITextViewDelegate { + func textView(_ textView: UITextView, + shouldInteractWith URL: URL, + in characterRange: NSRange, + interaction: UITextItemInteraction) -> Bool { + if URL.absoluteString == "UserAgreement" { + AppRouter.push(Route.web, userInfo: ["url": URLManager.shared.userAgreementUrl]) + } + if URL.absoluteString == "PrivacyPolicy" { + AppRouter.push(Route.web, userInfo: ["url": URLManager.shared.privacyPolicyUrl]) + } + return true + } + + func textViewShouldBeginEditing(_ textView: UITextView) -> Bool { + false + } + + private func openURL(_ url: String) {} +} diff --git a/QuickLocation/Section/Login/LoginViewModel.swift b/QuickLocation/Section/Login/LoginViewModel.swift index 49d83a0..6418bd0 100644 --- a/QuickLocation/Section/Login/LoginViewModel.swift +++ b/QuickLocation/Section/Login/LoginViewModel.swift @@ -41,6 +41,9 @@ final class LoginViewModel: BaseViewModel { guard let model = response.model else { return } Defaults[\.loginToken] = model.token DLToast.showSuccess(text: "登录成功") { + if let userId = model.uid { + MQTTService.shared.updateClientID("smartdrive_\(userId)") + } NotificationCenter.default.post(name: .RefreshUserConfigNotification, object: nil) AppRouter.shared.popOrDismiss() } diff --git a/QuickLocation/Section/Map/Navigation/NavigationVC.swift b/QuickLocation/Section/Map/Navigation/NavigationVC.swift index 9ac5a7e..d0737ae 100644 --- a/QuickLocation/Section/Map/Navigation/NavigationVC.swift +++ b/QuickLocation/Section/Map/Navigation/NavigationVC.swift @@ -64,7 +64,13 @@ class NavigationVC: BaseViewController { private func setupMap() { #if !targetEnvironment(simulator) rootView.mapView.delegate = self - rootView.mapView.showsUserLocation = true + // 添加起点标注(当前用户位置) + if let from = currentUserCoord, CLLocationCoordinate2DIsValid(from) { + let startAnn = MAPointAnnotation() + startAnn.coordinate = from + startAnn.title = "起点" + rootView.mapView.addAnnotation(startAnn) + } #endif } @@ -153,7 +159,7 @@ extension NavigationVC: AMapNaviDriveManagerDelegate { if let polyline = MAPolyline(coordinates: &mutableCoords, count: UInt(coords.count)) { rootView.mapView.add(polyline) routeOverlay = polyline - rootView.mapView.setVisibleMapRect(polyline.boundingMapRect, animated: true) + rootView.mapView.setVisibleMapRect(polyline.boundingMapRect, edgePadding: UIEdgeInsets(top: 80, left: 50, bottom: 260, right: 50), animated: true) } let distM = Int(route.routeLength) @@ -178,15 +184,16 @@ extension NavigationVC: AMapNaviDriveManagerDelegate { extension NavigationVC: MAMapViewDelegate { func mapView(_ mapView: MAMapView!, viewFor annotation: MAAnnotation!) -> MAAnnotationView! { if annotation is MAUserLocation { return nil } - let identifier = "NavPin" - var view = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? MAPinAnnotationView + let identifier = "NavEnd" + var view = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) if view == nil { - view = MAPinAnnotationView(annotation: annotation, reuseIdentifier: identifier) + view = MAAnnotationView(annotation: annotation, reuseIdentifier: identifier) } else { view?.annotation = annotation } -// view?.pinColor = .red -// view?.canShowCallout = true + let isEnd = annotation.title == member.name + view?.image = UIImage(named: isEnd ? "Map/end" : "Map/current") + view?.centerOffset = CGPoint(x: 0, y: -22) return view } diff --git a/QuickLocation/Section/VipRecharge/VipRechargeVC.swift b/QuickLocation/Section/VipRecharge/VipRechargeVC.swift index 4140a7b..46d5d31 100644 --- a/QuickLocation/Section/VipRecharge/VipRechargeVC.swift +++ b/QuickLocation/Section/VipRecharge/VipRechargeVC.swift @@ -52,6 +52,10 @@ class VipRechargeVC: BaseViewController { } private func reactiveAction() { + rootView.payBtnView.rx.tapGesture.subscribe(onNext: { _ in + self.requestOrderPayParams() + }).disposed(by: disposeBag) + Observable.zip( rootView.expenseCollectionView.rx.itemSelected, rootView.expenseCollectionView.rx.modelSelected(VipExpenseModel.self) @@ -84,7 +88,7 @@ class VipRechargeVC: BaseViewController { // MARK: - API private func requestRechargeInfo() { DLToast.showLoading() - SystemService.rechargeInfo(type: "member").subscribe(onNext: { [weak self] response in + OrderService.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) @@ -92,4 +96,64 @@ class VipRechargeVC: BaseViewController { self.rootView.discountLab.text = self.viewModel.discountPriceString }).disposed(by: disposeBag) } + + // MARK: - API支付参数 + private func requestOrderPayParams() { + DLToast.showLoading() + let types = viewModel.payType.components(separatedBy: ",").map { $0.trimmingCharacters(in: .whitespaces) } + OrderService.orderPayParams(goodsId: viewModel.goodsId, + payType: types[rootView.selectedPayTypeTag]).subscribe(onNext: { response in + guard let data = response.data else { return } + + let payType = types[self.rootView.selectedPayTypeTag] + if payType == "weixin" { // 微信 +// WXApi.registerApp(model.appId, universalLink: AppSettings.kAppsUniversalLink) +// +// let request: PayReq = PayReq() +// request.partnerId = model.partnerId +// request.prepayId = model.prepayId +// request.package = model.packageValue +// request.nonceStr = model.nonceStr +// request.timeStamp = UInt32(model.timeStamp) ?? 0 +// request.sign = model.sign +// WXApi.send(request) + } + else if payType == "alipay" { // 支付宝 + if let payParam = data["payParam"] as? String, let appId = data["payParam"] as? String { + AlipaySDK.defaultService().payOrder(payParam, fromScheme: "Alipay\(appId)") { resultDic in + print("支付宝callback -> \(resultDic)") + guard let result = resultDic, + let resultStatus = result["resultStatus"] as? String else { return } + /** + 9000 订单支付成功。 + 8000 正在处理中,支付结果未知(有可能已经支付成功),请查询商家订单列表中订单的支付状态。 + 4000 订单支付失败。 + 5000 重复请求。 + 6001 用户中途取消。 + 6002 网络连接出错。 + 6004 支付结果未知(有可能已经支付成功),请查询商家订单列表中订单的支付状态。 + 其它 其它支付错误。 + */ + if resultStatus == "9000" { + self.showPayResultPop(showCloseBtn: false, + status: true, + title: "支付成功", + message: "恭喜您!成功开通\(self.viewModel.goodsName)", + confirmText: "确定", confirmBlock: { + AppRouter.shared.popOrDismiss() + }, cancelBlock: { }) + } + else { + self.showPayResultPop(status: false, + title: "支付失败", + message: "很抱歉,请您重新支付!", + confirmText: "重新支付", confirmBlock: { }, cancelText: "取消", cancelBlock: { + AppRouter.shared.popOrDismiss() + }) + } + } + } + } + }, onError: { _ in }).disposed(by: disposeBag) + } } diff --git a/QuickLocation/Section/VipRecharge/VipRechargeVM.swift b/QuickLocation/Section/VipRecharge/VipRechargeVM.swift index 3e7571a..34ac31a 100644 --- a/QuickLocation/Section/VipRecharge/VipRechargeVM.swift +++ b/QuickLocation/Section/VipRecharge/VipRechargeVM.swift @@ -51,6 +51,17 @@ class VipRechargeVM { } } var list: [VipExpenseModel] = [] + + var goodsId: String { + guard list.count > 0 else { return "" } + return list[selectedIndex].goods_id + } + + var goodsName: String { + guard list.count > 0 else { return "" } + return list[selectedIndex].goods_name + } + var payType: String { guard list.count > 0 else { return "" } return list[selectedIndex].pay_type diff --git a/QuickLocation/Section/VipRecharge/VipRechargeView.swift b/QuickLocation/Section/VipRecharge/VipRechargeView.swift index a0fc12d..665ad95 100644 --- a/QuickLocation/Section/VipRecharge/VipRechargeView.swift +++ b/QuickLocation/Section/VipRecharge/VipRechargeView.swift @@ -70,17 +70,17 @@ class VipRechargeView: UIView { .edgesHorzontal() .heightToWidth(144/375) .bottom() - - payTypeStackView.layoutChain - .top(25) - .edgesHorzontal(16) - .centerX() - + payBtnView.layoutChain .edgesHorzontal(16) .heightToWidth(50/343) .centerY() + payTypeStackView.layoutChain + .top(13) + .edgesHorzontal(16) + .bottomToTopOfView(payBtnView) + payPriceView.layoutChain .left(32) .centerY(payBtnView, offset: -7) @@ -549,8 +549,8 @@ class VipRechargeView: UIView { let stack = UIStackView() stack.axis = .horizontal stack.spacing = 30 -// stack.distribution = .fillEqually - stack.alignment = .center +// stack.distribution = .fill + stack.alignment = .leading return stack }() @@ -571,6 +571,7 @@ class VipRechargeView: UIView { for (idx, (icon, name)) in payTypeMap.enumerated() { let isSelected = idx == 0 let view = makePayTypeView(tag: idx, icon: icon, name: name, isSelected: isSelected) + view.layoutChain.width(110).height(22) payTypeStackView.addArrangedSubview(view) } } diff --git a/QuickLocation/Service/OrderService.swift b/QuickLocation/Service/OrderService.swift new file mode 100644 index 0000000..55cc55a --- /dev/null +++ b/QuickLocation/Service/OrderService.swift @@ -0,0 +1,36 @@ +// +// OrderService.swift +// QuickLocation +// +// Created by 八条 on 2026/6/17. +// + +import RxSwift +import Moya + +struct OrderService { + static let disposeBag = DisposeBag() + + /// 充值内容 + /// - Parameters: + /// - type: 类型 member + static func rechargeInfo(type: String) -> Observable { + let api = OrderAPI.rechargeInfo(type: type).multiTarget + return APIProvider.request(token: api) + .map(VipExpenseResponse.self) + .asObservable() + } + + /// 支付参数 + /// - Parameters: + /// - goodsId: 商品ID + /// - payType: alipay、weixin + /// - source: center + /// - extra: 额外参数 + static func orderPayParams(goodsId: String, payType: String, source: String="center", extra: [String:Any]=[:]) -> Observable { + let api = OrderAPI.orderPayParams(goodsId: goodsId, payType: payType, source: source, extra: extra).multiTarget + return APIProvider.request(token: api) + .map(ResponseModel.self) + .asObservable() + } +} diff --git a/QuickLocation/Service/SystemService.swift b/QuickLocation/Service/SystemService.swift index 6a7914a..58bdb51 100644 --- a/QuickLocation/Service/SystemService.swift +++ b/QuickLocation/Service/SystemService.swift @@ -26,16 +26,6 @@ struct SystemService { .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() - } - /// 微信客服 static func wechatService() -> Observable { let api = SystemAPI.wechatService.multiTarget diff --git a/QuickLocation/Service/UserService.swift b/QuickLocation/Service/UserService.swift index 2e1659e..7aa47b9 100644 --- a/QuickLocation/Service/UserService.swift +++ b/QuickLocation/Service/UserService.swift @@ -41,6 +41,14 @@ struct UserService { .asObservable() } + /// 签到信息 + static func signInInfo() -> Observable { + let api = UserAPI.signInInfo.multiTarget + return APIProvider.request(token: api) + .map(SignInInfoResponse.self) + .asObservable() + } + /// 更换手机 static func changePhone(timestamp: String, phone: String, code: String) -> Observable { let api = UserAPI.changePhone(timestamp: timestamp, phone: phone, code: code).multiTarget diff --git a/QuickLocation/UIKit/Pop/PayResultPopVC.swift b/QuickLocation/UIKit/Pop/PayResultPopVC.swift new file mode 100644 index 0000000..a222075 --- /dev/null +++ b/QuickLocation/UIKit/Pop/PayResultPopVC.swift @@ -0,0 +1,281 @@ +// +// PayResultPopVC.swift +// QuickLocation +// +// Created by 八条 on 2026/6/17. +// + +import UIKit +import RxSwift +import RxCocoa +import URLNavigator + +class PayResultPopVC: DLCustomPopVC { + + var disposeBag = DisposeBag() + + // MARK: - Accessor + public var confirmBlock: (() -> Void)? + public var cancelBlock: (() -> Void)? + + public var status: Bool? { + didSet { + guard let status = status else { return } + logoImgView.image = UIImage(named: status ? "Popup/success" : "Popup/failure") + } + } + public var titleText: String? { + didSet { + titleLab.text = titleText + } + } + public var contentText: String? { + didSet { + contentLab.text = contentText + } + } + public var cancelText: String? { + didSet { + closeBtn.setTitle(cancelText, for: .normal) + } + } + public var confirmText: String? { + didSet { + confirmBtn.setTitle(confirmText, for: .normal) + } + } + + private func setupSubviews() { + contentBgView.addSubview(logoImgView) + contentView.addSubview(headerBgImg) + + if titleText != nil, + titleText?.isEmpty == false { + contentView.addSubview(titleLab) + } + + if contentText != nil, + contentText?.isEmpty == false { + contentView.addSubview(contentLab) + } + + if cancelText?.isEmpty == false || confirmText?.isEmpty == false { + contentView.addSubview(stackView) + } + + closeBtn.isHidden = !(cancelText?.isEmpty == false) + confirmBtn.isHidden = !(confirmText?.isEmpty == false) + } + + private func setupLayout() { + logoImgView.layoutChain + .centerX() + .top(-44) + .width(88) + .height(88) + + headerBgImg.layoutChain + .edges(excludingEdge: .bottom) + .heightToWidth(100/315) + + var tempView: UIView? + if titleText != nil, + titleText?.isEmpty == false { + titleLab.layoutChain + .top(69) + .edgesHorzontal(20) + tempView = titleLab + } + + if contentText != nil, + contentText?.isEmpty == false { + + if let tempView = tempView { + contentLab.layoutChain + .edgesHorzontal(20) + .topToBottomOfView(tempView, offset: 20) + } else { + contentLab.layoutChain + .top(53) + .edgesHorzontal(20) + } + tempView = contentLab + } + + guard let tempView = tempView else { return } + if cancelText?.isEmpty == false || confirmText?.isEmpty == false { + stackView.layoutChain + .topToBottomOfView(tempView, offset: 30) + .left(20) + .right(20) + .bottom(15) + + confirmBtn.layoutChain + .height(50) + + closeBtn.layoutChain + .height(50) + } + } + + // MARK: - UI + private lazy var bgView: UIView = { + let view = UIView() + view.backgroundColor = .black.withAlphaComponent(0.5) + return view + }() + + lazy var popView: UIView = { + let view = UIView() + view.backgroundColor = .clear + return view + }() + + lazy var popBgView: UIView = { + let view = UIView() + view.backgroundColor = .white + view.cornerRadius = 30 + return view + }() + + lazy var logoImgView: UIImageView = { + let view = UIImageView() + view.backgroundColor = .clear + return view + }() + + lazy var headerBgImg: UIImageView = { + let view = UIImageView(image: UIImage(named: "Popup/header_bg")) + view.contentMode = .scaleAspectFill + return view + }() + + lazy var titleLab: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 20, weight: .bold) + label.textColor = UIColor(hexStr: "#1A1A1A") + label.textAlignment = .center + label.numberOfLines = 0 + return label + }() + + lazy var contentLab: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 14, weight: .medium) + label.textColor = UIColor(hexStr: "#767676") + label.textAlignment = .center + label.numberOfLines = 0 + return label + }() + + lazy var stackView: UIStackView = { + let view = UIStackView(arrangedSubviews: [confirmBtn, closeBtn]) + view.spacing = 10 + view.axis = .vertical + view.alignment = .fill + view.distribution = .fill + return view + }() + + lazy var confirmBtn: UIButton = { + let btn = UIButton(type: .custom) + btn.setTitle("是", for: .normal) + btn.setTitleColor(.white, for: .normal) + btn.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium) + btn.setBackgroundImage(UIImage(named: "Common/button_bg_2"), for: .normal) + btn.cornerRadius = 25 + btn.addTouchBlock { [weak self] _ in + self?.dismiss(animated: true, completion: { + self?.confirmBlock?() + }) + } + return btn + }() + + lazy var closeBtn: UIButton = { + let btn = UIButton(type: .custom) + btn.setTitle("否", for: .normal) + btn.setTitleColor(UIColor(hexStr: "#16B3FF"), for: .normal) + btn.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium) + btn.backgroundColor = .clear + btn.addTouchBlock { [weak self] _ in + self?.dismiss(animated: true, completion: { + self?.cancelBlock?() + }) + } + return btn + }() + + // MARK: - Lifecycle + override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + popStyle = .center + centerCornerRadius = 30 + } + + public required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func viewDidLoad() { + super.viewDidLoad() + + setupSubviews() + setupLayout() + } +} + +public extension UIViewController { + /// 显示弹窗 + /// - Parameters: + /// - status: 支付状态 true false + /// - title: 标题 + /// - message: 文本内容 + /// - confirmText: 确认按钮文案 + /// - confirmBlock: 确认按钮点击回调 + /// - cancelText: 取消按钮文案 + /// - cancelBlock: 取消按钮点击回调 + func showPayResultPop(showCloseBtn: Bool = true, + status: Bool, + title: String?=nil, + message: String?=nil, + confirmText: String?=nil, + confirmBlock: (() -> Void)?=nil, + cancelText: String?=nil, + cancelBlock: (() -> Void)?=nil) { + if title == nil && message == nil { return } + let vc = PayResultPopVC() + vc.titleText = title + vc.status = status + vc.contentText = message + vc.confirmText = confirmText + vc.confirmBlock = confirmBlock + vc.cancelText = cancelText + vc.cancelBlock = cancelBlock + vc.closeBtn.isHidden = !showCloseBtn + vc.dimmingClick = showCloseBtn + present(vc, animated: true) + } + + static func showPayResultPop(showCloseBtn: Bool = true, + status: Bool, + title: String?=nil, + message: String?=nil, + confirmText: String?=nil, + confirmBlock: (() -> Void)?=nil, + cancelText: String?=nil, + cancelBlock: (() -> Void)?=nil) { + if title == nil && message == nil { return } + let vc = PayResultPopVC() + vc.titleText = title + vc.status = status + vc.contentText = message + vc.confirmText = confirmText + vc.confirmBlock = confirmBlock + vc.cancelText = cancelText + vc.cancelBlock = cancelBlock + vc.closeBtn.isHidden = !showCloseBtn + vc.dimmingClick = showCloseBtn + UIViewController.topMost?.present(vc, animated: true) + } +}