瀏覽代碼

修改一些显示问题

afan 5 天之前
父節點
當前提交
5af5150a33
共有 29 個文件被更改,包括 701 次插入381 次删除
  1. 2
    2
      LotteryTracker.xcodeproj/project.pbxproj
  2. 78
    0
      LotteryTracker.xcodeproj/xcshareddata/xcschemes/LotteryTracker.xcscheme
  3. 24
    0
      LotteryTracker.xcodeproj/xcuserdata/aaa.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
  4. 8
    0
      LotteryTracker.xcodeproj/xcuserdata/aaa.xcuserdatad/xcschemes/xcschememanagement.plist
  5. 5
    1
      LotteryTracker/App/AppState.swift
  6. 14
    1
      LotteryTracker/App/LotteryTrackerApp.swift
  7. 1
    1
      LotteryTracker/App/PersistenceController.swift
  8. 3
    0
      LotteryTracker/Assets.xcassets/AppIcon.appiconset/Contents.json
  9. 二進制
      LotteryTracker/Assets.xcassets/AppIcon.appiconset/已移除背景的生成特定比例向日葵图片 (1) 1.png
  10. 二進制
      LotteryTracker/Assets.xcassets/AppIcon.appiconset/已移除背景的生成特定比例向日葵图片 (1) 2.png
  11. 二進制
      LotteryTracker/Assets.xcassets/AppIcon.appiconset/已移除背景的生成特定比例向日葵图片 (1).png
  12. 2
    2
      LotteryTracker/LotteryTracker.xcdatamodeld/LotteryTracker.xcdatamodel/contents
  13. 20
    7
      LotteryTracker/Models/LotteryTicket+Extensions.swift
  14. 4
    4
      LotteryTracker/Services/DemoDataGenerator.swift
  15. 5
    5
      LotteryTracker/Services/LotteryChecker.swift
  16. 23
    23
      LotteryTracker/Services/StatisticsService.swift
  17. 1
    1
      LotteryTracker/Utils/Formatters.swift
  18. 6
    6
      LotteryTracker/ViewModels/AddTicketViewModel.swift
  19. 6
    3
      LotteryTracker/ViewModels/HomeViewModel.swift
  20. 5
    5
      LotteryTracker/Views/AddTicket/AmountInputView.swift
  21. 12
    4
      LotteryTracker/Views/Home/HomeView.swift
  22. 4
    4
      LotteryTracker/Views/Home/StatsCard.swift
  23. 143
    0
      LotteryTracker/Views/Home/TicketDetailView.swift
  24. 5
    5
      LotteryTracker/Views/Home/TicketRow.swift
  25. 24
    24
      LotteryTracker/Views/My/MyView.swift
  26. 0
    279
      LotteryTracker/Views/My/SettingsView.swift
  27. 89
    0
      LotteryTracker/Views/Settings/DeveloperView.swift
  28. 213
    0
      LotteryTracker/Views/Settings/SettingsView.swift
  29. 4
    4
      LotteryTracker/Views/Statistics/StatisticsView.swift

+ 2
- 2
LotteryTracker.xcodeproj/project.pbxproj 查看文件

@@ -160,7 +160,7 @@
160 160
 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
161 161
 				COPY_PHASE_STRIP = NO;
162 162
 				DEBUG_INFORMATION_FORMAT = dwarf;
163
-				DEVELOPMENT_TEAM = 85DNK99BC3;
163
+				DEVELOPMENT_TEAM = U546LG93DF;
164 164
 				ENABLE_STRICT_OBJC_MSGSEND = YES;
165 165
 				ENABLE_TESTABILITY = YES;
166 166
 				ENABLE_USER_SCRIPT_SANDBOXING = YES;
@@ -224,7 +224,7 @@
224 224
 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
225 225
 				COPY_PHASE_STRIP = NO;
226 226
 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
227
-				DEVELOPMENT_TEAM = 85DNK99BC3;
227
+				DEVELOPMENT_TEAM = U546LG93DF;
228 228
 				ENABLE_NS_ASSERTIONS = NO;
229 229
 				ENABLE_STRICT_OBJC_MSGSEND = YES;
230 230
 				ENABLE_USER_SCRIPT_SANDBOXING = YES;

+ 78
- 0
LotteryTracker.xcodeproj/xcshareddata/xcschemes/LotteryTracker.xcscheme 查看文件

@@ -0,0 +1,78 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<Scheme
3
+   LastUpgradeVersion = "2620"
4
+   version = "1.7">
5
+   <BuildAction
6
+      parallelizeBuildables = "YES"
7
+      buildImplicitDependencies = "YES"
8
+      buildArchitectures = "Automatic">
9
+      <BuildActionEntries>
10
+         <BuildActionEntry
11
+            buildForTesting = "YES"
12
+            buildForRunning = "YES"
13
+            buildForProfiling = "YES"
14
+            buildForArchiving = "YES"
15
+            buildForAnalyzing = "YES">
16
+            <BuildableReference
17
+               BuildableIdentifier = "primary"
18
+               BlueprintIdentifier = "00CDA5D52F212364003E59B4"
19
+               BuildableName = "LotteryTracker.app"
20
+               BlueprintName = "LotteryTracker"
21
+               ReferencedContainer = "container:LotteryTracker.xcodeproj">
22
+            </BuildableReference>
23
+         </BuildActionEntry>
24
+      </BuildActionEntries>
25
+   </BuildAction>
26
+   <TestAction
27
+      buildConfiguration = "Debug"
28
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
29
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
30
+      shouldUseLaunchSchemeArgsEnv = "YES"
31
+      shouldAutocreateTestPlan = "YES">
32
+   </TestAction>
33
+   <LaunchAction
34
+      buildConfiguration = "Debug"
35
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
36
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
37
+      launchStyle = "0"
38
+      useCustomWorkingDirectory = "NO"
39
+      ignoresPersistentStateOnLaunch = "NO"
40
+      debugDocumentVersioning = "YES"
41
+      debugServiceExtension = "internal"
42
+      allowLocationSimulation = "YES">
43
+      <BuildableProductRunnable
44
+         runnableDebuggingMode = "0">
45
+         <BuildableReference
46
+            BuildableIdentifier = "primary"
47
+            BlueprintIdentifier = "00CDA5D52F212364003E59B4"
48
+            BuildableName = "LotteryTracker.app"
49
+            BlueprintName = "LotteryTracker"
50
+            ReferencedContainer = "container:LotteryTracker.xcodeproj">
51
+         </BuildableReference>
52
+      </BuildableProductRunnable>
53
+   </LaunchAction>
54
+   <ProfileAction
55
+      buildConfiguration = "Release"
56
+      shouldUseLaunchSchemeArgsEnv = "YES"
57
+      savedToolIdentifier = ""
58
+      useCustomWorkingDirectory = "NO"
59
+      debugDocumentVersioning = "YES">
60
+      <BuildableProductRunnable
61
+         runnableDebuggingMode = "0">
62
+         <BuildableReference
63
+            BuildableIdentifier = "primary"
64
+            BlueprintIdentifier = "00CDA5D52F212364003E59B4"
65
+            BuildableName = "LotteryTracker.app"
66
+            BlueprintName = "LotteryTracker"
67
+            ReferencedContainer = "container:LotteryTracker.xcodeproj">
68
+         </BuildableReference>
69
+      </BuildableProductRunnable>
70
+   </ProfileAction>
71
+   <AnalyzeAction
72
+      buildConfiguration = "Debug">
73
+   </AnalyzeAction>
74
+   <ArchiveAction
75
+      buildConfiguration = "Release"
76
+      revealArchiveInOrganizer = "YES">
77
+   </ArchiveAction>
78
+</Scheme>

+ 24
- 0
LotteryTracker.xcodeproj/xcuserdata/aaa.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist 查看文件

