Integrate高德 SDK with simulator-safe build flags, add map views for operating area and punch points, refactor main tabs and key feature screens to UIKit with Diffable lists, and document Swift concurrency defaults in AGENTS.md. Co-authored-by: Cursor <cursoragent@cursor.com>
211 lines
7.2 KiB
Swift
211 lines
7.2 KiB
Swift
//
|
||
// 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
|
||
}
|
||
}
|