// // InviteModels.swift // suixinkan // import Foundation /// 邀请信息响应实体,表示邀请码、邀请链接和规则文案。 struct InviteInfoResponse: Decodable, Equatable { let enableInvite: Bool let inviteCode: String let inviteUrl: String let description: [String] enum CodingKeys: String, CodingKey { case enableInvite = "enable_invite" case inviteCode = "invite_code" case inviteUrl = "invite_url" case description } /// 创建邀请信息响应实体。 init(enableInvite: Bool, inviteCode: String, inviteUrl: String, description: [String]) { self.enableInvite = enableInvite self.inviteCode = inviteCode self.inviteUrl = inviteUrl self.description = description } /// 宽松解码邀请信息,兼容后端字符串/数字/布尔混用。 init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) enableInvite = try container.decodeInviteLossyBool(forKey: .enableInvite) ?? true inviteCode = try container.decodeInviteLossyString(forKey: .inviteCode) inviteUrl = try container.decodeInviteLossyString(forKey: .inviteUrl) description = (try? container.decodeIfPresent([String].self, forKey: .description)) ?? [] } } /// 邀请用户实体,表示邀请记录里的摄影师。 struct InviteUserItem: Decodable, Hashable { let id: Int let realName: String let phone: String let avatar: String let createdAt: String let inviteLevel: Int enum CodingKeys: String, CodingKey { case id case realName = "real_name" case phone case avatar case createdAt = "created_at" case inviteLevel = "invite_level" } /// 创建邀请用户实体。 init(id: Int, realName: String, phone: String, avatar: String, createdAt: String, inviteLevel: Int) { self.id = id self.realName = realName self.phone = phone self.avatar = avatar self.createdAt = createdAt self.inviteLevel = inviteLevel } /// 宽松解码邀请用户字段。 init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decodeInviteLossyInt(forKey: .id) ?? 0 realName = try container.decodeInviteLossyString(forKey: .realName) phone = try container.decodeInviteLossyString(forKey: .phone) avatar = try container.decodeInviteLossyString(forKey: .avatar) createdAt = try container.decodeInviteLossyString(forKey: .createdAt) inviteLevel = try container.decodeInviteLossyInt(forKey: .inviteLevel) ?? 1 } } /// 邀请记录分段类型。 enum InviteRecordTab: Int, CaseIterable, Equatable { case invite = 0 case reward = 1 /// 分段标题。 var title: String { switch self { case .invite: return "邀请记录" case .reward: return "奖励记录" } } } /// 邀请记录行展示实体。 struct InviteDisplayRow: Hashable { let id: String let title: String let phone: String let avatar: String let time: String let inviteLevel: Int /// 邀请层级文案。 var levelText: String { inviteLevel == 1 ? "一级" : "二级" } /// 邀请层级是否为一级。 var isPrimaryLevel: Bool { inviteLevel == 1 } /// 头像缺省显示文字。 var avatarPlaceholder: String { if let first = title.first { return String(first) } if let first = phone.first { return String(first) } return "?" } } private extension KeyedDecodingContainer { func decodeInviteLossyString(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 "" } func decodeInviteLossyInt(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) { let text = value.trimmingCharacters(in: .whitespacesAndNewlines) return Int(text) ?? Double(text).map(Int.init) } return nil } func decodeInviteLossyBool(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) { let text = value.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() if ["1", "true", "yes"].contains(text) { return true } if ["0", "false", "no"].contains(text) { return false } } return nil } }