持久化服务端 expired 过期时间戳以正确恢复倒计时,切换在线/离线与上报时展示定位 Loading,移除重复的成功 Alert,并将 Toast 调整为居中半透明样式。 Co-authored-by: Cursor <cursoragent@cursor.com>
161 lines
5.3 KiB
Swift
161 lines
5.3 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 offlineReport = 3
|
||
|
||
var id: Int { rawValue }
|
||
|
||
/// 上报类型展示标题。
|
||
var title: String {
|
||
switch self {
|
||
case .all: "全部"
|
||
case .immediate: "立即上报"
|
||
case .marked: "标记点"
|
||
case .offlineReport: "离线上报"
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 位置坐标实体,表示定位或手动选点得到的经纬度。
|
||
struct LocationCoordinate: Equatable {
|
||
var latitude: Double
|
||
var longitude: Double
|
||
}
|
||
|
||
/// 位置上报详情响应实体,表示服务端返回的在线状态与过期时间。
|
||
struct LocationDetailResponse: Decodable, Equatable {
|
||
let status: Int
|
||
let needReport: Bool
|
||
let expireTime: Int
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case status
|
||
case needReport = "need_report"
|
||
case expireTime = "expire_time"
|
||
}
|
||
|
||
/// 创建位置上报详情响应,主要用于测试替身返回。
|
||
init(status: Int, needReport: Bool, expireTime: Int) {
|
||
self.status = status
|
||
self.needReport = needReport
|
||
self.expireTime = expireTime
|
||
}
|
||
|
||
/// 宽松解码详情响应字段。
|
||
init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
status = try container.decodeLossyInt(forKey: .status) ?? 0
|
||
if let boolValue = try? container.decodeIfPresent(Bool.self, forKey: .needReport) {
|
||
needReport = boolValue
|
||
} else {
|
||
let raw = try container.decodeLossyInt(forKey: .needReport) ?? 0
|
||
needReport = raw != 0
|
||
}
|
||
expireTime = try container.decodeLossyInt(forKey: .expireTime) ?? 0
|
||
}
|
||
}
|
||
|
||
/// 位置上报提交响应实体,表示服务端返回的上报人员、过期时间戳和状态。
|
||
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
|
||
}
|
||
}
|