Integrate高德 SDK with simulator-safe build flags, add map views for operating area and punch points, refactor main tabs and key feature screens to UIKit with Diffable lists, and document Swift concurrency defaults in AGENTS.md. Co-authored-by: Cursor <cursoragent@cursor.com>
229 lines
6.5 KiB
Swift
229 lines
6.5 KiB
Swift
//
|
||
// MessageCenterModels.swift
|
||
// suixinkan
|
||
//
|
||
// Created by Codex on 2026/6/25.
|
||
//
|
||
|
||
import Foundation
|
||
import UIKit
|
||
|
||
/// 消息中心列表响应,使用 last_id 游标分页。
|
||
struct MessageListResponse: Decodable, Equatable {
|
||
let hasMore: Bool
|
||
let lastId: Int
|
||
let items: [MessageEntity]
|
||
|
||
/// 枚举,定义相关常量或状态。
|
||
enum CodingKeys: String, CodingKey {
|
||
case hasMore = "has_more"
|
||
case lastId = "last_id"
|
||
case items
|
||
}
|
||
|
||
/// 创建空消息响应,主要用于测试和失败兜底。
|
||
init(hasMore: Bool = false, lastId: Int = 0, items: [MessageEntity] = []) {
|
||
self.hasMore = hasMore
|
||
self.lastId = lastId
|
||
self.items = items
|
||
}
|
||
|
||
/// 宽松解码分页状态,兼容后端数字/字符串/布尔类型不稳定。
|
||
init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
hasMore = try container.decodeLossyBool(forKey: .hasMore) ?? false
|
||
lastId = try container.decodeLossyInt(forKey: .lastId) ?? 0
|
||
items = try container.decodeIfPresent([MessageEntity].self, forKey: .items) ?? []
|
||
}
|
||
}
|
||
|
||
/// 后端消息实体。
|
||
struct MessageEntity: Decodable, Equatable, Identifiable {
|
||
let id: Int
|
||
let type: Int
|
||
let typeName: String
|
||
let title: String
|
||
let content: String
|
||
let pushAt: String
|
||
let createdAt: String
|
||
let isRead: Bool
|
||
|
||
/// 枚举,定义相关常量或状态。
|
||
enum CodingKeys: String, CodingKey {
|
||
case id
|
||
case type
|
||
case typeName = "type_name"
|
||
case title
|
||
case content
|
||
case pushAt = "push_at"
|
||
case createdAt = "created_at"
|
||
case isRead = "is_read"
|
||
}
|
||
|
||
/// 创建消息实体,主要用于测试。
|
||
init(
|
||
id: Int,
|
||
type: Int,
|
||
typeName: String = "",
|
||
title: String = "",
|
||
content: String = "",
|
||
pushAt: String = "",
|
||
createdAt: String = "",
|
||
isRead: Bool = false
|
||
) {
|
||
self.id = id
|
||
self.type = type
|
||
self.typeName = typeName
|
||
self.title = title
|
||
self.content = content
|
||
self.pushAt = pushAt
|
||
self.createdAt = createdAt
|
||
self.isRead = isRead
|
||
}
|
||
|
||
/// 宽松解码消息字段,避免单条异常字段导致整页失败。
|
||
init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
id = try container.decodeLossyInt(forKey: .id) ?? 0
|
||
type = try container.decodeLossyInt(forKey: .type) ?? 0
|
||
typeName = try container.decodeLossyString(forKey: .typeName)
|
||
title = try container.decodeLossyString(forKey: .title)
|
||
content = try container.decodeLossyString(forKey: .content)
|
||
pushAt = try container.decodeLossyString(forKey: .pushAt)
|
||
createdAt = try container.decodeLossyString(forKey: .createdAt)
|
||
isRead = try container.decodeLossyBool(forKey: .isRead) ?? false
|
||
}
|
||
}
|
||
|
||
/// 删除消息请求体。
|
||
struct MessageDeleteRequest: Encodable {
|
||
let id: Int
|
||
}
|
||
|
||
/// 页面展示用消息类型。
|
||
enum MessageType: Equatable {
|
||
case order
|
||
case writeOff
|
||
case system
|
||
|
||
/// 消息类型图标。
|
||
var iconName: String {
|
||
switch self {
|
||
case .order:
|
||
return "cart.fill"
|
||
case .writeOff:
|
||
return "qrcode.viewfinder"
|
||
case .system:
|
||
return "bell.badge.fill"
|
||
}
|
||
}
|
||
|
||
/// 消息类型标题。
|
||
var title: String {
|
||
switch self {
|
||
case .order:
|
||
return "订单"
|
||
case .writeOff:
|
||
return "核销"
|
||
case .system:
|
||
return "系统"
|
||
}
|
||
}
|
||
|
||
/// 消息类型颜色。
|
||
var color: UIColor {
|
||
switch self {
|
||
case .order:
|
||
return AppDesign.primary
|
||
case .writeOff:
|
||
return AppDesign.success
|
||
case .system:
|
||
return AppDesign.warning
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 页面展示用消息项。
|
||
struct MessageItem: Identifiable, Equatable {
|
||
let id: String
|
||
let msgId: Int?
|
||
let title: String
|
||
let detail: String
|
||
let time: String
|
||
var isRead: Bool
|
||
let type: MessageType
|
||
}
|
||
|
||
/// 消息筛选项。
|
||
enum MessageFilter: Int, CaseIterable, Identifiable {
|
||
case all = 0
|
||
case unread = 1
|
||
|
||
var id: Int { rawValue }
|
||
|
||
/// 筛选标题。
|
||
var title: String {
|
||
switch self {
|
||
case .all:
|
||
return "全部"
|
||
case .unread:
|
||
return "未读"
|
||
}
|
||
}
|
||
}
|
||
|
||
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、数字和 Bool 宽松解码为 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),
|
||
case let text = value.trimmingCharacters(in: .whitespacesAndNewlines),
|
||
!text.isEmpty {
|
||
return Int(text) ?? Int(Double(text) ?? 0)
|
||
}
|
||
if let value = try? decodeIfPresent(Bool.self, forKey: key) {
|
||
return value ? 1 : 0
|
||
}
|
||
return nil
|
||
}
|
||
|
||
/// 将 String、数字和 Bool 宽松解码为 Bool。
|
||
func decodeLossyBool(forKey key: Key) throws -> Bool? {
|
||
if let value = try? decodeIfPresent(Bool.self, forKey: key) {
|
||
return value
|
||
}
|
||
if let value = try? decodeIfPresent(Int.self, forKey: key) {
|
||
return value != 0
|
||
}
|
||
if let value = try? decodeIfPresent(String.self, forKey: key),
|
||
case let text = value.trimmingCharacters(in: .whitespacesAndNewlines).lowercased(),
|
||
!text.isEmpty {
|
||
if ["1", "true", "yes"].contains(text) { return true }
|
||
if ["0", "false", "no"].contains(text) { return false }
|
||
}
|
||
return nil
|
||
}
|
||
}
|