Decode numeric staff_id from the report API and auto-center the map when the first user location update arrives. Co-authored-by: Cursor <cursoragent@cursor.com>
197 lines
5.5 KiB
Swift
197 lines
5.5 KiB
Swift
//
|
||
// LocationModels.swift
|
||
// suixinkan
|
||
//
|
||
|
||
import Foundation
|
||
|
||
/// 位置上报详情,对齐 Android `LocationDetailResponse`。
|
||
struct LocationDetailResponse: Decodable, Sendable {
|
||
let status: Int
|
||
let needReport: Bool
|
||
let expireTime: Int64
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case status
|
||
case needReport = "need_report"
|
||
case expireTime = "expire_time"
|
||
}
|
||
|
||
init(status: Int = 0, needReport: Bool = true, expireTime: Int64 = 0) {
|
||
self.status = status
|
||
self.needReport = needReport
|
||
self.expireTime = expireTime
|
||
}
|
||
}
|
||
|
||
/// 位置上报结果,对齐 Android `LocationReportResponse`。
|
||
struct LocationReportResponse: Decodable, Sendable {
|
||
let staffId: String
|
||
let expired: Int64
|
||
let status: Int
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case staffId = "staff_id"
|
||
case expired
|
||
case status
|
||
case storeUserId = "store_user_id"
|
||
}
|
||
|
||
init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
staffId = Self.decodeFlexibleString(from: container, forKey: .staffId)
|
||
expired = try container.decodeIfPresent(Int64.self, forKey: .expired) ?? 0
|
||
status = try container.decodeIfPresent(Int.self, forKey: .status) ?? 0
|
||
}
|
||
|
||
init(staffId: String = "", expired: Int64 = 0, status: Int = 0) {
|
||
self.staffId = staffId
|
||
self.expired = expired
|
||
self.status = status
|
||
}
|
||
|
||
private static func decodeFlexibleString(
|
||
from container: KeyedDecodingContainer<CodingKeys>,
|
||
forKey key: CodingKeys,
|
||
defaultValue: String = ""
|
||
) -> String {
|
||
if let string = try? container.decode(String.self, forKey: key) {
|
||
return string
|
||
}
|
||
if let intValue = try? container.decode(Int.self, forKey: key) {
|
||
return String(intValue)
|
||
}
|
||
if let int64Value = try? container.decode(Int64.self, forKey: key) {
|
||
return String(int64Value)
|
||
}
|
||
return defaultValue
|
||
}
|
||
}
|
||
|
||
/// 历史上报类型筛选,对齐 Android `LocationReportFilterType`。
|
||
enum LocationReportFilterType: CaseIterable, Equatable {
|
||
case all
|
||
case immediate
|
||
case marked
|
||
case offline
|
||
|
||
var displayName: String {
|
||
switch self {
|
||
case .all: "全部"
|
||
case .immediate: "立即上报"
|
||
case .marked: "标记上报"
|
||
case .offline: "离线上报"
|
||
}
|
||
}
|
||
|
||
var typeCode: Int {
|
||
switch self {
|
||
case .all: 0
|
||
case .immediate: 1
|
||
case .marked: 2
|
||
case .offline: 3
|
||
}
|
||
}
|
||
|
||
var tagColorHex: UInt {
|
||
switch self {
|
||
case .all, .immediate: 0x0073FF
|
||
case .marked: 0x22C55E
|
||
case .offline: 0x999999
|
||
}
|
||
}
|
||
|
||
static func from(typeCode: Int) -> LocationReportFilterType {
|
||
switch typeCode {
|
||
case 1: .immediate
|
||
case 2: .marked
|
||
case 3: .offline
|
||
default: .all
|
||
}
|
||
}
|
||
|
||
static func fromReportType(_ type: Int) -> LocationReportFilterType {
|
||
switch type {
|
||
case 1: .immediate
|
||
case 2: .marked
|
||
case 3: .offline
|
||
default: .all
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 历史上报单条记录,对齐 Android `LocationReportHistoryItem`。
|
||
struct LocationReportHistoryItem: Decodable, Equatable, Identifiable {
|
||
let id: Int64
|
||
let staffId: Int64
|
||
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.decodeIfPresent(Int64.self, forKey: .id) ?? 0
|
||
staffId = try container.decodeIfPresent(Int64.self, forKey: .staffId) ?? 0
|
||
type = try container.decodeIfPresent(Int.self, forKey: .type) ?? 0
|
||
latitude = try container.decodeIfPresent(String.self, forKey: .latitude) ?? ""
|
||
longitude = try container.decodeIfPresent(String.self, forKey: .longitude) ?? ""
|
||
address = try container.decodeIfPresent(String.self, forKey: .address) ?? ""
|
||
ip = try container.decodeIfPresent(String.self, forKey: .ip)
|
||
remark = try container.decodeIfPresent(String.self, forKey: .remark) ?? ""
|
||
createdAt = try container.decodeIfPresent(String.self, forKey: .createdAt) ?? ""
|
||
}
|
||
|
||
init(
|
||
id: Int64 = 0,
|
||
staffId: Int64 = 0,
|
||
type: Int = 0,
|
||
latitude: String = "",
|
||
longitude: String = "",
|
||
address: String = "",
|
||
ip: String? = nil,
|
||
remark: String = "",
|
||
createdAt: String = ""
|
||
) {
|
||
self.id = id
|
||
self.staffId = staffId
|
||
self.type = type
|
||
self.latitude = latitude
|
||
self.longitude = longitude
|
||
self.address = address
|
||
self.ip = ip
|
||
self.remark = remark
|
||
self.createdAt = createdAt
|
||
}
|
||
|
||
var filterType: LocationReportFilterType {
|
||
LocationReportFilterType.fromReportType(type)
|
||
}
|
||
}
|
||
|
||
/// 历史上报列表响应。
|
||
struct LocationReportHistoryListResponse: Decodable, Equatable {
|
||
let list: [LocationReportHistoryItem]
|
||
let total: Int
|
||
|
||
init(list: [LocationReportHistoryItem] = [], total: Int = 0) {
|
||
self.list = list
|
||
self.total = total
|
||
}
|
||
}
|