parent
7cddc4499a
commit
6de0cfd68a
|
|
@ -199,6 +199,13 @@
|
||||||
30B74B412FF2437E00F6744D /* GroupMemberListVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30B74B402FF2437E00F6744D /* GroupMemberListVC.swift */; };
|
30B74B412FF2437E00F6744D /* GroupMemberListVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30B74B402FF2437E00F6744D /* GroupMemberListVC.swift */; };
|
||||||
30B74B432FF2438800F6744D /* GroupMemberListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30B74B422FF2438800F6744D /* GroupMemberListView.swift */; };
|
30B74B432FF2438800F6744D /* GroupMemberListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30B74B422FF2438800F6744D /* GroupMemberListView.swift */; };
|
||||||
30B74B452FF24D1B00F6744D /* GroupMemberListVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30B74B442FF24D1B00F6744D /* GroupMemberListVM.swift */; };
|
30B74B452FF24D1B00F6744D /* GroupMemberListVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30B74B442FF24D1B00F6744D /* GroupMemberListVM.swift */; };
|
||||||
|
30B74B472FF3608600F6744D /* ScheduleRecordModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30B74B462FF3608600F6744D /* ScheduleRecordModel.swift */; };
|
||||||
|
30B74B492FF3680200F6744D /* InputEmailPopupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30B74B482FF3680200F6744D /* InputEmailPopupView.swift */; };
|
||||||
|
30B74B4B2FF390EF00F6744D /* DrivingAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30B74B4A2FF390EF00F6744D /* DrivingAPI.swift */; };
|
||||||
|
30B74B4D2FF391FF00F6744D /* DrivingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30B74B4C2FF391FF00F6744D /* DrivingService.swift */; };
|
||||||
|
30B74B4F2FF392D800F6744D /* DrivingStatsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30B74B4E2FF392D800F6744D /* DrivingStatsModel.swift */; };
|
||||||
|
30B819F22FF3CE3C00FAB693 /* ItineraryTraceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30B819F12FF3CE3C00FAB693 /* ItineraryTraceView.swift */; };
|
||||||
|
30B819F42FF3CE4900FAB693 /* ItineraryTraceVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30B819F32FF3CE4900FAB693 /* ItineraryTraceVC.swift */; };
|
||||||
30BAB84D2FCD2FDE00C33B5C /* InviteJoinView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BAB84C2FCD2FDE00C33B5C /* InviteJoinView.swift */; };
|
30BAB84D2FCD2FDE00C33B5C /* InviteJoinView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BAB84C2FCD2FDE00C33B5C /* InviteJoinView.swift */; };
|
||||||
30BAB84F2FCD2FED00C33B5C /* InviteJoinVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BAB84E2FCD2FED00C33B5C /* InviteJoinVC.swift */; };
|
30BAB84F2FCD2FED00C33B5C /* InviteJoinVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BAB84E2FCD2FED00C33B5C /* InviteJoinVC.swift */; };
|
||||||
30BAB8512FCD331C00C33B5C /* GroupAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BAB8502FCD331C00C33B5C /* GroupAPI.swift */; };
|
30BAB8512FCD331C00C33B5C /* GroupAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BAB8502FCD331C00C33B5C /* GroupAPI.swift */; };
|
||||||
|
|
@ -486,6 +493,13 @@
|
||||||
30B74B402FF2437E00F6744D /* GroupMemberListVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupMemberListVC.swift; sourceTree = "<group>"; };
|
30B74B402FF2437E00F6744D /* GroupMemberListVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupMemberListVC.swift; sourceTree = "<group>"; };
|
||||||
30B74B422FF2438800F6744D /* GroupMemberListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupMemberListView.swift; sourceTree = "<group>"; };
|
30B74B422FF2438800F6744D /* GroupMemberListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupMemberListView.swift; sourceTree = "<group>"; };
|
||||||
30B74B442FF24D1B00F6744D /* GroupMemberListVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupMemberListVM.swift; sourceTree = "<group>"; };
|
30B74B442FF24D1B00F6744D /* GroupMemberListVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupMemberListVM.swift; sourceTree = "<group>"; };
|
||||||
|
30B74B462FF3608600F6744D /* ScheduleRecordModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleRecordModel.swift; sourceTree = "<group>"; };
|
||||||
|
30B74B482FF3680200F6744D /* InputEmailPopupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputEmailPopupView.swift; sourceTree = "<group>"; };
|
||||||
|
30B74B4A2FF390EF00F6744D /* DrivingAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DrivingAPI.swift; sourceTree = "<group>"; };
|
||||||
|
30B74B4C2FF391FF00F6744D /* DrivingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DrivingService.swift; sourceTree = "<group>"; };
|
||||||
|
30B74B4E2FF392D800F6744D /* DrivingStatsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DrivingStatsModel.swift; sourceTree = "<group>"; };
|
||||||
|
30B819F12FF3CE3C00FAB693 /* ItineraryTraceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItineraryTraceView.swift; sourceTree = "<group>"; };
|
||||||
|
30B819F32FF3CE4900FAB693 /* ItineraryTraceVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItineraryTraceVC.swift; sourceTree = "<group>"; };
|
||||||
30BAB84C2FCD2FDE00C33B5C /* InviteJoinView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteJoinView.swift; sourceTree = "<group>"; };
|
30BAB84C2FCD2FDE00C33B5C /* InviteJoinView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteJoinView.swift; sourceTree = "<group>"; };
|
||||||
30BAB84E2FCD2FED00C33B5C /* InviteJoinVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteJoinVC.swift; sourceTree = "<group>"; };
|
30BAB84E2FCD2FED00C33B5C /* InviteJoinVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteJoinVC.swift; sourceTree = "<group>"; };
|
||||||
30BAB8502FCD331C00C33B5C /* GroupAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupAPI.swift; sourceTree = "<group>"; };
|
30BAB8502FCD331C00C33B5C /* GroupAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupAPI.swift; sourceTree = "<group>"; };
|
||||||
|
|
@ -636,6 +650,7 @@
|
||||||
30BAB8502FCD331C00C33B5C /* GroupAPI.swift */,
|
30BAB8502FCD331C00C33B5C /* GroupAPI.swift */,
|
||||||
30D891F42FE22E0600E958FD /* OrderAPI.swift */,
|
30D891F42FE22E0600E958FD /* OrderAPI.swift */,
|
||||||
30D74AB52FEA34FF0050EB2C /* ItineraryAPI.swift */,
|
30D74AB52FEA34FF0050EB2C /* ItineraryAPI.swift */,
|
||||||
|
30B74B4A2FF390EF00F6744D /* DrivingAPI.swift */,
|
||||||
);
|
);
|
||||||
path = API;
|
path = API;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -1060,6 +1075,7 @@
|
||||||
30BAB8522FCD337C00C33B5C /* GroupService.swift */,
|
30BAB8522FCD337C00C33B5C /* GroupService.swift */,
|
||||||
30D891F62FE22E6E00E958FD /* OrderService.swift */,
|
30D891F62FE22E6E00E958FD /* OrderService.swift */,
|
||||||
30D74AB72FEA36A50050EB2C /* ItineraryService.swift */,
|
30D74AB72FEA36A50050EB2C /* ItineraryService.swift */,
|
||||||
|
30B74B4C2FF391FF00F6744D /* DrivingService.swift */,
|
||||||
);
|
);
|
||||||
path = Service;
|
path = Service;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -1337,6 +1353,10 @@
|
||||||
30B74B402FF2437E00F6744D /* GroupMemberListVC.swift */,
|
30B74B402FF2437E00F6744D /* GroupMemberListVC.swift */,
|
||||||
30B74B422FF2438800F6744D /* GroupMemberListView.swift */,
|
30B74B422FF2438800F6744D /* GroupMemberListView.swift */,
|
||||||
30B74B442FF24D1B00F6744D /* GroupMemberListVM.swift */,
|
30B74B442FF24D1B00F6744D /* GroupMemberListVM.swift */,
|
||||||
|
30B74B4E2FF392D800F6744D /* DrivingStatsModel.swift */,
|
||||||
|
30B74B462FF3608600F6744D /* ScheduleRecordModel.swift */,
|
||||||
|
30B819F32FF3CE4900FAB693 /* ItineraryTraceVC.swift */,
|
||||||
|
30B819F12FF3CE3C00FAB693 /* ItineraryTraceView.swift */,
|
||||||
);
|
);
|
||||||
path = GroupMemberList;
|
path = GroupMemberList;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -1402,6 +1422,7 @@
|
||||||
30CCDE502FE2785D00F5214A /* SignInVC.swift */,
|
30CCDE502FE2785D00F5214A /* SignInVC.swift */,
|
||||||
30CCDE522FE2786600F5214A /* SignInView.swift */,
|
30CCDE522FE2786600F5214A /* SignInView.swift */,
|
||||||
30CCDE542FE2903100F5214A /* SignInModel.swift */,
|
30CCDE542FE2903100F5214A /* SignInModel.swift */,
|
||||||
|
30B74B482FF3680200F6744D /* InputEmailPopupView.swift */,
|
||||||
);
|
);
|
||||||
path = SignIn;
|
path = SignIn;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -1802,6 +1823,7 @@
|
||||||
305A769F2FCA8C7000227D26 /* TextContentArrowCell.swift in Sources */,
|
305A769F2FCA8C7000227D26 /* TextContentArrowCell.swift in Sources */,
|
||||||
30D87D042FE1336300E958FD /* NavigationVC.swift in Sources */,
|
30D87D042FE1336300E958FD /* NavigationVC.swift in Sources */,
|
||||||
30D87D052FE1336300E958FD /* NavigationView.swift in Sources */,
|
30D87D052FE1336300E958FD /* NavigationView.swift in Sources */,
|
||||||
|
30B819F42FF3CE4900FAB693 /* ItineraryTraceVC.swift in Sources */,
|
||||||
305A76A02FCA8C7000227D26 /* TextTableViewCell.swift in Sources */,
|
305A76A02FCA8C7000227D26 /* TextTableViewCell.swift in Sources */,
|
||||||
305A76A12FCA8C7000227D26 /* UIButton+RTL.m in Sources */,
|
305A76A12FCA8C7000227D26 /* UIButton+RTL.m in Sources */,
|
||||||
30EFF3A62FD7C5AF00EB35D4 /* GroupSettingVC.swift in Sources */,
|
30EFF3A62FD7C5AF00EB35D4 /* GroupSettingVC.swift in Sources */,
|
||||||
|
|
@ -1827,6 +1849,7 @@
|
||||||
305A76AC2FCA8C7000227D26 /* String+Extension.swift in Sources */,
|
305A76AC2FCA8C7000227D26 /* String+Extension.swift in Sources */,
|
||||||
30D74AB82FEA36A50050EB2C /* ItineraryService.swift in Sources */,
|
30D74AB82FEA36A50050EB2C /* ItineraryService.swift in Sources */,
|
||||||
30EFF3C62FDA433E00EB35D4 /* ChangePhoneView.swift in Sources */,
|
30EFF3C62FDA433E00EB35D4 /* ChangePhoneView.swift in Sources */,
|
||||||
|
30B74B492FF3680200F6744D /* InputEmailPopupView.swift in Sources */,
|
||||||
305A76AD2FCA8C7000227D26 /* UIApplicationExtension.swift in Sources */,
|
305A76AD2FCA8C7000227D26 /* UIApplicationExtension.swift in Sources */,
|
||||||
305A76AE2FCA8C7000227D26 /* UIButton+Extension.swift in Sources */,
|
305A76AE2FCA8C7000227D26 /* UIButton+Extension.swift in Sources */,
|
||||||
305A76AF2FCA8C7000227D26 /* UIColor+Extension.swift in Sources */,
|
305A76AF2FCA8C7000227D26 /* UIColor+Extension.swift in Sources */,
|
||||||
|
|
@ -1841,6 +1864,7 @@
|
||||||
30EFF3E02FDA9CE300EB35D4 /* EmergencyContactModel.swift in Sources */,
|
30EFF3E02FDA9CE300EB35D4 /* EmergencyContactModel.swift in Sources */,
|
||||||
305A76B62FCA8C7000227D26 /* UITextField+Extensions.swift in Sources */,
|
305A76B62FCA8C7000227D26 /* UITextField+Extensions.swift in Sources */,
|
||||||
30EFF3A82FD7C6A400EB35D4 /* GroupSettingViewModel.swift in Sources */,
|
30EFF3A82FD7C6A400EB35D4 /* GroupSettingViewModel.swift in Sources */,
|
||||||
|
30B819F22FF3CE3C00FAB693 /* ItineraryTraceView.swift in Sources */,
|
||||||
305A76B72FCA8C7000227D26 /* UIView+Extension.swift in Sources */,
|
305A76B72FCA8C7000227D26 /* UIView+Extension.swift in Sources */,
|
||||||
305A76B82FCA8C7000227D26 /* UIViewController+Extension.swift in Sources */,
|
305A76B82FCA8C7000227D26 /* UIViewController+Extension.swift in Sources */,
|
||||||
305A76B92FCA8C7000227D26 /* URL+Extension.swift in Sources */,
|
305A76B92FCA8C7000227D26 /* URL+Extension.swift in Sources */,
|
||||||
|
|
@ -1872,6 +1896,7 @@
|
||||||
305A76C62FCA8C7000227D26 /* AppContextManager.swift in Sources */,
|
305A76C62FCA8C7000227D26 /* AppContextManager.swift in Sources */,
|
||||||
30D74ABD2FEA67EA0050EB2C /* CreateScheduleVC.swift in Sources */,
|
30D74ABD2FEA67EA0050EB2C /* CreateScheduleVC.swift in Sources */,
|
||||||
305A76C72FCA8C7000227D26 /* UserConfigModel.swift in Sources */,
|
305A76C72FCA8C7000227D26 /* UserConfigModel.swift in Sources */,
|
||||||
|
30B74B472FF3608600F6744D /* ScheduleRecordModel.swift in Sources */,
|
||||||
305A76C82FCA8C7000227D26 /* UserConfigResponse.swift in Sources */,
|
305A76C82FCA8C7000227D26 /* UserConfigResponse.swift in Sources */,
|
||||||
305A76C92FCA8C7000227D26 /* ApiManager.swift in Sources */,
|
305A76C92FCA8C7000227D26 /* ApiManager.swift in Sources */,
|
||||||
305A76CA2FCA8C7000227D26 /* AppSettings.swift in Sources */,
|
305A76CA2FCA8C7000227D26 /* AppSettings.swift in Sources */,
|
||||||
|
|
@ -1891,6 +1916,7 @@
|
||||||
305A76D42FCA8C7000227D26 /* GroupModel.swift in Sources */,
|
305A76D42FCA8C7000227D26 /* GroupModel.swift in Sources */,
|
||||||
305A798C2FCAB99300227D26 /* HomeViewModel.swift in Sources */,
|
305A798C2FCAB99300227D26 /* HomeViewModel.swift in Sources */,
|
||||||
30EFF3D12FDA69EC00EB35D4 /* AvatarIconListVC.swift in Sources */,
|
30EFF3D12FDA69EC00EB35D4 /* AvatarIconListVC.swift in Sources */,
|
||||||
|
30B74B4B2FF390EF00F6744D /* DrivingAPI.swift in Sources */,
|
||||||
305A76D52FCA8C7000227D26 /* SystemResponse.swift in Sources */,
|
305A76D52FCA8C7000227D26 /* SystemResponse.swift in Sources */,
|
||||||
305A76D62FCA8C7000227D26 /* ImagePlugin.swift in Sources */,
|
305A76D62FCA8C7000227D26 /* ImagePlugin.swift in Sources */,
|
||||||
305A76D72FCA8C7000227D26 /* NotEmpty.swift in Sources */,
|
305A76D72FCA8C7000227D26 /* NotEmpty.swift in Sources */,
|
||||||
|
|
@ -1901,6 +1927,7 @@
|
||||||
305A76DA2FCA8C7000227D26 /* Button+Action.swift in Sources */,
|
305A76DA2FCA8C7000227D26 /* Button+Action.swift in Sources */,
|
||||||
305A76DB2FCA8C7000227D26 /* Control+Action.swift in Sources */,
|
305A76DB2FCA8C7000227D26 /* Control+Action.swift in Sources */,
|
||||||
305A76DC2FCA8C7000227D26 /* InputSubject.swift in Sources */,
|
305A76DC2FCA8C7000227D26 /* InputSubject.swift in Sources */,
|
||||||
|
30B74B4D2FF391FF00F6744D /* DrivingService.swift in Sources */,
|
||||||
305A76DD2FCA8C7000227D26 /* NSObject+Rx.swift in Sources */,
|
305A76DD2FCA8C7000227D26 /* NSObject+Rx.swift in Sources */,
|
||||||
305A79902FCAC61A00227D26 /* InviteMemberVC.swift in Sources */,
|
305A79902FCAC61A00227D26 /* InviteMemberVC.swift in Sources */,
|
||||||
30A87A542FEE50B10095E7C6 /* ScheduleHistoryVM.swift in Sources */,
|
30A87A542FEE50B10095E7C6 /* ScheduleHistoryVM.swift in Sources */,
|
||||||
|
|
@ -1967,6 +1994,7 @@
|
||||||
30EFF3B32FD8F1C200EB35D4 /* ReviewMemberListView.swift in Sources */,
|
30EFF3B32FD8F1C200EB35D4 /* ReviewMemberListView.swift in Sources */,
|
||||||
305A76F82FCA8C7000227D26 /* DLAlert.swift in Sources */,
|
305A76F82FCA8C7000227D26 /* DLAlert.swift in Sources */,
|
||||||
305A76F92FCA8C7000227D26 /* DLToast.swift in Sources */,
|
305A76F92FCA8C7000227D26 /* DLToast.swift in Sources */,
|
||||||
|
30B74B4F2FF392D800F6744D /* DrivingStatsModel.swift in Sources */,
|
||||||
305A76FA2FCA8C7000227D26 /* DLEmptyDataSet.swift in Sources */,
|
305A76FA2FCA8C7000227D26 /* DLEmptyDataSet.swift in Sources */,
|
||||||
305A76FB2FCA8C7000227D26 /* EmptyDataSet.swift in Sources */,
|
305A76FB2FCA8C7000227D26 /* EmptyDataSet.swift in Sources */,
|
||||||
30BF300E2FED09CC00D9CB52 /* ScheduleDetailVC.swift in Sources */,
|
30BF300E2FED09CC00D9CB52 /* ScheduleDetailVC.swift in Sources */,
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -11,11 +11,14 @@ import SwiftyUserDefaults
|
||||||
|
|
||||||
public protocol MultiTargetProtocol: TargetType {
|
public protocol MultiTargetProtocol: TargetType {
|
||||||
var multiTarget: MultiTarget { get }
|
var multiTarget: MultiTarget { get }
|
||||||
|
/// 为 true 时跳过 Response Body 打印
|
||||||
|
var suppressResponseLog: Bool { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension MultiTargetProtocol {
|
public extension MultiTargetProtocol {
|
||||||
|
|
||||||
var multiTarget: MultiTarget { MultiTarget(self) }
|
var multiTarget: MultiTarget { MultiTarget(self) }
|
||||||
|
var suppressResponseLog: Bool { false }
|
||||||
|
|
||||||
var headers: [String : String]? {
|
var headers: [String : String]? {
|
||||||
AppNetworkConfig.shared.httpHeader?()
|
AppNetworkConfig.shared.httpHeader?()
|
||||||
|
|
|
||||||
|
|
@ -38,32 +38,53 @@ let requestClosure = { (endpoint: Endpoint, done: MoyaProvider.RequestResultClos
|
||||||
do {
|
do {
|
||||||
var request = try endpoint.urlRequest()
|
var request = try endpoint.urlRequest()
|
||||||
// Modify the request however you like.
|
// Modify the request however you like.
|
||||||
request.timeoutInterval = 15 //设置请求超时时间
|
request.timeoutInterval = 60 //设置请求超时时间
|
||||||
done(.success(request))
|
done(.success(request))
|
||||||
} catch {
|
} catch {
|
||||||
done(.failure(MoyaError.underlying(error, nil)))
|
done(.failure(MoyaError.underlying(error, nil)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let plugins: [PluginType] = [
|
/// 通用 Logger,仅 playback 跳过 Response Body
|
||||||
SignPlugin(),
|
private final class FilteredLogger: PluginType {
|
||||||
NetworkLoggerPlugin(configuration: .init(formatter: .init(responseData: JSONResponseDataFormatter), logOptions: .verbose)),
|
private let logger = NetworkLoggerPlugin(configuration: .init(
|
||||||
NetworkActivityPlugin(networkActivityClosure: { change, _ in
|
formatter: .init(responseData: JSONResponseDataFormatter),
|
||||||
#if !SHARE_EXTENSION
|
logOptions: .verbose
|
||||||
DispatchQueue.main.async {
|
))
|
||||||
switch change {
|
func willSend(_ request: RequestType, target: TargetType) {
|
||||||
case .began:
|
logger.willSend(request, target: target)
|
||||||
UIApplication.shared.isNetworkActivityIndicatorVisible = true
|
}
|
||||||
case .ended:
|
func didReceive(_ result: Result<Response, MoyaError>, target: TargetType) {
|
||||||
UIApplication.shared.isNetworkActivityIndicatorVisible = false
|
if let mt = target as? MultiTarget,
|
||||||
}
|
case let .target(underlying) = mt,
|
||||||
|
let api = underlying as? MultiTargetProtocol,
|
||||||
|
api.suppressResponseLog,
|
||||||
|
case .success(let response) = result {
|
||||||
|
print("[suppress] \(api.path) status: \(response.statusCode)")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
#endif
|
logger.didReceive(result, target: target)
|
||||||
})
|
}
|
||||||
]
|
}
|
||||||
|
|
||||||
public let APIProvider = MoyaProvider<MultiTarget>(requestClosure: requestClosure,
|
public let APIProvider = MoyaProvider<MultiTarget>(requestClosure: requestClosure,
|
||||||
plugins: plugins).rx
|
plugins: [
|
||||||
|
SignPlugin(),
|
||||||
|
FilteredLogger(),
|
||||||
|
NetworkActivityPlugin(networkActivityClosure: { change, _ in
|
||||||
|
#if !SHARE_EXTENSION
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
switch change {
|
||||||
|
case .began:
|
||||||
|
UIApplication.shared.isNetworkActivityIndicatorVisible = true
|
||||||
|
case .ended:
|
||||||
|
UIApplication.shared.isNetworkActivityIndicatorVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
})
|
||||||
|
]).rx
|
||||||
|
|
||||||
public extension Error {
|
public extension Error {
|
||||||
var underlyingError: NSError? {
|
var underlyingError: NSError? {
|
||||||
guard let moyaError = self as? MoyaError else { return nil }
|
guard let moyaError = self as? MoyaError else { return nil }
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
//
|
||||||
|
// DrivingAPI.swift
|
||||||
|
// QuickLocation
|
||||||
|
//
|
||||||
|
// Created by 八条 on 2026/6/30.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Moya
|
||||||
|
import SwiftyUserDefaults
|
||||||
|
internal import Alamofire
|
||||||
|
|
||||||
|
/// 驾驶事件API
|
||||||
|
enum DrivingAPI {
|
||||||
|
/// 驾驶事件
|
||||||
|
case drivingEvents(user_id: String, start_time: String, end_time: String)
|
||||||
|
/// 轨迹回放
|
||||||
|
case playback(user_id: String, date: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension DrivingAPI: MultiTargetProtocol {
|
||||||
|
|
||||||
|
var path: String {
|
||||||
|
switch self {
|
||||||
|
case .drivingEvents:
|
||||||
|
return "mapi/driving-events/stats"
|
||||||
|
case .playback:
|
||||||
|
return "mapi/trips/playback"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var method: Moya.Method {
|
||||||
|
switch self {
|
||||||
|
case .drivingEvents, .playback:
|
||||||
|
return .get
|
||||||
|
default:
|
||||||
|
return .post
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var suppressResponseLog: Bool {
|
||||||
|
switch self {
|
||||||
|
case .playback: return true
|
||||||
|
default: return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var task: Moya.Task {
|
||||||
|
switch self {
|
||||||
|
case let .drivingEvents(user_id, start_time, end_time):
|
||||||
|
var params = Parameters()
|
||||||
|
params["user_id"] = user_id
|
||||||
|
params["start_time"] = start_time
|
||||||
|
params["end_time"] = end_time
|
||||||
|
return .requestParameters(parameters: params, encoding: URLEncoding())
|
||||||
|
|
||||||
|
case let .playback(user_id, date):
|
||||||
|
var params = Parameters()
|
||||||
|
params["user_id"] = user_id
|
||||||
|
params["date"] = date
|
||||||
|
params["simplify"] = true
|
||||||
|
return .requestParameters(parameters: params, encoding: URLEncoding())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -72,6 +72,9 @@ enum UserAPI {
|
||||||
/// - enable:
|
/// - enable:
|
||||||
/// - keep_time:
|
/// - keep_time:
|
||||||
case bubble(enable: Bool, keep_time: Int)
|
case bubble(enable: Bool, keep_time: Int)
|
||||||
|
|
||||||
|
/// 紧急人邮箱
|
||||||
|
case setEmail(email: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
extension UserAPI: MultiTargetProtocol {
|
extension UserAPI: MultiTargetProtocol {
|
||||||
|
|
@ -108,6 +111,8 @@ extension UserAPI: MultiTargetProtocol {
|
||||||
return "mapi/user/followed"
|
return "mapi/user/followed"
|
||||||
case .bubble:
|
case .bubble:
|
||||||
return "mapi/bubble/operate"
|
return "mapi/bubble/operate"
|
||||||
|
case .setEmail:
|
||||||
|
return "mapi/user/signin/setemail"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -199,6 +204,11 @@ extension UserAPI: MultiTargetProtocol {
|
||||||
params["keep_time"] = keep_time
|
params["keep_time"] = keep_time
|
||||||
}
|
}
|
||||||
return .requestParameters(parameters: params, encoding: JSONEncoding())
|
return .requestParameters(parameters: params, encoding: JSONEncoding())
|
||||||
|
|
||||||
|
case let .setEmail(email):
|
||||||
|
var params = Parameters()
|
||||||
|
params["email"] = email
|
||||||
|
return .requestParameters(parameters: params, encoding: JSONEncoding())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
22
QuickLocation/Assets.xcassets/GroupMemberList/mask_bg.imageset/Contents.json
vendored
Normal file
22
QuickLocation/Assets.xcassets/GroupMemberList/mask_bg.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Group_2170@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Group_2170@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
QuickLocation/Assets.xcassets/GroupMemberList/mask_bg.imageset/Group_2170@2x.png
vendored
Normal file
BIN
QuickLocation/Assets.xcassets/GroupMemberList/mask_bg.imageset/Group_2170@2x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 657 KiB |
BIN
QuickLocation/Assets.xcassets/GroupMemberList/mask_bg.imageset/Group_2170@3x.png
vendored
Normal file
BIN
QuickLocation/Assets.xcassets/GroupMemberList/mask_bg.imageset/Group_2170@3x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
22
QuickLocation/Assets.xcassets/GroupMemberList/mask_tips.imageset/Contents.json
vendored
Normal file
22
QuickLocation/Assets.xcassets/GroupMemberList/mask_tips.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Group_2205@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Group_2205@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
QuickLocation/Assets.xcassets/GroupMemberList/mask_tips.imageset/Group_2205@2x.png
vendored
Normal file
BIN
QuickLocation/Assets.xcassets/GroupMemberList/mask_tips.imageset/Group_2205@2x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 172 KiB |
BIN
QuickLocation/Assets.xcassets/GroupMemberList/mask_tips.imageset/Group_2205@3x.png
vendored
Normal file
BIN
QuickLocation/Assets.xcassets/GroupMemberList/mask_tips.imageset/Group_2205@3x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 268 KiB |
|
|
@ -464,7 +464,7 @@ public extension String {
|
||||||
// MARK: - 正则
|
// MARK: - 正则
|
||||||
public extension String {
|
public extension String {
|
||||||
/// 检测Email 邮箱格式是否有效
|
/// 检测Email 邮箱格式是否有效
|
||||||
static func checkEmailAddressIsValid(address: String?) -> Bool {
|
static func checkEmailAddressIsValid(_ address: String?) -> Bool {
|
||||||
guard address != nil else {
|
guard address != nil else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -519,3 +519,32 @@ extension String {
|
||||||
return NSPredicate(format: "SELF MATCHES %@", reg).evaluate(with: self)
|
return NSPredicate(format: "SELF MATCHES %@", reg).evaluate(with: self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -
|
||||||
|
extension String {
|
||||||
|
// MARK: - 解析 ISO8601 字符串 2026-06-26T14:18:52+08:00 -> Date
|
||||||
|
private func parseISO8601(_ isoStr: String) -> Date? {
|
||||||
|
let formatter = ISO8601DateFormatter()
|
||||||
|
formatter.formatOptions = [.withInternetDateTime, .withTimeZone]
|
||||||
|
return formatter.date(from: isoStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Date 转自定义格式字符串
|
||||||
|
/// - Parameters:
|
||||||
|
/// - date: 目标日期
|
||||||
|
/// - format: 格式模板 如 yyyy-MM-dd HH:mm:ss
|
||||||
|
/// - Returns: 格式化后的日期字符串
|
||||||
|
func formatDate(_ date: Date, format: String) -> String {
|
||||||
|
let formatter = DateFormatter()
|
||||||
|
formatter.locale = Locale(identifier: "zh_CN")
|
||||||
|
formatter.timeZone = TimeZone(identifier: "Asia/Shanghai")
|
||||||
|
formatter.dateFormat = format
|
||||||
|
return formatter.string(from: date)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - ISO字符串 直接转自定义格式
|
||||||
|
func isoStringToCustom(_ isoStr: String, format: String) -> String? {
|
||||||
|
guard let date = parseISO8601(isoStr) else { return nil }
|
||||||
|
return formatDate(date, format: format)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,13 +72,23 @@ extension ApiManager {
|
||||||
DLToast.dismiss()
|
DLToast.dismiss()
|
||||||
switch result {
|
switch result {
|
||||||
case let .success(response):
|
case let .success(response):
|
||||||
|
let isPlayback = response.request?.url?.absoluteString.contains("mapi/trips/playback") ?? false
|
||||||
// 处理加密响应
|
// 处理加密响应
|
||||||
|
let t0 = CFAbsoluteTimeGetCurrent()
|
||||||
let decryptedData = ApiManager.decryptIfNeeded(response.data)
|
let decryptedData = ApiManager.decryptIfNeeded(response.data)
|
||||||
|
if isPlayback {
|
||||||
|
let rawSize = response.data.count
|
||||||
|
let decryptTime = CFAbsoluteTimeGetCurrent() - t0
|
||||||
|
print("[playback] raw: \(rawSize)B, decrypt: \(String(format: "%.2f", decryptTime))s")
|
||||||
|
}
|
||||||
// response转json
|
// response转json
|
||||||
let json = JSON(decryptedData ?? response.data)
|
let json = JSON(decryptedData ?? response.data)
|
||||||
print("============== decrypt ====================")
|
if !isPlayback {
|
||||||
print(json)
|
print("============== decrypt ====================")
|
||||||
|
print(json)
|
||||||
|
}
|
||||||
let code = json["code"].int
|
let code = json["code"].int
|
||||||
|
let success: Bool? = json["success"].bool
|
||||||
let message = json["message"].string
|
let message = json["message"].string
|
||||||
let combineMessage = message ?? "请检查网络连接"
|
let combineMessage = message ?? "请检查网络连接"
|
||||||
|
|
||||||
|
|
@ -100,6 +110,16 @@ extension ApiManager {
|
||||||
case GatewayStatusCode.failure.rawValue, GatewayStatusCode.noAuthority.rawValue: // 退出当前界面
|
case GatewayStatusCode.failure.rawValue, GatewayStatusCode.noAuthority.rawValue: // 退出当前界面
|
||||||
handlePopView(message)
|
handlePopView(message)
|
||||||
default:
|
default:
|
||||||
|
// 接口没返回code的时候取success字段
|
||||||
|
if success != nil, success == true {
|
||||||
|
if let decrypted = decryptedData {
|
||||||
|
let decryptedResponse = Response(statusCode: response.statusCode,
|
||||||
|
data: decrypted,
|
||||||
|
request: response.request, response: response.response)
|
||||||
|
return .success(decryptedResponse)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
/// 统一提示错误
|
/// 统一提示错误
|
||||||
let code = GatewayStatusCode(rawValue: code ?? -9999) ?? .unknownError
|
let code = GatewayStatusCode(rawValue: code ?? -9999) ?? .unknownError
|
||||||
if code == .unknownError, handle {
|
if code == .unknownError, handle {
|
||||||
|
|
@ -167,15 +187,15 @@ extension ApiManager {
|
||||||
let encrypt = json["encrypt"] as? Int,
|
let encrypt = json["encrypt"] as? Int,
|
||||||
encrypt == 1,
|
encrypt == 1,
|
||||||
let encryptedString = json["data"] as? String,
|
let encryptedString = json["data"] as? String,
|
||||||
let decryptedString = aes256CBCDecrypt(encryptedString, key: jieMiKey, iv: jieMiIV),
|
let decryptedData = aes256CBCDecrypt(encryptedString, key: jieMiKey, iv: jieMiIV)
|
||||||
let decryptedData = decryptedString.data(using: .utf8),
|
|
||||||
let decryptedJSON = try? JSONSerialization.jsonObject(with: decryptedData) as? [String: Any]
|
|
||||||
else { return nil }
|
else { return nil }
|
||||||
|
|
||||||
return try? JSONSerialization.data(withJSONObject: decryptedJSON)
|
// 验证解密结果是否为合法 JSON
|
||||||
|
guard (try? JSONSerialization.jsonObject(with: decryptedData)) != nil else { return nil }
|
||||||
|
return decryptedData
|
||||||
}
|
}
|
||||||
|
|
||||||
private class func aes256CBCDecrypt(_ base64String: String, key: String, iv: String) -> String? {
|
private class func aes256CBCDecrypt(_ base64String: String, key: String, iv: String) -> Data? {
|
||||||
guard let data = Data(base64Encoded: base64String),
|
guard let data = Data(base64Encoded: base64String),
|
||||||
let keyData = key.data(using: .utf8),
|
let keyData = key.data(using: .utf8),
|
||||||
let ivData = iv.data(using: .utf8) else { return nil }
|
let ivData = iv.data(using: .utf8) else { return nil }
|
||||||
|
|
@ -202,7 +222,7 @@ extension ApiManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
guard status == kCCSuccess else { return nil }
|
guard status == kCCSuccess else { return nil }
|
||||||
return String(data: Data(bytes: buffer, count: numBytesDecrypted), encoding: .utf8)
|
return Data(bytes: buffer, count: numBytesDecrypted)
|
||||||
}
|
}
|
||||||
|
|
||||||
class func getMD5ID() -> String {
|
class func getMD5ID() -> String {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
//
|
||||||
|
// DrivingStatsModel.swift
|
||||||
|
// QuickLocation
|
||||||
|
//
|
||||||
|
// Created by 八条 on 2026/6/30.
|
||||||
|
//
|
||||||
|
|
||||||
|
import ObjectMapper
|
||||||
|
import RxDataSources
|
||||||
|
|
||||||
|
/// 驾驶事件
|
||||||
|
struct DrivingStatsResponse: BaseModelProtocol {
|
||||||
|
// 状态码
|
||||||
|
var code: String?
|
||||||
|
// 消息
|
||||||
|
var message: String?
|
||||||
|
//
|
||||||
|
var model: DrivingStatsModel?
|
||||||
|
|
||||||
|
init?(map: Map) {}
|
||||||
|
|
||||||
|
mutating func mapping(map: Map) {
|
||||||
|
code <- map["code"]
|
||||||
|
message <- map["msg"]
|
||||||
|
model <- map["data"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DrivingStatsModel: Mappable {
|
||||||
|
var uuid: String = UUID().uuidString
|
||||||
|
/// 行驶总距离
|
||||||
|
var distance_km: Double = 0
|
||||||
|
/// 频繁变道次数
|
||||||
|
var frequent_lane_change: Int = 0
|
||||||
|
/// 急加速
|
||||||
|
var hard_acceleration: Int = 0
|
||||||
|
/// 急刹
|
||||||
|
var hard_braking: Int = 0
|
||||||
|
/// 长时间驾驶次数
|
||||||
|
var long_driving: Int = 0
|
||||||
|
/// 超低速
|
||||||
|
var low_speeding: Int = 0
|
||||||
|
/// 最大速度
|
||||||
|
var max_speed: Double = 0
|
||||||
|
/// 急转弯
|
||||||
|
var sharp_turn: Int = 0
|
||||||
|
/// 手机信号丢失
|
||||||
|
var signal_loss: Int = 0
|
||||||
|
/// 超速
|
||||||
|
var speeding: Int = 0
|
||||||
|
/// 总违规次数
|
||||||
|
var total: Int = 0
|
||||||
|
|
||||||
|
init?(map: Map) {}
|
||||||
|
|
||||||
|
mutating func mapping(map: Map) {
|
||||||
|
distance_km <- map["distance_km"]
|
||||||
|
frequent_lane_change <- map["frequent_lane_change"]
|
||||||
|
hard_acceleration <- map["hard_acceleration"]
|
||||||
|
hard_braking <- map["hard_braking"]
|
||||||
|
long_driving <- map["long_driving"]
|
||||||
|
low_speeding <- map["low_speeding"]
|
||||||
|
max_speed <- map["max_speed"]
|
||||||
|
sharp_turn <- map["sharp_turn"]
|
||||||
|
signal_loss <- map["signal_loss"]
|
||||||
|
speeding <- map["speeding"]
|
||||||
|
total <- map["total"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -31,12 +31,15 @@ class GroupMemberListVC: BaseViewController {
|
||||||
requestGroupInfo()
|
requestGroupInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
super.viewWillAppear(animated)
|
||||||
|
rootView.unlockVipMaskView.isHidden = AppContextManager.shared.vip > 1
|
||||||
|
}
|
||||||
|
|
||||||
private func reactiveAction() {
|
private func reactiveAction() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var selectedRow = 0
|
|
||||||
|
|
||||||
private func bindViewModel() {
|
private func bindViewModel() {
|
||||||
viewModel.output.sectionedItems
|
viewModel.output.sectionedItems
|
||||||
.bind(to: rootView.collectionView.rx.items(dataSource: memberDataSource))
|
.bind(to: rootView.collectionView.rx.items(dataSource: memberDataSource))
|
||||||
|
|
@ -46,24 +49,68 @@ class GroupMemberListVC: BaseViewController {
|
||||||
.bind(to: rootView.drivingEventCV.rx.items(dataSource: drivingDataSource))
|
.bind(to: rootView.drivingEventCV.rx.items(dataSource: drivingDataSource))
|
||||||
.disposed(by: disposeBag)
|
.disposed(by: disposeBag)
|
||||||
|
|
||||||
// 成员点击
|
viewModel.output.scheduleSectionedItems
|
||||||
|
.bind(to: rootView.tableView.rx.items(dataSource: scheduleDataSource))
|
||||||
|
.disposed(by: disposeBag)
|
||||||
|
|
||||||
|
viewModel.output.scheduleSectionedItems
|
||||||
|
.subscribe(onNext: { [weak self] items in
|
||||||
|
self?.rootView.updateTableViewHeight(count: items.first?.items.count ?? 0)
|
||||||
|
}).disposed(by: disposeBag)
|
||||||
|
|
||||||
|
// 日期选择 → 滚动报告到顶部 + 请求驾驶数据
|
||||||
|
rootView.selectedDate
|
||||||
|
.skip(1)
|
||||||
|
.subscribe(onNext: { [weak self] date in
|
||||||
|
self?.rootView.scrollReportToTop()
|
||||||
|
self?.requestDrivingEvents(for: date)
|
||||||
|
}).disposed(by: disposeBag)
|
||||||
|
|
||||||
|
// 成员点击 → 重置日期到当天(selectedDate 发射 → 自动触发 API 请求)
|
||||||
rootView.collectionView.rx.modelSelected(GroupMemberModel.self)
|
rootView.collectionView.rx.modelSelected(GroupMemberModel.self)
|
||||||
.subscribe(onNext: { [weak self] model in
|
.subscribe(onNext: { [weak self] model in
|
||||||
guard let self = self, let row = self.viewModel.rowOf(userId: model.user_id) else { return }
|
guard let self = self else { return }
|
||||||
self.selectedRow = row
|
self.viewModel.memberId = model.user_id
|
||||||
self.rootView.selectedMemberIsSelf = self.viewModel.isCurrentUser(id: model.user_id)
|
self.rootView.selectedMemberIsSelf = self.viewModel.isCurrentUser(id: model.user_id)
|
||||||
|
self.rootView.selectedDate.accept(Date())
|
||||||
|
self.rootView.scrollToToday()
|
||||||
|
self.rootView.scrollReportToTop()
|
||||||
self.rootView.collectionView.reloadData()
|
self.rootView.collectionView.reloadData()
|
||||||
}).disposed(by: disposeBag)
|
}).disposed(by: disposeBag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 根据日期生成起止时间戳并请求驾驶数据
|
||||||
|
private func requestDrivingEvents(for date: Date) {
|
||||||
|
guard !viewModel.memberId.isEmpty else { return }
|
||||||
|
let calendar = Calendar.current
|
||||||
|
let start = calendar.startOfDay(for: date)
|
||||||
|
let end: Date
|
||||||
|
if calendar.isDateInToday(date) {
|
||||||
|
end = Date() // 当天以当前时间为结束
|
||||||
|
} else {
|
||||||
|
end = calendar.date(bySettingHour: 23, minute: 59, second: 59, of: date) ?? date
|
||||||
|
}
|
||||||
|
requestDrivingEvents(start_time: "\(Int64(start.timeIntervalSince1970 * 1000))",
|
||||||
|
end_time: "\(Int64(end.timeIntervalSince1970 * 1000))")
|
||||||
|
|
||||||
|
requestPlayback(date: "\(Int64(start.timeIntervalSince1970 * 1000))")
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - dataSource
|
// MARK: - dataSource
|
||||||
|
private lazy var scheduleDataSource: RxTableViewSectionedReloadDataSource<ScheduleRecordSection> = {
|
||||||
|
RxTableViewSectionedReloadDataSource<ScheduleRecordSection> { _, tableView, indexPath, model in
|
||||||
|
let cell: ScheduleRecordCell = tableView.dequeueReusableCell(for: indexPath)
|
||||||
|
cell.configure(model)
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
private lazy var memberDataSource: RxCollectionViewSectionedReloadDataSource<GroupMemberListSectionModel> = {
|
private lazy var memberDataSource: RxCollectionViewSectionedReloadDataSource<GroupMemberListSectionModel> = {
|
||||||
RxCollectionViewSectionedReloadDataSource<GroupMemberListSectionModel> { [weak self] datasource, collectionView, indexPath, model in
|
RxCollectionViewSectionedReloadDataSource<GroupMemberListSectionModel> { [weak self] datasource, collectionView, indexPath, model in
|
||||||
let cell: GroupMemberListCell = collectionView.dequeueReusableCell(for: indexPath)
|
let cell: GroupMemberListCell = collectionView.dequeueReusableCell(for: indexPath)
|
||||||
cell.configure(model: model,
|
cell.configure(model: model,
|
||||||
isCurrentUser: self?.viewModel.isCurrentUser(id: model.user_id) ?? false,
|
isCurrentUser: self?.viewModel.isCurrentUser(id: model.user_id) ?? false,
|
||||||
isSelected: indexPath.row == (self?.selectedRow ?? 0))
|
isSelected: model.user_id == self?.viewModel.memberId)
|
||||||
return cell
|
return cell
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
@ -84,13 +131,38 @@ class GroupMemberListVC: BaseViewController {
|
||||||
guard let model = response.model else { return }
|
guard let model = response.model else { return }
|
||||||
self.viewModel.groupModel = model
|
self.viewModel.groupModel = model
|
||||||
self.viewModel.loadData(response.list)
|
self.viewModel.loadData(response.list)
|
||||||
// 默认选中第一个成员,更新日期可点范围
|
self.rootView.selectedMemberIsSelf = self.viewModel.isCurrentUser(id: self.viewModel.memberId)
|
||||||
if let first = self.viewModel.firstMemberId {
|
// 首次加载后请求今日驾驶数据
|
||||||
self.rootView.selectedMemberIsSelf = self.viewModel.isCurrentUser(id: first)
|
if !self.viewModel.memberId.isEmpty {
|
||||||
|
self.requestDrivingEvents(for: Date())
|
||||||
}
|
}
|
||||||
}.disposed(by: disposeBag)
|
}.disposed(by: disposeBag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 驾驶事件
|
||||||
|
private func requestDrivingEvents(start_time: String, end_time: String) {
|
||||||
|
DrivingService.drivingEvents(user_id: viewModel.memberId, start_time: start_time, end_time: end_time).subscribe { response in
|
||||||
|
guard let model = response.model else { return }
|
||||||
|
self.rootView.mileageLab.text = String(format: "%.1fkm", model.distance_km)
|
||||||
|
self.rootView.speedLab.text = String(format: "%.0fkm/h", model.max_speed)
|
||||||
|
self.viewModel.updateDrivingStats(model)
|
||||||
|
}.disposed(by: disposeBag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 行程记录
|
||||||
|
private func requestPlayback(date: String) {
|
||||||
|
let start = CFAbsoluteTimeGetCurrent()
|
||||||
|
dl.showLoading(text: "行程记录获取中...")
|
||||||
|
DrivingService.playback(user_id: viewModel.memberId, date: date).subscribe(onNext: { [weak self] response in
|
||||||
|
let elapsed = CFAbsoluteTimeGetCurrent() - start
|
||||||
|
print("[playback] total: \(String(format: "%.1f", elapsed))s, items: \(response.list.count)")
|
||||||
|
self?.dl.dismiss()
|
||||||
|
self?.viewModel.loadScheduleRecords(response.list)
|
||||||
|
}, onError: { _ in
|
||||||
|
self.dl.dismiss()
|
||||||
|
}).disposed(by: disposeBag)
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Init
|
// MARK: - Init
|
||||||
init(groupKey: String) {
|
init(groupKey: String) {
|
||||||
viewModel = GroupMemberListVM(groupKey: groupKey)
|
viewModel = GroupMemberListVM(groupKey: groupKey)
|
||||||
|
|
|
||||||
|
|
@ -10,22 +10,6 @@ import RxRelay
|
||||||
import RxDataSources
|
import RxDataSources
|
||||||
import ObjectMapper
|
import ObjectMapper
|
||||||
|
|
||||||
// MARK: - DrivingStats 模型
|
|
||||||
struct DrivingStatsData {
|
|
||||||
let distance_km: Double?
|
|
||||||
let frequent_lane_change: Int?
|
|
||||||
let hard_acceleration: Int?
|
|
||||||
let hard_braking: Int?
|
|
||||||
let long_driving: Int?
|
|
||||||
let low_speeding: Int?
|
|
||||||
let max_speed: Double?
|
|
||||||
let period: String?
|
|
||||||
let sharp_turn: Int?
|
|
||||||
let signal_loss: Int?
|
|
||||||
let speeding: Int?
|
|
||||||
let total: Int?
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DrivingEventItem: IdentifiableType, Equatable {
|
struct DrivingEventItem: IdentifiableType, Equatable {
|
||||||
typealias Identity = String
|
typealias Identity = String
|
||||||
let identity: String
|
let identity: String
|
||||||
|
|
@ -42,6 +26,7 @@ struct DrivingEventItem: IdentifiableType, Equatable {
|
||||||
}
|
}
|
||||||
|
|
||||||
typealias DrivingEventSection = SectionModel<String, DrivingEventItem>
|
typealias DrivingEventSection = SectionModel<String, DrivingEventItem>
|
||||||
|
typealias ScheduleRecordSection = SectionModel<String, ScheduleRecordModel>
|
||||||
|
|
||||||
class GroupMemberListVM {
|
class GroupMemberListVM {
|
||||||
|
|
||||||
|
|
@ -51,16 +36,19 @@ class GroupMemberListVM {
|
||||||
struct Output {
|
struct Output {
|
||||||
var sectionedItems: Observable<[GroupMemberListSectionModel]>
|
var sectionedItems: Observable<[GroupMemberListSectionModel]>
|
||||||
var drivingSectionedItems: Observable<[DrivingEventSection]>
|
var drivingSectionedItems: Observable<[DrivingEventSection]>
|
||||||
|
var scheduleSectionedItems: Observable<[ScheduleRecordSection]>
|
||||||
}
|
}
|
||||||
|
|
||||||
let output: Output
|
let output: Output
|
||||||
|
|
||||||
private var disposeBag = DisposeBag()
|
var memberId: String = ""
|
||||||
|
|
||||||
private let sectionedItems = PublishSubject<[GroupMemberListSectionModel]>()
|
private let sectionedItems = PublishSubject<[GroupMemberListSectionModel]>()
|
||||||
private let drivingItemsRelay = BehaviorRelay<[DrivingEventItem]>(value: GroupMemberListVM.defaultDrivingEvents)
|
private let drivingItemsRelay = BehaviorRelay<[DrivingEventItem]>(value: GroupMemberListVM.defaultDrivingEvents)
|
||||||
|
private let scheduleItemsRelay = BehaviorRelay<[ScheduleRecordModel]>(value: [])
|
||||||
|
|
||||||
private var memberList: [GroupMemberModel] = []
|
private var memberList: [GroupMemberModel] = []
|
||||||
|
|
||||||
private static let defaultDrivingEvents: [DrivingEventItem] = [
|
private static let defaultDrivingEvents: [DrivingEventItem] = [
|
||||||
DrivingEventItem(title: "急加速", iconName: "GroupMemberList/1"),
|
DrivingEventItem(title: "急加速", iconName: "GroupMemberList/1"),
|
||||||
DrivingEventItem(title: "急转向", iconName: "GroupMemberList/3"),
|
DrivingEventItem(title: "急转向", iconName: "GroupMemberList/3"),
|
||||||
|
|
@ -89,30 +77,33 @@ class GroupMemberListVM {
|
||||||
tempmemberList.moveToFirst { $0.user_id == AppContextManager.shared.userId }
|
tempmemberList.moveToFirst { $0.user_id == AppContextManager.shared.userId }
|
||||||
tempmemberList.moveToFirst { isGroupOwn(id: $0.user_id) }
|
tempmemberList.moveToFirst { isGroupOwn(id: $0.user_id) }
|
||||||
memberList = tempmemberList
|
memberList = tempmemberList
|
||||||
|
memberId = memberList.first?.user_id ?? ""
|
||||||
sectionedItems.onNext(memberList.mapSection())
|
sectionedItems.onNext(memberList.mapSection())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 第一个成员的 userId
|
|
||||||
var firstMemberId: String? { memberList.first?.user_id }
|
|
||||||
|
|
||||||
/// 获取成员在列表中的行号
|
/// 获取成员在列表中的行号
|
||||||
func rowOf(userId: String) -> Int? {
|
func rowOf(userId: String) -> Int? {
|
||||||
memberList.firstIndex(where: { $0.user_id == userId })
|
memberList.firstIndex(where: { $0.user_id == userId })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 加载行程记录
|
||||||
|
func loadScheduleRecords(_ list: [ScheduleRecordModel]) {
|
||||||
|
scheduleItemsRelay.accept(list)
|
||||||
|
}
|
||||||
|
|
||||||
/// 更新驾驶事件统计
|
/// 更新驾驶事件统计
|
||||||
func updateDrivingStats(_ stats: DrivingStatsData) {
|
func updateDrivingStats(_ stats: DrivingStatsModel) {
|
||||||
let items = Self.defaultDrivingEvents.enumerated().map { i, item in
|
let items = Self.defaultDrivingEvents.enumerated().map { i, item in
|
||||||
let count: Int
|
let count: Int
|
||||||
switch i {
|
switch i {
|
||||||
case 0: count = stats.hard_acceleration ?? 0
|
case 0: count = stats.hard_acceleration
|
||||||
case 1: count = stats.speeding ?? 0
|
case 1: count = stats.sharp_turn
|
||||||
case 2: count = stats.sharp_turn ?? 0
|
case 2: count = stats.hard_braking
|
||||||
case 3: count = stats.low_speeding ?? 0
|
case 3: count = stats.signal_loss
|
||||||
case 4: count = stats.hard_braking ?? 0
|
case 4: count = stats.speeding
|
||||||
case 5: count = stats.frequent_lane_change ?? 0
|
case 5: count = stats.low_speeding
|
||||||
case 6: count = stats.signal_loss ?? 0
|
case 6: count = stats.frequent_lane_change
|
||||||
case 7: count = stats.long_driving ?? 0
|
case 7: count = stats.long_driving
|
||||||
default: count = 0
|
default: count = 0
|
||||||
}
|
}
|
||||||
return DrivingEventItem(title: item.title, iconName: item.iconName, count: count)
|
return DrivingEventItem(title: item.title, iconName: item.iconName, count: count)
|
||||||
|
|
@ -124,7 +115,8 @@ class GroupMemberListVM {
|
||||||
self.groupKey = groupKey
|
self.groupKey = groupKey
|
||||||
output = Output(
|
output = Output(
|
||||||
sectionedItems: sectionedItems.asObservable(),
|
sectionedItems: sectionedItems.asObservable(),
|
||||||
drivingSectionedItems: drivingItemsRelay.map { $0.mapSection() }.asObservable()
|
drivingSectionedItems: drivingItemsRelay.map { $0.mapSection() }.asObservable(),
|
||||||
|
scheduleSectionedItems: scheduleItemsRelay.map { $0.mapSection() }.asObservable()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -166,6 +166,43 @@ class GroupMemberListView: UIView {
|
||||||
return cv
|
return cv
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// MARK: - 未解锁会员遮罩
|
||||||
|
lazy var unlockVipMaskView: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.backgroundColor = .clear
|
||||||
|
view.isHidden = true
|
||||||
|
|
||||||
|
let maskBg = UIImageView(image: UIImage(named: "GroupMemberList/mask_bg"))
|
||||||
|
view.addSubview(maskBg)
|
||||||
|
maskBg.layoutChain.edges()
|
||||||
|
|
||||||
|
let tipsImg = UIImageView(image: UIImage(named: "GroupMemberList/mask_tips"))
|
||||||
|
view.addSubview(tipsImg)
|
||||||
|
tipsImg.layoutChain
|
||||||
|
.top(83)
|
||||||
|
.centerX()
|
||||||
|
.width(283)
|
||||||
|
.height(194)
|
||||||
|
|
||||||
|
let btn = UIButton()
|
||||||
|
btn.setTitle("立即开通", for: .normal)
|
||||||
|
btn.setTitleColor(.white, for: .normal)
|
||||||
|
btn.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
|
||||||
|
btn.setBackgroundColor(UIColor(hexStr: "#FF7B24"), for: .normal)
|
||||||
|
btn.cornerRadius = 25
|
||||||
|
btn.rx.tap.subscribe(onNext: { _ in
|
||||||
|
AppRouter.push(Route.vipRecharge)
|
||||||
|
}).disposed(by: disposeBag)
|
||||||
|
view.addSubview(btn)
|
||||||
|
btn.layoutChain
|
||||||
|
.topToBottomOfView(tipsImg, offset: 23)
|
||||||
|
.centerX()
|
||||||
|
.width(224)
|
||||||
|
.height(50)
|
||||||
|
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
// MARK: - 报告
|
// MARK: - 报告
|
||||||
lazy var reportView: UIView = {
|
lazy var reportView: UIView = {
|
||||||
let view = UIView()
|
let view = UIView()
|
||||||
|
|
@ -221,6 +258,11 @@ class GroupMemberListView: UIView {
|
||||||
.topToBottomOfView(dateView)
|
.topToBottomOfView(dateView)
|
||||||
.edges(excludingEdge: .top)
|
.edges(excludingEdge: .top)
|
||||||
|
|
||||||
|
view.addSubview(unlockVipMaskView)
|
||||||
|
unlockVipMaskView.layoutChain
|
||||||
|
.topToBottomOfView(dateView)
|
||||||
|
.edges(excludingEdge: .top)
|
||||||
|
|
||||||
return view
|
return view
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
@ -342,12 +384,7 @@ class GroupMemberListView: UIView {
|
||||||
.rightToLeftOfView(dateNextBtn, offset: -5)
|
.rightToLeftOfView(dateNextBtn, offset: -5)
|
||||||
.edgesVertical()
|
.edgesVertical()
|
||||||
|
|
||||||
// 直接翻到最后一页(第 4 页,index 28 开始),今天在第 3 位
|
DispatchQueue.main.async { self.scrollToToday() }
|
||||||
DispatchQueue.main.async {
|
|
||||||
let page: CGFloat = 4 // 第 5 页,items 28-34
|
|
||||||
let offset = page * CGFloat(self.daysPerPage) * self.dateItemWidth
|
|
||||||
self.dateCollectionView.contentOffset.x = offset
|
|
||||||
}
|
|
||||||
|
|
||||||
return view
|
return view
|
||||||
}()
|
}()
|
||||||
|
|
@ -406,6 +443,11 @@ class GroupMemberListView: UIView {
|
||||||
.top(5)
|
.top(5)
|
||||||
.edgesHorzontal(15)
|
.edgesHorzontal(15)
|
||||||
.height(249)
|
.height(249)
|
||||||
|
|
||||||
|
contentView.addSubview(recordView)
|
||||||
|
recordView.layoutChain
|
||||||
|
.topToBottomOfView(drivingAnalysisView, offset: 15)
|
||||||
|
.edgesHorzontal(15)
|
||||||
.bottom(20)
|
.bottom(20)
|
||||||
|
|
||||||
return view
|
return view
|
||||||
|
|
@ -455,6 +497,76 @@ class GroupMemberListView: UIView {
|
||||||
return cv
|
return cv
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// MARK: - 行程记录
|
||||||
|
lazy var recordView: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.backgroundColor = .white
|
||||||
|
view.cornerRadius = 10
|
||||||
|
|
||||||
|
let titleBg = UIImageView(image: UIImage(named: "GroupMemberList/title_bg"))
|
||||||
|
view.addSubview(titleBg)
|
||||||
|
|
||||||
|
let titleLab = UILabel()
|
||||||
|
titleLab.text = "行程记录"
|
||||||
|
titleLab.font = .systemFont(ofSize: 16, weight: .medium)
|
||||||
|
view.addSubview(titleLab)
|
||||||
|
titleLab.layoutChain
|
||||||
|
.top(15)
|
||||||
|
.centerX()
|
||||||
|
|
||||||
|
titleBg.layoutChain
|
||||||
|
.centerX()
|
||||||
|
.bottomToView(titleLab, offset: 5)
|
||||||
|
|
||||||
|
view.addSubview(tableView)
|
||||||
|
tableView.layoutChain
|
||||||
|
.topToBottomOfView(titleBg, offset: 10)
|
||||||
|
.edgesHorzontal()
|
||||||
|
.bottom()
|
||||||
|
|
||||||
|
view.addSubview(nodataLab)
|
||||||
|
nodataLab.layoutChain.centerX().centerY()
|
||||||
|
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var tableView: UITableView = {
|
||||||
|
let tableView = UITableView(frame: .zero, style: .plain)
|
||||||
|
tableView.backgroundColor = .clear
|
||||||
|
tableView.separatorStyle = .none
|
||||||
|
tableView.estimatedRowHeight = 177
|
||||||
|
tableView.showsVerticalScrollIndicator = false
|
||||||
|
tableView.isScrollEnabled = false
|
||||||
|
tableView.register(ScheduleRecordCell.self)
|
||||||
|
return tableView
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var nodataLab: UILabel = {
|
||||||
|
let l = UILabel()
|
||||||
|
l.text = "暂无行程记录"
|
||||||
|
l.font = .systemFont(ofSize: 12, weight: .regular)
|
||||||
|
l.textColor = UIColor(hexStr: "#999999")
|
||||||
|
return l
|
||||||
|
}()
|
||||||
|
|
||||||
|
/// 滚动报告区域到顶部
|
||||||
|
func scrollReportToTop() {
|
||||||
|
scrollView.setContentOffset(.zero, animated: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 滚到今天所在页(第 5 页,items 28-34,今天在第 3 位)
|
||||||
|
func scrollToToday() {
|
||||||
|
let page: CGFloat = 4
|
||||||
|
let offset = page * CGFloat(daysPerPage) * dateItemWidth
|
||||||
|
dateCollectionView.contentOffset.x = offset
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateTableViewHeight(count: Int) {
|
||||||
|
let h = CGFloat(count) * 179
|
||||||
|
tableView.layoutChain.height(h > 0 ? h : 179)
|
||||||
|
nodataLab.isHidden = h > 0
|
||||||
|
}
|
||||||
|
|
||||||
override func layoutSubviews() {
|
override func layoutSubviews() {
|
||||||
super.layoutSubviews()
|
super.layoutSubviews()
|
||||||
updateArrowVisibility()
|
updateArrowVisibility()
|
||||||
|
|
@ -547,7 +659,7 @@ class DateCell: UICollectionViewCell {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - GroupMemberListCell
|
// MARK: - GroupMemberListCell 成员
|
||||||
class GroupMemberListCell: UICollectionViewCell {
|
class GroupMemberListCell: UICollectionViewCell {
|
||||||
|
|
||||||
func configure(model: GroupMemberModel, isCurrentUser: Bool, isSelected: Bool) {
|
func configure(model: GroupMemberModel, isCurrentUser: Bool, isSelected: Bool) {
|
||||||
|
|
@ -706,7 +818,7 @@ class GroupMemberListCell: UICollectionViewCell {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - DrivingEventCell
|
// MARK: - DrivingEventCell 驾驶事件
|
||||||
class DrivingEventCell: UICollectionViewCell {
|
class DrivingEventCell: UICollectionViewCell {
|
||||||
|
|
||||||
func configure(_ item: DrivingEventItem) {
|
func configure(_ item: DrivingEventItem) {
|
||||||
|
|
@ -758,3 +870,254 @@ class DrivingEventCell: UICollectionViewCell {
|
||||||
|
|
||||||
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - ScheduleRecordCells
|
||||||
|
class ScheduleRecordCell: UITableViewCell {
|
||||||
|
|
||||||
|
func configure(_ model: ScheduleRecordModel) {
|
||||||
|
distanceLab.text = String(format: "%.1fkm", model.distance_km)
|
||||||
|
speedLab.text = String(format: "最高时速 %.0fkm/h", model.max_speed)
|
||||||
|
scoreLab.text = model.score.string
|
||||||
|
|
||||||
|
dayLab.text = model.start_time.isoStringToCustom(model.start_time, format: "dd") ?? ""
|
||||||
|
yearMonthLab.text = model.start_time.isoStringToCustom(model.start_time, format: "yyyy.MM") ?? ""
|
||||||
|
|
||||||
|
let startTime = model.start_time.isoStringToCustom(model.start_time, format: "HH:mm") ?? ""
|
||||||
|
let endTime = model.end_time.isoStringToCustom(model.end_time, format: "HH:mm") ?? ""
|
||||||
|
durationLab.text = "\(startTime)-\(endTime)(\(model.duration_minutes)分钟)"
|
||||||
|
|
||||||
|
guard let start_address = model.start_address,
|
||||||
|
let end_address = model.end_address else { return }
|
||||||
|
|
||||||
|
startLab.text = start_address.street
|
||||||
|
endLab.text = end_address.street
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupViews() {
|
||||||
|
contentView.addSubview(dotView)
|
||||||
|
contentView.addSubview(dayLab)
|
||||||
|
contentView.addSubview(yearMonthLab)
|
||||||
|
contentView.addSubview(distanceLab)
|
||||||
|
contentView.addSubview(durationLab)
|
||||||
|
contentView.addSubview(detailView)
|
||||||
|
detailView.addSubview(headerBgView)
|
||||||
|
detailView.addSubview(speedLab)
|
||||||
|
detailView.addSubview(scroreTitleLab)
|
||||||
|
detailView.addSubview(scoreLab)
|
||||||
|
detailView.addSubview(unitLab)
|
||||||
|
detailView.addSubview(startTitleLab)
|
||||||
|
detailView.addSubview(startLab)
|
||||||
|
detailView.addSubview(endTitleLab)
|
||||||
|
detailView.addSubview(endLab)
|
||||||
|
contentView.addSubview(lineView)
|
||||||
|
|
||||||
|
dotView.layoutChain
|
||||||
|
.left(15)
|
||||||
|
.width(8)
|
||||||
|
.heightToWidth(1)
|
||||||
|
|
||||||
|
dayLab.layoutChain
|
||||||
|
.top(15)
|
||||||
|
.leftToRightOfView(dotView, offset: 8)
|
||||||
|
|
||||||
|
dotView.layoutChain.centerY(dayLab)
|
||||||
|
|
||||||
|
yearMonthLab.layoutChain
|
||||||
|
.leftToRightOfView(dayLab, offset: 4)
|
||||||
|
.bottomToView(dayLab, offset: -2)
|
||||||
|
|
||||||
|
distanceLab.layoutChain
|
||||||
|
.leftToRightOfView(yearMonthLab, offset: 8)
|
||||||
|
.bottomToView(yearMonthLab)
|
||||||
|
|
||||||
|
durationLab.layoutChain
|
||||||
|
.centerY(distanceLab)
|
||||||
|
.leftToRightOfView(distanceLab, offset: 6)
|
||||||
|
|
||||||
|
detailView.layoutChain
|
||||||
|
.topToBottomOfView(dayLab, offset: 15)
|
||||||
|
.left(28)
|
||||||
|
.right(15)
|
||||||
|
.height(110)
|
||||||
|
.bottom(15)
|
||||||
|
|
||||||
|
headerBgView.layoutChain
|
||||||
|
.edges(excludingEdge: .bottom)
|
||||||
|
.height(38)
|
||||||
|
|
||||||
|
speedLab.layoutChain
|
||||||
|
.top(11)
|
||||||
|
.left(15)
|
||||||
|
|
||||||
|
unitLab.layoutChain
|
||||||
|
.right(10)
|
||||||
|
.centerY(speedLab)
|
||||||
|
|
||||||
|
scoreLab.layoutChain
|
||||||
|
.rightToLeftOfView(unitLab)
|
||||||
|
.centerY(speedLab)
|
||||||
|
|
||||||
|
scroreTitleLab.layoutChain
|
||||||
|
.rightToLeftOfView(scoreLab, offset: -3)
|
||||||
|
.centerY(speedLab)
|
||||||
|
|
||||||
|
startTitleLab.layoutChain
|
||||||
|
.topToBottomOfView(headerBgView, offset: 12)
|
||||||
|
.left(15)
|
||||||
|
|
||||||
|
startLab.layoutChain
|
||||||
|
.leftToRightOfView(startTitleLab, offset: 2)
|
||||||
|
.centerY(startTitleLab)
|
||||||
|
|
||||||
|
endTitleLab.layoutChain
|
||||||
|
.topToBottomOfView(startTitleLab, offset: 11)
|
||||||
|
.leftToView(startTitleLab)
|
||||||
|
|
||||||
|
endLab.layoutChain
|
||||||
|
.leftToRightOfView(endTitleLab, offset: 2)
|
||||||
|
.centerY(endTitleLab)
|
||||||
|
|
||||||
|
lineView.layoutChain
|
||||||
|
.height(0.5)
|
||||||
|
.edgesHorzontal(15)
|
||||||
|
.bottom()
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy var dotView: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.backgroundColor = UIColor(hexStr: "#16B3FF")
|
||||||
|
view.cornerRadius = 4
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var dayLab: UILabel = {
|
||||||
|
let l = UILabel()
|
||||||
|
l.font = .systemFont(ofSize: 20, weight: .bold)
|
||||||
|
l.textColor = UIColor(hexStr: "#16B3FF")
|
||||||
|
return l
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var yearMonthLab: UILabel = {
|
||||||
|
let l = UILabel()
|
||||||
|
l.font = .systemFont(ofSize: 12, weight: .bold)
|
||||||
|
l.textColor = UIColor(hexStr: "#66CDFF")
|
||||||
|
return l
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var distanceLab: UILabel = {
|
||||||
|
let l = UILabel()
|
||||||
|
l.font = .systemFont(ofSize: 12, weight: .medium)
|
||||||
|
l.textColor = UIColor(hexStr: "#333333")
|
||||||
|
return l
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var durationLab: UILabel = {
|
||||||
|
let l = UILabel()
|
||||||
|
l.font = .systemFont(ofSize: 12, weight: .regular)
|
||||||
|
l.textColor = UIColor(hexStr: "#333333")
|
||||||
|
return l
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var detailView: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.backgroundColor = UIColor(hexStr: "#F5FBFF")
|
||||||
|
view.cornerRadius = 10
|
||||||
|
view.clipsToBounds = true
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var headerBgView: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.backgroundColor = UIColor(hexStr: "#C4E8FF")
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var speedLab: UILabel = {
|
||||||
|
let l = UILabel()
|
||||||
|
l.font = .systemFont(ofSize: 12, weight: .medium)
|
||||||
|
l.textColor = UIColor(hexStr: "#333333")
|
||||||
|
return l
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var scroreTitleLab: UILabel = {
|
||||||
|
let l = UILabel()
|
||||||
|
l.text = "安全评分"
|
||||||
|
l.font = .systemFont(ofSize: 10, weight: .medium)
|
||||||
|
l.textColor = UIColor(hexStr: "#333333")
|
||||||
|
return l
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var scoreLab: UILabel = {
|
||||||
|
let l = UILabel()
|
||||||
|
l.text = "0"
|
||||||
|
l.font = .systemFont(ofSize: 24, weight: .medium)
|
||||||
|
l.textColor = UIColor(hexStr: "#16B3FF")
|
||||||
|
return l
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var unitLab: UILabel = {
|
||||||
|
let l = UILabel()
|
||||||
|
l.text = "分"
|
||||||
|
l.font = .systemFont(ofSize: 12, weight: .medium)
|
||||||
|
l.textColor = UIColor(hexStr: "#16B3FF")
|
||||||
|
return l
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var startTitleLab: UILabel = {
|
||||||
|
let l = UILabel()
|
||||||
|
l.text = "起点:"
|
||||||
|
l.font = .systemFont(ofSize: 12, weight: .medium)
|
||||||
|
l.textColor = UIColor(hexStr: "#666666")
|
||||||
|
return l
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var startLab: UILabel = {
|
||||||
|
let l = UILabel()
|
||||||
|
l.font = .systemFont(ofSize: 12, weight: .medium)
|
||||||
|
l.textColor = UIColor(hexStr: "#333333")
|
||||||
|
return l
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var endTitleLab: UILabel = {
|
||||||
|
let l = UILabel()
|
||||||
|
l.text = "终点:"
|
||||||
|
l.font = .systemFont(ofSize: 12, weight: .medium)
|
||||||
|
l.textColor = UIColor(hexStr: "#666666")
|
||||||
|
return l
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var endLab: UILabel = {
|
||||||
|
let l = UILabel()
|
||||||
|
l.font = .systemFont(ofSize: 12, weight: .medium)
|
||||||
|
l.textColor = UIColor(hexStr: "#333333")
|
||||||
|
return l
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var lineView: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.backgroundColor = ThemeManager.shared.color.lineColor
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(style: CellStyle, reuseIdentifier: String?) {
|
||||||
|
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||||
|
selectionStyle = .none
|
||||||
|
backgroundColor = .clear
|
||||||
|
setupViews()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func awakeFromNib() {
|
||||||
|
super.awakeFromNib()
|
||||||
|
// Initialization code
|
||||||
|
}
|
||||||
|
|
||||||
|
override func setSelected(_ selected: Bool, animated: Bool) {
|
||||||
|
super.setSelected(selected, animated: animated)
|
||||||
|
|
||||||
|
// Configure the view for the selected state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
//
|
||||||
|
// ItineraryTraceVC.swift
|
||||||
|
// QuickLocation
|
||||||
|
//
|
||||||
|
// Created by 八条 on 2026/6/30.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class ItineraryTraceVC: BaseViewController {
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
// Do any additional setup after loading the view.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
// MARK: - Navigation
|
||||||
|
|
||||||
|
// In a storyboard-based application, you will often want to do a little preparation before navigation
|
||||||
|
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||||
|
// Get the new view controller using segue.destination.
|
||||||
|
// Pass the selected object to the new view controller.
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,186 @@
|
||||||
|
//
|
||||||
|
// ItineraryTraceView.swift
|
||||||
|
// QuickLocation
|
||||||
|
//
|
||||||
|
// Created by 八条 on 2026/6/30.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import RxSwift
|
||||||
|
import RxCocoa
|
||||||
|
import AMapNaviKit
|
||||||
|
|
||||||
|
class ItineraryTraceView: UIView {
|
||||||
|
|
||||||
|
var disposeBag = DisposeBag()
|
||||||
|
|
||||||
|
private func setupRx() {
|
||||||
|
backBtn.rx.tap.subscribe(onNext: { _ in
|
||||||
|
AppRouter.shared.popOrDismiss()
|
||||||
|
}).disposed(by: disposeBag)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupUI() {
|
||||||
|
addSubview(mapView)
|
||||||
|
addSubview(navBgView)
|
||||||
|
addSubview(navBarView)
|
||||||
|
navBarView.addSubview(backBtn)
|
||||||
|
navBarView.addSubview(navTitleLabel)
|
||||||
|
|
||||||
|
mapView.layoutChain
|
||||||
|
.edges()
|
||||||
|
|
||||||
|
navBgView.layoutChain
|
||||||
|
.edges(excludingEdge: .bottom)
|
||||||
|
.heightToWidth(160/375)
|
||||||
|
|
||||||
|
navBarView.layoutChain
|
||||||
|
.edges(excludingEdge: .bottom)
|
||||||
|
.height(kNaviHeight)
|
||||||
|
|
||||||
|
backBtn.layoutChain
|
||||||
|
.centerY(navTitleLabel)
|
||||||
|
.left(15)
|
||||||
|
.width(24).height(24)
|
||||||
|
|
||||||
|
navTitleLabel.layoutChain
|
||||||
|
.top(kStatusBarHeight + 12)
|
||||||
|
.centerX()
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Views
|
||||||
|
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 v = UIView()
|
||||||
|
v.backgroundColor = .clear
|
||||||
|
return v
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var backBtn: UIButton = {
|
||||||
|
let btn = UIButton(type: .custom)
|
||||||
|
btn.setImage(UIImage(named: "Common/back"), for: .normal)
|
||||||
|
btn.extendEdgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 30)
|
||||||
|
return btn
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var navTitleLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .systemFont(ofSize: 18, weight: .medium)
|
||||||
|
label.textColor = ThemeManager.shared.color.titleAuxColor
|
||||||
|
label.text = "行程轨迹"
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var deleteBtn: UIButton = {
|
||||||
|
let btn = UIButton()
|
||||||
|
btn.setImage(UIImage(named: "Common/delete"), for: .normal)
|
||||||
|
btn.extendEdgeInsets = UIEdgeInsets(top: 15, left: 20, bottom: 15, right: 15)
|
||||||
|
btn.isHidden = true
|
||||||
|
return btn
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var mapView: MAMapView! = {
|
||||||
|
let mv = MAMapView()
|
||||||
|
mv.zoomLevel = 14
|
||||||
|
mv.showsUserLocation = false
|
||||||
|
mv.showsCompass = false
|
||||||
|
mv.userTrackingMode = .none
|
||||||
|
DispatchQueue.main.async { mv.logoCenter = CGPoint(x: mv.bounds.width - 55, y: kNaviHeight) }
|
||||||
|
return mv
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var bottomView: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.backgroundColor = .white
|
||||||
|
view.layer.cornerRadius = 20
|
||||||
|
view.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||||
|
|
||||||
|
view.addSubview(titleLab)
|
||||||
|
titleLab.layoutChain
|
||||||
|
.top(20)
|
||||||
|
.left(20)
|
||||||
|
|
||||||
|
view.addSubview(startTimeLab)
|
||||||
|
startTimeLab.layoutChain
|
||||||
|
.topToBottomOfView(titleLab, offset: 8)
|
||||||
|
.leftToView(titleLab)
|
||||||
|
|
||||||
|
let dotView = UIView()
|
||||||
|
dotView.backgroundColor = UIColor(hexStr: "#16B3FF")
|
||||||
|
dotView.cornerRadius = 3
|
||||||
|
view.addSubview(dotView)
|
||||||
|
dotView.layoutChain
|
||||||
|
.leftToRightOfView(startTimeLab, offset: 10)
|
||||||
|
.centerY(startTimeLab)
|
||||||
|
.width(6)
|
||||||
|
.heightToWidth(1)
|
||||||
|
|
||||||
|
view.addSubview(startAddressLab)
|
||||||
|
startAddressLab.layoutChain
|
||||||
|
.centerY(startTimeLab)
|
||||||
|
.leftToRightOfView(dotView, offset: 12)
|
||||||
|
|
||||||
|
view.addSubview(endTimeLab)
|
||||||
|
endTimeLab.layoutChain
|
||||||
|
.topToBottomOfView(startTimeLab, offset: 24)
|
||||||
|
.leftToView(startTimeLab)
|
||||||
|
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
/// xxx 的行程详细信息
|
||||||
|
lazy var titleLab: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.textColor = UIColor(hexStr: "#0F2846")
|
||||||
|
label.font = .systemFont(ofSize: 16, weight: .semibold)
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var startTimeLab: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.textColor = UIColor(hexStr: "#666666")
|
||||||
|
label.font = .systemFont(ofSize: 16, weight: .medium)
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var startAddressLab: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.textColor = UIColor(hexStr: "#999999")
|
||||||
|
label.font = .systemFont(ofSize: 14, weight: .medium)
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var endTimeLab: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.textColor = UIColor(hexStr: "#666666")
|
||||||
|
label.font = .systemFont(ofSize: 16, weight: .medium)
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var endAddressLab: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.textColor = UIColor(hexStr: "#999999")
|
||||||
|
label.font = .systemFont(ofSize: 14, weight: .medium)
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
backgroundColor = .clear
|
||||||
|
setupUI()
|
||||||
|
setupRx()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,254 @@
|
||||||
|
//
|
||||||
|
// ScheduleRecordModel.swift
|
||||||
|
// QuickLocation
|
||||||
|
//
|
||||||
|
// Created by 八条 on 2026/6/30.
|
||||||
|
//
|
||||||
|
|
||||||
|
import ObjectMapper
|
||||||
|
import RxDataSources
|
||||||
|
|
||||||
|
/// 驾驶记录
|
||||||
|
struct ScheduleRecordListResponse: BaseModelProtocol {
|
||||||
|
// 状态码
|
||||||
|
var code: String?
|
||||||
|
// 消息
|
||||||
|
var message: String?
|
||||||
|
// 日期
|
||||||
|
var date = ""
|
||||||
|
// 用户ID
|
||||||
|
var user_id = ""
|
||||||
|
//
|
||||||
|
var list: [ScheduleRecordModel] = []
|
||||||
|
|
||||||
|
init?(map: Map) {}
|
||||||
|
|
||||||
|
mutating func mapping(map: Map) {
|
||||||
|
code <- map["code"]
|
||||||
|
message <- map["msg"]
|
||||||
|
date <- map["date"]
|
||||||
|
user_id <- map["user_id"]
|
||||||
|
list <- map["data.trips"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ScheduleRecordModel: Mappable, Equatable {
|
||||||
|
var uuid: String = UUID().uuidString
|
||||||
|
|
||||||
|
var id: String = ""
|
||||||
|
/// 开始时间
|
||||||
|
var start_time: String = ""
|
||||||
|
/// 结束时间
|
||||||
|
var end_time: String = ""
|
||||||
|
/// 公里数
|
||||||
|
var distance_km: String = ""
|
||||||
|
/// 驾驶时间
|
||||||
|
var duration_minutes: Int = 0
|
||||||
|
/// 平均时速
|
||||||
|
var avg_speed: String = ""
|
||||||
|
/// 最高时速
|
||||||
|
var max_speed: Double = 0
|
||||||
|
/// 评分
|
||||||
|
var score: Int = 0
|
||||||
|
|
||||||
|
var start_location: TripLocation?
|
||||||
|
var end_location: TripLocation?
|
||||||
|
var start_address: TripAddress?
|
||||||
|
var end_address: TripAddress?
|
||||||
|
var trajectory_path: [TrackPoint] = []
|
||||||
|
var driving_events: [TrackEvent] = []
|
||||||
|
var stay_points: [StayPoint] = []
|
||||||
|
var statistics: TripStatistics?
|
||||||
|
|
||||||
|
init?(map: Map) {}
|
||||||
|
|
||||||
|
mutating func mapping(map: Map) {
|
||||||
|
id <- map["id"]
|
||||||
|
start_time <- map["start_time"]
|
||||||
|
end_time <- map["end_time"]
|
||||||
|
distance_km <- map["distance_km"]
|
||||||
|
duration_minutes <- map["duration_minutes"]
|
||||||
|
avg_speed <- map["avg_speed"]
|
||||||
|
max_speed <- map["max_speed"]
|
||||||
|
score <- map["score"]
|
||||||
|
start_location <- map["start_location"]
|
||||||
|
end_location <- map["end_location"]
|
||||||
|
start_address <- map["start_address"]
|
||||||
|
end_address <- map["end_address"]
|
||||||
|
trajectory_path <- map["trajectory_path"]
|
||||||
|
driving_events <- map["driving_events"]
|
||||||
|
stay_points <- map["stay_points"]
|
||||||
|
statistics <- map["statistics"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ScheduleRecordModel: IdentifiableType {
|
||||||
|
public typealias Identity = String
|
||||||
|
|
||||||
|
public var identity: String {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 对应的子模型
|
||||||
|
struct TripLocation: Mappable, Equatable {
|
||||||
|
var uuid: String = UUID().uuidString
|
||||||
|
|
||||||
|
var lat: Double = 0
|
||||||
|
var lng: Double = 0
|
||||||
|
|
||||||
|
init?(map: Map) {}
|
||||||
|
|
||||||
|
mutating func mapping(map: Map) {
|
||||||
|
lat <- map["lat"]
|
||||||
|
lng <- map["lng"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension TripLocation: IdentifiableType {
|
||||||
|
public typealias Identity = String
|
||||||
|
|
||||||
|
public var identity: String {
|
||||||
|
return uuid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TripAddress: Mappable, Equatable {
|
||||||
|
var uuid: String = UUID().uuidString
|
||||||
|
|
||||||
|
var formatted_address: String = ""
|
||||||
|
var country: String = ""
|
||||||
|
var province: String = ""
|
||||||
|
var city: String = ""
|
||||||
|
var district: String = ""
|
||||||
|
var street: String = ""
|
||||||
|
|
||||||
|
init?(map: Map) {}
|
||||||
|
|
||||||
|
mutating func mapping(map: Map) {
|
||||||
|
formatted_address <- map["formatted_address"]
|
||||||
|
country <- map["country"]
|
||||||
|
province <- map["province"]
|
||||||
|
city <- map["city"]
|
||||||
|
district <- map["district"]
|
||||||
|
street <- map["street"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension TripAddress: IdentifiableType {
|
||||||
|
public typealias Identity = String
|
||||||
|
|
||||||
|
public var identity: String {
|
||||||
|
return uuid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TrackPoint: Mappable, Equatable {
|
||||||
|
var uuid: String = UUID().uuidString
|
||||||
|
|
||||||
|
var lat: Double = 0
|
||||||
|
var lng: Double = 0
|
||||||
|
var timestamp: Int64 = 0
|
||||||
|
|
||||||
|
init?(map: Map) {}
|
||||||
|
|
||||||
|
mutating func mapping(map: Map) {
|
||||||
|
lat <- map["lat"]
|
||||||
|
lng <- map["lng"]
|
||||||
|
timestamp <- map["timestamp"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension TrackPoint: IdentifiableType {
|
||||||
|
public typealias Identity = String
|
||||||
|
|
||||||
|
public var identity: String {
|
||||||
|
return uuid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TrackEvent: Mappable, Equatable {
|
||||||
|
var uuid: String = UUID().uuidString
|
||||||
|
|
||||||
|
var type: String = ""
|
||||||
|
var timestamp: Int64 = 0
|
||||||
|
var value: Int = 0
|
||||||
|
|
||||||
|
init?(map: Map) {}
|
||||||
|
|
||||||
|
mutating func mapping(map: Map) {
|
||||||
|
type <- map["type"]
|
||||||
|
timestamp <- map["timestamp"]
|
||||||
|
value <- map["value"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension TrackEvent: IdentifiableType {
|
||||||
|
public typealias Identity = String
|
||||||
|
|
||||||
|
public var identity: String {
|
||||||
|
return uuid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct StayPoint: Mappable, Equatable {
|
||||||
|
var uuid: String = UUID().uuidString
|
||||||
|
|
||||||
|
var lat: Double = 0
|
||||||
|
var lng: Double = 0
|
||||||
|
var start_time: Int64 = 0
|
||||||
|
var end_time: Int64 = 0
|
||||||
|
var address: String = ""
|
||||||
|
|
||||||
|
init?(map: Map) {}
|
||||||
|
|
||||||
|
mutating func mapping(map: Map) {
|
||||||
|
lat <- map["lat"]
|
||||||
|
lng <- map["lng"]
|
||||||
|
start_time <- map["start_time"]
|
||||||
|
end_time <- map["end_time"]
|
||||||
|
address <- map["address"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension StayPoint: IdentifiableType {
|
||||||
|
public typealias Identity = String
|
||||||
|
|
||||||
|
public var identity: String {
|
||||||
|
return uuid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TripStatistics: Mappable, Equatable {
|
||||||
|
var uuid: String = UUID().uuidString
|
||||||
|
|
||||||
|
var hard_acceleration: Int = 0
|
||||||
|
var hard_braking: Int = 0
|
||||||
|
var sharp_turn: Int = 0
|
||||||
|
var speeding: Int = 0
|
||||||
|
var low_speeding: Int = 0
|
||||||
|
var frequent_lane_change: Int = 0
|
||||||
|
var signal_loss: Int = 0
|
||||||
|
var long_driving: Int = 0
|
||||||
|
|
||||||
|
init?(map: Map) {}
|
||||||
|
|
||||||
|
mutating func mapping(map: Map) {
|
||||||
|
hard_acceleration <- map["hard_acceleration"]
|
||||||
|
hard_braking <- map["hard_braking"]
|
||||||
|
sharp_turn <- map["sharp_turn"]
|
||||||
|
speeding <- map["speeding"]
|
||||||
|
low_speeding <- map["low_speeding"]
|
||||||
|
frequent_lane_change <- map["frequent_lane_change"]
|
||||||
|
signal_loss <- map["signal_loss"]
|
||||||
|
long_driving <- map["long_driving"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension TripStatistics: IdentifiableType {
|
||||||
|
public typealias Identity = String
|
||||||
|
|
||||||
|
public var identity: String {
|
||||||
|
return uuid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -192,6 +192,7 @@ class GroupScheduleView: UIView {
|
||||||
mv.showsUserLocation = false
|
mv.showsUserLocation = false
|
||||||
mv.showsCompass = false
|
mv.showsCompass = false
|
||||||
mv.userTrackingMode = .none
|
mv.userTrackingMode = .none
|
||||||
|
DispatchQueue.main.async { mv.logoCenter = CGPoint(x: mv.bounds.width - 55, y: kNaviHeight) }
|
||||||
return mv
|
return mv
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ class HomeView: UIView {
|
||||||
mv.showsScale = false
|
mv.showsScale = false
|
||||||
mv.isRotateEnabled = false
|
mv.isRotateEnabled = false
|
||||||
mv.isRotateCameraEnabled = false
|
mv.isRotateCameraEnabled = false
|
||||||
|
DispatchQueue.main.async { mv.logoCenter = CGPoint(x: mv.bounds.width - 55, y: kStatusBarHeight + 30) }
|
||||||
return mv
|
return mv
|
||||||
}()
|
}()
|
||||||
#else
|
#else
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ class HomeViewController: BaseViewController {
|
||||||
|
|
||||||
private func startLocationTimer() {
|
private func startLocationTimer() {
|
||||||
locationTimer?.invalidate()
|
locationTimer?.invalidate()
|
||||||
locationTimer = Timer.scheduledTimer(withTimeInterval: 3, repeats: true) { [weak self] _ in
|
locationTimer = Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { [weak self] _ in
|
||||||
guard let self = self, let loc = self.lastLocation else { return }
|
guard let self = self, let loc = self.lastLocation else { return }
|
||||||
let coord = loc.coordinate
|
let coord = loc.coordinate
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,11 @@ class SOSView: UIView {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.disposed(by: disposeBag)
|
.disposed(by: disposeBag)
|
||||||
|
|
||||||
|
// 添加紧急联系人
|
||||||
|
addContactView.rx.tapGesture.subscribe(onNext: { _ in
|
||||||
|
AppRouter.push(Route.emergencyContact)
|
||||||
|
}).disposed(by: disposeBag)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setupUI() {
|
private func setupUI() {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,179 @@
|
||||||
|
//
|
||||||
|
// InputEmailPopupView.swift
|
||||||
|
// QuickLocation
|
||||||
|
//
|
||||||
|
// Created by 八条 on 2026/6/30.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import RxSwift
|
||||||
|
import RxCocoa
|
||||||
|
|
||||||
|
class InputEmailPopupView: UIView {
|
||||||
|
|
||||||
|
private static let shared = InputEmailPopupView(frame: CGRect(origin: .zero, size: kScreenSize))
|
||||||
|
|
||||||
|
var disposeBag = DisposeBag()
|
||||||
|
|
||||||
|
private var completion: ((String) -> Void)?
|
||||||
|
|
||||||
|
static func show(completion: @escaping ((String) -> Void)) {
|
||||||
|
guard let superView = kKeyWindow else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if InputEmailPopupView.shared.superview != nil {
|
||||||
|
InputEmailPopupView.shared.removeFromSuperview()
|
||||||
|
InputEmailPopupView.shared.bgView.frame = .zero
|
||||||
|
}
|
||||||
|
InputEmailPopupView.shared.bgView.alpha = 1
|
||||||
|
InputEmailPopupView.shared.bgView.frame = CGRect(x: 0, y: 0, width: kScreenWidth, height: kScreenHeight)
|
||||||
|
superView.addSubview(InputEmailPopupView.shared)
|
||||||
|
superView.bringSubviewToFront(InputEmailPopupView.shared)
|
||||||
|
|
||||||
|
UIView.animate(withDuration: 0.25) {
|
||||||
|
InputEmailPopupView.shared.bgView.alpha = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
InputEmailPopupView.shared.completion = { text in
|
||||||
|
completion(text)
|
||||||
|
InputEmailPopupView.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 关闭
|
||||||
|
static func dismiss() {
|
||||||
|
guard InputEmailPopupView.shared.superview != nil else { return }
|
||||||
|
|
||||||
|
UIView.animate(withDuration: 0.25, delay: 0, options: [.curveEaseIn]) {
|
||||||
|
InputEmailPopupView.shared.bgView.alpha = 0
|
||||||
|
} completion: { _ in
|
||||||
|
InputEmailPopupView.shared.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupRx() {
|
||||||
|
emailTF.rx.controlEvent(.editingDidEndOnExit)
|
||||||
|
.subscribe(onNext: { [weak self] in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.emailTF.resignFirstResponder()
|
||||||
|
})
|
||||||
|
.disposed(by: disposeBag)
|
||||||
|
|
||||||
|
confirmBtn.rx.tap.subscribe(onNext: { _ in
|
||||||
|
guard let text = self.emailTF.text, String.checkEmailAddressIsValid(text) else {
|
||||||
|
DLToast.show(text: "请输入正确的邮箱")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.completion?(text)
|
||||||
|
}).disposed(by: disposeBag)
|
||||||
|
|
||||||
|
cancelBtn.rx.tap.subscribe(onNext: { _ in
|
||||||
|
InputEmailPopupView.dismiss()
|
||||||
|
}).disposed(by: disposeBag)
|
||||||
|
}
|
||||||
|
|
||||||
|
private lazy var bgView: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.backgroundColor = .black.withAlphaComponent(0.5)
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var infoView: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.backgroundColor = .white
|
||||||
|
view.cornerRadius = 15
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var titleLab: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.text = "添加邮箱"
|
||||||
|
label.textColor = UIColor(hexStr: "#333333")
|
||||||
|
label.font = .systemFont(ofSize: 20, weight: .medium)
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var textFieldView: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.cornerRadius = 10
|
||||||
|
view.backgroundColor = UIColor(hexStr: "#F5FBFF")
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var emailTF: UITextField = {
|
||||||
|
let textField = UITextField()
|
||||||
|
textField.font = .systemFont(ofSize: 16, weight: .medium)
|
||||||
|
textField.placeholder = "请添加邮箱地址"
|
||||||
|
textField.returnKeyType = .done
|
||||||
|
return textField
|
||||||
|
}()
|
||||||
|
|
||||||
|
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
|
||||||
|
return btn
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var cancelBtn: 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
|
||||||
|
return btn
|
||||||
|
}()
|
||||||
|
|
||||||
|
// MARK: - Init
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
backgroundColor = .clear
|
||||||
|
addSubview(bgView)
|
||||||
|
|
||||||
|
bgView.addSubview(infoView)
|
||||||
|
infoView.addSubview(titleLab)
|
||||||
|
infoView.addSubview(textFieldView)
|
||||||
|
textFieldView.addSubview(emailTF)
|
||||||
|
infoView.addSubview(confirmBtn)
|
||||||
|
infoView.addSubview(cancelBtn)
|
||||||
|
|
||||||
|
infoView.layoutChain
|
||||||
|
.edgesHorzontal(30)
|
||||||
|
.heightToWidth(296/315)
|
||||||
|
.centerY()
|
||||||
|
|
||||||
|
titleLab.layoutChain
|
||||||
|
.top(24)
|
||||||
|
.centerX()
|
||||||
|
|
||||||
|
textFieldView.layoutChain
|
||||||
|
.topToBottomOfView(titleLab, offset: 28)
|
||||||
|
.edgesHorzontal(20)
|
||||||
|
.height(62)
|
||||||
|
|
||||||
|
emailTF.layoutChain
|
||||||
|
.edgesHorzontal(15)
|
||||||
|
.edgesVertical()
|
||||||
|
|
||||||
|
confirmBtn.layoutChain
|
||||||
|
.topToBottomOfView(textFieldView, offset: 46)
|
||||||
|
.edgesHorzontal(20)
|
||||||
|
.height(50)
|
||||||
|
|
||||||
|
cancelBtn.layoutChain
|
||||||
|
.topToBottomOfView(confirmBtn)
|
||||||
|
.edgesHorzontal(20)
|
||||||
|
.height(50)
|
||||||
|
|
||||||
|
setupRx()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -46,6 +46,12 @@ class SignInVC: BaseViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).disposed(by: disposeBag)
|
}).disposed(by: disposeBag)
|
||||||
|
|
||||||
|
rootView.emailView.rx.tapGesture.subscribe(onNext: { [weak self] _ in
|
||||||
|
InputEmailPopupView.show { email in
|
||||||
|
self?.requestSetEmail(email: email)
|
||||||
|
}
|
||||||
|
}).disposed(by: disposeBag)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - API
|
// MARK: - API
|
||||||
|
|
@ -58,6 +64,13 @@ class SignInVC: BaseViewController {
|
||||||
}, onError: { _ in }).disposed(by: disposeBag)
|
}, onError: { _ in }).disposed(by: disposeBag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func requestSetEmail(email: String) {
|
||||||
|
DLToast.showLoading()
|
||||||
|
UserService.setEmail(email: email).subscribe(onNext: { response in
|
||||||
|
self.rootView.emailLab.text = email
|
||||||
|
}).disposed(by: disposeBag)
|
||||||
|
}
|
||||||
|
|
||||||
init(lastLocation: CLLocation?) {
|
init(lastLocation: CLLocation?) {
|
||||||
self.lastLocation = lastLocation
|
self.lastLocation = lastLocation
|
||||||
super.init(nibName: nil, bundle: nil)
|
super.init(nibName: nil, bundle: nil)
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,19 @@ class SignInView: UIView {
|
||||||
var disposeBag = DisposeBag()
|
var disposeBag = DisposeBag()
|
||||||
|
|
||||||
func setupData(_ model: SignInModel) {
|
func setupData(_ model: SignInModel) {
|
||||||
|
emailLab.text = model.email
|
||||||
signInTextImg.image = UIImage(named: model.signInStatus == 1 ? "SignIn/today_text" : "SignIn/signIn_text")
|
signInTextImg.image = UIImage(named: model.signInStatus == 1 ? "SignIn/today_text" : "SignIn/signIn_text")
|
||||||
|
|
||||||
let text = "已连续签到\(model.signCount)天"
|
let fileName = model.signInStatus == 2 ? "sign_in_un_continuous_data" : "sign_in_continuous_data"
|
||||||
|
if let path = Bundle.main.path(forResource: fileName, ofType: "json") {
|
||||||
|
self.signInLottieView.animation = LottieAnimation.filepath(path)
|
||||||
|
self.signInLottieView.play()
|
||||||
|
}
|
||||||
|
|
||||||
|
let dayText = model.signInStatus == 2 ? model.missCount.string : model.signCount.string
|
||||||
|
let text = model.signInStatus == 2 ? "已有\(model.missCount)天未签到" : "已连续签到\(model.signCount)天"
|
||||||
let attr = NSMutableAttributedString(string: text)
|
let attr = NSMutableAttributedString(string: text)
|
||||||
let range = (text as NSString).range(of: model.signCount.string)
|
let range = (text as NSString).range(of: dayText)
|
||||||
attr.addAttribute(.foregroundColor, value: UIColor(hexStr: "#FF8B39"), range: range)
|
attr.addAttribute(.foregroundColor, value: UIColor(hexStr: "#FF8B39"), range: range)
|
||||||
signInDaysLab.attributedText = attr
|
signInDaysLab.attributedText = attr
|
||||||
}
|
}
|
||||||
|
|
@ -168,7 +175,7 @@ class SignInView: UIView {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
lazy var signInLottieView: LottieAnimationView = {
|
lazy var signInLottieView: LottieAnimationView = {
|
||||||
let view = LottieAnimationView(name: "sign_in_continuous_data")
|
let view = LottieAnimationView()
|
||||||
view.loopMode = .loop
|
view.loopMode = .loop
|
||||||
return view
|
return view
|
||||||
}()
|
}()
|
||||||
|
|
@ -223,8 +230,8 @@ class SignInView: UIView {
|
||||||
textView.backgroundColor = .clear
|
textView.backgroundColor = .clear
|
||||||
textView.isEditable = false
|
textView.isEditable = false
|
||||||
textView.isScrollEnabled = false
|
textView.isScrollEnabled = false
|
||||||
textView.isSelectable = false
|
textView.isSelectable = true
|
||||||
textView.linkTextAttributes = [:]
|
textView.linkTextAttributes = [.foregroundColor: UIColor(hexStr: "#16B3FF")]
|
||||||
textView.delegate = self
|
textView.delegate = self
|
||||||
|
|
||||||
let text = "签到即同意 用户协议 和 隐私政策"
|
let text = "签到即同意 用户协议 和 隐私政策"
|
||||||
|
|
@ -247,8 +254,6 @@ class SignInView: UIView {
|
||||||
backgroundColor = .white
|
backgroundColor = .white
|
||||||
setupUI()
|
setupUI()
|
||||||
setupRx()
|
setupRx()
|
||||||
|
|
||||||
signInLottieView.play()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
|
|
||||||
|
|
@ -217,6 +217,7 @@ class CreateScheduleView: UIView {
|
||||||
mv.showsUserLocation = false
|
mv.showsUserLocation = false
|
||||||
mv.showsCompass = false
|
mv.showsCompass = false
|
||||||
mv.userTrackingMode = .none
|
mv.userTrackingMode = .none
|
||||||
|
DispatchQueue.main.async { mv.logoCenter = CGPoint(x: mv.bounds.width - 55, y: kNaviHeight) }
|
||||||
return mv
|
return mv
|
||||||
}()
|
}()
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,7 @@ class ItineraryDetailView: UIView {
|
||||||
mv.showsUserLocation = false
|
mv.showsUserLocation = false
|
||||||
mv.showsCompass = false
|
mv.showsCompass = false
|
||||||
mv.userTrackingMode = .none
|
mv.userTrackingMode = .none
|
||||||
|
DispatchQueue.main.async { mv.logoCenter = CGPoint(x: mv.bounds.width - 55, y: kNaviHeight) }
|
||||||
return mv
|
return mv
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,7 @@ class LocationPickerView: UIView {
|
||||||
mv.zoomLevel = 18
|
mv.zoomLevel = 18
|
||||||
mv.showsUserLocation = false
|
mv.showsUserLocation = false
|
||||||
mv.showsCompass = false
|
mv.showsCompass = false
|
||||||
|
DispatchQueue.main.async { mv.logoCenter = CGPoint(x: mv.bounds.width - 55, y: 25) }
|
||||||
return mv
|
return mv
|
||||||
}()
|
}()
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
//
|
||||||
|
// DrivingService.swift
|
||||||
|
// QuickLocation
|
||||||
|
//
|
||||||
|
// Created by 八条 on 2026/6/30.
|
||||||
|
//
|
||||||
|
|
||||||
|
import RxSwift
|
||||||
|
import Moya
|
||||||
|
|
||||||
|
struct DrivingService {
|
||||||
|
static let disposeBag = DisposeBag()
|
||||||
|
|
||||||
|
/// 驾驶事件
|
||||||
|
/// - Parameters:
|
||||||
|
/// - user_id: 用户id
|
||||||
|
/// - start_time: 开始时间戳
|
||||||
|
/// - end_time: 结束时间戳
|
||||||
|
static func drivingEvents(user_id: String, start_time: String, end_time: String) -> Observable<DrivingStatsResponse> {
|
||||||
|
let api = DrivingAPI.drivingEvents(user_id: user_id, start_time: start_time, end_time: end_time).multiTarget
|
||||||
|
return APIProvider.request(token: api)
|
||||||
|
.map(DrivingStatsResponse.self)
|
||||||
|
.asObservable()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 轨迹回放
|
||||||
|
/// - Parameters:
|
||||||
|
/// - user_id: 用户id
|
||||||
|
/// - date: 开始时间戳
|
||||||
|
static func playback(user_id: String, date: String) -> Observable<ScheduleRecordListResponse> {
|
||||||
|
let api = DrivingAPI.playback(user_id: user_id, date: date).multiTarget
|
||||||
|
return APIProvider.request(token: api)
|
||||||
|
.map(ScheduleRecordListResponse.self)
|
||||||
|
.asObservable()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -151,4 +151,12 @@ struct UserService {
|
||||||
.map(ResponseModel.self)
|
.map(ResponseModel.self)
|
||||||
.asObservable()
|
.asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 紧急人邮箱
|
||||||
|
static func setEmail(email: String) -> Observable<ResponseModel> {
|
||||||
|
let api = UserAPI.setEmail(email: email).multiTarget
|
||||||
|
return APIProvider.request(token: api)
|
||||||
|
.map(ResponseModel.self)
|
||||||
|
.asObservable()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue