98b7600797
对标安卓正式版、对接 app-server(8770)的 SwiftUI 客户端。iOS 无障碍受限、无比价核心,做自成一体:美团 CPS 导购 + 福利(签到/任务/金币)+ 查看(比价记录/省钱战绩)。 功能完整 M0-M5:脚手架、短信登录(JWT+Keychain+401 自动刷新)、首页券 feed 导购、比价记录+省钱战绩、福利、我的(资料/反馈/设置/注销)。 单 target、0 三方依赖、XcodeGen 生成工程;Debug/Release 双 plist 分流 ATS。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
92 lines
4.0 KiB
Swift
92 lines
4.0 KiB
Swift
import Foundation
|
|
|
|
// 比价记录(「我的比价记录」)。对齐后端 schemas/compare_record.py。
|
|
// 金额两种口径:记录级 *_cents 是分(Int);逐平台 price / couponSaved 是元(Double)。
|
|
// 来自 raw JSON 列,字段可能不全 → 容错性字段一律 optional。
|
|
|
|
struct ComparisonResultItemDTO: Codable, Hashable {
|
|
let platformId: String?
|
|
let platformName: String?
|
|
let `package`: String? // 安卓包名,跳转用;Swift 关键字,反引号转义
|
|
let price: Double? // 元;失败/未知为 null
|
|
let isSource: Bool?
|
|
let rank: Int?
|
|
let couponSaved: Double? // 纯红包优惠额(元);没用/没抠到为 null
|
|
}
|
|
|
|
struct ComparisonItemDTO: Codable, Hashable {
|
|
let name: String
|
|
var qty: Int = 1
|
|
let specs: [String]?
|
|
|
|
enum CodingKeys: String, CodingKey { case name, qty, specs }
|
|
|
|
init(from decoder: Decoder) throws {
|
|
let c = try decoder.container(keyedBy: CodingKeys.self)
|
|
name = try c.decode(String.self, forKey: .name)
|
|
qty = (try? c.decode(Int.self, forKey: .qty)) ?? 1
|
|
specs = try? c.decode([String].self, forKey: .specs)
|
|
}
|
|
}
|
|
|
|
struct ComparisonRecordDTO: Codable, Identifiable, Hashable {
|
|
let id: Int
|
|
let businessType: String
|
|
let traceId: String
|
|
let sourcePlatformId: String?
|
|
let sourcePlatformName: String?
|
|
let sourcePackage: String?
|
|
let sourcePriceCents: Int?
|
|
let bestPlatformId: String?
|
|
let bestPlatformName: String?
|
|
let bestPriceCents: Int?
|
|
let savedAmountCents: Int?
|
|
let isSourceBest: Bool?
|
|
let storeName: String?
|
|
let totalDishCount: Int?
|
|
let skippedDishCount: Int?
|
|
let status: String
|
|
let information: String?
|
|
var items: [ComparisonItemDTO] = []
|
|
var comparisonResults: [ComparisonResultItemDTO] = []
|
|
var skippedDishNames: [String] = []
|
|
let createdAt: String
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case id, businessType, traceId, sourcePlatformId, sourcePlatformName, sourcePackage
|
|
case sourcePriceCents, bestPlatformId, bestPlatformName, bestPriceCents, savedAmountCents
|
|
case isSourceBest, storeName, totalDishCount, skippedDishCount, status, information
|
|
case items, comparisonResults, skippedDishNames, createdAt
|
|
}
|
|
|
|
init(from decoder: Decoder) throws {
|
|
let c = try decoder.container(keyedBy: CodingKeys.self)
|
|
id = try c.decode(Int.self, forKey: .id)
|
|
businessType = (try? c.decode(String.self, forKey: .businessType)) ?? "food"
|
|
traceId = (try? c.decode(String.self, forKey: .traceId)) ?? ""
|
|
sourcePlatformId = try? c.decode(String.self, forKey: .sourcePlatformId)
|
|
sourcePlatformName = try? c.decode(String.self, forKey: .sourcePlatformName)
|
|
sourcePackage = try? c.decode(String.self, forKey: .sourcePackage)
|
|
sourcePriceCents = try? c.decode(Int.self, forKey: .sourcePriceCents)
|
|
bestPlatformId = try? c.decode(String.self, forKey: .bestPlatformId)
|
|
bestPlatformName = try? c.decode(String.self, forKey: .bestPlatformName)
|
|
bestPriceCents = try? c.decode(Int.self, forKey: .bestPriceCents)
|
|
savedAmountCents = try? c.decode(Int.self, forKey: .savedAmountCents)
|
|
isSourceBest = try? c.decode(Bool.self, forKey: .isSourceBest)
|
|
storeName = try? c.decode(String.self, forKey: .storeName)
|
|
totalDishCount = try? c.decode(Int.self, forKey: .totalDishCount)
|
|
skippedDishCount = try? c.decode(Int.self, forKey: .skippedDishCount)
|
|
status = (try? c.decode(String.self, forKey: .status)) ?? "unknown"
|
|
information = try? c.decode(String.self, forKey: .information)
|
|
items = (try? c.decode([ComparisonItemDTO].self, forKey: .items)) ?? []
|
|
comparisonResults = (try? c.decode([ComparisonResultItemDTO].self, forKey: .comparisonResults)) ?? []
|
|
skippedDishNames = (try? c.decode([String].self, forKey: .skippedDishNames)) ?? []
|
|
createdAt = (try? c.decode(String.self, forKey: .createdAt)) ?? ""
|
|
}
|
|
}
|
|
|
|
struct ComparisonRecordPageDTO: Codable {
|
|
let items: [ComparisonRecordDTO]
|
|
let nextCursor: Int?
|
|
}
|