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

AmountInputView.swift 8.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. //
  2. // AmountInputView.swift
  3. // LotteryTracker
  4. //
  5. // Created by aaa on 2026/1/22.
  6. //
  7. import SwiftUI
  8. struct AmountInputView: View {
  9. @Binding var betCount: Int
  10. @Binding var amountPerBet: String
  11. @FocusState private var isAmountFocused: Bool
  12. let totalAmount: Double
  13. // 常用注数选项
  14. private let betCountOptions = [1, 3, 5, 10, 20]
  15. // 常用金额选项
  16. private let amountOptions = [2.0, 5.0, 10.0, 20.0, 50.0]
  17. var body: some View {
  18. VStack(alignment: .leading, spacing: 20) {
  19. // 注数选择
  20. VStack(alignment: .leading, spacing: 12) {
  21. Text("注数")
  22. .font(.headline)
  23. // 注数选择器
  24. HStack {
  25. Stepper(value: $betCount, in: 1...100) {
  26. HStack {
  27. Text("\(betCount) 注")
  28. .font(.body)
  29. .foregroundColor(.primary)
  30. Spacer()
  31. Text("¥\(totalAmount, specifier: "%.2f")")
  32. .font(.body.bold())
  33. .foregroundColor(.blue)
  34. }
  35. }
  36. }
  37. // 快速选择按钮
  38. ScrollView(.horizontal, showsIndicators: false) {
  39. HStack(spacing: 10) {
  40. ForEach(betCountOptions, id: \.self) { count in
  41. Button(action: {
  42. withAnimation {
  43. betCount = count
  44. }
  45. }) {
  46. Text("\(count)注")
  47. .font(.system(size: 14))
  48. .padding(.horizontal, 12)
  49. .padding(.vertical, 6)
  50. .background(betCount == count ? Color.blue : Color.gray.opacity(0.1))
  51. .foregroundColor(betCount == count ? .white : .primary)
  52. .cornerRadius(8)
  53. }
  54. }
  55. }
  56. .padding(.horizontal, 2)
  57. }
  58. }
  59. Divider()
  60. // 每注金额
  61. VStack(alignment: .leading, spacing: 12) {
  62. Text("每注金额")
  63. .font(.headline)
  64. // 金额输入
  65. HStack {
  66. Text("¥")
  67. .font(.title3)
  68. .foregroundColor(.secondary)
  69. TextField("0.00", text: $amountPerBet)
  70. .font(.title3)
  71. .keyboardType(.decimalPad)
  72. .focused($isAmountFocused)
  73. .padding(.vertical, 8)
  74. .padding(.horizontal, 12)
  75. .background(Color(.systemGray6))
  76. .cornerRadius(8)
  77. .overlay(
  78. RoundedRectangle(cornerRadius: 8)
  79. .stroke(isAmountFocused ? Color.blue : Color.clear, lineWidth: 1)
  80. )
  81. .onChange(of: amountPerBet) { oldValue, newValue in
  82. // iOS 17 新API:接收 oldValue 和 newValue 两个参数
  83. // 限制输入为数字和小数点
  84. let filtered = newValue.filter { "0123456789.".contains($0) }
  85. if filtered != newValue {
  86. amountPerBet = filtered
  87. }
  88. // 限制小数点后最多两位
  89. if let dotIndex = filtered.firstIndex(of: ".") {
  90. let decimalPart = filtered[filtered.index(after: dotIndex)...]
  91. if decimalPart.count > 2 {
  92. amountPerBet = String(filtered.prefix(upTo: filtered.index(dotIndex, offsetBy: 3)))
  93. }
  94. }
  95. }
  96. }
  97. // 快速金额按钮
  98. ScrollView(.horizontal, showsIndicators: false) {
  99. HStack(spacing: 10) {
  100. ForEach(amountOptions, id: \.self) { amount in
  101. Button(action: {
  102. withAnimation {
  103. amountPerBet = String(format: "%.2f", amount)
  104. }
  105. }) {
  106. Text("¥\(amount, specifier: "%.0f")")
  107. .font(.system(size: 14))
  108. .padding(.horizontal, 12)
  109. .padding(.vertical, 6)
  110. .background(amountPerBet == String(format: "%.2f", amount) ? Color.green : Color.gray.opacity(0.1))
  111. .foregroundColor(amountPerBet == String(format: "%.2f", amount) ? .white : .primary)
  112. .cornerRadius(8)
  113. }
  114. }
  115. }
  116. .padding(.horizontal, 2)
  117. }
  118. }
  119. Divider()
  120. // 总计显示
  121. VStack(alignment: .leading, spacing: 8) {
  122. Text("总计")
  123. .font(.headline)
  124. HStack {
  125. VStack(alignment: .leading, spacing: 4) {
  126. Text("\(betCount) 注 × ¥\(Double(amountPerBet) ?? 0, specifier: "%.2f")/注")
  127. .font(.subheadline)
  128. .foregroundColor(.secondary)
  129. Text("¥\(totalAmount, specifier: "%.2f")")
  130. .font(.system(size: 32, weight: .bold))
  131. .foregroundColor(.blue)
  132. }
  133. Spacer()
  134. Image(systemName: "creditcard.fill")
  135. .font(.system(size: 36))
  136. .foregroundColor(.green.opacity(0.7))
  137. }
  138. .padding()
  139. .background(
  140. RoundedRectangle(cornerRadius: 12)
  141. .fill(Color.blue.opacity(0.05))
  142. .overlay(
  143. RoundedRectangle(cornerRadius: 12)
  144. .stroke(Color.blue.opacity(0.2), lineWidth: 1)
  145. )
  146. )
  147. }
  148. }
  149. .padding(.vertical, 8)
  150. }
  151. }
  152. // 预览
  153. struct AmountInputView_Previews: PreviewProvider {
  154. static var previews: some View {
  155. // 方法1:使用简单的 State 包装
  156. Group {
  157. // 预览1:默认状态
  158. AmountInputViewPreviewWrapper()
  159. // 预览2:大额状态
  160. AmountInputViewPreviewWrapper(betCount: 10, amountPerBet: "50.00")
  161. // 预览3:小数金额
  162. AmountInputViewPreviewWrapper(betCount: 3, amountPerBet: "2.50")
  163. }
  164. }
  165. }
  166. // 预览包装器
  167. struct AmountInputViewPreviewWrapper: View {
  168. @State private var betCount: Int
  169. @State private var amountPerBet: String
  170. init(betCount: Int = 3, amountPerBet: String = "2.00") {
  171. self._betCount = State(initialValue: betCount)
  172. self._amountPerBet = State(initialValue: amountPerBet)
  173. }
  174. var body: some View {
  175. AmountInputView(
  176. betCount: $betCount,
  177. amountPerBet: $amountPerBet,
  178. totalAmount: (Double(amountPerBet) ?? 0) * Double(betCount)
  179. )
  180. .padding()
  181. .previewLayout(.sizeThatFits)
  182. }
  183. }