Преглед изворни кода

新增快乐八大乐透界面选号

afan пре 4 дана
родитељ
комит
3aa84d5ca8

+ 31
- 5
LotteryTracker/ViewModels/AddTicketViewModel.swift Прегледај датотеку

@@ -124,11 +124,11 @@ class AddTicketViewModel: ObservableObject {
124 124
             return validateSuperLotto(numbers)
125 125
         default:
126 126
             // 其他彩票类型
127
-            return !numbers.isEmpty
127
+            return validateDefault(numbers)
128 128
         }
129 129
     }
130 130
     
131
-    // 具体的验证方法
131
+    // 双色球验证方法
132 132
     private func validateDoubleColorBall(_ numbers: String) -> Bool {
133 133
         // 双色球格式: "01 05 12 23 28 33 + 09"
134 134
         let components = numbers.split(separator: "+")
@@ -145,15 +145,41 @@ class AddTicketViewModel: ObservableObject {
145 145
             errorMessage = ""
146 146
             return true
147 147
         } else {
148
-            errorMessage = "双色球个数不正确"
148
+            errorMessage = "双色球选择个数不正确"
149 149
             return false
150 150
         }
151 151
     }
152 152
     
153
+    // 大乐透验证方法
153 154
     private func validateSuperLotto(_ numbers: String) -> Bool {
154 155
         // 大乐透格式: "01 05 12 23 28 + 03 09"
155
-        // ... 类似的验证逻辑
156
-        validateDoubleColorBall(numbers)
156
+        let components = numbers.split(separator: "+")
157
+        guard components.count == 2 else { return false }
158
+        
159
+        let frontPart = components[0].trimmingCharacters(in: .whitespaces)
160
+        let backPart = components[1].trimmingCharacters(in: .whitespaces)
161
+        
162
+        let frontNumbers = frontPart.split(separator: " ").compactMap { Int($0) }
163
+        let blueNumbers = backPart.split(separator: " ").compactMap { Int($0) }
164
+        
165
+        // 前区5个,后区2个
166
+        if (frontNumbers.count == 5 && blueNumbers.count == 2) {
167
+            errorMessage = ""
168
+            return true
169
+        } else {
170
+            errorMessage = "大乐透选择个数不正确"
171
+            return false
172
+        }
173
+    }
174
+    
175
+    private func validateDefault(_ numbers: String) -> Bool {
176
+        if (!numbers.isEmpty) {
177
+            errorMessage = ""
178
+            return true
179
+        } else {
180
+            errorMessage = "选择号码不能为空"
181
+            return false
182
+        }
157 183
     }
158 184
     
159 185
     // .....

+ 5
- 2
LotteryTracker/Views/AddTicket/AddTicketView.swift Прегледај датотеку

