将项目管理、排期管理与摄影师邀请流程从占位页迁移为完整实现,包含项目图片 OSS 上传与共享业务模型。 Co-authored-by: Cursor <cursoragent@cursor.com>
628 lines
22 KiB
Swift
628 lines
22 KiB
Swift
//
|
||
// WalletModels.swift
|
||
// suixinkan
|
||
//
|
||
// Created by Codex on 2026/6/22.
|
||
//
|
||
|
||
import Foundation
|
||
|
||
/// 钱包汇总响应实体,表示累计收益、当前余额和可提现金额。
|
||
struct WalletSummaryResponse: Decodable, Equatable {
|
||
let amountTotal: String
|
||
let amountCurrentBalance: String
|
||
let amountWithdrawable: String
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case amountTotal = "amount_total"
|
||
case amountCurrentBalance = "amount_current_balance"
|
||
case amountWithdrawable = "amount_withdrawable"
|
||
}
|
||
|
||
/// 创建钱包汇总响应实体,主要用于测试和空状态。
|
||
init(amountTotal: String = "0.00", amountCurrentBalance: String = "0.00", amountWithdrawable: String = "0.00") {
|
||
self.amountTotal = amountTotal
|
||
self.amountCurrentBalance = amountCurrentBalance
|
||
self.amountWithdrawable = amountWithdrawable
|
||
}
|
||
|
||
/// 自定义解码,兼容金额字段类型不稳定。
|
||
init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
amountTotal = try container.decodeLossyString(forKey: .amountTotal)
|
||
amountCurrentBalance = try container.decodeLossyString(forKey: .amountCurrentBalance)
|
||
amountWithdrawable = try container.decodeLossyString(forKey: .amountWithdrawable)
|
||
}
|
||
}
|
||
|
||
/// 钱包收益明细响应实体,表示日分组收益和分页总数。
|
||
struct WalletEarningDetailResponse: Decodable, Equatable {
|
||
let totalAmount: String
|
||
let totalPoints: Int
|
||
let total: Int
|
||
let list: [WalletEarningDetailGroup]
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case totalAmount = "total_amount"
|
||
case totalPoints = "total_points"
|
||
case total
|
||
case list
|
||
}
|
||
|
||
/// 创建收益明细响应实体,主要用于测试替身。
|
||
init(totalAmount: String = "0.00", totalPoints: Int = 0, total: Int = 0, list: [WalletEarningDetailGroup] = []) {
|
||
self.totalAmount = totalAmount
|
||
self.totalPoints = totalPoints
|
||
self.total = total
|
||
self.list = list
|
||
}
|
||
|
||
/// 自定义解码,兼容后端字段缺失和类型不稳定。
|
||
init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
totalAmount = try container.decodeLossyString(forKey: .totalAmount)
|
||
totalPoints = try container.decodeLossyInt(forKey: .totalPoints) ?? 0
|
||
total = try container.decodeLossyInt(forKey: .total) ?? 0
|
||
list = try container.decodeIfPresent([WalletEarningDetailGroup].self, forKey: .list) ?? []
|
||
}
|
||
}
|
||
|
||
/// 钱包收益日分组实体,表示某一天的收益、积分和明细。
|
||
struct WalletEarningDetailGroup: Decodable, Equatable, Identifiable {
|
||
let date: String
|
||
let dayAmount: String
|
||
let dayPoints: Int
|
||
let items: [WalletEarningDetailItem]
|
||
|
||
var id: String { date }
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case date
|
||
case dayAmount = "day_amount"
|
||
case dayPoints = "day_points"
|
||
case items
|
||
}
|
||
|
||
/// 创建收益日分组实体,主要用于测试替身。
|
||
init(date: String, dayAmount: String = "0.00", dayPoints: Int = 0, items: [WalletEarningDetailItem] = []) {
|
||
self.date = date
|
||
self.dayAmount = dayAmount
|
||
self.dayPoints = dayPoints
|
||
self.items = items
|
||
}
|
||
|
||
/// 自定义解码,兼容后端字段缺失和类型不稳定。
|
||
init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
date = try container.decodeLossyString(forKey: .date)
|
||
dayAmount = try container.decodeLossyString(forKey: .dayAmount)
|
||
dayPoints = try container.decodeLossyInt(forKey: .dayPoints) ?? 0
|
||
items = try container.decodeIfPresent([WalletEarningDetailItem].self, forKey: .items) ?? []
|
||
}
|
||
}
|
||
|
||
/// 钱包收益明细实体,表示一笔订单收益或积分来源。
|
||
struct WalletEarningDetailItem: Decodable, Equatable, Identifiable {
|
||
let id: Int64
|
||
let amount: String
|
||
let points: Int
|
||
let type: Int
|
||
let typeLabel: String
|
||
let orderNumberSuffix: String
|
||
let createdAt: String
|
||
let withdrawLabel: String?
|
||
let source: String?
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case id
|
||
case amount
|
||
case points
|
||
case type
|
||
case typeLabel = "type_label"
|
||
case orderNumberSuffix = "order_number_suffix"
|
||
case createdAt = "created_at"
|
||
case withdrawLabel = "withdraw_label"
|
||
case source
|
||
}
|
||
|
||
/// 创建收益明细实体,主要用于测试替身和本地空状态。
|
||
init(
|
||
id: Int64 = 0,
|
||
amount: String = "",
|
||
points: Int = 0,
|
||
type: Int = 0,
|
||
typeLabel: String = "",
|
||
orderNumberSuffix: String = "",
|
||
createdAt: String = "",
|
||
withdrawLabel: String? = nil,
|
||
source: String? = nil
|
||
) {
|
||
self.id = id
|
||
self.amount = amount
|
||
self.points = points
|
||
self.type = type
|
||
self.typeLabel = typeLabel
|
||
self.orderNumberSuffix = orderNumberSuffix
|
||
self.createdAt = createdAt
|
||
self.withdrawLabel = withdrawLabel
|
||
self.source = source
|
||
}
|
||
|
||
/// 自定义解码,兼容字段类型不稳定。
|
||
init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
id = Int64(try container.decodeLossyInt(forKey: .id) ?? 0)
|
||
amount = try container.decodeLossyString(forKey: .amount)
|
||
points = try container.decodeLossyInt(forKey: .points) ?? 0
|
||
type = try container.decodeLossyInt(forKey: .type) ?? 0
|
||
typeLabel = try container.decodeLossyString(forKey: .typeLabel)
|
||
orderNumberSuffix = try container.decodeLossyString(forKey: .orderNumberSuffix)
|
||
createdAt = try container.decodeLossyString(forKey: .createdAt)
|
||
withdrawLabel = try container.decodeIfPresent(String.self, forKey: .withdrawLabel)
|
||
source = try container.decodeIfPresent(String.self, forKey: .source)
|
||
}
|
||
}
|
||
|
||
/// 钱包提现记录响应实体,表示提现分页列表。
|
||
struct WalletWithdrawListResponse: Decodable, Equatable {
|
||
let total: Int
|
||
let item: [WalletWithdrawRecord]
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case total
|
||
case item
|
||
}
|
||
|
||
/// 创建提现记录响应实体,主要用于测试替身。
|
||
init(total: Int = 0, item: [WalletWithdrawRecord] = []) {
|
||
self.total = total
|
||
self.item = item
|
||
}
|
||
|
||
/// 自定义解码,兼容 total 字段类型不稳定。
|
||
init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
total = try container.decodeLossyInt(forKey: .total) ?? 0
|
||
item = try container.decodeIfPresent([WalletWithdrawRecord].self, forKey: .item) ?? []
|
||
}
|
||
}
|
||
|
||
/// 钱包提现记录实体,表示一笔提现申请。
|
||
struct WalletWithdrawRecord: Decodable, Equatable, Identifiable {
|
||
let id: Int64
|
||
let amount: String
|
||
let createdAt: String
|
||
let statusLabel: String
|
||
let expectedAt: String?
|
||
let auditTime: String?
|
||
let completedAt: String?
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case id
|
||
case amount
|
||
case createdAt = "created_at"
|
||
case statusLabel = "status_label"
|
||
case expectedAt = "expected_at"
|
||
case auditTime = "audit_time"
|
||
case completedAt = "completed_at"
|
||
}
|
||
|
||
/// 自定义解码,兼容字段类型不稳定。
|
||
init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
id = Int64(try container.decodeLossyInt(forKey: .id) ?? 0)
|
||
amount = try container.decodeLossyString(forKey: .amount)
|
||
createdAt = try container.decodeLossyString(forKey: .createdAt)
|
||
statusLabel = try container.decodeLossyString(forKey: .statusLabel)
|
||
expectedAt = try container.decodeIfPresent(String.self, forKey: .expectedAt)
|
||
auditTime = try container.decodeIfPresent(String.self, forKey: .auditTime)
|
||
completedAt = try container.decodeIfPresent(String.self, forKey: .completedAt)
|
||
}
|
||
}
|
||
|
||
/// 银行卡信息响应实体,包裹当前银行卡资料。
|
||
struct BankCardInfoResponse: Decodable, Equatable {
|
||
let bankCard: WalletBankCardInfo?
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case bankCard = "bank_card"
|
||
}
|
||
}
|
||
|
||
/// 钱包银行卡实体,表示银行卡资料和审核状态。
|
||
struct WalletBankCardInfo: Decodable, Equatable, Identifiable {
|
||
var id: String { cardNumber }
|
||
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
|
||
|
||
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"
|
||
}
|
||
|
||
/// 自定义解码,兼容审核状态字段类型不稳定。
|
||
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.decodeIfPresent(String.self, forKey: .rejectReason)
|
||
auditStatus = try container.decodeLossyInt(forKey: .auditStatus) ?? 0
|
||
auditStatusLabel = try container.decodeLossyString(forKey: .auditStatusLabel)
|
||
}
|
||
}
|
||
|
||
/// 银行列表响应实体,表示可选择银行名称。
|
||
struct BankListResponse: Decodable, Equatable {
|
||
let banks: [String]
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case banks
|
||
}
|
||
|
||
/// 创建银行列表响应实体,主要用于测试替身。
|
||
init(banks: [String] = []) {
|
||
self.banks = banks
|
||
}
|
||
}
|
||
|
||
/// 省市区节点实体,表示提现银行卡设置中的地区树。
|
||
struct AreaNode: Decodable, Equatable, Identifiable {
|
||
let id: String
|
||
let code: String
|
||
let name: String
|
||
let children: [AreaNode]
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case id
|
||
case code
|
||
case name
|
||
case children
|
||
}
|
||
|
||
/// 自定义解码,兼容 id/code 类型不稳定。
|
||
init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
code = try container.decodeLossyString(forKey: .code)
|
||
let rawId = try container.decodeLossyString(forKey: .id).trimmingCharacters(in: .whitespacesAndNewlines)
|
||
id = rawId.isEmpty ? code : rawId
|
||
name = try container.decodeLossyString(forKey: .name)
|
||
children = try container.decodeIfPresent([AreaNode].self, forKey: .children) ?? []
|
||
}
|
||
}
|
||
|
||
/// 提现信息响应实体,表示提现上限、手机号和银行卡摘要。
|
||
struct WithdrawInfoResponse: Decodable, Equatable {
|
||
let amountWithdrawable: String
|
||
let minWithdrawAmount: String
|
||
let maxSingleWithdrawAmount: String
|
||
let maxDailyWithdrawAmount: String
|
||
let userPhone: String
|
||
let bankCard: WithdrawBankCardInfo
|
||
let withdrawInfo: [String]
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case amountWithdrawable = "amount_withdrawable"
|
||
case minWithdrawAmount = "min_withdraw_amount"
|
||
case maxSingleWithdrawAmount = "max_single_withdraw_amount"
|
||
case maxDailyWithdrawAmount = "max_daily_withdraw_amount"
|
||
case userPhone = "user_phone"
|
||
case bankCard = "bank_card"
|
||
case withdrawInfo = "withdraw_info"
|
||
}
|
||
|
||
/// 自定义解码,兼容金额字段类型不稳定。
|
||
init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
amountWithdrawable = try container.decodeLossyString(forKey: .amountWithdrawable)
|
||
minWithdrawAmount = try container.decodeLossyString(forKey: .minWithdrawAmount)
|
||
maxSingleWithdrawAmount = try container.decodeLossyString(forKey: .maxSingleWithdrawAmount)
|
||
maxDailyWithdrawAmount = try container.decodeLossyString(forKey: .maxDailyWithdrawAmount)
|
||
userPhone = try container.decodeLossyString(forKey: .userPhone)
|
||
bankCard = try container.decodeIfPresent(WithdrawBankCardInfo.self, forKey: .bankCard) ?? WithdrawBankCardInfo()
|
||
withdrawInfo = try container.decodeIfPresent([String].self, forKey: .withdrawInfo) ?? []
|
||
}
|
||
}
|
||
|
||
/// 提现银行卡摘要实体,表示提现页面展示的银行卡信息。
|
||
struct WithdrawBankCardInfo: Decodable, Equatable {
|
||
let realName: String
|
||
let bankName: String
|
||
let cardNumber: String
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case realName = "real_name"
|
||
case bankName = "bank_name"
|
||
case cardNumber = "card_number"
|
||
}
|
||
|
||
/// 创建提现银行卡摘要,主要用于空状态兜底。
|
||
init(realName: String = "", bankName: String = "", cardNumber: String = "") {
|
||
self.realName = realName
|
||
self.bankName = bankName
|
||
self.cardNumber = cardNumber
|
||
}
|
||
|
||
/// 自定义解码,兼容字段类型不稳定。
|
||
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)
|
||
cardNumber = try container.decodeLossyString(forKey: .cardNumber)
|
||
}
|
||
}
|
||
|
||
/// 提现申请请求实体,表示金额和短信验证码。
|
||
struct WithdrawApplyRequest: Encodable {
|
||
let amount: String
|
||
let smsCode: String
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case amount
|
||
case smsCode = "sms_code"
|
||
}
|
||
}
|
||
|
||
/// 银行卡更新请求实体,表示银行卡资料、证件图片 URL 和验证码。
|
||
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"
|
||
}
|
||
}
|
||
|
||
/// 积分概览响应实体,表示当前积分可兑换状态。
|
||
struct PointOverviewResponse: Decodable, Equatable {
|
||
let totalPoints: Int
|
||
let availablePoints: Int
|
||
let withdrawnPoints: Int
|
||
let pendingPoints: Int
|
||
let time: String
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case totalPoints = "total_points"
|
||
case availablePoints = "available_points"
|
||
case withdrawnPoints = "withdrawn_points"
|
||
case pendingPoints = "pending_points"
|
||
case time
|
||
}
|
||
|
||
/// 创建积分概览响应实体,主要用于空状态和测试替身。
|
||
init(totalPoints: Int = 0, availablePoints: Int = 0, withdrawnPoints: Int = 0, pendingPoints: Int = 0, time: String = "") {
|
||
self.totalPoints = totalPoints
|
||
self.availablePoints = availablePoints
|
||
self.withdrawnPoints = withdrawnPoints
|
||
self.pendingPoints = pendingPoints
|
||
self.time = time
|
||
}
|
||
|
||
/// 自定义解码,兼容积分字段类型不稳定。
|
||
init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
totalPoints = try container.decodeLossyInt(forKey: .totalPoints) ?? 0
|
||
availablePoints = try container.decodeLossyInt(forKey: .availablePoints) ?? 0
|
||
withdrawnPoints = try container.decodeLossyInt(forKey: .withdrawnPoints) ?? 0
|
||
pendingPoints = try container.decodeLossyInt(forKey: .pendingPoints) ?? 0
|
||
time = try container.decodeLossyString(forKey: .time)
|
||
}
|
||
}
|
||
|
||
/// 积分兑换申请请求实体。
|
||
struct PointWithdrawApplyRequest: Encodable {
|
||
let points: Int
|
||
let remark: String
|
||
}
|
||
|
||
/// 积分兑换列表请求实体。
|
||
struct PointWithdrawListRequest: Encodable {
|
||
let status: Int?
|
||
let page: Int
|
||
let pageSize: Int
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case status
|
||
case page
|
||
case pageSize = "page_size"
|
||
}
|
||
}
|
||
|
||
/// 积分兑换列表响应实体。
|
||
struct PointWithdrawListResponse: Decodable, Equatable {
|
||
let total: Int
|
||
let list: [PointWithdrawItem]
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case total
|
||
case list
|
||
}
|
||
|
||
/// 创建积分兑换列表响应实体,主要用于测试替身。
|
||
init(total: Int = 0, list: [PointWithdrawItem] = []) {
|
||
self.total = total
|
||
self.list = list
|
||
}
|
||
|
||
/// 自定义解码,兼容字段缺失和类型不稳定。
|
||
init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
total = try container.decodeLossyInt(forKey: .total) ?? 0
|
||
list = try container.decodeIfPresent([PointWithdrawItem].self, forKey: .list) ?? []
|
||
}
|
||
}
|
||
|
||
/// 积分兑换记录实体。
|
||
struct PointWithdrawItem: Decodable, Equatable, Identifiable {
|
||
let id: Int
|
||
let points: Int
|
||
let amount: Double
|
||
let status: Int
|
||
let createdAt: String
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case id
|
||
case points
|
||
case amount
|
||
case status
|
||
case createdAt = "created_at"
|
||
}
|
||
|
||
/// 自定义解码,兼容字段类型不稳定。
|
||
init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
id = try container.decodeLossyInt(forKey: .id) ?? 0
|
||
points = try container.decodeLossyInt(forKey: .points) ?? 0
|
||
amount = try container.decodeLossyDouble(forKey: .amount) ?? 0
|
||
status = try container.decodeLossyInt(forKey: .status) ?? 0
|
||
createdAt = try container.decodeLossyString(forKey: .createdAt)
|
||
}
|
||
}
|
||
|
||
/// 钱包本地路由实体,表示钱包内二级页面。
|
||
enum WalletRoute: Hashable, Identifiable {
|
||
case withdrawApply
|
||
case bankCardSettings
|
||
case pointsRedemption
|
||
case realNameAuth
|
||
|
||
var id: String {
|
||
switch self {
|
||
case .withdrawApply: "withdrawApply"
|
||
case .bankCardSettings: "bankCardSettings"
|
||
case .pointsRedemption: "pointsRedemption"
|
||
case .realNameAuth: "realNameAuth"
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 提现资格决策实体,表示点击提现后的下一步页面或提示。
|
||
enum WalletWithdrawDecision: Equatable {
|
||
case route(WalletRoute)
|
||
case message(String)
|
||
}
|
||
|
||
/// 钱包筛选区间实体,表示收益明细查询时间范围。
|
||
enum WalletDateFilter: String, CaseIterable, Identifiable {
|
||
case last7
|
||
case last30
|
||
case last180
|
||
|
||
var id: String { rawValue }
|
||
|
||
/// 当前筛选区间的展示标题。
|
||
var title: String {
|
||
switch self {
|
||
case .last7: "近7日"
|
||
case .last30: "近30日"
|
||
case .last180: "近180日"
|
||
}
|
||
}
|
||
|
||
/// 根据当前日期计算开始和结束日期。
|
||
func range(today: Date = Date(), calendar: Calendar = .current) -> (start: String, end: String) {
|
||
let formatter = DateFormatter()
|
||
formatter.calendar = calendar
|
||
formatter.dateFormat = "yyyy-MM-dd"
|
||
let days: Int
|
||
switch self {
|
||
case .last7:
|
||
days = 6
|
||
case .last30:
|
||
days = 29
|
||
case .last180:
|
||
days = 179
|
||
}
|
||
let startDate = calendar.date(byAdding: .day, value: -days, to: today) ?? today
|
||
return (formatter.string(from: startDate), formatter.string(from: today))
|
||
}
|
||
}
|
||
|
||
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 value.truncatingRemainder(dividingBy: 1) == 0 ? String(Int(value)) : String(value)
|
||
}
|
||
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 decodeLossyDouble(forKey key: Key) throws -> Double? {
|
||
if let value = try decodeIfPresent(Double.self, forKey: key) {
|
||
return value
|
||
}
|
||
if let value = try decodeIfPresent(Int.self, forKey: key) {
|
||
return Double(value)
|
||
}
|
||
if let value = try decodeIfPresent(String.self, forKey: key) {
|
||
return Double(value.trimmingCharacters(in: .whitespacesAndNewlines))
|
||
}
|
||
return nil
|
||
}
|
||
}
|