ios应用,记录购买彩票,统计盈亏

AddTicketViewModel.swift 7.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. //
  2. // AddTicketViewModel.swift
  3. // LotteryTracker
  4. //
  5. // Created by aaa on 2026/1/22.
  6. //
  7. import Foundation
  8. import Combine
  9. internal import CoreData
  10. class AddTicketViewModel: ObservableObject {
  11. // 输入字段
  12. @Published var selectedType: LotteryType = .doubleColorBall
  13. @Published var numbers: String = ""
  14. @Published var betCount: Int = 1
  15. @Published var amountPerBet: Int = 2
  16. // 购买日期(默认今天)
  17. @Published var purchaseDate: Date = Date()
  18. // 开奖日期根据彩票类型自动计算
  19. var drawDate: Date {
  20. calculateDrawDate()
  21. }
  22. // 验证状态
  23. @Published var isValid: Bool = false
  24. @Published var errorMessage: String = ""
  25. // 计算属性
  26. var totalAmount: Float {
  27. let amount = amountPerBet
  28. return Float(amount * betCount)
  29. }
  30. var formattedPurchaseDate: String {
  31. let formatter = DateFormatter()
  32. formatter.dateFormat = "yyyy年MM月dd日"
  33. return formatter.string(from: purchaseDate)
  34. }
  35. var formattedDrawDate: String {
  36. let formatter = DateFormatter()
  37. formatter.dateFormat = "yyyy年MM月dd日"
  38. return formatter.string(from: drawDate)
  39. }
  40. private var cancellables = Set<AnyCancellable>()
  41. init() {
  42. setupValidation()
  43. }
  44. // 计算开奖日期(根据彩票类型)
  45. private func calculateDrawDate() -> Date {
  46. let calendar = Calendar.current
  47. switch selectedType {
  48. case .doubleColorBall:
  49. // 双色球:每周二、四、日开奖
  50. return nextDrawDate(for: [2, 4, 7], from: purchaseDate)
  51. case .superLotto:
  52. // 大乐透:每周一、三、六开奖
  53. return nextDrawDate(for: [1, 3, 6], from: purchaseDate)
  54. case .happy8:
  55. // 快乐八:每天开奖
  56. return calendar.date(byAdding: .day, value: 1, to: purchaseDate) ?? purchaseDate
  57. case .sevenStar:
  58. // 七星彩:每周二、五、日开奖
  59. return nextDrawDate(for: [2, 5, 7], from: purchaseDate)
  60. case .other:
  61. // 其他:默认3天后
  62. return calendar.date(byAdding: .day, value: 3, to: purchaseDate) ?? purchaseDate
  63. }
  64. }
  65. // 计算下一个开奖日期
  66. private func nextDrawDate(for weekdays: [Int], from date: Date) -> Date {
  67. let calendar = Calendar.current
  68. let currentWeekday = calendar.component(.weekday, from: date)
  69. // 查找下一个开奖日
  70. for weekday in weekdays.sorted() {
  71. if weekday >= currentWeekday {
  72. let daysToAdd = weekday - currentWeekday
  73. return calendar.date(byAdding: .day, value: daysToAdd, to: date) ?? date
  74. }
  75. }
  76. // 如果本周没有,取下一周的第一个开奖日
  77. let daysToAdd = (7 - currentWeekday) + weekdays.min()!
  78. return calendar.date(byAdding: .day, value: daysToAdd, to: date) ?? date
  79. }
  80. // 设置表单验证
  81. private func setupValidation() {
  82. // 只监听 numbers 的变化
  83. $numbers
  84. .map { numbers in
  85. // 验证 numbers 的格式和数量
  86. return self.validateNumbers(numbers)
  87. }
  88. .assign(to: \.isValid, on: self)
  89. .store(in: &cancellables)
  90. }
  91. private func validateNumbers(_ numbers: String) -> Bool {
  92. // 1. 非空检查
  93. guard !numbers.isEmpty else {
  94. errorMessage = "请填写完整信息"
  95. return false
  96. }
  97. // 2. 根据彩票类型验证
  98. switch selectedType {
  99. case .doubleColorBall:
  100. return validateDoubleColorBall(numbers)
  101. case .superLotto:
  102. return validateSuperLotto(numbers)
  103. default:
  104. // 其他彩票类型
  105. return validateDefault(numbers)
  106. }
  107. }
  108. // 双色球验证方法
  109. private func validateDoubleColorBall(_ numbers: String) -> Bool {
  110. // 双色球格式: "01 05 12 23 28 33 + 09"
  111. let components = numbers.split(separator: "+")
  112. guard components.count == 2 else { return false }
  113. let redPart = components[0].trimmingCharacters(in: .whitespaces)
  114. let bluePart = components[1].trimmingCharacters(in: .whitespaces)
  115. let redNumbers = redPart.split(separator: " ").compactMap { Int($0) }
  116. let blueNumbers = bluePart.split(separator: " ").compactMap { Int($0) }
  117. // 红球6个,蓝球1个
  118. if (redNumbers.count == 6 && blueNumbers.count == 1) {
  119. errorMessage = ""
  120. return true
  121. } else {
  122. errorMessage = "双色球选择个数不正确"
  123. return false
  124. }
  125. }
  126. // 大乐透验证方法
  127. private func validateSuperLotto(_ numbers: String) -> Bool {
  128. // 大乐透格式: "01 05 12 23 28 + 03 09"
  129. let components = numbers.split(separator: "+")
  130. guard components.count == 2 else { return false }
  131. let frontPart = components[0].trimmingCharacters(in: .whitespaces)
  132. let backPart = components[1].trimmingCharacters(in: .whitespaces)
  133. let frontNumbers = frontPart.split(separator: " ").compactMap { Int($0) }
  134. let blueNumbers = backPart.split(separator: " ").compactMap { Int($0) }
  135. // 前区5个,后区2个
  136. if (frontNumbers.count == 5 && blueNumbers.count == 2) {
  137. errorMessage = ""
  138. return true
  139. } else {
  140. errorMessage = "大乐透选择个数不正确"
  141. return false
  142. }
  143. }
  144. private func validateDefault(_ numbers: String) -> Bool {
  145. if (!numbers.isEmpty) {
  146. errorMessage = ""
  147. return true
  148. } else {
  149. errorMessage = "选择号码不能为空"
  150. return false
  151. }
  152. }
  153. // .....
  154. // 重置表单
  155. func resetForm() {
  156. selectedType = .doubleColorBall
  157. numbers = ""
  158. betCount = 1
  159. amountPerBet = 2
  160. purchaseDate = Date()
  161. errorMessage = ""
  162. }
  163. // 保存彩票记录
  164. func saveTicket(context: NSManagedObjectContext) -> Bool {
  165. guard isValid else {
  166. return false
  167. }
  168. let ticket = LotteryTicket(context: context)
  169. ticket.id = UUID()
  170. ticket.lotteryType = selectedType
  171. ticket.numbers = numbers
  172. ticket.betCount = Int16(betCount)
  173. ticket.amount = totalAmount
  174. ticket.date = purchaseDate // 使用用户选择的购买日期
  175. ticket.drawDate = drawDate // 自动计算的开奖日期
  176. ticket.ticketStatus = .pending
  177. ticket.prizeAmount = 0
  178. do {
  179. try context.save()
  180. print("✅ 彩票记录保存成功")
  181. print(" 类型: \(selectedType.rawValue)")
  182. print(" 注数: \(betCount)")
  183. print(" 金额: " + Formatters.formatCurrency(totalAmount))
  184. print(" 购买: \(formattedPurchaseDate)")
  185. print(" 开奖: \(formattedDrawDate)")
  186. return true
  187. } catch {
  188. errorMessage = "保存失败: \(error.localizedDescription)"
  189. print("❌ 保存失败: \(error)")
  190. context.delete(ticket)
  191. return false
  192. }
  193. }
  194. // 快速填充测试数据
  195. // func fillSampleData() {
  196. // selectedType = LotteryType.allCases.randomElement() ?? .doubleColorBall
  197. // numbers = generateSampleNumbers(for: selectedType)
  198. // betCount = Int.random(in: 1...5)
  199. // amountPerBet = String(format: "%.2f", Double.random(in: 2.0...10.0))
  200. // purchaseDate = Date().addingTimeInterval(Double.random(in: -30...0) * 24 * 3600)
  201. // }
  202. }