Add location report map page and history list aligned with Android.
Extract shared LocationReportService, wire location_report menu routing, and add unit tests for reporting cooldown and history pagination. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -36,3 +36,130 @@ struct LocationReportResponse: Decodable, Sendable {
|
||||
case status
|
||||
}
|
||||
}
|
||||
|
||||
/// 历史上报类型筛选,对齐 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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user