|
|
@ -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 = "<group>"; };
|
||||
30C4C01F2FDC0EC5009215C1 /* GroupInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupInfoView.swift; sourceTree = "<group>"; };
|
||||
30C4C0212FDC0ED3009215C1 /* GroupInfoVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupInfoVC.swift; sourceTree = "<group>"; };
|
||||
30CCDE4D2FE26CEA00F5214A /* PayResultPopVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayResultPopVC.swift; sourceTree = "<group>"; };
|
||||
30CCDE502FE2785D00F5214A /* SignInVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInVC.swift; sourceTree = "<group>"; };
|
||||
30CCDE522FE2786600F5214A /* SignInView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInView.swift; sourceTree = "<group>"; };
|
||||
30CCDE542FE2903100F5214A /* SignInModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInModel.swift; sourceTree = "<group>"; };
|
||||
30D87CDA2FDFA9EE00E958FD /* MQTTService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MQTTService.swift; sourceTree = "<group>"; };
|
||||
30D87CDC2FDFF07500E958FD /* InteractionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractionView.swift; sourceTree = "<group>"; };
|
||||
30D87CDE2FDFF1A100E958FD /* QuickMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickMessageView.swift; sourceTree = "<group>"; };
|
||||
30D87D012FE1336300E958FD /* NavigationVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationVC.swift; sourceTree = "<group>"; };
|
||||
30D87D022FE1336300E958FD /* NavigationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationView.swift; sourceTree = "<group>"; };
|
||||
30D891F42FE22E0600E958FD /* OrderAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderAPI.swift; sourceTree = "<group>"; };
|
||||
30D891F62FE22E6E00E958FD /* OrderService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderService.swift; sourceTree = "<group>"; };
|
||||
30DC18512FD009CD0041DCD1 /* VipExpenseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VipExpenseModel.swift; sourceTree = "<group>"; };
|
||||
30DC18532FD00C4A0041DCD1 /* VipRechargeVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VipRechargeVM.swift; sourceTree = "<group>"; };
|
||||
30DC18552FD11E7A0041DCD1 /* NavigationTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationTitleView.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -479,15 +491,11 @@
|
|||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||
3070777D2FD2A214004C37CC /* lotties */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
);
|
||||
path = lotties;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
30D87CEF2FDFF52100E958FD /* TTGTagCollectionView */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
);
|
||||
path = TTGTagCollectionView;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
|
|
@ -526,6 +534,7 @@
|
|||
305A74CE2FCA8C7000227D26 /* SystemAPI.swift */,
|
||||
305A74CF2FCA8C7000227D26 /* UserAPI.swift */,
|
||||
30BAB8502FCD331C00C33B5C /* GroupAPI.swift */,
|
||||
30D891F42FE22E0600E958FD /* OrderAPI.swift */,
|
||||
);
|
||||
path = API;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -866,6 +875,7 @@
|
|||
30A7A9102FCAEE3D00105780 /* GroupListPopView.swift */,
|
||||
30D87CDE2FDFF1A100E958FD /* QuickMessageView.swift */,
|
||||
30D87CDC2FDFF07500E958FD /* InteractionView.swift */,
|
||||
30CCDE4F2FE2782700F5214A /* SignIn */,
|
||||
);
|
||||
path = Home;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -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 = "<group>";
|
||||
|
|
@ -939,6 +949,7 @@
|
|||
305A763B2FCA8C7000227D26 /* SystemService.swift */,
|
||||
305A763C2FCA8C7000227D26 /* UserService.swift */,
|
||||
30BAB8522FCD337C00C33B5C /* GroupService.swift */,
|
||||
30D891F62FE22E6E00E958FD /* OrderService.swift */,
|
||||
);
|
||||
path = Service;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -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 = "<group>";
|
||||
};
|
||||
30CCDE4F2FE2782700F5214A /* SignIn */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
30CCDE502FE2785D00F5214A /* SignInVC.swift */,
|
||||
30CCDE522FE2786600F5214A /* SignInView.swift */,
|
||||
30CCDE542FE2903100F5214A /* SignInModel.swift */,
|
||||
);
|
||||
path = SignIn;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -3,4 +3,22 @@
|
|||
uuid = "AE547311-A826-480F-B1ED-BD6B815729FA"
|
||||
type = "0"
|
||||
version = "2.0">
|
||||
<Breakpoints>
|
||||
<BreakpointProxy
|
||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||
<BreakpointContent
|
||||
uuid = "AA447600-D860-4876-A728-F18CC2D566FC"
|
||||
shouldBeEnabled = "Yes"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
filePath = "QuickLocation/AppDelegate.swift"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "70"
|
||||
endingLineNumber = "70"
|
||||
landmarkName = "application(_:open:options:)"
|
||||
landmarkType = "7">
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
</Breakpoints>
|
||||
</Bucket>
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"provides-namespace" : true
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
BIN
QuickLocation/Assets.xcassets/SignIn/signIn_text.imageset/Group 2404@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
QuickLocation/Assets.xcassets/SignIn/signIn_text.imageset/Group 2404@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
|
@ -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
|
||||
}
|
||||
}
|
||||
BIN
QuickLocation/Assets.xcassets/SignIn/today_text.imageset/today_signin@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
QuickLocation/Assets.xcassets/SignIn/today_text.imageset/today_signin@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 19 KiB |
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 765 B |
|
Before Width: | Height: | Size: 8.1 KiB |
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 761 B |
|
Before Width: | Height: | Size: 8.1 KiB |
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 773 B |
|
Before Width: | Height: | Size: 8.8 KiB |
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 766 B |
|
Before Width: | Height: | Size: 8.4 KiB |
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 772 B |
|
Before Width: | Height: | Size: 8.6 KiB |
|
|
@ -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")
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,16 @@
|
|||
<dict>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>alipay</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>Alipay2021005185689681</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
|
|
@ -17,13 +27,13 @@
|
|||
</array>
|
||||
<key>LSApplicationQueriesSchemes</key>
|
||||
<array>
|
||||
<string>alipays</string>
|
||||
<string>baidumap</string>
|
||||
<string>qqmap</string>
|
||||
<string>iosamap</string>
|
||||
<string>weixin</string>
|
||||
<string>weixinULAPI</string>
|
||||
<string>weixinURLParamsAPI</string>
|
||||
<string>alipays</string>
|
||||
<string>baidumap</string>
|
||||
<string>qqmap</string>
|
||||
<string>iosamap</string>
|
||||
<string>weixin</string>
|
||||
<string>weixinULAPI</string>
|
||||
<string>weixinURLParamsAPI</string>
|
||||
</array>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
|
|
|
|||
|
|
@ -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?()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,4 +25,7 @@
|
|||
// 微信
|
||||
#import <WechatOpenSDK/WXApi.h>
|
||||
|
||||
// 支付宝
|
||||
#import <AlipaySDK/AlipaySDK.h>
|
||||
|
||||
#endif /* QuickLocation_Bridging_Header_h */
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict/>
|
||||
<dict>
|
||||
<key>com.apple.developer.associated-domains</key>
|
||||
<array>
|
||||
<string>applinks:smartdrive.zuom8.cn</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -258,6 +258,7 @@ class GroupMemberCell: UITableViewCell {
|
|||
.centerY(locationLab)
|
||||
.width(10)
|
||||
.height(10)
|
||||
.right(80, relation: .greaterThanOrEqual)
|
||||
|
||||
tagView.layoutChain
|
||||
.leftToRightOfView(nameLab, offset: 4)
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<VipExpenseResponse> {
|
||||
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<ResponseModel> {
|
||||
let api = OrderAPI.orderPayParams(goodsId: goodsId, payType: payType, source: source, extra: extra).multiTarget
|
||||
return APIProvider.request(token: api)
|
||||
.map(ResponseModel.self)
|
||||
.asObservable()
|
||||
}
|
||||
}
|
||||
|
|
@ -26,16 +26,6 @@ struct SystemService {
|
|||
.asObservable()
|
||||
}
|
||||
|
||||
/// 充值内容
|
||||
/// - Parameters:
|
||||
/// - type: 类型 member
|
||||
static func rechargeInfo(type: String) -> Observable<VipExpenseResponse> {
|
||||
let api = SystemAPI.rechargeInfo(type: type).multiTarget
|
||||
return APIProvider.request(token: api)
|
||||
.map(VipExpenseResponse.self)
|
||||
.asObservable()
|
||||
}
|
||||
|
||||
/// 微信客服
|
||||
static func wechatService() -> Observable<ResponseModel> {
|
||||
let api = SystemAPI.wechatService.multiTarget
|
||||
|
|
|
|||
|
|
@ -41,6 +41,14 @@ struct UserService {
|
|||
.asObservable()
|
||||
}
|
||||
|
||||
/// 签到信息
|
||||
static func signInInfo() -> Observable<SignInInfoResponse> {
|
||||
let api = UserAPI.signInInfo.multiTarget
|
||||
return APIProvider.request(token: api)
|
||||
.map(SignInInfoResponse.self)
|
||||
.asObservable()
|
||||
}
|
||||
|
||||
/// 更换手机
|
||||
static func changePhone(timestamp: String, phone: String, code: String) -> Observable<ResponseModel> {
|
||||
let api = UserAPI.changePhone(timestamp: timestamp, phone: phone, code: code).multiTarget
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||