// // ProfileModels.swift // suixinkan // import Foundation /// 用户资料响应实体,表示“我的”页面展示的账号基础信息。 struct UserInfoResponse: Decodable, Equatable { let avatar: String let realName: String let phone: String let nickname: String let roleName: String let status: Int let statusName: String enum CodingKeys: String, CodingKey { case avatar case realName = "real_name" case phone case nickname case roleName = "role_name" case status case statusName = "status_name" } init( avatar: String = "", realName: String = "", phone: String = "", nickname: String = "", roleName: String = "", status: Int = 0, statusName: String = "" ) { self.avatar = avatar self.realName = realName self.phone = phone self.nickname = nickname self.roleName = roleName self.status = status self.statusName = statusName } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) avatar = try container.decodeLossyString(forKey: .avatar) realName = try container.decodeLossyString(forKey: .realName) phone = try container.decodeLossyString(forKey: .phone) nickname = try container.decodeLossyString(forKey: .nickname) roleName = try container.decodeLossyString(forKey: .roleName) status = try container.decodeLossyInt(forKey: .status) ?? 0 statusName = try container.decodeLossyString(forKey: .statusName) } } /// 用户资料更新请求实体,表示昵称、密码和头像修改参数。 struct UpdateInfoRequest: Encodable { let nickname: String? let password: String? let avatar: String? } /// 摄影师个人空间配置响应,对齐 Android `ProfileInfoResponse`。 struct PhotographerProfileInfoResponse: Decodable, Equatable { let id: Int let scenicId: Int let photogUid: Int let realName: String let nickname: String let avatar: String let scenicCertification: [String] let description: String let attrLabel: [String] let shootLabel: [String] let cameraDevice: [String] let businessStartTime: String let businessEndTime: String let holidayStartTime: String let holidayEndTime: String let businessRange: [Int] let acceptOrderStatus: Int let schedule: [String: [PhotographerSchedule]] let virtualPhone: String enum CodingKeys: String, CodingKey { case id case scenicId = "scenic_id" case photogUid = "photog_uid" case realName = "real_name" case nickname case avatar case scenicCertification = "scenic_certification" case description case attrLabel = "attr_label" case shootLabel = "shoot_label" case cameraDevice = "camera_device" case businessStartTime = "business_start_time" case businessEndTime = "business_end_time" case holidayStartTime = "holiday_start_time" case holidayEndTime = "holiday_end_time" case businessRange = "business_range" case acceptOrderStatus = "accept_order_status" case schedule case virtualPhone = "virtual_phone" } init( id: Int = 0, scenicId: Int = 0, photogUid: Int = 0, realName: String = "", nickname: String = "", avatar: String = "", scenicCertification: [String] = [], description: String = "", attrLabel: [String] = [], shootLabel: [String] = [], cameraDevice: [String] = [], businessStartTime: String = "", businessEndTime: String = "", holidayStartTime: String = "", holidayEndTime: String = "", businessRange: [Int] = [], acceptOrderStatus: Int = 0, schedule: [String: [PhotographerSchedule]] = [:], virtualPhone: String = "" ) { self.id = id self.scenicId = scenicId self.photogUid = photogUid self.realName = realName self.nickname = nickname self.avatar = avatar self.scenicCertification = scenicCertification self.description = description self.attrLabel = attrLabel self.shootLabel = shootLabel self.cameraDevice = cameraDevice self.businessStartTime = businessStartTime self.businessEndTime = businessEndTime self.holidayStartTime = holidayStartTime self.holidayEndTime = holidayEndTime self.businessRange = businessRange self.acceptOrderStatus = acceptOrderStatus self.schedule = schedule self.virtualPhone = virtualPhone } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decodeLossyInt(forKey: .id) ?? 0 scenicId = try container.decodeLossyInt(forKey: .scenicId) ?? 0 photogUid = try container.decodeLossyInt(forKey: .photogUid) ?? 0 realName = try container.decodeLossyString(forKey: .realName) nickname = try container.decodeLossyString(forKey: .nickname) avatar = try container.decodeLossyString(forKey: .avatar) scenicCertification = try container.decodeLossyStringArray(forKey: .scenicCertification) description = try container.decodeLossyString(forKey: .description) attrLabel = try container.decodeLossyStringArray(forKey: .attrLabel) shootLabel = try container.decodeLossyStringArray(forKey: .shootLabel) cameraDevice = try container.decodeLossyStringArray(forKey: .cameraDevice) businessStartTime = try container.decodeLossyString(forKey: .businessStartTime) businessEndTime = try container.decodeLossyString(forKey: .businessEndTime) holidayStartTime = try container.decodeLossyString(forKey: .holidayStartTime) holidayEndTime = try container.decodeLossyString(forKey: .holidayEndTime) businessRange = try container.decodeLossyIntArray(forKey: .businessRange) acceptOrderStatus = try container.decodeLossyInt(forKey: .acceptOrderStatus) ?? 0 schedule = try container.decodeIfPresent([String: [PhotographerSchedule]].self, forKey: .schedule) ?? [:] virtualPhone = try container.decodeLossyString(forKey: .virtualPhone) } } /// 摄影师日程实体,对齐 Android `ScheduleEntity`。 struct PhotographerSchedule: Decodable, Equatable, Hashable, Sendable { let id: Int let name: String let remark: String let startTime: String let endTime: String let scheduleDate: String let orderNumber: String? let userNickname: String? let userPhone: String? let orderStatus: Int? let orderStatusName: String? enum CodingKeys: String, CodingKey { case id case name case remark case startTime = "start_time" case endTime = "end_time" case scheduleDate = "schedule_date" case orderNumber = "order_number" case userNickname = "user_nickname" case userPhone = "user_phone" case orderStatus = "order_status" case orderStatusName = "order_status_name" } init( id: Int = 0, name: String = "", remark: String = "", startTime: String = "", endTime: String = "", scheduleDate: String = "", orderNumber: String? = nil, userNickname: String? = nil, userPhone: String? = nil, orderStatus: Int? = nil, orderStatusName: String? = nil ) { self.id = id self.name = name self.remark = remark self.startTime = startTime self.endTime = endTime self.scheduleDate = scheduleDate self.orderNumber = orderNumber self.userNickname = userNickname self.userPhone = userPhone self.orderStatus = orderStatus self.orderStatusName = orderStatusName } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decodeLossyInt(forKey: .id) ?? 0 name = try container.decodeLossyString(forKey: .name) remark = try container.decodeLossyString(forKey: .remark) startTime = try container.decodeLossyString(forKey: .startTime) endTime = try container.decodeLossyString(forKey: .endTime) scheduleDate = try container.decodeLossyString(forKey: .scheduleDate) orderNumber = try container.decodeIfPresent(String.self, forKey: .orderNumber) userNickname = try container.decodeIfPresent(String.self, forKey: .userNickname) userPhone = try container.decodeIfPresent(String.self, forKey: .userPhone) orderStatus = try container.decodeLossyInt(forKey: .orderStatus) orderStatusName = try container.decodeIfPresent(String.self, forKey: .orderStatusName) } } /// 摄影师个人空间配置更新请求,对齐 Android `UpdateProfileInfoRequest`。 struct UpdatePhotographerProfileInfoRequest: Encodable, Equatable { let scenicId: String let description: String let cameraDevice: [String] let attrLabel: [String] let shootLabel: [String] let businessStartTime: String let businessEndTime: String let holidayStartTime: String let holidayEndTime: String let businessRange: [Int] let acceptOrderStatus: Int enum CodingKeys: String, CodingKey { case scenicId = "scenic_id" case description case cameraDevice = "camera_device" case attrLabel = "attr_label" case shootLabel = "shoot_label" case businessStartTime = "business_start_time" case businessEndTime = "business_end_time" case holidayStartTime = "holiday_start_time" case holidayEndTime = "holiday_end_time" case businessRange = "business_range" case acceptOrderStatus = "accept_order_status" } } /// 摄影师新增日程请求,对齐 Android `AddScheduleRequest`。 struct AddPhotographerScheduleRequest: Encodable, Equatable { var scenicId: String var name: String var remark: String var startTime: String var endTime: String var scheduleDate: String var orderNumber: String? enum CodingKeys: String, CodingKey { case scenicId = "scenic_id" case name case remark case startTime = "start_time" case endTime = "end_time" case scheduleDate = "schedule_date" case orderNumber = "order_number" } } /// 摄影师删除日程请求,对齐 Android `ScheduleDeleteRequest`。 struct PhotographerScheduleDeleteRequest: Encodable, Equatable { let id: Int } /// 实名认证响应实体,包裹当前用户的认证详情。 struct RealNameInfoResponse: Decodable, Equatable { let realNameInfo: RealNameInfo? enum CodingKeys: String, CodingKey { case realNameInfo = "real_name_info" } } /// 实名认证信息实体,表示当前用户实名审核状态和失败原因。 struct RealNameInfo: Decodable, Equatable { let realName: String let idCardNo: String let auditStatus: Int let auditStatusText: String? let rejectReason: String? let startDate: String? let endDate: String? let isLongValid: Bool let frontUrl: String? let backUrl: String? let auditorId: Int? let auditor: RealNameAuditor? let auditAt: String? var verified: Bool { auditStatus == 2 } enum CodingKeys: String, CodingKey { case realName = "real_name" case idCardNo = "id_card_no" case auditStatus = "audit_status" case auditStatusText = "audit_status_text" case rejectReason = "reject_reason" case startDate = "start_date" case endDate = "end_date" case isLongValid = "is_long_valid" case frontUrl = "front_url" case backUrl = "back_url" case auditorId = "auditor_id" case auditor case auditAt = "audit_at" } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) realName = try container.decodeLossyString(forKey: .realName) idCardNo = try container.decodeLossyString(forKey: .idCardNo) auditStatus = try container.decodeLossyInt(forKey: .auditStatus) ?? 0 auditStatusText = try container.decodeIfPresent(String.self, forKey: .auditStatusText) rejectReason = try container.decodeIfPresent(String.self, forKey: .rejectReason) startDate = try container.decodeIfPresent(String.self, forKey: .startDate) endDate = try container.decodeIfPresent(String.self, forKey: .endDate) isLongValid = (try container.decodeLossyInt(forKey: .isLongValid) ?? 0) == 1 frontUrl = try container.decodeIfPresent(String.self, forKey: .frontUrl) backUrl = try container.decodeIfPresent(String.self, forKey: .backUrl) auditorId = try container.decodeLossyInt(forKey: .auditorId) auditor = try container.decodeIfPresent(RealNameAuditor.self, forKey: .auditor) auditAt = try container.decodeIfPresent(String.self, forKey: .auditAt) } } /// 实名认证审核人实体,表示后台审核人的基础展示信息。 struct RealNameAuditor: Decodable, Equatable { let id: Int let name: String enum CodingKeys: String, CodingKey { case id case name } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decodeLossyInt(forKey: .id) ?? 0 name = try container.decodeLossyString(forKey: .name) } } /// 实名认证提交请求实体,表示用户填写的姓名、证件号、短信和证件图片。 struct RealNameAuthRequest: Encodable, Equatable { let realName: String let idCardNo: String let smsVerifyCode: String let startDate: String let endDate: String let isLongValid: Int let frontUrl: String let backUrl: String enum CodingKeys: String, CodingKey { case realName = "real_name" case idCardNo = "id_card_no" case smsVerifyCode = "sms_verify_code" case startDate = "start_date" case endDate = "end_date" case isLongValid = "is_long_valid" case frontUrl = "front_url" case backUrl = "back_url" } } /// 银行卡信息响应实体。 struct BankCardInfoResponse: Decodable, Equatable { let bankCard: BankCardInfo? enum CodingKeys: String, CodingKey { case bankCard = "bank_card" } } /// 银行卡信息实体,表示提现设置与审核状态。 struct BankCardInfo: Decodable, Equatable { let realName: String let bankName: String let branchName: String let cardNumber: String let frontUrl: String? let backUrl: String? let provinceCode: String? let cityCode: String? let rejectReason: String let auditStatus: Int let auditStatusLabel: String let auditAt: String? let auditorId: Int? let auditor: RealNameAuditor? enum CodingKeys: String, CodingKey { case realName = "real_name" case bankName = "bank_name" case branchName = "branch_name" case cardNumber = "card_number" case frontUrl = "front_url" case backUrl = "back_url" case provinceCode = "province_code" case cityCode = "city_code" case rejectReason = "reject_reason" case auditStatus = "audit_status" case auditStatusLabel = "audit_status_label" case auditAt = "audit_at" case auditorId = "auditor_id" case auditor } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) realName = try container.decodeLossyString(forKey: .realName) bankName = try container.decodeLossyString(forKey: .bankName) branchName = try container.decodeLossyString(forKey: .branchName) cardNumber = try container.decodeLossyString(forKey: .cardNumber) frontUrl = try container.decodeIfPresent(String.self, forKey: .frontUrl) backUrl = try container.decodeIfPresent(String.self, forKey: .backUrl) provinceCode = try container.decodeIfPresent(String.self, forKey: .provinceCode) cityCode = try container.decodeIfPresent(String.self, forKey: .cityCode) rejectReason = try container.decodeLossyString(forKey: .rejectReason) auditStatus = try container.decodeLossyInt(forKey: .auditStatus) ?? 0 auditStatusLabel = try container.decodeLossyString(forKey: .auditStatusLabel) auditAt = try container.decodeIfPresent(String.self, forKey: .auditAt) auditorId = try container.decodeLossyInt(forKey: .auditorId) auditor = try container.decodeIfPresent(RealNameAuditor.self, forKey: .auditor) } } /// 银行列表响应实体。 struct BankListResponse: Decodable, Equatable { let banks: [String] enum CodingKeys: String, CodingKey { case banks } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) banks = try container.decodeIfPresent([String].self, forKey: .banks) ?? [] } } /// 省市区响应实体。 struct AreasResponse: Decodable, Equatable, Identifiable { let id: Int let code: String let name: String let children: [AreasResponse] enum CodingKeys: String, CodingKey { case id case code case name case children } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decodeLossyInt(forKey: .id) ?? container.decodeLossyInt(forKey: .code) ?? 0 let decodedCode = try container.decodeLossyString(forKey: .code) .trimmingCharacters(in: .whitespacesAndNewlines) code = decodedCode.isEmpty ? "\(id)" : decodedCode name = try container.decodeLossyString(forKey: .name) children = try container.decodeIfPresent([AreasResponse].self, forKey: .children) ?? [] } init(id: Int, code: String, name: String, children: [AreasResponse] = []) { self.id = id self.code = code self.name = name self.children = children } } /// 更新银行卡信息请求实体。 struct UpdateBankInfoRequest: Encodable, Equatable { let realName: String let cardNumber: String let bankName: String let branchName: String let frontUrl: String let backUrl: String let provinceCode: String let cityCode: String let smsVerifyCode: String enum CodingKeys: String, CodingKey { case realName = "real_name" case cardNumber = "card_number" case bankName = "bank_name" case branchName = "branch_name" case frontUrl = "front_url" case backUrl = "back_url" case provinceCode = "province_code" case cityCode = "city_code" case smsVerifyCode = "sms_verify_code" } } /// 个人信息页校验错误实体。 enum ProfileValidationError: LocalizedError, Equatable { case emptyNickname case shortPassword var errorDescription: String? { switch self { case .emptyNickname: "请输入昵称" case .shortPassword: "密码至少 6 位" } } } private extension KeyedDecodingContainer { 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 ? "true" : "false" } return "" } func decodeLossyInt(forKey key: Key) throws -> Int? { if let value = try? decodeIfPresent(Int.self, forKey: key) { return value } if let value = try? decodeIfPresent(String.self, forKey: key) { let text = value.trimmingCharacters(in: .whitespacesAndNewlines) if let intValue = Int(text) { return intValue } if let doubleValue = Double(text) { return Int(doubleValue) } } if let value = try? decodeIfPresent(Double.self, forKey: key) { return Int(value) } return nil } func decodeLossyStringArray(forKey key: Key) throws -> [String] { if let values = try? decodeIfPresent([String].self, forKey: key) { return values } if let values = try? decodeIfPresent([Int].self, forKey: key) { return values.map(String.init) } if let value = try? decodeIfPresent(String.self, forKey: key) { let text = value.trimmingCharacters(in: .whitespacesAndNewlines) return text.isEmpty ? [] : [text] } return [] } func decodeLossyIntArray(forKey key: Key) throws -> [Int] { if let values = try? decodeIfPresent([Int].self, forKey: key) { return values } if let values = try? decodeIfPresent([String].self, forKey: key) { return values.compactMap { Int($0.trimmingCharacters(in: .whitespacesAndNewlines)) } } if let value = try? decodeIfPresent(Int.self, forKey: key) { return [value] } return [] } }