新增支付与钱包模块,并接入首页路由
引入真实收款与钱包页面替换首页占位入口,通过 RootView 接入 API,并支持银行卡 OSS 上传及二维码保存到相册。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
604
suixinkan/Features/Wallet/Models/WalletModels.swift
Normal file
604
suixinkan/Features/Wallet/Models/WalletModels.swift
Normal file
@ -0,0 +1,604 @@
|
||||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user