Procházet zdrojové kódy

修改一些显示问题

afan před 6 dny
rodič
revize
5af5150a33
29 změnil soubory, kde provedl 701 přidání a 381 odebrání
  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. binární
      LotteryTracker/Assets.xcassets/AppIcon.appiconset/已移除背景的生成特定比例向日葵图片 (1) 1.png
  10. binární
      LotteryTracker/Assets.xcassets/AppIcon.appiconset/已移除背景的生成特定比例向日葵图片 (1) 2.png
  11. binární
      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 Zobrazit soubor

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

+ 78
- 0
LotteryTracker.xcodeproj/xcshareddata/xcschemes/LotteryTracker.xcscheme Zobrazit soubor

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 Zobrazit soubor

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 Zobrazit soubor

10
 			<integer>0</integer>
10
 			<integer>0</integer>
11
 		</dict>
11
 		</dict>
12
 	</dict>
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
 </dict>
21
 </dict>
14
 </plist>
22
 </plist>

+ 5
- 1
LotteryTracker/App/AppState.swift Zobrazit soubor

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

+ 14
- 1
LotteryTracker/App/LotteryTrackerApp.swift Zobrazit soubor

70
     
70
     
71
     var body: some View {
71
     var body: some View {
72
         TabView(selection: $appState.selectedTab) {
72
         TabView(selection: $appState.selectedTab) {
73
+            // 首页界面
73
             HomeView()
74
             HomeView()
74
                 .tabItem {
75
                 .tabItem {
75
                     Label("首页", systemImage: "house.fill")
76
                     Label("首页", systemImage: "house.fill")
76
                 }
77
                 }
77
                 .tag(Tab.home)
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
             MyView()
92
             MyView()
80
                 .tabItem {
93
                 .tabItem {
81
                     Label("我的", systemImage: "person.fill")
94
                     Label("我的", systemImage: "person.fill")

+ 1
- 1
LotteryTracker/App/PersistenceController.swift Zobrazit soubor

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

+ 3
- 0
LotteryTracker/Assets.xcassets/AppIcon.appiconset/Contents.json Zobrazit soubor

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

binární
LotteryTracker/Assets.xcassets/AppIcon.appiconset/已移除背景的生成特定比例向日葵图片 (1) 1.png Zobrazit soubor


binární
LotteryTracker/Assets.xcassets/AppIcon.appiconset/已移除背景的生成特定比例向日葵图片 (1) 2.png Zobrazit soubor


binární
LotteryTracker/Assets.xcassets/AppIcon.appiconset/已移除背景的生成特定比例向日葵图片 (1).png Zobrazit soubor


+ 2
- 2
LotteryTracker/LotteryTracker.xcdatamodeld/LotteryTracker.xcdatamodel/contents Zobrazit soubor

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

+ 20
- 7
LotteryTracker/Models/LotteryTicket+Extensions.swift Zobrazit soubor

1
+//
2
+//  LotteryTicket+Extensions.swift
3
+//  LotteryTracker
4
+//
5
+//  Created by aaa on 2026/1/21.
6
+//
7
+
1
 import Foundation
8
 import Foundation
2
 internal import CoreData
9
 internal import CoreData
3
 
10
 
48
     }
55
     }
49
     
56
     
50
     // 计算属性:计算盈亏
57
     // 计算属性:计算盈亏
51
-    var profit: Double {
58
+    var profit: Float {
52
         return prizeAmount - amount
59
         return prizeAmount - amount
53
     }
60
     }
54
     
61
     
65
         formatter.dateFormat = "yyyy-MM-dd"
72
         formatter.dateFormat = "yyyy-MM-dd"
66
         return formatter.string(from: drawDate ?? Date())
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
 // 扩展 FetchRequest
88
 // 扩展 FetchRequest
87
         ]
105
         ]
88
         return request
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 Zobrazit soubor

38
             ticket.betCount = Int16.random(in: 1...10)
38
             ticket.betCount = Int16.random(in: 1...10)
39
             
39
             
40
             // 随机金额(每注2-20元)
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
             // 随机购买日期(过去60天内)
44
             // 随机购买日期(过去60天内)
45
             let daysAgo = Int.random(in: 0...60)
45
             let daysAgo = Int.random(in: 0...60)
55
             let status = Int.random(in: 0...100)
55
             let status = Int.random(in: 0...100)
56
             if status < 30 {
56
             if status < 30 {
57
                 ticket.ticketStatus = .won
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
             } else if status < 60 {
59
             } else if status < 60 {
60
                 ticket.ticketStatus = .lost
60
                 ticket.ticketStatus = .lost
61
                 ticket.prizeAmount = 0
61
                 ticket.prizeAmount = 0
64
                 ticket.prizeAmount = 0
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
         do {
70
         do {

+ 5
- 5
LotteryTracker/Services/LotteryChecker.swift Zobrazit soubor

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

+ 23
- 23
LotteryTracker/Services/StatisticsService.swift Zobrazit soubor

188
                     return
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
                 for ticket in tickets {
194
                 for ticket in tickets {
195
                     distribution[ticket.lotteryType, default: 0] += ticket.amount
195
                     distribution[ticket.lotteryType, default: 0] += ticket.amount
199
                     TypeDistribution(
199
                     TypeDistribution(
200
                         type: type,
200
                         type: type,
201
                         amount: amount,
201
                         amount: amount,
202
-                        percentage: totalAmount > 0 ? (amount / totalAmount) * 100 : 0
202
+                        percentage: totalAmount > 0 ? Float((amount / totalAmount)) * 100 : 0
203
                     )
203
                     )
204
                 }.sorted { $0.amount > $1.amount }
204
                 }.sorted { $0.amount > $1.amount }
205
                 
205
                 
230
                 let endDate = Date()
230
                 let endDate = Date()
231
                 let startDate = calendar.date(byAdding: .day, value: -days, to: endDate)!
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
                 for ticket in tickets {
235
                 for ticket in tickets {
236
                     guard let date = ticket.date else { continue }
236
                     guard let date = ticket.date else { continue }
258
     
258
     
259
     // 计算统计数据
259
     // 计算统计数据
260
     private func calculateStats(for tickets: [LotteryTicket]) -> MonthStats {
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
         let profit = totalPrize - totalSpent
263
         let profit = totalPrize - totalSpent
264
         
264
         
265
         let winningTickets = tickets.filter { $0.ticketStatus == .won }.count
265
         let winningTickets = tickets.filter { $0.ticketStatus == .won }.count
266
         // 修复:winningTickets 已经是 Int,不需要 .count
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
         return MonthStats(
269
         return MonthStats(
270
-            ticketCount: tickets.count,
270
+            ticketCount: Int16(tickets.count),
271
             totalSpent: totalSpent,
271
             totalSpent: totalSpent,
272
             totalProfit: profit,
272
             totalProfit: profit,
273
             winRate: winRate,
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
         self.lastMonth = lastMonth
286
         self.lastMonth = lastMonth
287
     }
287
     }
288
     
288
     
289
-    var profitChange: Double {
289
+    var profitChange: Float {
290
         guard lastMonth.totalSpent > 0 else { return 0 }
290
         guard lastMonth.totalSpent > 0 else { return 0 }
291
         return ((currentMonth.totalProfit - lastMonth.totalProfit) / lastMonth.totalProfit) * 100
291
         return ((currentMonth.totalProfit - lastMonth.totalProfit) / lastMonth.totalProfit) * 100
292
     }
292
     }
293
 }
293
 }
294
 
294
 
295
 struct MonthStats {
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
     init(
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
         self.ticketCount = ticketCount
309
         self.ticketCount = ticketCount
310
         self.totalSpent = totalSpent
310
         self.totalSpent = totalSpent
317
 struct TypeDistribution: Identifiable {
317
 struct TypeDistribution: Identifiable {
318
     let id = UUID()
318
     let id = UUID()
319
     let type: LotteryType
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
 struct DailyProfit: Identifiable {
324
 struct DailyProfit: Identifiable {
325
     let id = UUID()
325
     let id = UUID()
326
     let date: Date
326
     let date: Date
327
-    let profit: Double
327
+    let profit: Float
328
 }
328
 }

+ 1
- 1
LotteryTracker/Utils/Formatters.swift Zobrazit soubor

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

+ 6
- 6
LotteryTracker/ViewModels/AddTicketViewModel.swift Zobrazit soubor

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

+ 6
- 3
LotteryTracker/ViewModels/HomeViewModel.swift Zobrazit soubor

5
 
5
 
6
 class HomeViewModel: ObservableObject {
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
     @Published var totalTickets: Int = 0
10
     @Published var totalTickets: Int = 0
11
     @Published var winningTickets: Int = 0
11
     @Published var winningTickets: Int = 0
12
     @Published var pendingTickets: Int = 0
12
     @Published var pendingTickets: Int = 0
69
             } else if Calendar.current.isDateInYesterday(date) {
69
             } else if Calendar.current.isDateInYesterday(date) {
70
                 return "昨天"
70
                 return "昨天"
71
             } else {
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
                 return formatter.string(from: date)
76
                 return formatter.string(from: date)
74
             }
77
             }
75
         }
78
         }

+ 5
- 5
LotteryTracker/Views/AddTicket/AmountInputView.swift Zobrazit soubor

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

+ 12
- 4
LotteryTracker/Views/Home/HomeView.swift Zobrazit soubor

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

+ 4
- 4
LotteryTracker/Views/Home/StatsCard.swift Zobrazit soubor

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

+ 143
- 0
LotteryTracker/Views/Home/TicketDetailView.swift Zobrazit soubor

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 Zobrazit soubor

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

+ 24
- 24
LotteryTracker/Views/My/MyView.swift Zobrazit soubor

13
     
13
     
14
     var body: some View {
14
     var body: some View {
15
         NavigationView {
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 Zobrazit soubor

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 Zobrazit soubor

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 Zobrazit soubor

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 Zobrazit soubor

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