Files
suixinkan_uikit/suixinkan/Features/Profile/Models/ProfileModels.swift
T

627 lines
22 KiB
Swift

//
// 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.decodeScheduleMap(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 []
}
func decodeScheduleMap(forKey key: Key) throws -> [String: [PhotographerSchedule]] {
if let values = try? decodeIfPresent([String: [PhotographerSchedule]].self, forKey: key) {
return values
}
if let values = try? decodeIfPresent([PhotographerSchedule].self, forKey: key) {
return Dictionary(grouping: values) { $0.scheduleDate }
.filter { !$0.key.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }
}
return [:]
}
}