Ver código fonte

修改主页彩票详情界面可以自定义中奖

afan 1 dia atrás
pai
commit
2d7a86127d

+ 2
- 3
LotteryTracker.xcodeproj/project.pbxproj Ver arquivo

@@ -86,11 +86,10 @@
86 86
 				};
87 87
 			};
88 88
 			buildConfigurationList = 00CDA5D12F212364003E59B4 /* Build configuration list for PBXProject "LotteryTracker" */;
89
-			developmentRegion = en;
89
+			developmentRegion = "zh-Hans";
90 90
 			hasScannedForEncodings = 0;
91 91
 			knownRegions = (
92
-				en,
93
-				Base,
92
+				"zh-Hans",
94 93
 			);
95 94
 			mainGroup = 00CDA5CD2F212364003E59B4;
96 95
 			minimizedProjectReferenceProxies = 1;

+ 6
- 6
LotteryTracker/App/LotteryTrackerApp.swift Ver arquivo

@@ -17,18 +17,18 @@ struct LotteryTrackerApp: App {
17 17
                 .onAppear {
18 18
                     setupAppearance()
19 19
                     // App启动时检查一次
20
-                    if appState.autoCheckDraw {
21
-                        checkDraws()
22
-                    }
20
+//                    if appState.autoCheckDraw {
21
+//                        checkDraws()
22
+//                    }
23 23
                 }
24 24
                 .onChange(of: scenePhase) { oldPhase, newPhase in
25 25
                     // 只记录,不修改任何状态
26 26
                     switch newPhase {
27 27
                     case .active:
28 28
                         print("App变为活跃状态")
29
-                        if appState.autoCheckDraw {
30
-                            checkDraws()
31
-                        }
29
+//                        if appState.autoCheckDraw {
30
+//                            checkDraws()
31
+//                        }
32 32
                     case .inactive:
33 33
                         print("App变为不活跃状态")
34 34
                     case .background:

+ 1
- 1
LotteryTracker/Models/LotteryTicket+Extensions.swift Ver arquivo

@@ -87,7 +87,7 @@ extension LotteryTicket {
87 87
     // 计算属性:格式化日期
88 88
     var formattedDate: String {
89 89
         let formatter = DateFormatter()
90
-        formatter.dateFormat = "yyyy-MM-dd HH:mm"
90
+        formatter.dateFormat = "yyyy-MM-dd"
91 91
         return formatter.string(from: date ?? Date())
92 92
     }
93 93
     

+ 1
- 1
LotteryTracker/Views/AddTicket/HappyEightCompoents.swift Ver arquivo

@@ -116,7 +116,7 @@ struct HappyEightSection: View {
116 116
                             Text(selectionType.string)
117 117
                                 .font(.subheadline)
118 118
                                 .fontWeight(.semibold)
119
-                                .foregroundColor(Color(.black))
119
+                                .foregroundColor(.primary)
120 120
                         )
121 121
                 }
122 122
                 

+ 2
- 1
LotteryTracker/Views/AddTicket/LotteryTypePicker.swift Ver arquivo

@@ -27,7 +27,8 @@ struct LotteryTypePicker: View {
27 27
             
28 28
             ScrollView(.horizontal, showsIndicators: false) {
29 29
                 HStack(spacing: 12) {
30
-                    ForEach(LotteryType.allCases) { type in
30
+                    ForEach([LotteryType.doubleColorBall, LotteryType.superLotto, LotteryType.happy8]) { type in
31
+//                    ForEach(LotteryType.allCases) { type in
31 32
                         LotteryTypeCard(
32 33
                             type: type,
33 34
                             description: typeDescriptions[type] ?? "",

+ 4
- 4
LotteryTracker/Views/AddTicket/NumberSelectionGrid.swift Ver arquivo

@@ -75,10 +75,10 @@ struct NumberSelectionGrid: View {
75 75
                 // 触觉反馈
76 76
                 let generator = UIImpactFeedbackGenerator(style: .light)
77 77
                 generator.impactOccurred()
78
-            } else {
79
-                // 已达上限提示
80
-                let generator = UINotificationFeedbackGenerator()
81
-                generator.notificationOccurred(.warning)
78
+//            } else {
79
+//                // 已达上限提示
80
+//                let generator = UINotificationFeedbackGenerator()
81
+//                generator.notificationOccurred(.warning)
82 82
             }
83 83
         }
84 84
     }

+ 520
- 92
LotteryTracker/Views/Home/TicketDetailView.swift Ver arquivo

@@ -9,88 +9,424 @@ import SwiftUI
9 9
 internal import CoreData
10 10
 
11 11
 struct TicketDetailView: View {
12
-    let ticket: LotteryTicket
12
+    @ObservedObject var ticket: LotteryTicket
13 13
     @Environment(\.dismiss) private var dismiss
14 14
     @Environment(\.managedObjectContext) private var context
15 15
     
16
+    // 状态管理
17
+    @State private var showingPrizeEditor = false
18
+    @State private var isWinning: Bool = false
19
+    @State private var prizeAmount: Float = 0
20
+    @State private var animateBackground = false
21
+    @State private var canResetToPending: Bool = false  // 缓存状态
22
+    
23
+    // 动画状态
24
+    @State private var numberScale: [Bool] = []
25
+    
16 26
     var body: some View {
17
-        ScrollView {
18
-            VStack(spacing: 20) {
19
-                // 顶部卡片
20
-                VStack(spacing: 16) {
21
-                    Image(systemName: "ticket.fill")
22
-                        .font(.system(size: 60))
23
-                        .foregroundColor(ticketColor)
24
-                    
25
-                    Text(ticket.type!)
26
-                        .font(.largeTitle)
27
-                        .fontWeight(.bold)
28
-                    
29
-                    Text(Formatters.formatCurrency(ticket.amount))
30
-                        .font(.title2)
31
-                        .foregroundColor(.secondary)
32
-                }
33
-                .padding()
34
-                .frame(maxWidth: .infinity)
35
-                .background(ticketColor.opacity(0.1))
36
-                .cornerRadius(20)
37
-                .padding()
38
-                
39
-//                // 号码信息
40
-                VStack(alignment: .leading, spacing: 12) {
41
-                    Text("彩票号码")
42
-                        .font(.headline)
27
+        ZStack {
28
+            // 动态背景
29
+            LinearGradient(
30
+                gradient: Gradient(colors: [
31
+                    ticketColor.opacity(0.08),
32
+                    .clear,
33
+                    ticketColor.opacity(0.03)
34
+                ]),
35
+                startPoint: .topLeading,
36
+                endPoint: .bottomTrailing
37
+            )
38
+            .ignoresSafeArea()
39
+            .hueRotation(.degrees(animateBackground ? 10 : 0))
40
+            .animation(
41
+                Animation.easeInOut(duration: 4).repeatForever(autoreverses: true),
42
+                value: animateBackground
43
+            )
44
+            .onAppear { animateBackground = true }
45
+            
46
+            ScrollView {
47
+                VStack(spacing: 24) {
48
+                    // 1. 顶部状态卡片
49
+                    VStack(spacing: 20) {
50
+                        // 票种图标与状态
51
+                        HStack {
52
+                            VStack(alignment: .leading, spacing: 4) {
53
+                                Text(ticket.type ?? "彩票")
54
+                                    .font(.title2)
55
+                                    .fontWeight(.bold)
56
+                                    .foregroundColor(.primary)
57
+                                
58
+                                Text(ticketStatusText)
59
+                                    .font(.caption)
60
+                                    .fontWeight(.medium)
61
+                                    .padding(.horizontal, 8)
62
+                                    .padding(.vertical, 4)
63
+                                    .background(statusColor.opacity(0.2))
64
+                                    .foregroundColor(statusColor)
65
+                                    .clipShape(Capsule())
66
+                            }
67
+                            
68
+                            Spacer()
69
+                            
70
+                            ZStack {
71
+                                Circle()
72
+                                    .fill(ticketColor.opacity(0.15))
73
+                                    .frame(width: 70, height: 70)
74
+                                
75
+                                Image(systemName: ticketIconName)
76
+                                    .font(.system(size: 32))
77
+                                    .foregroundColor(ticketColor)
78
+                                    .shadow(color: ticketColor.opacity(0.3), radius: 4)
79
+                            }
80
+                        }
81
+                        
82
+                        // 金额显示
83
+                        VStack(spacing: 8) {
84
+                            Text("投注金额")
85
+                                .font(.caption)
86
+                                .foregroundColor(.secondary)
87
+                            
88
+                            Text(Formatters.formatCurrency(ticket.amount))
89
+                                .font(.system(size: 36, weight: .heavy, design: .rounded))
90
+                                .foregroundColor(ticketColor)
91
+                        }
92
+                        .padding(.vertical, 12)
93
+                        .frame(maxWidth: .infinity)
94
+                        .background(
95
+                            RoundedRectangle(cornerRadius: 16)
96
+                                .fill(ticketColor.opacity(0.06))
97
+                                .overlay(
98
+                                    RoundedRectangle(cornerRadius: 16)
99
+                                        .stroke(ticketColor.opacity(0.2), lineWidth: 1)
100
+                                )
101
+                        )
102
+                    }
103
+                    .padding()
104
+                    .background(
105
+                        RoundedRectangle(cornerRadius: 20)
106
+                            .fill(.ultraThinMaterial)
107
+                            .shadow(color: .black.opacity(0.05), radius: 10, y: 5)
108
+                    )
109
+                    .padding(.horizontal)
43 110
                     
44
-                    LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 5), spacing: 10) {
45
-                        ForEach(ticket.numberArray, id: \.self) { number in
46
-                            Text("\(number)")
47
-                                .font(.title3)
48
-                                .fontWeight(.semibold)
49
-                                .frame(width: 50, height: 50)
50
-                                .background(Color.blue)
51
-                                .foregroundColor(.white)
52
-                                .clipShape(Circle())
111
+                    // 2. 彩票号码 - 优化显示
112
+                    VStack(alignment: .leading, spacing: 16) {
113
+                        HStack {
114
+                            Text("投注号码")
115
+                                .font(.headline)
116
+                            
117
+                            Spacer()
118
+                            
119
+                            Text("共 \(ticket.numberArray.count) 个号码")
120
+                                .font(.caption)
121
+                                .foregroundColor(.secondary)
122
+                        }
123
+                        
124
+                        // 智能网格布局:根据数量自动调整列数
125
+                        LazyVGrid(
126
+                            columns: Array(
127
+                                repeating: GridItem(.flexible(), spacing: 10),
128
+                                count: min(ticket.numberArray.count, 6) > 5 ? 6 : 5
129
+                            ),
130
+                            spacing: 12
131
+                        ) {
132
+                            ForEach(Array(ticket.numberArray.enumerated()), id: \.offset) { index, number in
133
+                                NumberBall(number: number, index: index, color: ticketColor)
134
+                            }
53 135
                         }
54 136
                     }
55
-                }
56
-                .padding()
57
-                .background(Color(.systemBackground))
58
-                .cornerRadius(15)
59
-                .shadow(color: .black.opacity(0.05), radius: 5)
60
-                .padding(.horizontal)
61
-                
62
-                // 日期信息
63
-                VStack(alignment: .leading, spacing: 8) {
64
-                    InfoRow(title: "购买时间", value: ticket.formattedDate)
65
-                    InfoRow(title: "开奖时间", value: ticket.formattedDrawDate)
66
-                    InfoRow(title: "状态", value: (ticket.ticketStatus != .pending) ? "已开奖" : "待开奖")
137
+                    .padding()
138
+                    .background(
139
+                        RoundedRectangle(cornerRadius: 20)
140
+                            .fill(.ultraThinMaterial)
141
+                            .shadow(color: .black.opacity(0.05), radius: 10, y: 5)
142
+                    )
143
+                    .padding(.horizontal)
67 144
                     
68
-                    if ticket.ticketStatus != .pending {
145
+                    // 3. 信息卡片
146
+                    VStack(spacing: 0) {
69 147
                         InfoRow(
70
-                            title: "中奖金额",
71
-                            value: Formatters.formatCurrency(ticket.prizeAmount)
148
+                            icon: "calendar",
149
+                            title: "购买时间",
150
+                            value: ticket.formattedDate,
151
+                            iconColor: .blue
72 152
                         )
73
-                        .foregroundColor(ticket.profit > 0 ? .green : .red)
153
+                        
154
+                        Divider()
155
+                            .padding(.horizontal)
156
+                        
157
+                        InfoRow(
158
+                            icon: "clock",
159
+                            title: "开奖时间",
160
+                            value: ticket.formattedDrawDate,
161
+                            iconColor: .orange
162
+                        )
163
+                        
164
+                        if ticket.ticketStatus != .pending {
165
+                            Divider()
166
+                                .padding(.horizontal)
167
+                            
168
+                            InfoRow(
169
+                                icon: "trophy",
170
+                                title: "中奖状态",
171
+                                value: ticket.ticketStatus == .won ? "已中奖" : "未中奖",
172
+                                iconColor: ticket.ticketStatus == .won ? .yellow : .gray
173
+                            )
174
+                            
175
+                            if ticket.ticketStatus == .won {
176
+                                Divider()
177
+                                    .padding(.horizontal)
178
+                                
179
+                                InfoRow(
180
+                                    icon: "banknote",
181
+                                    title: "中奖金额",
182
+                                    value: Formatters.formatCurrency(ticket.prizeAmount),
183
+                                    iconColor: .green
184
+                                )
185
+                                .foregroundColor(.green)
186
+                            }
187
+                        }
188
+                    }
189
+                    .padding()
190
+                    .background(
191
+                        RoundedRectangle(cornerRadius: 20)
192
+                            .fill(.ultraThinMaterial)
193
+                            .shadow(color: .black.opacity(0.05), radius: 10, y: 5)
194
+                    )
195
+                    .padding(.horizontal)
196
+                    
197
+                    // 4. 中奖管理按钮
198
+                    if ticket.ticketStatus == .pending {
199
+                        VStack(spacing: 16) {
200
+                            Text("开奖结果管理")
201
+                                .font(.headline)
202
+                                .frame(maxWidth: .infinity, alignment: .leading)
203
+                            
204
+                            HStack(spacing: 12) {
205
+                                 Button(action: { updateWinningStatus(isWinning: false) }) {
206
+                                     HStack {
207
+                                         Image(systemName: "xmark.circle")
208
+                                         Text("未中奖")
209
+                                     }
210
+                                     .frame(maxWidth: .infinity)
211
+                                     .padding(.vertical, 14)
212
+                                     .background(Color.gray.opacity(0.15))
213
+                                     .foregroundColor(.secondary)
214
+                                     .clipShape(RoundedRectangle(cornerRadius: 14))
215
+                                 }
216
+                                
217
+                                Button(action: { showingPrizeEditor = true }) {
218
+                                    HStack {
219
+                                        Image(systemName: "trophy")
220
+                                        Text("设置中奖")
221
+                                    }
222
+                                    .frame(maxWidth: .infinity)
223
+                                    .padding(.vertical, 14)
224
+                                    .background(ticketColor.opacity(0.15))
225
+                                    .foregroundColor(ticketColor)
226
+                                    .clipShape(RoundedRectangle(cornerRadius: 14))
227
+                                }
228
+                            }
229
+                        }
230
+                        .padding()
231
+                        .background(
232
+                            RoundedRectangle(cornerRadius: 20)
233
+                                .fill(.ultraThinMaterial)
234
+                                .shadow(color: .black.opacity(0.05), radius: 10, y: 5)
235
+                        )
236
+                        .padding(.horizontal)
74 237
                     }
75 238
                 }
76
-                .padding()
77
-                .background(Color(.systemBackground))
78
-                .cornerRadius(15)
79
-                .shadow(color: .black.opacity(0.05), radius: 5)
80
-                .padding(.horizontal)
239
+                .padding(.vertical, 24)
81 240
             }
82
-            .padding(.vertical)
83 241
         }
84 242
         .navigationTitle("彩票详情")
85 243
         .navigationBarTitleDisplayMode(.inline)
86 244
         .toolbar {
87 245
             ToolbarItem(placement: .navigationBarTrailing) {
88
-                Button("删除") {
89
-                    deleteTicket()
246
+                Menu {
247
+                    if canResetToPending {
248
+                        Button(action: { resetToPending() }) {
249
+                            Label("重置为待开奖", systemImage: "arrow.uturn.backward")
250
+                        }
251
+                    }
252
+                    
253
+                    Button(role: .destructive, action: deleteTicket) {
254
+                        Label("删除彩票", systemImage: "trash")
255
+                    }
256
+                } label: {
257
+                    Image(systemName: "ellipsis.circle")
258
+                        .font(.system(size: 20))
90 259
                 }
91
-                .foregroundColor(.red)
92 260
             }
93 261
         }
262
+        .sheet(isPresented: $showingPrizeEditor) {
263
+            PrizeEditorView(
264
+                isPresented: $showingPrizeEditor,
265
+                prizeAmount: $prizeAmount,
266
+                onSave: { saveWinningStatus() }
267
+            )
268
+        }
269
+        .onAppear {
270
+            // 初始化状态
271
+            isWinning = ticket.ticketStatus == .won
272
+            prizeAmount = ticket.prizeAmount
273
+            canResetToPending = ticket.ticketStatus != .pending
274
+        }
275
+        .onChange(of: ticket.ticketStatus) { oldValue, newValue in
276
+            canResetToPending = newValue != .pending
277
+        }
278
+    }
279
+    
280
+    // MARK: - 辅助视图
281
+    
282
+    // 数字球组件
283
+    struct NumberBall: View {
284
+        let number: String
285
+        let index: Int
286
+        let color: Color
287
+        @State private var isAnimated = false
288
+        
289
+        var body: some View {
290
+            Text(number)
291
+                .font(.system(size: 18, weight: .bold, design: .rounded))
292
+                .frame(width: 50, height: 50)
293
+                .background(
294
+                    Circle()
295
+                        .fill(color)
296
+                        .shadow(color: color.opacity(0.4), radius: 3, y: 2)
297
+                )
298
+                .foregroundColor(.white)
299
+                .scaleEffect(isAnimated ? 1 : 0.8)
300
+                .opacity(isAnimated ? 1 : 0.5)
301
+                .onAppear {
302
+                    withAnimation(
303
+                        .spring(response: 0.6, dampingFraction: 0.7)
304
+                        .delay(Double(index) * 0.05)
305
+                    ) {
306
+                        isAnimated = true
307
+                    }
308
+                }
309
+        }
310
+    }
311
+    
312
+    // 信息行组件(增强版)
313
+    struct InfoRow: View {
314
+        let icon: String
315
+        let title: String
316
+        let value: String
317
+        let iconColor: Color
318
+        
319
+        var body: some View {
320
+            HStack(spacing: 16) {
321
+                Image(systemName: icon)
322
+                    .font(.system(size: 18))
323
+                    .frame(width: 30)
324
+                    .foregroundColor(iconColor)
325
+                
326
+                VStack(alignment: .leading, spacing: 2) {
327
+                    Text(title)
328
+                        .font(.caption)
329
+                        .foregroundColor(.secondary)
330
+                    
331
+                    Text(value)
332
+                        .font(.body)
333
+                        .fontWeight(.medium)
334
+                }
335
+                
336
+                Spacer()
337
+            }
338
+            .padding(.vertical, 12)
339
+            .padding(.horizontal)
340
+        }
341
+    }
342
+    
343
+    // MARK: - 业务逻辑
344
+    
345
+    private var ticketStatusText: String {
346
+        switch ticket.ticketStatus {
347
+        case .pending:
348
+            return "待开奖"
349
+        case .won:
350
+            return "已中奖"
351
+        case .lost:
352
+            return "未中奖"
353
+        }
354
+    }
355
+    
356
+    private var statusColor: Color {
357
+        switch ticket.ticketStatus {
358
+        case .pending:
359
+            return .orange
360
+        case .won:
361
+            return .green
362
+        case .lost:
363
+            return .gray
364
+        }
365
+    }
366
+    
367
+    private var ticketIconName: String {
368
+        switch ticket.lotteryType {
369
+        case .doubleColorBall:
370
+            return "circle.grid.2x2.fill"
371
+        case .superLotto:
372
+            return "star.circle.fill"
373
+        case .happy8:
374
+            return "8.circle.fill"
375
+        case .sevenStar:
376
+            return "7.circle.fill"
377
+        case .other:
378
+            return "ticket.fill"
379
+        }
380
+    }
381
+    
382
+    private var ticketColor: Color {
383
+        switch ticket.lotteryType {
384
+        case .doubleColorBall: return Color(red: 0.9, green: 0.2, blue: 0.2)    // 红色
385
+        case .superLotto: return Color(red: 0.2, green: 0.4, blue: 0.9)         // 蓝色
386
+        case .happy8: return Color(red: 0.7, green: 0.2, blue: 0.8)             // 紫色
387
+        case .sevenStar: return Color(red: 0.2, green: 0.7, blue: 0.4)          // 绿色
388
+        case .other: return Color(red: 1.0, green: 0.5, blue: 0.7)              // 粉色
389
+        }
390
+    }
391
+    
392
+    private func updateWinningStatus(isWinning: Bool) {
393
+        if isWinning {
394
+            showingPrizeEditor = true
395
+        } else {
396
+            ticket.ticketStatus = .lost
397
+            ticket.prizeAmount = 0
398
+            saveChanges()  // 这里会发送通知
399
+        }
400
+    }
401
+    
402
+    private func saveWinningStatus() {
403
+        ticket.ticketStatus = .won
404
+        ticket.prizeAmount = prizeAmount
405
+        // prizeAmount 已经在 PrizeEditorView 中通过绑定更新了
406
+        saveChanges()  // 这里会发送通知
407
+        showingPrizeEditor = false
408
+    }
409
+    
410
+    private func resetToPending() {
411
+        ticket.ticketStatus = .pending
412
+        ticket.prizeAmount = 0
413
+        saveChanges()  // 这里会发送通知
414
+    }
415
+    
416
+    private func saveChanges() {
417
+        do {
418
+            try context.save()
419
+            
420
+            // 发送数据更新通知
421
+            NotificationCenter.default.post(name: .ticketsUpdated, object: nil)
422
+            
423
+//            // 添加保存成功的视觉反馈
424
+//            let feedback = UINotificationFeedbackGenerator()
425
+//            feedback.notificationOccurred(.success)
426
+//            print("✅ 彩票状态已更新并发送通知")
427
+        } catch {
428
+            print("保存失败: \(error)")
429
+        }
94 430
     }
95 431
     
96 432
     private func deleteTicket() {
@@ -102,42 +438,134 @@ struct TicketDetailView: View {
102 438
             print("删除失败: \(error)")
103 439
         }
104 440
     }
105
-    
106
-    private var ticketColor: Color {
107
-        switch ticket.lotteryType {
108
-        case .doubleColorBall: return .red
109
-        case .superLotto: return .blue
110
-        case .happy8: return .purple
111
-        case .sevenStar: return .green
112
-        case .other: return .pink
113
-        }
114
-    }
115 441
 }
116 442
 
117
-struct InfoRow: View {
118
-    let title: String
119
-    let value: String
120
-    
121
-    init(title: String, value: String) {
122
-        self.title = title
123
-        self.value = value
124
-    }
443
+// MARK: - 中奖金额编辑器视图
444
+struct PrizeEditorView: View {
445
+    @Binding var isPresented: Bool
446
+    @Binding var prizeAmount: Float
447
+    let onSave: () -> Void
125 448
     
126
-    init(title: String, value: Date, formatter: DateFormatter.Style = .medium) {
127
-        self.title = title
128
-        let dateFormatter = DateFormatter()
129
-        dateFormatter.dateStyle = formatter
130
-        self.value = dateFormatter.string(from: value)
131
-    }
449
+    @State private var amountText: String = ""
450
+    @FocusState private var isInputFocused: Bool
132 451
     
133 452
     var body: some View {
134
-        HStack {
135
-            Text(title)
136
-                .foregroundColor(.secondary)
137
-            Spacer()
138
-            Text(value)
139
-                .fontWeight(.medium)
453
+        NavigationView {
454
+            VStack(spacing: 30) {
455
+                Image(systemName: "trophy.fill")
456
+                    .font(.system(size: 70))
457
+                    .foregroundColor(.yellow)
458
+                    .padding()
459
+                    .background(
460
+                        Circle()
461
+                            .fill(.yellow.opacity(0.15))
462
+                    )
463
+                
464
+                VStack(spacing: 8) {
465
+                    Text("中奖金额")
466
+                        .font(.title2)
467
+                        .fontWeight(.bold)
468
+                    
469
+                    Text("请输入您的中奖金额")
470
+                        .font(.body)
471
+                        .foregroundColor(.secondary)
472
+                }
473
+                
474
+                // 金额输入
475
+                VStack(spacing: 20) {
476
+                    HStack(alignment: .firstTextBaseline) {
477
+                        TextField(Formatters.formatCurrency(0), text: $amountText)
478
+                            .font(.system(size: 48, weight: .bold, design: .rounded))
479
+                            .keyboardType(.decimalPad)
480
+                            .multilineTextAlignment(.center)
481
+                            .focused($isInputFocused)
482
+                            .onChange(of: amountText) { oldValue, newValue in
483
+                                // 格式化输入,只允许数字和小数点
484
+                                let filtered = newValue.filter { "0123456789.".contains($0) }
485
+                                let components = filtered.split(separator: ".")
486
+                                if components.count > 2 {
487
+                                    // 多于一个小数点,去掉多余的
488
+                                    amountText = String(filtered.dropLast())
489
+                                } else if components.count == 2 && components[1].count > 2 {
490
+                                    // 小数部分超过2位
491
+                                    amountText = String(filtered.dropLast())
492
+                                } else {
493
+                                    amountText = filtered
494
+                                }
495
+                                
496
+                                // 更新绑定的金额
497
+                                prizeAmount = Float(amountText) ?? 0
498
+                            }
499
+                    }
500
+                    
501
+                    // 快捷金额按钮
502
+                    VStack(spacing: 12) {
503
+                        Text("快捷金额")
504
+                            .font(.caption)
505
+                            .foregroundColor(.secondary)
506
+                        
507
+                        LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 4), spacing: 12) {
508
+                            ForEach([5, 10, 20, 50], id: \.self) { amount in
509
+                                Button(action: {
510
+                                    prizeAmount = Float(amount)
511
+                                    amountText = String(format: "%.0f", prizeAmount)
512
+                                }) {
513
+                                    Text(Formatters.formatCurrency(Float(amount)))
514
+                                        .font(.system(size: 14, weight: .medium))
515
+                                        .padding(.vertical, 8)
516
+                                        .frame(maxWidth: .infinity)
517
+                                        .background(Color.green.opacity(0.1))
518
+                                        .foregroundColor(.green)
519
+                                        .clipShape(Capsule())
520
+                                }
521
+                            }
522
+                        }
523
+                    }
524
+                }
525
+                .padding(.horizontal, 40)
526
+                
527
+                Spacer()
528
+                
529
+                Button(action: {
530
+                    onSave()
531
+                    isPresented = false
532
+                }) {
533
+                    Text("确认保存")
534
+                        .font(.headline)
535
+                        .foregroundColor(.white)
536
+                        .frame(maxWidth: .infinity)
537
+                        .padding(.vertical, 16)
538
+                        .background(Color.green)
539
+                        .clipShape(RoundedRectangle(cornerRadius: 16))
540
+                }
541
+                .padding(.horizontal, 20)
542
+                .padding(.bottom, 10)
543
+                .disabled(prizeAmount <= 0)
544
+                .opacity(prizeAmount <= 0 ? 0.6 : 1)
545
+            }
546
+            .padding()
547
+            .navigationTitle("设置中奖金额")
548
+            .navigationBarTitleDisplayMode(.inline)
549
+            .toolbar {
550
+                ToolbarItem(placement: .navigationBarLeading) {
551
+                    Button("取消") {
552
+                        isPresented = false
553
+                    }
554
+                }
555
+                
556
+                ToolbarItemGroup(placement: .keyboard) {
557
+                    Spacer()
558
+                    Button("完成") {
559
+                        isInputFocused = false
560
+                    }
561
+                }
562
+            }
563
+            .onAppear {
564
+                // 初始化输入框内容
565
+                amountText = prizeAmount > 0 ? Formatters.formatCurrency(prizeAmount) : ""
566
+                isInputFocused = true
567
+            }
140 568
         }
141
-        .padding(.vertical, 4)
142 569
     }
143 570
 }
571
+

+ 1
- 1
LotteryTracker/Views/Home/TicketRow.swift Ver arquivo

@@ -9,7 +9,7 @@ import SwiftUI
9 9
 internal import CoreData
10 10
 
11 11
 struct TicketRow: View {
12
-    let ticket: LotteryTicket
12
+    @ObservedObject var ticket: LotteryTicket
13 13
     
14 14
     var body: some View {
15 15
         HStack(spacing: 16) {