@@ -116,8 +116,11 @@ struct AddTicketView: View {
116 116
                 .cornerRadius(12)
117 117
                 .shadow(color: Color.black.opacity(0.05), radius: 5, x: 0, y: 2)
118 118
                 .background(
119
-                    // 日历专用背景色
120
-                    Color(red: 0.98, green: 0.97, blue: 0.95)
119
+                    Color(uiColor: UIColor { traitCollection in
120
+                        return traitCollection.userInterfaceStyle == .dark ?
121
+                        UIColor(red: 0.15, green: 0.15, blue: 0.17, alpha: 1.0) : // 深色模式
122
+                        UIColor(red: 0.98, green: 0.97, blue: 0.95, alpha: 1.0)  // 浅色模式
123
+                    })
121 124
                 )
122 125
                 
123 126
                 // 开奖信息显示(只读)

+ 11
- 243
LotteryTracker/Views/AddTicket/DoubleColorBallCompoents.swift Прегледај датотеку

@@ -24,6 +24,7 @@ struct DoubleColorBallSection: View {
24 24
             // 红球选择区
25 25
             VStack(alignment: .leading, spacing: 12) {
26 26
                 NumberSelectionGrid(
27
+                    lotteryType: .doubleColorBall,
27 28
                     title: "红球区",
28 29
                     color: .red,
29 30
                     range: 1...33,
@@ -35,6 +36,7 @@ struct DoubleColorBallSection: View {
35 36
             // 蓝球选择区
36 37
             VStack(alignment: .leading, spacing: 12) {
37 38
                 NumberSelectionGrid(
39
+                    lotteryType: .doubleColorBall,
38 40
                     title: "蓝球区",
39 41
                     color: .blue,
40 42
                     range: 1...16,
@@ -44,14 +46,19 @@ struct DoubleColorBallSection: View {
44 46
             }
45 47
             
46 48
             // 快速操作栏
47
-            QuickActionsBar(
49
+            QuickActionsBar2(
50
+                maxSelection: betCount,
51
+                count: redNumbers.count + blueNumbers.count,
52
+                isEmpty: redNumbers.isEmpty && blueNumbers.isEmpty,
48 53
                 onRandom: generateRandomNumbers,
49
-                onClear: clearAllNumbers
54
+                onClear: clearAllNumbers,
55
+                onSmartRandom: generateRandomNumbers
50 56
             )
51 57
             
58
+            
52 59
             // 红球/蓝球已选显示
53 60
             if (!redNumbers.isEmpty || !blueNumbers.isEmpty) {
54
-                SelectedNumbersView(
61
+                SelectedNumbersViewDoubleColorBall(
55 62
                     reds: redNumbers,
56 63
                     blues: blueNumbers,
57 64
                     maxCount: 7,
@@ -91,7 +98,6 @@ struct DoubleColorBallSection: View {
91 98
     }
92 99
 }
93 100
 
94
-
95 101
 // MARK: - 头部组件
96 102
 struct SectionHeader: View {
97 103
     let title: String
@@ -104,170 +110,13 @@ struct SectionHeader: View {
104 110
                 .foregroundColor(.primary)
105 111
             
106 112
             Spacer()
107
-            
108
-            // 状态指示器
109
-            Capsule()
110
-                .fill(Color.green.opacity(0.1))
111
-                .frame(width: 80, height: 24)
112
-                .overlay(
113
-                    HStack(spacing: 4) {
114
-                        Circle()
115
-                            .fill(Color.green)
116
-                            .frame(width: 8, height: 8)
117
-                        
118
-                        Text("可选")
119
-                            .font(.caption2)
120
-                            .foregroundColor(.green)
121
-                    }
122
-                )
123 113
         }
124 114
     }
125 115
 }
126 116
 
127 117
 
128
-// MARK: - 数字选择网格
129
-struct NumberSelectionGrid: View {
130
-    let title: String
131
-    let color: Color
132
-    let range: ClosedRange<Int>
133
-    let maxSelection: Int
134
-    @Binding var selectedNumbers: [Int]
135
-    
136
-    private let columns = Array(repeating: GridItem(.flexible()), count: 10)
137
-    
138
-    var body: some View {
139
-        VStack(alignment: .leading, spacing: 8) {
140
-            // 标题和计数
141
-            HStack {
142
-                Text(title)
143
-                    .font(.subheadline)
144
-                    .fontWeight(.semibold)
145
-                    .foregroundColor(color)
146
-                
147
-                Spacer()
148
-                
149
-                Text("\(selectedNumbers.count)/\(maxSelection)")
150
-                    .font(.caption)
151
-                    .fontWeight(.medium)
152
-                    .foregroundColor(.secondary)
153
-                    .padding(.horizontal, 8)
154
-                    .padding(.vertical, 2)
155
-                    .background(
156
-                        Capsule()
157
-                            .fill(color.opacity(0.1))
158
-                    )
159
-            }
160
-            .padding(.horizontal, 4)
161
-            
162
-            // 数字网格
163
-            LazyVGrid(columns: columns, spacing: 10) {
164
-                ForEach(range, id: \.self) { number in
165
-                    NumberButton(
166
-                        number: number,
167
-                        color: color,
168
-                        isSelected: selectedNumbers.contains(number),
169
-                        isSelectable: selectedNumbers.count < maxSelection || selectedNumbers.contains(number)
170
-                    ) {
171
-                        toggleNumber(number)
172
-                    }
173
-                }
174
-            }
175
-        }
176
-        .padding(16)
177
-        .background(Color(.secondarySystemBackground))
178
-        .cornerRadius(12)
179
-        .overlay(
180
-            RoundedRectangle(cornerRadius: 12)
181
-                .stroke(color.opacity(0.2), lineWidth: 1)
182
-        )
183
-    }
184
-    
185
-    private func toggleNumber(_ number: Int) {
186
-        withAnimation(.spring(response: 0.3)) {
187
-            if selectedNumbers.contains(number) {
188
-                selectedNumbers.removeAll { $0 == number }
189
-            } else if selectedNumbers.count < maxSelection {
190
-                selectedNumbers.append(number)
191
-                // 触觉反馈
192
-                let generator = UIImpactFeedbackGenerator(style: .light)
193
-                generator.impactOccurred()
194
-            }
195
-        }
196
-    }
197
-}
198
-
199
-// MARK: - 数字按钮
200
-struct NumberButton: View {
201
-    let number: Int
202
-    let color: Color
203
-    let isSelected: Bool
204
-    let isSelectable: Bool
205
-    let action: () -> Void
206
-    
207
-    @State private var isPressed = false
208
-    
209
-    var body: some View {
210
-        Button(action: handleAction) {
211
-            ZStack {
212
-                // 背景圆圈
213
-                Circle()
214
-                    .fill(backgroundColor)
215
-                    .frame(width: 32, height: 32)
216
-                
217
-                // 数字
218
-                Text(String(format: "%02d", number))
219
-                    .font(.system(size: 16, weight: .semibold, design: .rounded))
220
-                    .foregroundColor(textColor)
221
-                
222
-                // 选中标记
223
-                if isSelected {
224
-                    Circle()
225
-                        .stroke(color, lineWidth: 2)
226
-                        .frame(width: 32, height: 32)
227
-                    
228
-                    Image(systemName: "checkmark.circle.fill")
229
-                        .font(.system(size: 12))
230
-                        .foregroundColor(color)
231
-                        .background(Color.white)
232
-                        .clipShape(Circle())
233
-                        .offset(x: 16, y: -16)
234
-                }
235
-            }
236
-        }
237
-        .buttonStyle(PressableButtonStyle())
238
-        .disabled(!isSelectable && !isSelected)
239
-        .opacity(isSelectable || isSelected ? 1.0 : 0.4)
240
-    }
241
-    
242
-    private func handleAction() {
243
-        if isSelectable {
244
-            action()
245
-        } else {
246
-            let generator = UINotificationFeedbackGenerator()
247
-            generator.notificationOccurred(.warning)
248
-        }
249
-    }
250
-    
251
-    private var backgroundColor: Color {
252
-        isSelected ? color.opacity(0.15) : Color(.systemBackground)
253
-    }
254
-    
255
-    private var textColor: Color {
256
-        isSelected ? color : .primary
257
-    }
258
-}
259
-
260
-struct PressableButtonStyle: ButtonStyle {
261
-    func makeBody(configuration: Configuration) -> some View {
262
-        configuration.label
263
-            .scaleEffect(configuration.isPressed ? 0.95 : 1.0)
264
-            .opacity(configuration.isPressed ? 0.9 : 1.0)
265
-            .animation(.easeInOut(duration: 0.1), value: configuration.isPressed)
266
-    }
267
-}
268
-
269 118
 // MARK: - 已选号码显示
270
-struct SelectedNumbersView: View {
119
+struct SelectedNumbersViewDoubleColorBall: View {
271 120
     let reds: [Int]
272 121
     let blues: [Int]
273 122
     let maxCount: Int
@@ -333,84 +182,3 @@ struct SelectedNumbersView: View {
333 182
     }
334 183
 }
335 184
 
336
-// MARK: - 快速操作栏
337
-struct QuickActionsBar: View {
338
-    let onRandom: () -> Void
339
-    let onClear: () -> Void
340
-    
341
-    var body: some View {
342
-        HStack(spacing: 12) {
343
-            // 随机生成
344
-            ActionButton(
345
-                title: "随机一注",
346
-                icon: "dice.fill",
347
-                color: .purple,
348
-                action: onRandom
349
-            )
350
-            
351
-            // 清空全部
352
-            ActionButton(
353
-                title: "清空重选",
354
-                icon: "trash",
355
-                color: .gray,
356
-                action: onClear
357
-            )
358
-        }
359
-    }
360
-}
361
-
362
-struct ActionButton: View {
363
-    let title: String
364
-    let icon: String
365
-    let color: Color
366
-    let action: () -> Void
367
-    
368
-    var body: some View {
369
-        Button(action: {
370
-            action()
371
-            let generator = UIImpactFeedbackGenerator(style: .light)
372
-            generator.impactOccurred()
373
-        }) {
374
-            HStack(spacing: 6) {
375
-                Image(systemName: icon)
376
-                    .font(.system(size: 14))
377
-                
378
-                Text(title)
379
-                    .font(.caption)
380
-                    .fontWeight(.medium)
381
-            }
382
-            .padding(.horizontal, 16)
383
-            .padding(.vertical, 10)
384
-            .frame(maxWidth: .infinity)
385
-            .background(color.opacity(0.1))
386
-            .foregroundColor(color)
387
-            .cornerRadius(10)
388
-        }
389
-    }
390
-}
391
-
392
-
393
-struct ChipButton: View {
394
-    let title: String
395
-    let isSelected: Bool
396
-    let action: () -> Void
397
-    
398
-    var body: some View {
399
-        Button(action: action) {
400
-            Text(title)
401
-                .font(.system(size: 14, weight: .medium))
402
-                .padding(.horizontal, 16)
403
-                .padding(.vertical, 8)
404
-                .background(
405
-                    Capsule()
406
-                        .fill(isSelected ? Color.blue : Color.gray.opacity(0.1))
407
-                )
408
-                .foregroundColor(isSelected ? .white : .primary)
409
-                .overlay(
410
-                    Capsule()
411
-                        .stroke(isSelected ? Color.blue : Color.gray.opacity(0.3), lineWidth: 1)
412
-                )
413
-        }
414
-    }
415
-}
416
-

+ 212
- 0
LotteryTracker/Views/AddTicket/HappyEightCompoents.swift Прегледај датотеку

@@ -0,0 +1,212 @@
1
+//
2
+//  HappyEightCompoents.swift
3
+//  LotteryTracker
4
+//
5
+//  Created by aaa on 2026/1/26.
6
+//
7
+
8
+import SwiftUI
9
+
10
+struct HappyEightSection: View {
11
+    @Binding var numbers: String
12
+    @State private var selectedNumbers: [Int] = []
13
+    
14
+    private let maxSelection = 10
15
+    private let columns = Array(repeating: GridItem(.flexible()), count: 10) // 10列网格
16
+    
17
+    var body: some View {
18
+        VStack(alignment: .leading, spacing: 16) {
19
+            // 头部标题
20
+            SectionHeader(title: "选择号码", icon: "number.circle")
21
+            
22
+            // 选择区号码网格(1-80)
23
+            VStack(alignment: .leading, spacing: 12) {
24
+                NumberSelectionGrid(
25
+                    lotteryType: .happy8,
26
+                    title: "号码区",
27
+                    color: .primary,
28
+                    range: 1...80,
29
+                    maxSelection: 10,
30
+                    selectedNumbers: $selectedNumbers
31
+                )
32
+            }
33
+
34
+            // 快速操作栏
35
+            QuickActionsBar2(
36
+                maxSelection: 1,
37
+                count: selectedNumbers.count,
38
+                isEmpty: selectedNumbers.isEmpty,
39
+                onRandom: generateRandomNumbers,
40
+                onClear: clearAllNumbers,
41
+                onSmartRandom: generateSmartNumbers
42
+            )
43
+            
44
+            // 已选号码展示
45
+            if !selectedNumbers.isEmpty {
46
+                SelectedNumbersDisplay()
47
+            }
48
+        }
49
+        .onChange(of: selectedNumbers) {
50
+            updateNumbers()
51
+        }
52
+        .onAppear {
53
+            if numbers.isEmpty {
54
+                generateRandomNumbers()
55
+            }
56
+        }
57
+    }
58
+    
59
+    // MARK: - 子组件
60
+    
61
+    private func SelectionBadgeView() -> some View {
62
+        HStack(spacing: 6) {
63
+            Image(systemName: "checkmark.circle.fill")
64
+                .font(.caption)
65
+                .foregroundColor(selectedNumbers.count >= 5 ? .green : .orange)
66
+            
67
+            Text("\(selectedNumbers.count)/\(maxSelection)")
68
+                .font(.system(size: 14, weight: .bold))
69
+                .foregroundColor(selectedNumbers.count == maxSelection ? .white : .primary)
70
+        }
71
+        .padding(.horizontal, 12)
72
+        .padding(.vertical, 8)
73
+        .background(
74
+            Capsule()
75
+                .fill(selectedNumbers.count == maxSelection ?
76
+                      Color.green.gradient :
77
+                      Color(.systemGray6).gradient)
78
+        )
79
+        .overlay(
80
+            Capsule()
81
+                .stroke(selectedNumbers.count == maxSelection ?
82
+                        Color.green.opacity(0.5) :
83
+                        Color.gray.opacity(0.3),
84
+                        lineWidth: 1)
85
+        )
86
+        .shadow(color: selectedNumbers.count == maxSelection ?
87
+                Color.green.opacity(0.3) : Color.clear,
88
+                radius: 3, x: 0, y: 2)
89
+    }
90
+    
91
+    private func SelectedNumbersDisplay() -> some View {
92
+        VStack(alignment: .leading, spacing: 12) {
93
+            HStack {
94
+                Text("已选号码")
95
+                    .font(.subheadline)
96
+                    .fontWeight(.semibold)
97
+                    .foregroundColor(.primary)
98
+                
99
+                Spacer()
100
+                
101
+                if selectedNumbers.count == maxSelection {
102
+                    HStack(spacing: 4) {
103
+                        Image(systemName: "checkmark.seal.fill")
104
+                            .font(.caption)
105
+                            .foregroundColor(.green)
106
+                        
107
+                        Text("已选满")
108
+                            .font(.caption)
109
+                            .fontWeight(.medium)
110
+                            .foregroundColor(.green)
111
+                    }
112
+                }
113
+            }
114
+            
115
+            // 号码展示
116
+            LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 5), spacing: 8) {
117
+                ForEach(selectedNumbers.sorted(), id: \.self) { number in
118
+                    SelectedNumberPill(number: number)
119
+                }
120
+            }
121
+        }
122
+        .padding(16)
123
+        .background(Color(.systemBackground))
124
+        .cornerRadius(12)
125
+        .overlay(
126
+            RoundedRectangle(cornerRadius: 12)
127
+                .stroke(Color.blue.opacity(0.1), lineWidth: 1)
128
+        )
129
+    }
130
+    
131
+    private func SelectedNumberPill(number: Int) -> some View {
132
+        HStack(spacing: 6) {
133
+            Text(String(format: "%02d", number))
134
+                .font(.system(size: 16, weight: .bold, design: .rounded))
135
+            
136
+//            Button {
137
+//                removeNumber(number)
138
+//            } label: {
139
+//                Image(systemName: "xmark.circle.fill")
140
+//                    .font(.caption)
141
+//                    .foregroundColor(.red.opacity(0.7))
142
+//            }
143
+        }
144
+        .frame(width: 22, height: 22)
145
+        .padding(.horizontal, 10)
146
+        .padding(.vertical, 10)
147
+        .background(
148
+            LinearGradient(
149
+                colors: [Color.blue, Color.purple],
150
+                startPoint: .topLeading,
151
+                endPoint: .bottomTrailing
152
+            ).opacity(0.15)
153
+        )
154
+        .foregroundColor(.blue)
155
+        .cornerRadius(20)
156
+        .overlay(
157
+            RoundedRectangle(cornerRadius: 20)
158
+                .stroke(Color.blue.opacity(0.3), lineWidth: 1)
159
+        )
160
+    }
161
+    
162
+    // MARK: - 功能方法
163
+    
164
+//    private func removeNumber(_ number: Int) {
165
+//        withAnimation(.spring(response: 0.3)) {
166
+//            selectedNumbers.removeAll { $0 == number }
167
+//        }
168
+//    }
169
+    
170
+    private func generateRandomNumbers() {
171
+        withAnimation(.spring(response: 0.4)) {
172
+            var numbers = Set<Int>()
173
+            while numbers.count < maxSelection {
174
+                numbers.insert(Int.random(in: 1...80))
175
+            }
176
+            selectedNumbers = Array(numbers).sorted()
177
+        }
178
+        
179
+        // 触觉反馈
180
+        let generator = UIImpactFeedbackGenerator(style: .medium)
181
+        generator.impactOccurred()
182
+    }
183
+    
184
+    private func generateSmartNumbers() {
185
+        // 智能选号:保证奇偶、大小均衡
186
+        var selected = Set<Int>()
187
+        
188
+        // 确保有5个奇数,5个偶数
189
+        let oddNumbers = (1...80).filter { $0 % 2 == 1 }.shuffled()
190
+        let evenNumbers = (1...80).filter { $0 % 2 == 0 }.shuffled()
191
+        
192
+        selected.formUnion(oddNumbers.prefix(5))
193
+        selected.formUnion(evenNumbers.prefix(5))
194
+        
195
+        withAnimation(.spring(response: 0.4)) {
196
+            selectedNumbers = Array(selected).sorted()
197
+        }
198
+    }
199
+    
200
+    private func clearAllNumbers() {
201
+        withAnimation(.spring(response: 0.3)) {
202
+            selectedNumbers.removeAll()
203
+        }
204
+    }
205
+    
206
+    private func updateNumbers() {
207
+        let numberStr = selectedNumbers.sorted().map { String(format: "%02d", $0) }.joined(separator: " ")
208
+        numbers = numberStr
209
+    }
210
+}
211
+
212
+

+ 298
- 0
LotteryTracker/Views/AddTicket/NumberSelectionGrid.swift Прегледај датотеку

@@ -0,0 +1,298 @@
1
+//
2
+//  NumberSelectionGrid.swift
3
+//  LotteryTracker
4
+//
5
+//  Created by aaa on 2026/1/26.
6
+//
7
+
8
+import SwiftUI
9
+
10
+// MARK: - 数字选择网格 (双色球/大乐透/快乐八)
11
+struct NumberSelectionGrid: View {
12
+    let lotteryType: LotteryType
13
+    let title: String
14
+    let color: Color
15
+    let range: ClosedRange<Int>
16
+    let maxSelection: Int
17
+    @Binding var selectedNumbers: [Int]
18
+    
19
+    private let columns = Array(repeating: GridItem(.flexible()), count: 10)
20
+    
21
+    var body: some View {
22
+        VStack(alignment: .leading, spacing: 8) {
23
+            // 标题和计数
24
+            HStack {
25
+                Text(title)
26
+                    .font(.subheadline)
27
+                    .fontWeight(.semibold)
28
+                    .foregroundColor(color)
29
+                
30
+                Spacer()
31
+                
32
+                Text("\(selectedNumbers.count)/\(maxSelection)")
33
+                    .font(.caption)
34
+                    .fontWeight(.medium)
35
+                    .foregroundColor(.secondary)
36
+                    .padding(.horizontal, 8)
37
+                    .padding(.vertical, 2)
38
+                    .background(
39
+                        Capsule()
40
+                            .fill(color.opacity(0.1))
41
+                    )
42
+            }
43
+            .padding(.horizontal, 4)
44
+            
45
+            // 号码网格
46
+            LazyVGrid(columns: columns, spacing: 10) {
47
+                ForEach(range, id: \.self) { number in
48
+                    NumberButton(
49
+                        lotteryType: lotteryType,
50
+                        number: number,
51
+                        color: color,
52
+                        isSelected: selectedNumbers.contains(number),
53
+                        isSelectable: selectedNumbers.count < maxSelection || selectedNumbers.contains(number)
54
+                    ) {
55
+                        toggleNumber(number)
56
+                    }
57
+                }
58
+            }
59
+        }
60
+        .padding(16)
61
+        .background(Color(.secondarySystemBackground))
62
+        .cornerRadius(12)
63
+        .overlay(
64
+            RoundedRectangle(cornerRadius: 12)
65
+                .stroke(.primary.opacity(0.2), lineWidth: 1)
66
+        )
67
+    }
68
+    
69
+    private func toggleNumber(_ number: Int) {
70
+        withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) {
71
+            if selectedNumbers.contains(number) {
72
+                selectedNumbers.removeAll { $0 == number }
73
+            } else if selectedNumbers.count < maxSelection {
74
+                selectedNumbers.append(number)
75
+                // 触觉反馈
76
+                let generator = UIImpactFeedbackGenerator(style: .light)
77
+                generator.impactOccurred()
78
+            } else {
79
+                // 已达上限提示
80
+                let generator = UINotificationFeedbackGenerator()
81
+                generator.notificationOccurred(.warning)
82
+            }
83
+        }
84
+    }
85
+}
86
+
87
+
88
+// MARK: - 号码按钮
89
+struct NumberButton: View {
90
+    let lotteryType: LotteryType
91
+    let number: Int
92
+    let color: Color
93
+    let isSelected: Bool
94
+    let isSelectable: Bool
95
+    let action: () -> Void
96
+    
97
+    @State private var isPressed = false
98
+    
99
+    var body: some View {
100
+        Button(action: action) {
101
+            ZStack {
102
+                // 背景
103
+                Circle()
104
+                    .fill(backgroundColor)
105
+                    .frame(width: 32, height: 32)
106
+                    .shadow(color: isSelected ? Color.purple.opacity(0.3) : Color.clear,
107
+                           radius: isSelected ? 3 : 0,
108
+                           x: 0, y: isSelected ? 1 : 0)
109
+                
110
+                // 数字
111
+                Text(String(format: "%02d", number))
112
+                    .font(.system(size: 16, weight: isSelected ? .bold : .semibold, design: .rounded))
113
+                    .foregroundColor(textColor)
114
+                
115
+                // 选中标记
116
+                if isSelected {
117
+                    Circle()
118
+                        .stroke(borderColor, lineWidth: 2)
119
+                        .frame(width: 32, height: 32)
120
+                    
121
+                    Image(systemName: "checkmark.circle.fill")
122
+                        .font(.system(size: 10, weight: .black))
123
+                        .foregroundColor(.white)
124
+                        .frame(width: 12, height: 12)
125
+                        .background(borderColor)
126
+                        .clipShape(Circle())
127
+                        .offset(x: 12, y: -12)
128
+                }
129
+            }
130
+        }
131
+        .buttonStyle(PressableButtonStyle())
132
+        .scaleEffect(isSelected ? 0.94 : 1.0)
133
+        .opacity(isSelectable || isSelected ? 1.0 : 0.5)
134
+        .disabled(!isSelectable && !isSelected)
135
+        .animation(.spring(response: 0.2), value: isSelected)
136
+    }
137
+    
138
+    private var backgroundColor: Color {
139
+        isSelected ? borderColor.opacity(0.15) : numberColor.opacity(0.1)
140
+    }
141
+    
142
+    private var textColor: Color {
143
+        isSelected ? borderColor : numberColor
144
+    }
145
+    
146
+    private var borderColor: Color {
147
+        numberColor
148
+    }
149
+    
150
+    private var numberColor: Color {
151
+        if (lotteryType == .doubleColorBall || lotteryType == .superLotto) {
152
+            return color
153
+        } else {
154
+            // 根据数字范围分配颜色
155
+            switch number {
156
+            case 1...20: return .blue
157
+            case 21...40: return .green
158
+            case 41...60: return .orange
159
+            case 61...80: return .purple
160
+            default: return .gray
161
+            }
162
+        }
163
+    }
164
+}
165
+
166
+// MARK: - 按压球样式
167
+struct PressableButtonStyle: ButtonStyle {
168
+    func makeBody(configuration: Configuration) -> some View {
169
+        configuration.label
170
+            .scaleEffect(configuration.isPressed ? 0.95 : 1.0)
171
+            .opacity(configuration.isPressed ? 0.8 : 1.0)
172
+            .animation(.easeInOut(duration: 0.15), value: configuration.isPressed)
173
+    }
174
+}
175
+
176
+// MARK: - 快速倍数按钮
177
+struct ChipButton: View {
178
+    let title: String
179
+    let isSelected: Bool
180
+    let action: () -> Void
181
+    
182
+    var body: some View {
183
+        Button(action: action) {
184
+            Text(title)
185
+                .font(.system(size: 14, weight: .medium))
186
+                .padding(.horizontal, 16)
187
+                .padding(.vertical, 8)
188
+                .background(
189
+                    Capsule()
190
+                        .fill(isSelected ? Color.blue : Color.gray.opacity(0.1))
191
+                )
192
+                .foregroundColor(isSelected ? .white : .primary)
193
+                .overlay(
194
+                    Capsule()
195
+                        .stroke(isSelected ? Color.blue : Color.gray.opacity(0.3), lineWidth: 1)
196
+                )
197
+        }
198
+    }
199
+}
200
+
201
+// MARK: -已选号码展示区
202
+
203
+
204
+
205
+// MARK: - 快速操作栏
206
+struct QuickActionsBar: View {
207
+    let onRandom: () -> Void
208
+    let onClear: () -> Void
209
+    
210
+    var body: some View {
211
+        HStack(spacing: 12) {
212
+            // 随机生成
213
+            ActionButton(
214
+                title: "随机一注",
215
+                icon: "dice.fill",
216
+                color: .purple,
217
+                action: onRandom
218
+            )
219
+            
220
+            // 清空全部
221
+            ActionButton(
222
+                title: "清空重选",
223
+                icon: "trash",
224
+                color: .gray,
225
+                action: onClear
226
+            )
227
+        }
228
+    }
229
+}
230
+
231
+struct QuickActionsBar2: View {
232
+    let maxSelection: Int
233
+    let count: Int
234
+    let isEmpty: Bool
235
+    let onRandom: () -> Void
236
+    let onClear: () -> Void
237
+    let onSmartRandom: () -> Void
238
+    
239
+    var body: some View {
240
+        HStack(spacing: 12) {
241
+            // 随机生成
242
+            ActionButton(
243
+                title: "随机\(maxSelection)注",
244
+                icon: "dice.fill",
245
+                color: .purple,
246
+                action: onRandom
247
+            )
248
+            
249
+            // 清空
250
+            ActionButton(
251
+                title: isEmpty ? "清空" : "清空(\(count))",
252
+                icon: "trash",
253
+                color: isEmpty ? .gray : .red,
254
+                action: onClear
255
+            )
256
+            
257
+            // 智能选号
258
+            ActionButton(
259
+                title: "智能",
260
+                icon: "brain.head.profile",
261
+                color: .orange,
262
+                action: onSmartRandom
263
+            )
264
+        }
265
+        .padding(.vertical, 4)
266
+    }
267
+}
268
+
269
+struct ActionButton: View {
270
+    let title: String
271
+    let icon: String
272
+    let color: Color
273
+    let action: () -> Void
274
+    
275
+    var body: some View {
276
+        Button(action: {
277
+            action()
278
+            let generator = UIImpactFeedbackGenerator(style: .light)
279
+            generator.impactOccurred()
280
+        }) {
281
+            HStack(spacing: 6) {
282
+                Image(systemName: icon)
283
+                    .font(.system(size: 14))
284
+                
285
+                Text(title)
286
+                    .font(.caption)
287
+                    .fontWeight(.medium)
288
+            }
289
+            .padding(.horizontal, 16)
290
+            .padding(.vertical, 10)
291
+            .frame(maxWidth: .infinity)
292
+            .background(color.opacity(0.1))
293
+            .foregroundColor(color)
294
+            .cornerRadius(10)
295
+        }
296
+    }
297
+}
298
+

+ 167
- 0
LotteryTracker/Views/AddTicket/SuperLottoCompoents.swift Прегледај датотеку

@@ -0,0 +1,167 @@
1
+//
2
+//  SuperLottoCompoents.swift
3
+//  LotteryTracker
4
+//
5
+//  Created by aaa on 2026/1/26.
6
+//
7
+
8
+import SwiftUI
9
+
10
+struct SuperLottoSection: View {
11
+    @Binding var numbers: String
12
+    
13
+    @State private var frontNumbers: [Int] = []  // 前区号码
14
+    @State private var backNumbers: [Int] = []   // 后区号码
15
+    @State private var betCount = 1
16
+    
17
+    var body: some View {
18
+        VStack(alignment: .leading, spacing: 16) {
19
+            // 头部标题
20
+            SectionHeader(title: "选择号码", icon: "number.circle")
21
+            
22
+            // 前区选择(红球)
23
+            VStack(alignment: .leading, spacing: 12) {
24
+                NumberSelectionGrid(
25
+                    lotteryType: .superLotto,
26
+                    title: "前区",
27
+                    color: .orange,  // 使用橙色区分双色球的红色
28
+                    range: 1...35,
29
+                    maxSelection: 5,
30
+                    selectedNumbers: $frontNumbers
31
+                )
32
+            }
33
+            
34
+            // 后区选择(蓝球)
35
+            VStack(alignment: .leading, spacing: 12) {
36
+                NumberSelectionGrid(
37
+                    lotteryType: .superLotto,
38
+                    title: "后区",
39
+                    color: .blue,
40
+                    range: 1...12,
41
+                    maxSelection: 2,
42
+                    selectedNumbers: $backNumbers
43
+                )
44
+            }
45
+            
46
+            // 快速操作栏
47
+            QuickActionsBar2(
48
+                maxSelection: betCount,
49
+                count: frontNumbers.count + backNumbers.count,
50
+                isEmpty: frontNumbers.isEmpty && backNumbers.isEmpty,
51
+                onRandom: generateRandomNumbers,
52
+                onClear: clearAllNumbers,
53
+                onSmartRandom: generateRandomNumbers
54
+            )
55
+            
56
+            // 已选号码显示
57
+            if !frontNumbers.isEmpty || !backNumbers.isEmpty {
58
+                SelectedNumbersViewSuperLotto(
59
+                    fronts: frontNumbers,
60
+                    backs: backNumbers,
61
+                    onClear: {
62
+                        frontNumbers.removeAll()
63
+                        backNumbers.removeAll()
64
+                    }
65
+                )
66
+            }
67
+        }
68
+        .onChange(of: frontNumbers) {
69
+            updateNumbers()
70
+        }
71
+        .onChange(of: backNumbers) {
72
+            updateNumbers()
73
+        }
74
+    }
75
+    
76
+    // MARK: - 方法
77
+    
78
+    private func generateRandomNumbers() {
79
+        // 前区:从1-35中随机选择5个不重复的号码
80
+        frontNumbers = Array(Set((1...35).shuffled().prefix(5))).sorted()
81
+        
82
+        // 后区:从1-12中随机选择2个不重复的号码
83
+        backNumbers = Array(Set((1...12).shuffled().prefix(2))).sorted()
84
+    }
85
+    
86
+    private func clearAllNumbers() {
87
+        withAnimation {
88
+            frontNumbers.removeAll()
89
+            backNumbers.removeAll()
90
+        }
91
+    }
92
+    
93
+    private func updateNumbers() {
94
+        let frontStr = frontNumbers.sorted().map { String(format: "%02d", $0) }.joined(separator: " ")
95
+        let backStr = backNumbers.sorted().map { String(format: "%02d", $0) }.joined(separator: " ")
96
+        numbers = "\(frontStr) + \(backStr)"
97
+    }
98
+    
99
+}
100
+
101
+// MARK: - 大乐透专用已选号码视图
102
+struct SelectedNumbersViewSuperLotto: View {
103
+    let fronts: [Int]
104
+    let backs: [Int]
105
+    let onClear: () -> Void
106
+    
107
+    var body: some View {
108
+        HStack(spacing: 8) {
109
+            // 标签
110
+            Capsule()
111
+                .fill(Color(.gray).opacity(0.1))
112
+                .frame(width: 36, height: 28)
113
+                .overlay(
114
+                    Text("已选")
115
+                        .font(.caption2)
116
+                        .fontWeight(.medium)
117
+                        .foregroundColor(Color(.black))
118
+                )
119
+            
120
+            // 号码显示
121
+            ScrollView(.horizontal, showsIndicators: false) {
122
+                HStack(spacing: 6) {
123
+                    // 前区号码
124
+                    ForEach(fronts.sorted(), id: \.self) { number in
125
+                        Text(String(format: "%02d", number))
126
+                            .font(.system(size: 14, weight: .medium, design: .monospaced))
127
+                            .frame(width: 28, height: 28)
128
+                            .background(Color.orange.opacity(0.15))
129
+                            .foregroundColor(Color.orange)
130
+                            .clipShape(Circle())
131
+                    }
132
+                    
133
+                    // 后区号码
134
+                    ForEach(backs.sorted(), id: \.self) { number in
135
+                        Text(String(format: "%02d", number))
136
+                            .font(.system(size: 14, weight: .medium, design: .monospaced))
137
+                            .frame(width: 28, height: 28)
138
+                            .background(Color.blue.opacity(0.15))
139
+                            .foregroundColor(Color.blue)
140
+                            .clipShape(Circle())
141
+                    }
142
+                }
143
+            }
144
+            
145
+            Spacer()
146
+            
147
+            // 清空按钮
148
+            if !fronts.isEmpty || !backs.isEmpty {
149
+                Button(action: {
150
+                    withAnimation {
151
+                        onClear()
152
+                    }
153
+                }) {
154
+                    Image(systemName: "xmark.circle.fill")
155
+                        .font(.system(size: 16))
156
+                        .foregroundColor(.secondary)
157
+                        .padding(4)
158
+                }
159
+            }
160
+        }
161
+        .padding(.horizontal, 16)
162
+        .padding(.vertical, 12)
163
+        .background(Color(.systemBackground))
164
+        .cornerRadius(10)
165
+        .shadow(color: Color.black.opacity(0.05), radius: 2, x: 0, y: 1)
166
+    }
167
+}

+ 2
- 2
LotteryTracker/Views/AddTicket/TicketInputView.swift Прегледај датотеку

@@ -31,9 +31,9 @@ struct TicketInputView: View {
31 31
             if (selectedType == .doubleColorBall) {
32 32
                 DoubleColorBallSection(numbers: $numbers)
33 33
             } else if (selectedType == .superLotto) {
34
-                numbersInputSection
34
+                SuperLottoSection(numbers: $numbers)
35 35
             } else if (selectedType == .happy8) {
36
-                numbersInputSection
36
+                HappyEightSection(numbers: $numbers)
37 37
             } else if (selectedType == .sevenStar) {
38 38
                 numbersInputSection
39 39
             } else {