// // 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, Hashable { 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) } /// 解析Amount数据。 private static func parseAmount(_ text: String) -> Double { let normalized = text.filter { "0123456789.-".contains($0) } return Double(normalized) ?? 0 } } /// 每日统计实体,表示某天订单数、客单价、退款和实收。 struct StatisticsDailyItem: Decodable, Identifiable, Equatable, Hashable { 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) } /// 解析Amount数据。 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 } }