@@ -0,0 +1,24 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<Bucket
3
+   uuid = "CE76B2B6-5786-4FCE-9737-C911B3404806"
4
+   type = "1"
5
+   version = "2.0">
6
+   <Breakpoints>
7
+      <BreakpointProxy
8
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
9
+         <BreakpointContent
10
+            uuid = "BF48DEC5-7F87-4A19-86EF-4B87FD6246FD"
11
+            shouldBeEnabled = "No"
12
+            ignoreCount = "0"
13
+            continueAfterRunningActions = "No"
14
+            filePath = "LotteryTracker/App/LotteryTrackerApp.swift"
15
+            startingColumnNumber = "9223372036854775807"
16
+            endingColumnNumber = "9223372036854775807"
17
+            startingLineNumber = "5"
18
+            endingLineNumber = "5"
19
+            landmarkName = "LotteryTrackerApp"
20
+            landmarkType = "14">
21
+         </BreakpointContent>
22
+      </BreakpointProxy>
23
+   </Breakpoints>
24
+</Bucket>

+ 8
- 0
LotteryTracker.xcodeproj/xcuserdata/aaa.xcuserdatad/xcschemes/xcschememanagement.plist 查看文件

@@ -10,5 +10,13 @@
10 10
 			<integer>0</integer>
11 11
 		</dict>
12 12
 	</dict>
13
+	<key>SuppressBuildableAutocreation</key>
14
+	<dict>
15
+		<key>00CDA5D52F212364003E59B4</key>
16
+		<dict>
17
+			<key>primary</key>
18
+			<true/>
19
+		</dict>
20
+	</dict>
13 21
 </dict>
14 22
 </plist>

+ 5
- 1
LotteryTracker/App/AppState.swift 查看文件

