Sync migrated iOS modules
This commit is contained in:
206
suixinkan/Features/Statistics/Models/StatisticsModels.swift
Normal file
206
suixinkan/Features/Statistics/Models/StatisticsModels.swift
Normal file
@ -0,0 +1,206 @@
|
||||
//
|
||||
// StatisticsModels.swift
|
||||
// suixinkan
|
||||
//
|
||||
// Created by Codex on 2026/6/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// 数据统计时间段实体,表示看板顶部的快捷日期范围。
|
||||
enum StatisticsPeriod: String, CaseIterable, Identifiable {
|
||||
case today = "今日"
|
||||
case yesterday = "昨日"
|
||||
case sevenDays = "7日"
|
||||
case thisMonth = "本月"
|
||||
|
||||
var id: String { rawValue }
|
||||
|
||||
/// 后端汇总接口需要的范围标识。
|
||||
var apiRange: String {
|
||||
switch self {
|
||||
case .today:
|
||||
return "1"
|
||||
case .yesterday:
|
||||
return "2"
|
||||
case .sevenDays:
|
||||
return "3"
|
||||
case .thisMonth:
|
||||
return "4"
|
||||
}
|
||||
}
|
||||
|
||||
/// 当前时间段对应的本地日期区间。
|
||||
var interval: (start: Date, end: Date) {
|
||||
let calendar = Calendar.current
|
||||
let today = calendar.startOfDay(for: Date())
|
||||
switch self {
|
||||
case .today:
|
||||
return (today, today)
|
||||
case .yesterday:
|
||||
let day = calendar.date(byAdding: .day, value: -1, to: today) ?? today
|
||||
return (day, day)
|
||||
case .sevenDays:
|
||||
return (calendar.date(byAdding: .day, value: -6, to: today) ?? today, today)
|
||||
case .thisMonth:
|
||||
let components = calendar.dateComponents([.year, .month], from: today)
|
||||
return (calendar.date(from: components) ?? today, today)
|
||||
}
|
||||
}
|
||||
|
||||
/// 页面展示的时间范围文案。
|
||||
var selectedTimeText: String {
|
||||
let dates = interval
|
||||
return "\(Self.uiDateFormatter.string(from: dates.start))至\(Self.uiDateFormatter.string(from: dates.end))"
|
||||
}
|
||||
|
||||
/// 后端列表接口开始日期。
|
||||
var apiStartTime: String {
|
||||
Self.apiDateFormatter.string(from: interval.start)
|
||||
}
|
||||
|
||||
/// 后端列表接口结束日期。
|
||||
var apiEndTime: String {
|
||||
Self.apiDateFormatter.string(from: interval.end)
|
||||
}
|
||||
|
||||
private static let uiDateFormatter: DateFormatter = {
|
||||
let formatter = DateFormatter()
|
||||
formatter.locale = Locale(identifier: "zh_CN")
|
||||
formatter.dateFormat = "yyyy/MM/dd"
|
||||
return formatter
|
||||
}()
|
||||
|
||||
private static let apiDateFormatter: DateFormatter = {
|
||||
let formatter = DateFormatter()
|
||||
formatter.locale = Locale(identifier: "en_US_POSIX")
|
||||
formatter.dateFormat = "yyyy-MM-dd"
|
||||
return formatter
|
||||
}()
|
||||
}
|
||||
|
||||
/// 数据统计汇总实体,表示订单金额、数量、客单价、实收和退款。
|
||||
struct StatisticsSummaryResponse: Decodable, Equatable {
|
||||
let orderAmountSum: String
|
||||
let orderCount: Int
|
||||
let orderPriceAverage: String
|
||||
let receivedAmountSum: String
|
||||
let refundAmountSum: String
|
||||
|
||||
var orderAmountValue: Double { Self.parseAmount(orderAmountSum) }
|
||||
var orderPriceAverageValue: Double { Self.parseAmount(orderPriceAverage) }
|
||||
var receivedAmountValue: Double { Self.parseAmount(receivedAmountSum) }
|
||||
var refundAmountValue: Double { Self.parseAmount(refundAmountSum) }
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case orderAmountSum = "order_amount_sum"
|
||||
case orderCount = "order_count"
|
||||
case orderPriceAverage = "order_price_avg"
|
||||
case receivedAmountSum = "received_amount_sum"
|
||||
case refundAmountSum = "refund_amount_sum"
|
||||
}
|
||||
|
||||
/// 创建统计汇总实体,主要用于初始状态和测试。
|
||||
init(
|
||||
orderAmountSum: String = "",
|
||||
orderCount: Int = 0,
|
||||
orderPriceAverage: String = "",
|
||||
receivedAmountSum: String = "",
|
||||
refundAmountSum: String = ""
|
||||
) {
|
||||
self.orderAmountSum = orderAmountSum
|
||||
self.orderCount = orderCount
|
||||
self.orderPriceAverage = orderPriceAverage
|
||||
self.receivedAmountSum = receivedAmountSum
|
||||
self.refundAmountSum = refundAmountSum
|
||||
}
|
||||
|
||||
/// 宽松解码统计汇总字段。
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
orderAmountSum = try container.decodeLossyString(forKey: .orderAmountSum)
|
||||
orderCount = try container.decodeLossyInt(forKey: .orderCount) ?? 0
|
||||
orderPriceAverage = try container.decodeLossyString(forKey: .orderPriceAverage)
|
||||
receivedAmountSum = try container.decodeLossyString(forKey: .receivedAmountSum)
|
||||
refundAmountSum = try container.decodeLossyString(forKey: .refundAmountSum)
|
||||
}
|
||||
|
||||
private static func parseAmount(_ text: String) -> Double {
|
||||
let normalized = text.filter { "0123456789.-".contains($0) }
|
||||
return Double(normalized) ?? 0
|
||||
}
|
||||
}
|
||||
|
||||
/// 每日统计实体,表示某天订单数、客单价、退款和实收。
|
||||
struct StatisticsDailyItem: Decodable, Identifiable, Equatable {
|
||||
var id: String { date }
|
||||
|
||||
let date: String
|
||||
let orderCount: Int
|
||||
let orderPrice: String
|
||||
let refund: String
|
||||
let received: String
|
||||
|
||||
var orderPriceValue: Double { Self.parseAmount(orderPrice) }
|
||||
var refundValue: Double { Self.parseAmount(refund) }
|
||||
var receivedValue: Double { Self.parseAmount(received) }
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case date
|
||||
case orderCount = "order_count"
|
||||
case orderPrice = "order_price"
|
||||
case refund
|
||||
case received
|
||||
}
|
||||
|
||||
/// 宽松解码每日统计字段。
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
date = try container.decodeLossyString(forKey: .date)
|
||||
orderCount = try container.decodeLossyInt(forKey: .orderCount) ?? 0
|
||||
orderPrice = try container.decodeLossyString(forKey: .orderPrice)
|
||||
refund = try container.decodeLossyString(forKey: .refund)
|
||||
received = try container.decodeLossyString(forKey: .received)
|
||||
}
|
||||
|
||||
private static func parseAmount(_ text: String) -> Double {
|
||||
let normalized = text.filter { "0123456789.-".contains($0) }
|
||||
return Double(normalized) ?? 0
|
||||
}
|
||||
}
|
||||
|
||||
private extension KeyedDecodingContainer {
|
||||
/// 将任意常见 JSON 值宽松解码成字符串。
|
||||
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)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
/// 将 Int、Double 或数字字符串宽松解码成整数。
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user