// // 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 } }