@@ -57,11 +57,15 @@ class AppState: ObservableObject {
57 57
 // Tab枚举
58 58
 enum Tab: Int {
59 59
     case home = 0
60
-    case my = 1
60
+    case statistics = 1
61
+    case settings = 2
62
+    case my = 3
61 63
     
62 64
     var title: String {
63 65
         switch self {
64 66
         case .home: return "首页"
67
+        case .statistics: return "统计"
68
+        case .settings: return "设置"
65 69
         case .my: return "我的"
66 70
         }
67 71
     }

+ 14
- 1
LotteryTracker/App/LotteryTrackerApp.swift 查看文件

@@ -70,12 +70,25 @@ struct ContentView: View {
70 70
     
71 71
     var body: some View {
72 72
         TabView(selection: $appState.selectedTab) {
73
+            // 首页界面
73 74
             HomeView()
74 75
                 .tabItem {
75 76
                     Label("首页", systemImage: "house.fill")
76 77
                 }
77 78
                 .tag(Tab.home)
78
-            
79
+            // 统计界面
80
+            StatisticsView()
81
+                .tabItem {
82
+                    Label("统计", systemImage: "chart.bar.fill")
83
+                }
84
+                .tag(Tab.statistics)
85
+            // 设置界面
86
+            SettingsView()
87
+                .tabItem {
88
+                    Label("设置", systemImage: "gearshape.fill")
89
+                }
90
+                .tag(Tab.settings)
91
+            // 我的界面
79 92
             MyView()
80 93
                 .tabItem {
81 94
                     Label("我的", systemImage: "person.fill")

+ 1
- 1
LotteryTracker/App/PersistenceController.swift 查看文件

@@ -18,7 +18,7 @@ class PersistenceController: ObservableObject {
18 18
             newTicket.type = i % 2 == 0 ? "双色球" : "大乐透"
19 19
             newTicket.numbers = "预览号码 \(i)"
20 20
             newTicket.betCount = Int16(i % 3 + 1)
21
-            newTicket.amount = Double(newTicket.betCount) * 2.0
21
+            newTicket.amount = Float(newTicket.betCount * 2)
22 22
             newTicket.date = Date().addingTimeInterval(Double(-i) * 86400)
23 23
             newTicket.drawDate = Date().addingTimeInterval(Double(i) * 86400)
24 24
             newTicket.status = i % 3 == 0 ? "待开奖" : (i % 3 == 1 ? "已中奖" : "未中奖")

+ 3
- 0
LotteryTracker/Assets.xcassets/AppIcon.appiconset/Contents.json 查看文件

@@ -1,6 +1,7 @@
1 1
 {
2 2
   "images" : [
3 3
     {
4
+      "filename" : "已移除背景的生成特定比例向日葵图片 (1).png",
4 5
       "idiom" : "universal",
5 6
       "platform" : "ios",
6 7
       "size" : "1024x1024"
@@ -12,6 +13,7 @@
12 13
           "value" : "dark"
13 14
         }
14 15
       ],
16
+      "filename" : "已移除背景的生成特定比例向日葵图片 (1) 1.png",
15 17
       "idiom" : "universal",
16 18
       "platform" : "ios",
17 19
       "size" : "1024x1024"
@@ -23,6 +25,7 @@
23 25
           "value" : "tinted"
24 26
         }
25 27
       ],
28
+      "filename" : "已移除背景的生成特定比例向日葵图片 (1) 2.png",
26 29
       "idiom" : "universal",
27 30
       "platform" : "ios",
28 31
       "size" : "1024x1024"

二進制
LotteryTracker/Assets.xcassets/AppIcon.appiconset/已移除背景的生成特定比例向日葵图片 (1) 1.png 查看文件


二進制
LotteryTracker/Assets.xcassets/AppIcon.appiconset/已移除背景的生成特定比例向日葵图片 (1) 2.png 查看文件


二進制
LotteryTracker/Assets.xcassets/AppIcon.appiconset/已移除背景的生成特定比例向日葵图片 (1).png 查看文件


+ 2
- 2
LotteryTracker/LotteryTracker.xcdatamodeld/LotteryTracker.xcdatamodel/contents 查看文件

@@ -1,13 +1,13 @@
1 1
 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2 2
 <model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="24512" systemVersion="24G419" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
3 3
     <entity name="LotteryTicket" representedClassName="LotteryTicket" syncable="YES" codeGenerationType="class">
4
-        <attribute name="amount" attributeType="Double" defaultValueString="0" usesScalarValueType="YES"/>
4
+        <attribute name="amount" attributeType="Float" defaultValueString="0" usesScalarValueType="YES"/>
5 5
         <attribute name="betCount" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
6 6
         <attribute name="date" attributeType="Date" defaultDateTimeInterval="790702140" usesScalarValueType="NO"/>
7 7
         <attribute name="drawDate" attributeType="Date" defaultDateTimeInterval="790702200" usesScalarValueType="NO"/>
8 8
         <attribute name="id" attributeType="UUID" usesScalarValueType="NO"/>
9 9
         <attribute name="numbers" optional="YES" attributeType="String"/>
10
-        <attribute name="prizeAmount" attributeType="Double" defaultValueString="0" usesScalarValueType="YES"/>
10
+        <attribute name="prizeAmount" attributeType="Float" defaultValueString="0" usesScalarValueType="YES"/>
11 11
         <attribute name="status" attributeType="String" defaultValueString="pending"/>
12 12
         <attribute name="type" attributeType="String"/>
13 13
     </entity>

+ 20
- 7
LotteryTracker/Models/LotteryTicket+Extensions.swift 查看文件

@@ -1,3 +1,10 @@
1
+//
2
+//  LotteryTicket+Extensions.swift
3
+//  LotteryTracker
4
+//
5
+//  Created by aaa on 2026/1/21.
6
+//
7
+
1 8
 import Foundation
2 9
 internal import CoreData
3 10
 
@@ -48,7 +55,7 @@ extension LotteryTicket {
48 55
     }
49 56
     
50 57
     // 计算属性:计算盈亏
51
-    var profit: Double {
58
+    var profit: Float {
52 59
         return prizeAmount - amount
53 60
     }
54 61
     
@@ -65,6 +72,17 @@ extension LotteryTicket {
65 72
         formatter.dateFormat = "yyyy-MM-dd"
66 73
         return formatter.string(from: drawDate ?? Date())
67 74
     }
75
+    
76
+    // 计算属性:获取号码数组
77
+    var numberArray: [String] {
78
+        guard let numbers = numbers, !numbers.isEmpty else {
79
+            return []
80
+        }
81
+        
82
+        // 按空格或逗号分割
83
+        let components = numbers.components(separatedBy: CharacterSet(charactersIn: " "))
84
+        return components.filter { !$0.isEmpty }
85
+    }
68 86
 }
69 87
 
70 88
 // 扩展 FetchRequest
@@ -87,10 +105,5 @@ extension LotteryTicket {
87 105
         ]
88 106
         return request
89 107
     }
90
-}//
91
-//  LotteryTicket+Extensions.swift
92
-//  LotteryTracker
93
-//
94
-//  Created by aaa on 2026/1/21.
95
-//
108
+}
96 109
 

+ 4
- 4
LotteryTracker/Services/DemoDataGenerator.swift 查看文件

@@ -38,8 +38,8 @@ class DemoDataGenerator {
38 38
             ticket.betCount = Int16.random(in: 1...10)
39 39
             
40 40
             // 随机金额(每注2-20元)
41
-            let amountPerBet = Double.random(in: 2...20)
42
-            ticket.amount = Double(ticket.betCount) * amountPerBet
41
+            let amountPerBet: Int16 = 2
42
+            ticket.amount = Float(ticket.betCount * amountPerBet)
43 43
             
44 44
             // 随机购买日期(过去60天内)
45 45
             let daysAgo = Int.random(in: 0...60)
@@ -55,7 +55,7 @@ class DemoDataGenerator {
55 55
             let status = Int.random(in: 0...100)
56 56
             if status < 30 {
57 57
                 ticket.ticketStatus = .won
58
-                ticket.prizeAmount = ticket.amount * Double.random(in: 1...50)
58
+                ticket.prizeAmount = ticket.amount * Float.random(in: 1...50)
59 59
             } else if status < 60 {
60 60
                 ticket.ticketStatus = .lost
61 61
                 ticket.prizeAmount = 0
@@ -64,7 +64,7 @@ class DemoDataGenerator {
64 64
                 ticket.prizeAmount = 0
65 65
             }
66 66
             
67
-            print("🎫 生成记录 \(i)/\(count): \(type.rawValue), ¥\(ticket.amount, default: "%.2f")")
67
+            print("🎫 生成记录 \(i)/\(count): \(type.rawValue), " + Formatters.formatCurrency(ticket.amount))
68 68
         }
69 69
         
70 70
         do {

+ 5
- 5
LotteryTracker/Services/LotteryChecker.swift 查看文件

@@ -40,7 +40,7 @@ class LotteryChecker {
40 40
             if drawDate <= now {
41 41
                 // 模拟开奖结果(实际应用中应调用真实API)
42 42
                 let isWin = Bool.random()
43
-                let winMultiplier = Double.random(in: 1...100)
43
+                let winMultiplier = Float.random(in: 1...100)
44 44
                 
45 45
                 ticket.ticketStatus = isWin ? .won : .lost
46 46
                 ticket.prizeAmount = isWin ? ticket.amount * winMultiplier : 0
@@ -114,7 +114,7 @@ class LotteryChecker {
114 114
         
115 115
         // 模拟开奖
116 116
         let isWin = Bool.random()
117
-        let winMultiplier = Double.random(in: 1...50)
117
+        let winMultiplier = Float.random(in: 1...50)
118 118
         
119 119
         ticket.ticketStatus = isWin ? .won : .lost
120 120
         ticket.prizeAmount = isWin ? ticket.amount * winMultiplier : 0
@@ -151,10 +151,10 @@ class NotificationService {
151 151
         }
152 152
     }
153 153
     
154
-    func sendWinNotification(amount: Double, ticketType: String) {
154
+    func sendWinNotification(amount: Float, ticketType: String) {
155 155
         let content = UNMutableNotificationContent()
156 156
         content.title = "🎉 恭喜中奖!"
157
-        content.body = "您的\(ticketType)中奖了!奖金:¥\(String(format: "%.2f", amount))"
157
+        content.body = "您的\(ticketType)中奖了!奖金:" + Formatters.formatCurrency(amount)
158 158
         content.sound = .default
159 159
         
160 160
         let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
@@ -193,4 +193,4 @@ class NotificationService {
193 193
         
194 194
         center.add(request)
195 195
     }
196
-}
196
+}

+ 23
- 23
LotteryTracker/Services/StatisticsService.swift 查看文件

@@ -188,8 +188,8 @@ class StatisticsService: ObservableObject {
188 188
                     return
189 189
                 }
190 190
                 
191
-                var distribution: [LotteryType: Double] = [:]
192
-                let totalAmount = tickets.reduce(0.0) { $0 + $1.amount }
191
+                var distribution: [LotteryType: Float] = [:]
192
+                let totalAmount = tickets.reduce(0) { $0 + $1.amount }
193 193
                 
194 194
                 for ticket in tickets {
195 195
                     distribution[ticket.lotteryType, default: 0] += ticket.amount
@@ -199,7 +199,7 @@ class StatisticsService: ObservableObject {
199 199
                     TypeDistribution(
200 200
                         type: type,
201 201
                         amount: amount,
202
-                        percentage: totalAmount > 0 ? (amount / totalAmount) * 100 : 0
202
+                        percentage: totalAmount > 0 ? Float((amount / totalAmount)) * 100 : 0
203 203
                     )
204 204
                 }.sorted { $0.amount > $1.amount }
205 205
                 
@@ -230,7 +230,7 @@ class StatisticsService: ObservableObject {
230 230
                 let endDate = Date()
231 231
                 let startDate = calendar.date(byAdding: .day, value: -days, to: endDate)!
232 232
                 
233
-                var dailyProfits: [Date: Double] = [:]
233
+                var dailyProfits: [Date: Float] = [:]
234 234
                 
235 235
                 for ticket in tickets {
236 236
                     guard let date = ticket.date else { continue }
@@ -258,20 +258,20 @@ class StatisticsService: ObservableObject {
258 258
     
259 259
     // 计算统计数据
260 260
     private func calculateStats(for tickets: [LotteryTicket]) -> MonthStats {
261
-        let totalSpent = tickets.reduce(0.0) { $0 + $1.amount }
262
-        let totalPrize = tickets.reduce(0.0) { $0 + $1.prizeAmount }
261
+        let totalSpent = tickets.reduce(0) { $0 + $1.amount }
262
+        let totalPrize = tickets.reduce(0) { $0 + $1.prizeAmount }
263 263
         let profit = totalPrize - totalSpent
264 264
         
265 265
         let winningTickets = tickets.filter { $0.ticketStatus == .won }.count
266 266
         // 修复:winningTickets 已经是 Int,不需要 .count
267
-        let winRate = tickets.isEmpty ? 0 : Double(winningTickets) / Double(tickets.count) * 100
267
+        let winRate = tickets.isEmpty ? 0 : Float(winningTickets) / Float(tickets.count) * 100
268 268
         
269 269
         return MonthStats(
270
-            ticketCount: tickets.count,
270
+            ticketCount: Int16(tickets.count),
271 271
             totalSpent: totalSpent,
272 272
             totalProfit: profit,
273 273
             winRate: winRate,
274
-            averageBet: tickets.isEmpty ? 0 : totalSpent / Double(tickets.count)
274
+            averageBet: tickets.isEmpty ? 0 : totalSpent / Float(tickets.count)
275 275
         )
276 276
     }
277 277
 }
@@ -286,25 +286,25 @@ struct MonthlyStats {
286 286
         self.lastMonth = lastMonth
287 287
     }
288 288
     
289
-    var profitChange: Double {
289
+    var profitChange: Float {
290 290
         guard lastMonth.totalSpent > 0 else { return 0 }
291 291
         return ((currentMonth.totalProfit - lastMonth.totalProfit) / lastMonth.totalProfit) * 100
292 292
     }
293 293
 }
294 294
 
295 295
 struct MonthStats {
296
-    let ticketCount: Int
297
-    let totalSpent: Double
298
-    let totalProfit: Double
299
-    let winRate: Double
300
-    let averageBet: Double
296
+    let ticketCount: Int16
297
+    let totalSpent: Float
298
+    let totalProfit: Float
299
+    let winRate: Float
300
+    let averageBet: Float
301 301
     
302 302
     init(
303
-        ticketCount: Int = 0,
304
-        totalSpent: Double = 0,
305
-        totalProfit: Double = 0,
306
-        winRate: Double = 0,
307
-        averageBet: Double = 0
303
+        ticketCount: Int16 = 0,
304
+        totalSpent: Float = 0,
305
+        totalProfit: Float = 0,
306
+        winRate: Float = 0,
307
+        averageBet: Float = 0
308 308
     ) {
309 309
         self.ticketCount = ticketCount
310 310
         self.totalSpent = totalSpent
@@ -317,12 +317,12 @@ struct MonthStats {
317 317
 struct TypeDistribution: Identifiable {
318 318
     let id = UUID()
319 319
     let type: LotteryType
320
-    let amount: Double
321
-    let percentage: Double
320
+    let amount: Float
321
+    let percentage: Float
322 322
 }
323 323
 
324 324
 struct DailyProfit: Identifiable {
325 325
     let id = UUID()
326 326
     let date: Date
327
-    let profit: Double
327
+    let profit: Float
328 328
 }

+ 1
- 1
LotteryTracker/Utils/Formatters.swift 查看文件

@@ -27,7 +27,7 @@ class Formatters {
27 27
     }()
28 28
     
29 29
     // 格式化金额
30
-    static func formatCurrency(_ amount: Double) -> String {
30
+    static func formatCurrency(_ amount: Float) -> String {
31 31
         currencyFormatter.string(from: NSNumber(value: amount)) ?? "¥0.00"
32 32
     }
33 33
     

+ 6
- 6
LotteryTracker/ViewModels/AddTicketViewModel.swift 查看文件

@@ -14,7 +14,7 @@ class AddTicketViewModel: ObservableObject {
14 14
     @Published var selectedType: LotteryType = .doubleColorBall
15 15
     @Published var numbers: String = ""
16 16
     @Published var betCount: Int = 1
17
-    @Published var amountPerBet: String = "2.00"
17
+    @Published var amountPerBet: String = "2"
18 18
     
19 19
     // 修改:改为购买日期(默认今天)
20 20
     @Published var purchaseDate: Date = Date()
@@ -29,9 +29,9 @@ class AddTicketViewModel: ObservableObject {
29 29
     @Published var errorMessage: String = ""
30 30
     
31 31
     // 计算属性
32
-    var totalAmount: Double {
33
-        let amount = Double(amountPerBet) ?? 0.0
34
-        return amount * Double(betCount)
32
+    var totalAmount: Float {
33
+        let amount = Int(amountPerBet) ?? 0
34
+        return Float(amount * betCount)
35 35
     }
36 36
     
37 37
     var formattedPurchaseDate: String {
@@ -148,14 +148,14 @@ class AddTicketViewModel: ObservableObject {
148 148
         ticket.date = purchaseDate  // 使用用户选择的购买日期
149 149
         ticket.drawDate = drawDate  // 自动计算的开奖日期
150 150
         ticket.ticketStatus = .pending
151
-        ticket.prizeAmount = 0.0
151
+        ticket.prizeAmount = 0
152 152
         
153 153
         do {
154 154
             try context.save()
155 155
             print("✅ 彩票记录保存成功")
156 156
             print("   类型: \(selectedType.rawValue)")
157 157
             print("   注数: \(betCount)")
158
-            print("   金额: ¥\(totalAmount)")
158
+            print("   金额: " + Formatters.formatCurrency(totalAmount))
159 159
             print("   购买: \(formattedPurchaseDate)")
160 160
             print("   开奖: \(formattedDrawDate)")
161 161
             return true

+ 6
- 3
LotteryTracker/ViewModels/HomeViewModel.swift 查看文件

@@ -5,8 +5,8 @@ import Combine
5 5
 
6 6
 class HomeViewModel: ObservableObject {
7 7
     // 发布统计数据
8
-    @Published var totalSpent: Double = 0.0
9
-    @Published var totalProfit: Double = 0.0
8
+    @Published var totalSpent: Float = 0
9
+    @Published var totalProfit: Float = 0
10 10
     @Published var totalTickets: Int = 0
11 11
     @Published var winningTickets: Int = 0
12 12
     @Published var pendingTickets: Int = 0
@@ -69,7 +69,10 @@ class HomeViewModel: ObservableObject {
69 69
             } else if Calendar.current.isDateInYesterday(date) {
70 70
                 return "昨天"
71 71
             } else {
72
-                formatter.dateFormat = "MM月dd日"
72
+                let formateYear = Calendar.current.component(.year, from: Date())
73
+                let currentYear = Calendar.current.component(.year, from: date)
74
+                
75
+                formatter.dateFormat = (currentYear == formateYear) ? "MM月dd日" : "yyyy年MM月dd日"
73 76
                 return formatter.string(from: date)
74 77
             }
75 78
         }

+ 5
- 5
LotteryTracker/Views/AddTicket/AmountInputView.swift 查看文件

@@ -12,7 +12,7 @@ struct AmountInputView: View {
12 12
     @Binding var amountPerBet: String
13 13
     @FocusState private var isAmountFocused: Bool
14 14
     
15
-    let totalAmount: Double
15
+    let totalAmount: Float
16 16
     
17 17
     // 常用注数选项
18 18
     private let betCountOptions = [1, 3, 5, 10, 20]
@@ -37,7 +37,7 @@ struct AmountInputView: View {
37 37
                             
38 38
                             Spacer()
39 39
                             
40
-                            Text("¥\(totalAmount, specifier: "%.2f")")
40
+                            Text(Formatters.formatCurrency(totalAmount))
41 41
                                 .font(.body.bold())
42 42
                                 .foregroundColor(.blue)
43 43
                         }
@@ -120,7 +120,7 @@ struct AmountInputView: View {
120 120
                                     amountPerBet = String(format: "%.2f", amount)
121 121
                                 }
122 122
                             }) {
123
-                                Text("¥\(amount, specifier: "%.0f")")
123
+                                Text(Formatters.formatCurrency(Float(amount)))
124 124
                                     .font(.system(size: 14))
125 125
                                     .padding(.horizontal, 12)
126 126
                                     .padding(.vertical, 6)
@@ -147,7 +147,7 @@ struct AmountInputView: View {
147 147
                             .font(.subheadline)
148 148
                             .foregroundColor(.secondary)
149 149
                         
150
-                        Text("¥\(totalAmount, specifier: "%.2f")")
150
+                        Text(Formatters.formatCurrency(totalAmount))
151 151
                             .font(.system(size: 32, weight: .bold))
152 152
                             .foregroundColor(.blue)
153 153
                     }
@@ -204,7 +204,7 @@ struct AmountInputViewPreviewWrapper: View {
204 204
         AmountInputView(
205 205
             betCount: $betCount,
206 206
             amountPerBet: $amountPerBet,
207
-            totalAmount: (Double(amountPerBet) ?? 0) * Double(betCount)
207
+            totalAmount: (Float(amountPerBet) ?? 0) * Float(betCount)
208 208
         )
209 209
         .padding()
210 210
         .previewLayout(.sizeThatFits)

+ 12
- 4
LotteryTracker/Views/Home/HomeView.swift 查看文件

@@ -5,6 +5,7 @@ struct HomeView: View {
5 5
     @Environment(\.managedObjectContext) private var viewContext
6 6
     @StateObject private var viewModel: HomeViewModel
7 7
     @State private var showingAddTicket = false
8
+    @State private var selectedTicket: LotteryTicket?
8 9
     
9 10
     private var ticketsUpdatedPublisher: NotificationCenter.Publisher {
10 11
         NotificationCenter.default.publisher(for: .ticketsUpdated)
@@ -18,7 +19,7 @@ struct HomeView: View {
18 19
     }
19 20
     
20 21
     var body: some View {
21
-        NavigationView {
22
+        NavigationStack {
22 23
             VStack(spacing: 0) {
23 24
                 // 顶部统计卡片
24 25
                 StatsCard(
@@ -33,6 +34,9 @@ struct HomeView: View {
33 34
                 // 记录列表或空状态
34 35
                 if viewModel.totalTickets > 0 {
35 36
                     ticketList
37
+                        .navigationDestination(item: $selectedTicket) { ticket in
38
+                            TicketDetailView(ticket: ticket)
39
+                        }
36 40
                 } else {
37 41
                     EmptyStateView(onAddTapped: {
38 42
                         // 触发添加按钮动作
@@ -41,7 +45,6 @@ struct HomeView: View {
41 45
                     .padding(.top, 80)
42 46
                 }
43 47
                 
44
-                Spacer()
45 48
             }
46 49
             .navigationTitle("彩票记录")
47 50
             .navigationBarTitleDisplayMode(.inline)
@@ -94,6 +97,10 @@ struct HomeView: View {
94 97
                 Section {
95 98
                     ForEach(viewModel.ticketsForDate(date), id: \.id) { ticket in
96 99
                         TicketRow(ticket: ticket)
100
+                            .contentShape(Rectangle()) // 让整个区域可点击
101
+                            .onTapGesture {
102
+                                selectedTicket = ticket
103
+                            }
97 104
                             .swipeActions(edge: .trailing) {
98 105
                                 Button(role: .destructive) {
99 106
                                     deleteTicket(ticket)
@@ -115,6 +122,7 @@ struct HomeView: View {
115 122
         .refreshable {
116 123
             viewModel.refreshData()
117 124
         }
125
+        .padding(.top, 10)
118 126
     }
119 127
     
120 128
     // 删除记录
@@ -146,10 +154,10 @@ struct HomeView_Previews: PreviewProvider {
146 154
             ticket.type = i % 2 == 0 ? "双色球" : "大乐透"
147 155
             ticket.numbers = "测试号码 \(i)"
148 156
             ticket.betCount = Int16(i % 3 + 1)
149
-            ticket.amount = Double(ticket.betCount) * 2.0
157
+            ticket.amount = Float(ticket.betCount) * 2.0
150 158
             ticket.date = Date().addingTimeInterval(Double(-i) * 86400)
151 159
             ticket.drawDate = Date().addingTimeInterval(Double(i) * 86400)
152
-            ticket.status = i % 3 == 0 ? "待开奖" : (i % 3 == 1 ? "已中奖" : "未中奖")
160
+            ticket.status = i % 3 == 0 ? "待开奖" : (i % 3 == 2 ? "已中奖" : "未中奖")
153 161
             ticket.prizeAmount = ticket.status == "已中奖" ? ticket.amount * 5 : 0
154 162
         }
155 163
         

+ 4
- 4
LotteryTracker/Views/Home/StatsCard.swift 查看文件

@@ -8,8 +8,8 @@
8 8
 import SwiftUI
9 9
 
10 10
 struct StatsCard: View {
11
-    let totalSpent: Double
12
-    let totalProfit: Double
11
+    let totalSpent: Float
12
+    let totalProfit: Float
13 13
     let totalTickets: Int
14 14
     let winningTickets: Int
15 15
     let pendingTickets: Int
@@ -22,7 +22,7 @@ struct StatsCard: View {
22 22
                     Text("总投入")
23 23
                         .font(.caption)
24 24
                         .foregroundColor(.secondary)
25
-                    Text("¥\(totalSpent, specifier: "%.2f")")
25
+                    Text(Formatters.formatCurrency(totalSpent))
26 26
                         .font(.title2.bold())
27 27
                         .foregroundColor(.primary)
28 28
                 }
@@ -33,7 +33,7 @@ struct StatsCard: View {
33 33
                     Text("总盈亏")
34 34
                         .font(.caption)
35 35
                         .foregroundColor(.secondary)
36
-                    Text("¥\(totalProfit, specifier: "%.2f")")
36
+                    Text(Formatters.formatCurrency(totalProfit))
37 37
                         .font(.title2.bold())
38 38
                         .foregroundColor(profitColor)
39 39
                 }

+ 143
- 0
LotteryTracker/Views/Home/TicketDetailView.swift 查看文件

@@ -0,0 +1,143 @@
1
+//
2
+//  TicketDetailView.swift
3
+//  LotteryTracker
4
+//
5
+//  Created by aaa on 2026/1/24.
6
+//
7
+
8
+import SwiftUI
9
+internal import CoreData
10
+
11
+struct TicketDetailView: View {
12
+    let ticket: LotteryTicket
13
+    @Environment(\.dismiss) private var dismiss
14
+    @Environment(\.managedObjectContext) private var context
15
+    
16
+    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)
43
+                    
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())
53
+                        }
54
+                    }
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) ? "已开奖" : "待开奖")
67
+                    
68
+                    if ticket.ticketStatus != .pending {
69
+                        InfoRow(
70
+                            title: "中奖金额",
71
+                            value: Formatters.formatCurrency(ticket.prizeAmount)
72
+                        )
73
+                        .foregroundColor(ticket.profit > 0 ? .green : .red)
74
+                    }
75
+                }
76
+                .padding()
77
+                .background(Color(.systemBackground))
78
+                .cornerRadius(15)
79
+                .shadow(color: .black.opacity(0.05), radius: 5)
80
+                .padding(.horizontal)
81
+            }
82
+            .padding(.vertical)
83
+        }
84
+        .navigationTitle("彩票详情")
85
+        .navigationBarTitleDisplayMode(.inline)
86
+        .toolbar {
87
+            ToolbarItem(placement: .navigationBarTrailing) {
88
+                Button("删除") {
89
+                    deleteTicket()
90
+                }
91
+                .foregroundColor(.red)
92
+            }
93
+        }
94
+    }
95
+    
96
+    private func deleteTicket() {
97
+        context.delete(ticket)
98
+        do {
99
+            try context.save()
100
+            dismiss()
101
+        } catch {
102
+            print("删除失败: \(error)")
103
+        }
104
+    }
105
+    
106
+    private var ticketColor: Color {
107
+        switch ticket.lotteryType {
108
+        case .doubleColorBall: return .red
109
+        case .superLotto: return .blue
110
+        case .welfare3D: return .purple
111
+        case .sevenStar: return .green
112
+        case .other: return .pink
113
+        }
114
+    }
115
+}
116
+
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
+    }
125
+    
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
+    }
132
+    
133
+    var body: some View {
134
+        HStack {
135
+            Text(title)
136
+                .foregroundColor(.secondary)
137
+            Spacer()
138
+            Text(value)
139
+                .fontWeight(.medium)
140
+        }
141
+        .padding(.vertical, 4)
142
+    }
143
+}

+ 5
- 5
LotteryTracker/Views/Home/TicketRow.swift 查看文件

@@ -25,7 +25,7 @@ struct TicketRow: View {
25 25
                     
26 26
                     Spacer()
27 27
                     
28
-                    Text("¥\(ticket.amount, specifier: "%.2f")")
28
+                    Text(Formatters.formatCurrency(ticket.amount))
29 29
                         .font(.headline)
30 30
                         .foregroundColor(.primary)
31 31
                 }
@@ -135,11 +135,11 @@ struct TicketRow_Previews: PreviewProvider {
135 135
         ticket.type = LotteryType.doubleColorBall.rawValue
136 136
         ticket.numbers = "01 02 03 04 05 06 07"  // 有号码的情况
137 137
         ticket.betCount = 2
138
-        ticket.amount = 4.0
138
+        ticket.amount = 4
139 139
         ticket.date = Date()
140 140
         ticket.drawDate = Date().addingTimeInterval(86400)
141 141
         ticket.status = TicketStatus.pending.rawValue
142
-        ticket.prizeAmount = 0.0
142
+        ticket.prizeAmount = 0
143 143
         
144 144
         // 测试无号码的情况
145 145
         let ticket2 = LotteryTicket(context: context)
@@ -147,11 +147,11 @@ struct TicketRow_Previews: PreviewProvider {
147 147
         ticket2.type = LotteryType.superLotto.rawValue
148 148
         ticket2.numbers = ""  // 空字符串
149 149
         ticket2.betCount = 1
150
-        ticket2.amount = 2.0
150
+        ticket2.amount = 2
151 151
         ticket2.date = Date()
152 152
         ticket2.drawDate = Date().addingTimeInterval(86400)
153 153
         ticket2.status = TicketStatus.won.rawValue
154
-        ticket2.prizeAmount = 10.0
154
+        ticket2.prizeAmount = 10
155 155
         
156 156
         return Group {
157 157
             TicketRow(ticket: ticket)

+ 24
- 24
LotteryTracker/Views/My/MyView.swift 查看文件

@@ -13,33 +13,33 @@ struct MyView: View {
13 13
     
14 14
     var body: some View {
15 15
         NavigationView {
16
-            TabView(selection: $selectedTab) {
17
-                // 统计页面
18
-                StatisticsView()
19
-                    .tabItem {
20
-                        Label("统计", systemImage: "chart.bar.fill")
21
-                    }
22
-                    .tag(0)
23
-                
24
-                // 设置页面
25
-                SettingsView()
26
-                    .tabItem {
27
-                        Label("设置", systemImage: "gearshape.fill")
28
-                    }
29
-                    .tag(1)
30
-            }
31
-            .navigationTitle(tabTitle)
32
-            .navigationBarTitleDisplayMode(.inline)
16
+//            TabView(selection: $selectedTab) {
17
+//                // 统计页面
18
+//                StatisticsView()
19
+//                    .tabItem {
20
+//                        Label("统计", systemImage: "chart.bar.fill")
21
+//                    }
22
+//                    .tag(0)
23
+//                
24
+//                // 设置页面
25
+//                SettingsView()
26
+//                    .tabItem {
27
+//                        Label("设置", systemImage: "gearshape.fill")
28
+//                    }
29
+//                    .tag(1)
30
+//            }
31
+//            .navigationTitle(tabTitle)
32
+//            .navigationBarTitleDisplayMode(.inline)
33 33
         }
34 34
     }
35 35
     
36
-    private var tabTitle: String {
37
-        switch selectedTab {
38
-        case 0: return "数据统计"
39
-        case 1: return "设置"
40
-        default: return "我的"
41
-        }
42
-    }
36
+//    private var tabTitle: String {
37
+//        switch selectedTab {
38
+//        case 0: return "数据统计"
39
+//        case 1: return "设置"
40
+//        default: return "我的"
41
+//        }
42
+//    }
43 43
 }
44 44
 
45 45
 // 预览

+ 0
- 279
LotteryTracker/Views/My/SettingsView.swift 查看文件

@@ -1,279 +0,0 @@
1
-import SwiftUI
2
-internal import CoreData
3
-
4
-struct SettingsView: View {
5
-    @Environment(\.managedObjectContext) private var viewContext
6
-    @EnvironmentObject private var appState: AppState
7
-    
8
-    @AppStorage("enableNotifications") private var enableNotifications = true
9
-    @AppStorage("autoCheckDraw") private var autoCheckDraw = true
10
-    @AppStorage("currencySymbol") private var currencySymbol = "¥"
11
-    
12
-    @State private var showingClearAlert = false
13
-    @State private var showingClearSuccess = false
14
-    @State private var showingClearError = false
15
-    @State private var clearErrorMessage = ""
16
-    @State private var isClearing = false
17
-    
18
-    var body: some View {
19
-        List {
20
-            // 通知设置
21
-            Section("通知设置") {
22
-                Toggle("开启通知提醒", isOn: $enableNotifications)
23
-                
24
-                if enableNotifications {
25
-                    Toggle("启动时检查开奖", isOn: $autoCheckDraw)
26
-                    
27
-                    Button("测试通知") {
28
-                        testNotification()
29
-                    }
30
-                    .foregroundColor(.blue)
31
-                }
32
-            }
33
-            
34
-            // 显示设置
35
-            Section("显示设置") {
36
-                Picker("货币符号", selection: $currencySymbol) {
37
-                    Text("¥").tag("¥")
38
-                    Text("$").tag("$")
39
-                    Text("€").tag("€")
40
-                    Text("£").tag("£")
41
-                }
42
-            }
43
-            
44
-            // 数据管理
45
-            Section("数据管理") {
46
-                Button("导出数据") {
47
-                    exportData()
48
-                }
49
-                .foregroundColor(.blue)
50
-                
51
-                Button("备份数据") {
52
-                    backupData()
53
-                }
54
-                .foregroundColor(.blue)
55
-                
56
-                if isClearing {
57
-                    HStack {
58
-                        ProgressView()
59
-                            .scaleEffect(0.8)
60
-                        Text("正在清空数据...")
61
-                            .foregroundColor(.secondary)
62
-                    }
63
-                } else {
64
-                    Button("清空所有数据") {
65
-                        showingClearAlert = true
66
-                    }
67
-                    .foregroundColor(.red)
68
-                }
69
-            }
70
-            
71
-            // 关于
72
-            Section("关于") {
73
-                HStack {
74
-                    Text("版本")
75
-                    Spacer()
76
-                    Text("1.0.0")
77
-                        .foregroundColor(.secondary)
78
-                }
79
-                
80
-                Button("检查更新") {
81
-                    checkForUpdates()
82
-                }
83
-                .foregroundColor(.blue)
84
-                
85
-                Button("用户协议") {
86
-                    // 显示用户协议
87
-                }
88
-                .foregroundColor(.blue)
89
-                
90
-                Button("隐私政策") {
91
-                    // 显示隐私政策
92
-                }
93
-                .foregroundColor(.blue)
94
-            }
95
-            
96
-            // 开发者选项
97
-            Section {
98
-                NavigationLink("开发者选项") {
99
-                    DeveloperView()
100
-                }
101
-            }
102
-        }
103
-        .navigationTitle("设置")
104
-        .navigationBarTitleDisplayMode(.inline)
105
-        .alert("清空数据", isPresented: $showingClearAlert) {
106
-            Button("取消", role: .cancel) { }
107
-            Button("清空", role: .destructive) {
108
-                clearAllData()
109
-            }
110
-        } message: {
111
-            Text("这将删除所有彩票记录,此操作不可撤销。确定要继续吗?")
112
-        }
113
-        .alert("清空成功", isPresented: $showingClearSuccess) {
114
-            Button("确定", role: .cancel) { }
115
-        } message: {
116
-            Text("所有数据已成功清空")
117
-        }
118
-        .alert("清空失败", isPresented: $showingClearError) {
119
-            Button("确定", role: .cancel) { }
120
-        } message: {
121
-            Text(clearErrorMessage)
122
-        }
123
-    }
124
-    
125
-    // MARK: - 方法
126
-    
127
-    private func testNotification() {
128
-        print("测试通知功能")
129
-        // 这里可以添加测试通知的代码
130
-    }
131
-    
132
-    private func exportData() {
133
-        print("导出数据功能")
134
-    }
135
-    
136
-    private func backupData() {
137
-        print("数据备份功能")
138
-    }
139
-    
140
-    // 清空所有数据(可靠版本)
141
-    private func clearAllData() {
142
-        isClearing = true
143
-        
144
-        // 在后台线程执行删除操作
145
-        DispatchQueue.global(qos: .userInitiated).async {
146
-            let fetchRequest: NSFetchRequest<NSFetchRequestResult> = LotteryTicket.fetchRequest()
147
-            
148
-            do {
149
-                // 先获取所有记录
150
-                if let tickets = try? self.viewContext.fetch(fetchRequest) as? [LotteryTicket] {
151
-                    print("找到 \(tickets.count) 条记录需要删除")
152
-                    
153
-                    // 逐一删除
154
-                    for ticket in tickets {
155
-                        self.viewContext.delete(ticket)
156
-                    }
157
-                    
158
-                    // 保存更改
159
-                    if self.viewContext.hasChanges {
160
-                        try self.viewContext.save()
161
-                        print("✅ 保存删除操作")
162
-                    }
163
-                    
164
-                    // 重置上下文
165
-                    self.viewContext.reset()
166
-                }
167
-                
168
-                DispatchQueue.main.async {
169
-                    self.isClearing = false
170
-                    self.showingClearSuccess = true
171
-                    
172
-                    // 发送数据更新通知,标记为清空操作
173
-                    NotificationCenter.default.post(
174
-                        name: .ticketsUpdated,
175
-                        object: nil,
176
-                        userInfo: ["isClearOperation": true]  // 添加标识
177
-                    )
178
-                    
179
-                    // 可选:发送应用重置通知
180
-                    NotificationCenter.default.post(name: .appReset, object: nil)
181
-                    
182
-                    print("✅ 所有数据已成功清空,已发送清空通知")
183
-                }
184
-            } catch {
185
-                DispatchQueue.main.async {
186
-                    self.isClearing = false
187
-                    self.clearErrorMessage = "清空失败: \(error.localizedDescription)"
188
-                    self.showingClearError = true
189
-                    
190
-                    print("❌ 清空数据失败: \(error)")
191
-                }
192
-            }
193
-        }
194
-    }
195
-    
196
-    private func checkForUpdates() {
197
-        print("检查更新")
198
-    }
199
-}
200
-
201
-// 开发者视图
202
-struct DeveloperView: View {
203
-    @Environment(\.managedObjectContext) private var context
204
-    @State private var showingTestDataAlert = false
205
-    
206
-    var body: some View {
207
-        List {
208
-            Section("测试功能") {
209
-                Button("生成测试数据") {
210
-                    generateTestData()
211
-                }
212
-                
213
-                Button("检查所有开奖") {
214
-                    checkAllDraws()
215
-                }
216
-                
217
-                Button("重置所有设置") {
218
-                    resetSettings()
219
-                }
220
-            }
221
-            
222
-            Section("调试信息") {
223
-                HStack {
224
-                    Text("数据库路径")
225
-                    Spacer()
226
-                    Text(getDatabasePath())
227
-                        .font(.caption)
228
-                        .foregroundColor(.secondary)
229
-                        .lineLimit(1)
230
-                }
231
-                
232
-                Button("显示日志") {
233
-                    showLogs()
234
-                }
235
-            }
236
-        }
237
-        .navigationTitle("开发者选项")
238
-        .alert("测试数据", isPresented: $showingTestDataAlert) {
239
-            Button("确定", role: .cancel) { }
240
-        } message: {
241
-            Text("已生成50条测试数据")
242
-        }
243
-    }
244
-    
245
-    private func generateTestData() {
246
-        print("生成测试数据功能")
247
-        // 这里可以添加生成测试数据的代码
248
-    }
249
-    
250
-    private func checkAllDraws() {
251
-        print("检查所有开奖")
252
-        // 这里可以添加检查开奖的代码
253
-    }
254
-    
255
-    private func resetSettings() {
256
-        if let bundleID = Bundle.main.bundleIdentifier {
257
-            UserDefaults.standard.removePersistentDomain(forName: bundleID)
258
-        }
259
-        print("✅ 所有设置已重置")
260
-    }
261
-    
262
-    private func getDatabasePath() -> String {
263
-        let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
264
-        return paths.first?.absoluteString ?? "未知"
265
-    }
266
-    
267
-    private func showLogs() {
268
-        print("显示日志...")
269
-    }
270
-}
271
-
272
-// 预览
273
-struct SettingsView_Previews: PreviewProvider {
274
-    static var previews: some View {
275
-        NavigationView {
276
-            SettingsView()
277
-        }
278
-    }
279
-}

+ 89
- 0
LotteryTracker/Views/Settings/DeveloperView.swift 查看文件

@@ -0,0 +1,89 @@
1
+//
2
+//  DeveloperView.swift
3
+//  LotteryTracker
4
+//
5
+//  Created by aaa on 2026/1/24.
6
+//
7
+
8
+import SwiftUI
9
+import Foundation
10
+
11
+// 开发者视图
12
+struct DeveloperView: View {
13
+    @Environment(\.managedObjectContext) private var context
14
+    @State private var showingTestDataAlert = false
15
+    
16
+    var body: some View {
17
+        List {
18
+            Section("测试功能") {
19
+                Button("生成测试数据") {
20
+                    generateTestData()
21
+                }
22
+                
23
+                Button("检查所有开奖") {
24
+                    checkAllDraws()
25
+                }
26
+                
27
+                Button("重置所有设置") {
28
+                    resetSettings()
29
+                }
30
+            }
31
+            
32
+            Section("调试信息") {
33
+                HStack {
34
+                    Text("数据库路径")
35
+                    Spacer()
36
+                    Text(getDatabasePath())
37
+                        .font(.caption)
38
+                        .foregroundColor(.secondary)
39
+                        .lineLimit(1)
40
+                }
41
+                
42
+                Button("显示日志") {
43
+                    showLogs()
44
+                }
45
+            }
46
+        }
47
+        .navigationTitle("开发者选项")
48
+        .alert("测试数据", isPresented: $showingTestDataAlert) {
49
+            Button("确定", role: .cancel) { }
50
+        } message: {
51
+            Text("已生成50条测试数据")
52
+        }
53
+    }
54
+    
55
+    private func generateTestData() {
56
+        print("生成测试数据功能")
57
+        // 这里可以添加生成测试数据的代码
58
+    }
59
+    
60
+    private func checkAllDraws() {
61
+        print("检查所有开奖")
62
+        // 这里可以添加检查开奖的代码
63
+    }
64
+    
65
+    private func resetSettings() {
66
+        if let bundleID = Bundle.main.bundleIdentifier {
67
+            UserDefaults.standard.removePersistentDomain(forName: bundleID)
68
+        }
69
+        print("✅ 所有设置已重置")
70
+    }
71
+    
72
+    private func getDatabasePath() -> String {
73
+        let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
74
+        return paths.first?.absoluteString ?? "未知"
75
+    }
76
+    
77
+    private func showLogs() {
78
+        print("显示日志...")
79
+    }
80
+}
81
+
82
+// 预览
83
+struct DeveloperView_Previews: PreviewProvider {
84
+    static var previews: some View {
85
+        NavigationView {
86
+            DeveloperView()
87
+        }
88
+    }
89
+}

+ 213
- 0
LotteryTracker/Views/Settings/SettingsView.swift 查看文件

@@ -0,0 +1,213 @@
1
+import SwiftUI
2
+internal import CoreData
3
+
4
+struct SettingsView: View {
5
+    @Environment(\.managedObjectContext) private var viewContext
6
+    @EnvironmentObject private var appState: AppState
7
+    
8
+    @AppStorage("enableNotifications") private var enableNotifications = true
9
+    @AppStorage("autoCheckDraw") private var autoCheckDraw = true
10
+    @AppStorage("currencySymbol") private var currencySymbol = "¥"
11
+    
12
+    @State private var showingClearAlert = false
13
+    @State private var showingClearSuccess = false
14
+    @State private var showingClearError = false
15
+    @State private var clearErrorMessage = ""
16
+    @State private var isClearing = false
17
+    
18
+    // 添加开发者选项的导航状态
19
+//    @State private var showingDeveloperView = false
20
+    
21
+    var body: some View {
22
+        NavigationView {
23
+            List {
24
+                // 通知设置
25
+                Section("通知设置") {
26
+                    Toggle("开启通知提醒", isOn: $enableNotifications)
27
+                    
28
+                    if enableNotifications {
29
+                        Toggle("启动时检查开奖", isOn: $autoCheckDraw)
30
+                        
31
+                        Button("测试通知") {
32
+                            testNotification()
33
+                        }
34
+                        .foregroundColor(.blue)
35
+                    }
36
+                }
37
+                
38
+                // 显示设置
39
+                Section("显示设置") {
40
+                    Picker("货币符号", selection: $currencySymbol) {
41
+                        Text("¥").tag("¥")
42
+                        Text("$").tag("$")
43
+                        Text("€").tag("€")
44
+                        Text("£").tag("£")
45
+                    }
46
+                }
47
+                
48
+                // 数据管理
49
+                Section("数据管理") {
50
+                    Button("导出数据") {
51
+                        exportData()
52
+                    }
53
+                    .foregroundColor(.blue)
54
+                    
55
+                    Button("备份数据") {
56
+                        backupData()
57
+                    }
58
+                    .foregroundColor(.blue)
59
+                    
60
+                    if isClearing {
61
+                        HStack {
62
+                            ProgressView()
63
+                                .scaleEffect(0.8)
64
+                            Text("正在清空数据...")
65
+                                .foregroundColor(.secondary)
66
+                        }
67
+                    } else {
68
+                        Button("清空所有数据") {
69
+                            showingClearAlert = true
70
+                        }
71
+                        .foregroundColor(.red)
72
+                    }
73
+                }
74
+                
75
+                // 关于
76
+                Section("关于") {
77
+                    HStack {
78
+                        Text("版本")
79
+                        Spacer()
80
+                        Text("1.0.0")
81
+                            .foregroundColor(.secondary)
82
+                    }
83
+                    
84
+                    Button("检查更新") {
85
+                        checkForUpdates()
86
+                    }
87
+                    .foregroundColor(.blue)
88
+                    
89
+                    Button("用户协议") {
90
+                        // 显示用户协议
91
+                    }
92
+                    .foregroundColor(.blue)
93
+                    
94
+                    Button("隐私政策") {
95
+                        // 显示隐私政策
96
+                    }
97
+                    .foregroundColor(.blue)
98
+                }
99
+                
100
+                // 开发者选项
101
+                Section {
102
+                    NavigationLink("开发者选项") {
103
+                        DeveloperView()
104
+                    }
105
+                    .foregroundColor(.blue)
106
+                }
107
+            }
108
+            .navigationTitle("设置")
109
+            .navigationBarTitleDisplayMode(.inline)
110
+            .alert("清空数据", isPresented: $showingClearAlert) {
111
+                Button("取消", role: .cancel) { }
112
+                Button("清空", role: .destructive) {
113
+                    clearAllData()
114
+                }
115
+            } message: {
116
+                Text("这将删除所有彩票记录,此操作不可撤销。确定要继续吗?")
117
+            }
118
+            .alert("清空成功", isPresented: $showingClearSuccess) {
119
+                Button("确定", role: .cancel) { }
120
+            } message: {
121
+                Text("所有数据已成功清空")
122
+            }
123
+            .alert("清空失败", isPresented: $showingClearError) {
124
+                Button("确定", role: .cancel) { }
125
+            } message: {
126
+                Text(clearErrorMessage)
127
+            }
128
+        }}
129
+    
130
+    // MARK: - 方法
131
+    
132
+    private func testNotification() {
133
+        print("测试通知功能")
134
+        // 这里可以添加测试通知的代码
135
+    }
136
+    
137
+    private func exportData() {
138
+        print("导出数据功能")
139
+    }
140
+    
141
+    private func backupData() {
142
+        print("数据备份功能")
143
+    }
144
+    
145
+    // 清空所有数据(可靠版本)
146
+    private func clearAllData() {
147
+        isClearing = true
148
+        
149
+        // 在后台线程执行删除操作
150
+        DispatchQueue.global(qos: .userInitiated).async {
151
+            let fetchRequest: NSFetchRequest<NSFetchRequestResult> = LotteryTicket.fetchRequest()
152
+            
153
+            do {
154
+                // 先获取所有记录
155
+                if let tickets = try? self.viewContext.fetch(fetchRequest) as? [LotteryTicket] {
156
+                    print("找到 \(tickets.count) 条记录需要删除")
157
+                    
158
+                    // 逐一删除
159
+                    for ticket in tickets {
160
+                        self.viewContext.delete(ticket)
161
+                    }
162
+                    
163
+                    // 保存更改
164
+                    if self.viewContext.hasChanges {
165
+                        try self.viewContext.save()
166
+                        print("✅ 保存删除操作")
167
+                    }
168
+                    
169
+                    // 重置上下文
170
+                    self.viewContext.reset()
171
+                }
172
+                
173
+                DispatchQueue.main.async {
174
+                    self.isClearing = false
175
+                    self.showingClearSuccess = true
176
+                    
177
+                    // 发送数据更新通知,标记为清空操作
178
+                    NotificationCenter.default.post(
179
+                        name: .ticketsUpdated,
180
+                        object: nil,
181
+                        userInfo: ["isClearOperation": true]  // 添加标识
182
+                    )
183
+                    
184
+                    // 可选:发送应用重置通知
185
+                    NotificationCenter.default.post(name: .appReset, object: nil)
186
+                    
187
+                    print("✅ 所有数据已成功清空,已发送清空通知")
188
+                }
189
+            } catch {
190
+                DispatchQueue.main.async {
191
+                    self.isClearing = false
192
+                    self.clearErrorMessage = "清空失败: \(error.localizedDescription)"
193
+                    self.showingClearError = true
194
+                    
195
+                    print("❌ 清空数据失败: \(error)")
196
+                }
197
+            }
198
+        }
199
+    }
200
+    
201
+    private func checkForUpdates() {
202
+        print("检查更新")
203
+    }
204
+}
205
+
206
+// 预览
207
+struct SettingsView_Previews: PreviewProvider {
208
+    static var previews: some View {
209
+        NavigationView {
210
+            SettingsView()
211
+        }
212
+    }
213
+}

LotteryTracker/Views/My/StatisticsView.swift → LotteryTracker/Views/Statistics/StatisticsView.swift 查看文件

@@ -89,13 +89,13 @@ struct StatisticsView: View {
89 89
             HStack(spacing: 20) {
90 90
                 StatItem(
91 91
                     title: "本月投入",
92
-                    value: "¥\(statsService.monthlyStats.currentMonth.totalSpent, default: "%.2f")",
92
+                    value: Formatters.formatCurrency(statsService.monthlyStats.currentMonth.totalSpent),
93 93
                     color: .blue
94 94
                 )
95 95
                 
96 96
                 StatItem(
97 97
                     title: "本月盈亏",
98
-                    value: "¥\(statsService.monthlyStats.currentMonth.totalProfit, default: "%.2f")",
98
+                    value: Formatters.formatCurrency(statsService.monthlyStats.currentMonth.totalProfit),
99 99
                     color: statsService.monthlyStats.currentMonth.totalProfit >= 0 ? .green : .red
100 100
                 )
101 101
                 
@@ -244,7 +244,7 @@ struct StatisticsView: View {
244 244
                     StatSummaryItem(
245 245
                         icon: "dollarsign.circle.fill",
246 246
                         title: "平均投注",
247
-                        value: "¥\(statsService.monthlyStats.currentMonth.averageBet, default: "%.2f")",
247
+                        value: Formatters.formatCurrency(statsService.monthlyStats.currentMonth.averageBet),
248 248
                         color: .orange
249 249
                     )
250 250
                     
@@ -310,7 +310,7 @@ struct TypeDistributionDetailView: View {
310 310
                     Spacer()
311 311
                     
312 312
                     VStack(alignment: .trailing) {
313
-                        Text("¥\(item.amount, specifier: "%.2f")")
313
+                        Text(Formatters.formatCurrency(item.amount))
314 314
                             .font(.headline)
315 315
                         
316 316
                         Text("\(item.percentage, specifier: "%.1f")%")