Introduce real payment collection and wallet screens to replace home menu placeholders, wire APIs through RootView, and support bank card OSS uploads plus QR code saving to the photo library. Co-authored-by: Cursor <cursoragent@cursor.com>
605 lines
21 KiB
Swift
605 lines
21 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(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
|
||
}
|
||
}
|