// // LocationReportModels.swift // suixinkan // // Created by Codex on 2026/6/24. // import Foundation /// 位置上报类型实体,表示立即上报、标记点上报和在线状态上报。 enum LocationReportType: Int, CaseIterable, Identifiable { case all = 0 case immediate = 1 case marked = 2 case onlineStatus = 3 var id: Int { rawValue } /// 上报类型展示标题。 var title: String { switch self { case .all: "全部" case .immediate: "立即上报" case .marked: "标记点" case .onlineStatus: "在线状态" } } } /// 位置坐标实体,表示定位或手动选点得到的经纬度。 struct LocationCoordinate: Equatable { var latitude: Double var longitude: Double } /// 位置上报提交响应实体,表示服务端返回的上报人员、过期秒数和状态。 struct LocationReportSubmitResponse: Decodable, Equatable { let staffId: String let expired: Int let status: Int enum CodingKeys: String, CodingKey { case staffId = "staff_id" case expired case status } /// 创建位置上报提交响应,主要用于测试替身返回。 init(staffId: String, expired: Int, status: Int) { self.staffId = staffId self.expired = expired self.status = status } /// 宽松解码提交响应字段。 init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) staffId = try container.decodeLossyString(forKey: .staffId) expired = try container.decodeLossyInt(forKey: .expired) ?? 0 status = try container.decodeLossyInt(forKey: .status) ?? 0 } } /// 位置上报历史项实体,表示一次历史上报记录。 struct LocationReportHistoryItem: Decodable, Equatable, Identifiable { let id: Int let staffId: Int let type: Int let latitude: String let longitude: String let address: String let ip: String let remark: String let createdAt: String enum CodingKeys: String, CodingKey { case id case staffId = "staff_id" case type case latitude case longitude case address case ip case remark case createdAt = "created_at" } /// 宽松解码历史项字段。 init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decodeLossyInt(forKey: .id) ?? 0 staffId = try container.decodeLossyInt(forKey: .staffId) ?? 0 type = try container.decodeLossyInt(forKey: .type) ?? 0 latitude = try container.decodeLossyString(forKey: .latitude) longitude = try container.decodeLossyString(forKey: .longitude) address = try container.decodeLossyString(forKey: .address) ip = try container.decodeLossyString(forKey: .ip) remark = try container.decodeLossyString(forKey: .remark) createdAt = try container.decodeLossyString(forKey: .createdAt) } /// 上报类型展示文案。 var typeTitle: String { LocationReportType(rawValue: type)?.title ?? "未知" } } private extension KeyedDecodingContainer { /// 将 String、数字和 Bool 宽松解码为字符串。 func decodeLossyString(forKey key: Key) throws -> String { if let value = try? decodeIfPresent(String.self, forKey: key) { return value } if let value = try? decodeIfPresent(Int.self, forKey: key) { return String(value) } if let value = try? decodeIfPresent(Double.self, forKey: key) { return String(value) } if let value = try? decodeIfPresent(Bool.self, forKey: key) { return value ? "1" : "0" } return "" } /// 将 String、Double 和 Int 宽松解码为 Int。 func decodeLossyInt(forKey key: Key) throws -> Int? { if let value = try? decodeIfPresent(Int.self, forKey: key) { return value } if let value = try? decodeIfPresent(Double.self, forKey: key) { return Int(value) } if let value = try? decodeIfPresent(String.self, forKey: key) { return Int(value.trimmingCharacters(in: .whitespacesAndNewlines)) } return nil } }