Migrate check-in point management and location reporting from placeholders to full MVVM flows, including foreground location support and unit tests. Co-authored-by: Cursor <cursoragent@cursor.com>
128 lines
4.2 KiB
Swift
128 lines
4.2 KiB
Swift
//
|
||
// 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
|
||
}
|
||
}
|