Add PunchPoint and LocationReport modules with home routing.
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>
This commit is contained in:
@ -0,0 +1,127 @@
|
||||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user